RESTUtil.java
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.synapse.transport.nhttp.util;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.soap.impl.llom.soap11.SOAP11Factory;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.description.AxisBindingOperation;
import org.apache.axis2.description.AxisEndpoint;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.WSDL20DefaultValueHolder;
import org.apache.axis2.description.WSDL2Constants;
import org.apache.axis2.dispatchers.HTTPLocationBasedDispatcher;
import org.apache.axis2.dispatchers.RequestURIBasedDispatcher;
import org.apache.axis2.dispatchers.RequestURIOperationDispatcher;
import org.apache.axis2.engine.AxisEngine;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.transport.http.util.URIEncoderDecoder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.synapse.transport.nhttp.NHttpConfiguration;
import org.apache.synapse.transport.nhttp.NhttpConstants;
import javax.xml.namespace.QName;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
/**
* This class provides a set of utility methods to manage the REST invocation calls
* going out from the nhttp transport in the HTTP GET method
*/
public class RESTUtil {
private static final Log log = LogFactory.getLog(RESTUtil.class);
private static RequestURIBasedDispatcher requestDispatcher = new RequestURIBasedDispatcher();
private static HTTPLocationBasedDispatcher httpLocationBasedDispatcher =
new HTTPLocationBasedDispatcher();
private static RequestURIOperationDispatcher requestURIOperationDispatcher =
new RequestURIOperationDispatcher();
private static Object dispatcherInstance = null;
private static Method invokeMethod = null;
static {
String extendedURIBasedDispatcher = System.getProperty("ei.extendedURIBasedDispatcher");
try {
if (extendedURIBasedDispatcher != null) {
Class<?> extendedURIBasedDispatcherClass = RESTUtil.class.getClassLoader().loadClass(extendedURIBasedDispatcher);
dispatcherInstance = extendedURIBasedDispatcherClass.newInstance();
invokeMethod = extendedURIBasedDispatcherClass.getMethod("invoke", MessageContext.class);
}
} catch (IllegalAccessException | InstantiationException | NoSuchMethodException | ClassNotFoundException e) {
log.fatal(e);
}
}
/**
* This method will return the URI part for the GET HTTPRequest by converting
* the SOAP infoset to the URL-encoded GET format
*
* @param messageContext - from which the SOAP infoset will be extracted to encode
* @param address - address of the actual service
* @return uri - ERI of the GET request
* @throws AxisFault - if the SOAP infoset cannot be converted in to the GET URL-encoded format
*/
public static String getURI(MessageContext messageContext, String address) throws AxisFault {
OMElement firstElement;
address = address.substring(address.indexOf("//") + 2);
address = address.substring(address.indexOf("/"));
String queryParameterSeparator = (String) messageContext
.getProperty(WSDL2Constants.ATTR_WHTTP_QUERY_PARAMETER_SEPARATOR);
// In case queryParameterSeparator is null we better use the default value
if (queryParameterSeparator == null) {
queryParameterSeparator = WSDL20DefaultValueHolder
.getDefaultValue(WSDL2Constants.ATTR_WHTTP_QUERY_PARAMETER_SEPARATOR);
}
firstElement = messageContext.getEnvelope().getBody().getFirstElement();
String params = "";
if (firstElement != null) {
// first element corresponds to the operation name
address = address + "/" + firstElement.getLocalName();
} else {
firstElement = messageContext.getEnvelope().getBody();
}
Iterator iter = firstElement.getChildElements();
String legalCharacters = WSDL2Constants
.LEGAL_CHARACTERS_IN_QUERY.replaceAll(queryParameterSeparator, "");
StringBuffer buff = new StringBuffer(params);
// iterate through the child elements and find the request parameters
while (iter.hasNext()) {
OMElement element = (OMElement) iter.next();
try {
buff.append(URIEncoderDecoder.quoteIllegal(element.getLocalName(),
legalCharacters)).append("=").append(URIEncoderDecoder.quoteIllegal(element.getText(),
legalCharacters)).append(queryParameterSeparator);
} catch (UnsupportedEncodingException e) {
throw new AxisFault("URI Encoding error : " + element.getLocalName()
+ "=" + element.getText(), e);
}
}
params = buff.toString();
if (params.trim().length() != 0) {
int index = address.indexOf("?");
if (index == -1) {
address = address + "?" + params.substring(0, params.length() - 1);
} else if (index == address.length() - 1) {
address = address + params.substring(0, params.length() - 1);
} else {
address = address
+ queryParameterSeparator + params.substring(0, params.length() - 1);
}
}
return address;
}
/**
* Processes the HTTP GET / DELETE request and builds the SOAP info-set of the REST message
*
* @param msgContext The MessageContext of the Request Message
* @param out The output stream of the response
* @param requestURI The URL that the request came to
* @param contentTypeHeader The contentType header of the request
* @param httpMethod The http method of the request
* @param dispatching Weather we should do service dispatching
* @throws AxisFault - Thrown in case a fault occurs
*/
public static void processGetAndDeleteRequest(MessageContext msgContext, OutputStream out,
String requestURI, Header contentTypeHeader,
String httpMethod, boolean dispatching)
throws AxisFault {
String contentType = contentTypeHeader != null ? contentTypeHeader.getValue() : null;
prepareMessageContext(msgContext, requestURI, httpMethod, out, contentType, dispatching);
msgContext.setProperty(NhttpConstants.NO_ENTITY_BODY, Boolean.TRUE);
org.apache.axis2.transport.http.util.RESTUtil.processURLRequest(msgContext, out,
contentType);
}
/**
* Processes the HTTP GET request and builds the SOAP info-set of the REST message
*
* @param msgContext The MessageContext of the Request Message
* @param out The output stream of the response
* @param soapAction SoapAction of the request
* @param requestURI The URL that the request came to
* @throws AxisFault - Thrown in case a fault occurs
*/
public static void processURLRequest(MessageContext msgContext, OutputStream out,
String soapAction, String requestURI) throws AxisFault {
if ((soapAction != null) && soapAction.startsWith("\"") && soapAction.endsWith("\"")) {
soapAction = soapAction.substring(1, soapAction.length() - 1);
}
msgContext.setSoapAction(soapAction);
msgContext.setTo(new EndpointReference(requestURI));
msgContext.setProperty(MessageContext.TRANSPORT_OUT, out);
msgContext.setServerSide(true);
msgContext.setDoingREST(true);
msgContext.setEnvelope(new SOAP11Factory().getDefaultEnvelope());
msgContext.setProperty(NhttpConstants.NO_ENTITY_BODY, Boolean.TRUE);
AxisEngine.receive(msgContext);
}
/**
* Processes the HTTP POST request and builds the SOAP info-set of the REST message
*
* @param msgContext The MessageContext of the Request Message
* @param is The input stream of the request
* @param os The output stream of the response
* @param requestURI The URL that the request came to
* @param contentTypeHeader The contentType header of the request
* @param dispatching Weather we should do dispatching
* @throws AxisFault - Thrown in case a fault occurs
*/
public static void processPOSTRequest(MessageContext msgContext, InputStream is,
OutputStream os, String requestURI,
Header contentTypeHeader,
boolean dispatching) throws AxisFault {
String contentType = contentTypeHeader != null ? contentTypeHeader.getValue() : null;
processPOSTRequest(msgContext, is, os, requestURI, contentType, dispatching);
}
/**
* Processes the HTTP POST request and builds the SOAP info-set of the REST message
*
* @param msgContext MessageContext of the Request Message
* @param is Input stream of the request
* @param os Output stream of the response
* @param requestURI URL that the request came to
* @param contentType ContentType header of the request
* @param dispatching Whether we should do dispatching
* @throws AxisFault - Thrown in case a fault occurs
*/
public static void processPOSTRequest(MessageContext msgContext, InputStream is,
OutputStream os, String requestURI,
String contentType,
boolean dispatching) throws AxisFault {
prepareMessageContext(msgContext, requestURI, HTTPConstants.HTTP_METHOD_POST,
os, contentType, dispatching);
org.apache.axis2.transport.http.util.RESTUtil.processXMLRequest(msgContext, is, os,
contentType);
}
/**
* prepare message context prior to call axis2 RestUtils
*
* @param msgContext The MessageContext of the Request Message
* @param requestURI The URL that the request came to
* @param httpMethod The http method of the request
* @param out The output stream of the response
* @param contentType The content type of the request
* @param dispatching weather we should do dispatching
* @throws AxisFault Thrown in case a fault occurs
*/
private static void prepareMessageContext(MessageContext msgContext,
String requestURI,
String httpMethod,
OutputStream out,
String contentType,
boolean dispatching) throws AxisFault {
msgContext.setTo(new EndpointReference(requestURI));
msgContext.setProperty(HTTPConstants.HTTP_METHOD, httpMethod);
msgContext.setServerSide(true);
msgContext.setDoingREST(true);
msgContext.setProperty(MessageContext.TRANSPORT_OUT, out);
msgContext.setProperty(NhttpConstants.REST_REQUEST_CONTENT_TYPE, contentType);
// workaround to get REST working in the case of
// 1) Based on the request URI , it is possible to find a service name and operation.
// However, there is no actual service deployed in the synapse ( no i.e proxy or other)
// e.g http://localhost:8280/services/StudentService/students where there is no proxy
// service with name StudentService.This is a senario where StudentService is in an external
// server and it is needed to call it from synapse using the main sequence
// 2) request is to be injected into the main sequence .i.e. http://localhost:8280
// This method does not cause any performance issue ...
// Proper fix should be refractoring axis2 RestUtil in a proper way
/**
* This reverseProxyMode was introduce to avoid the LB exposing it's own web service when REST call was initiated
*/
boolean reverseProxyMode = Boolean.parseBoolean(System.getProperty("reverseProxyMode"));
AxisService axisService = null;
if(!reverseProxyMode){
axisService = requestDispatcher.findService(msgContext);
}
boolean isCustomRESTDispatcher = false;
if (requestURI.matches(NHttpConfiguration.getInstance().getRestUriApiRegex())
|| requestURI.matches(NHttpConfiguration.getInstance().getRestUriProxyRegex())) {
isCustomRESTDispatcher = true;
}
//the logic determines which service dispatcher to get invoke, this will be determine
//based on parameter defines at disableRestServiceDispatching, and if super tenant invoke, with isTenantRequest
//identifies whether the request to be dispatch to custom REST Dispatcher Service.
if (dispatching || !isCustomRESTDispatcher) {
if (axisService == null) {
String defaultSvcName = NHttpConfiguration.getInstance().getStringValue("nhttp.default.service", "__SynapseService");
axisService = msgContext.getConfigurationContext().getAxisConfiguration().getService(defaultSvcName);
}
msgContext.setAxisService(axisService);
//When receiving rest request axis2 checks whether axis operation is set in the message context to build
// the request message. Therefore we have to set axis operation before we handed over request to axis
// engine. If axis operation is already set in the message context take it from there. If not take the
// axis operation from axis service.
setAxisOperation(msgContext, axisService);
} else {
String multiTenantDispatchService = NHttpConfiguration.getInstance().getRESTDispatchService();
axisService = msgContext.getConfigurationContext().getAxisConfiguration().getService(multiTenantDispatchService);
msgContext.setAxisService(axisService);
setAxisOperation(msgContext, axisService);
}
}
public static void dispatchAndVerify(MessageContext msgContext) throws AxisFault {
String extendedURIBasedDispatcher = System.getProperty("ei.extendedURIBasedDispatcher");
if (extendedURIBasedDispatcher == null) {
requestDispatcher.invoke(msgContext);
} else {
try {
invokeMethod.invoke(dispatcherInstance, msgContext);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new AxisFault(e.getMessage());
}
}
AxisService axisService = msgContext.getAxisService();
if (axisService != null) {
httpLocationBasedDispatcher.invoke(msgContext);
if (msgContext.getAxisOperation() == null) {
requestURIOperationDispatcher.invoke(msgContext);
}
AxisOperation axisOperation;
if ((axisOperation = msgContext.getAxisOperation()) != null) {
AxisEndpoint axisEndpoint =
(AxisEndpoint) msgContext.getProperty(WSDL2Constants.ENDPOINT_LOCAL_NAME);
if (axisEndpoint != null) {
AxisBindingOperation axisBindingOperation = (AxisBindingOperation) axisEndpoint
.getBinding().getChild(axisOperation.getName());
msgContext.setProperty(Constants.AXIS_BINDING_OPERATION, axisBindingOperation);
}
msgContext.setAxisOperation(axisOperation);
}
}
}
/**
* Sets the axis operation to the message context. If axis operation is already set in the message context take it
* from there. If not take the axis operation from axis service.
*
* @param msgContext messageContext message context
* @param axisService axis service
*/
private static void setAxisOperation(MessageContext msgContext, AxisService axisService) {
if (msgContext.getAxisOperation() == null && axisService != null) {
AxisOperation axisOperation = findOperation(axisService, msgContext);
msgContext.setAxisOperation(axisOperation);
}
}
/**
* Get the axis operation from the operation name used by the Synapse service (for message mediation).
*
* @param svc axis service
* @param mc messageContext message context
* @return axis operation for the message context
*/
public static AxisOperation findOperation(AxisService svc, MessageContext mc) {
AxisOperation operation = svc.getOperation(NhttpConstants.SYNAPSE_OPERATION_NAME);
if (operation == null && mc.getAxisService() != null) {
operation = processOperationValidation(svc);
}
return operation;
}
/**
* Get the default mediation operation from axis service.
*
* @param svc axis service which should check for axis operation
* @return axis operation if service contains default mediate operation otherwise null
*/
private static AxisOperation processOperationValidation(AxisService svc) {
Object operationObj = svc.getParameterValue(NhttpConstants.DEFAULT_MEDIATE_OPERATION);
if (operationObj != null) {
return (AxisOperation) operationObj;
}
return null;
}
}