/*
* Copyright 2015 WSO2, Inc. (http://wso2.com)
*
* Licensed 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.wso2.carbon.mediation.transport.handlers;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.xpath.AXIOMXPath;
import org.apache.axiom.util.blob.OverflowBlob;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.util.JavaUtils;
import org.apache.axis2.util.XMLUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpInetConnection;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.nio.NHttpServerConnection;
import org.apache.http.protocol.HTTP;
import org.apache.synapse.transport.nhttp.NHttpConfiguration;
import org.apache.synapse.transport.nhttp.NhttpConstants;
import org.apache.synapse.transport.passthru.HttpGetRequestProcessor;
import org.apache.synapse.transport.passthru.PassThroughConstants;
import org.apache.synapse.transport.passthru.ProtocolState;
import org.apache.synapse.transport.passthru.SourceContext;
import org.apache.synapse.transport.passthru.SourceHandler;
import org.jaxen.SimpleNamespaceContext;
import org.jaxen.XPath;
import org.wso2.carbon.base.ServerConfiguration;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.core.multitenancy.utils.TenantAxisUtils;
import org.wso2.carbon.core.transports.CarbonHttpRequest;
import org.wso2.carbon.core.transports.CarbonHttpResponse;
import org.wso2.carbon.mediation.transport.handlers.utils.RequestProcessorDispatcherUtil;
import org.wso2.carbon.utils.ServerConstants;
import javax.servlet.ServletException;
import javax.xml.namespace.QName;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
/**
* Get Processor implementation for PassThrough Transport.
*/
public class PassThroughNHttpGetProcessor implements HttpGetRequestProcessor {
private Map<String, org.wso2.carbon.core.transports.HttpGetRequestProcessor> getRequestProcessors =
new LinkedHashMap<String, org.wso2.carbon.core.transports.HttpGetRequestProcessor>();
private ConfigurationContext cfgCtx;
private SourceHandler sourceHandler;
private static final QName ITEM_QN = new QName(ServerConstants.CARBON_SERVER_XML_NAMESPACE, "Item");
private static final QName CLASS_QN = new QName(ServerConstants.CARBON_SERVER_XML_NAMESPACE, "Class");
private static final String CONTENT_TYPE = "Content-Type";
private static final String TEXT_HTML = "text/html";
private static final Log log = LogFactory.getLog(PassThroughNHttpGetProcessor.class);
private void populateGetRequestProcessors() throws AxisFault {
try {
OMElement docEle = XMLUtils.toOM(ServerConfiguration.getInstance().getDocumentElement());
if (docEle != null) {
SimpleNamespaceContext nsCtx = new SimpleNamespaceContext();
nsCtx.addNamespace("wsas", ServerConstants.CARBON_SERVER_XML_NAMESPACE);
XPath xp = new AXIOMXPath("//wsas:HttpGetRequestProcessors/wsas:Processor");
xp.setNamespaceContext(nsCtx);
List nodeList = xp.selectNodes(docEle);
for (Object aNodeList : nodeList) {
OMElement processorEle = (OMElement) aNodeList;
OMElement itemEle = processorEle.getFirstChildWithName(ITEM_QN);
if (itemEle == null) {
throw new ServletException("Required element, 'Item' not found!");
}
OMElement classEle = processorEle.getFirstChildWithName(CLASS_QN);
org.wso2.carbon.core.transports.HttpGetRequestProcessor processor;
if (classEle == null) {
throw new ServletException("Required element, 'Class' not found!");
} else {
processor =
(org.wso2.carbon.core.transports.HttpGetRequestProcessor)
Class.forName(classEle.getText().trim()).newInstance();
}
getRequestProcessors.put(itemEle.getText().trim(), processor);
}
}
} catch (Exception e) {
handleException("Error populating GetRequestProcessors", e);
}
}
private void processWithGetProcessor(HttpRequest request,
HttpResponse response,
String requestUri,
String requestUrl,
String queryString,
String item,
OutputStream outputStream,
NHttpServerConnection conn) throws Exception {
OverflowBlob temporaryData = new OverflowBlob(256, 4048, "_nhttp", ".dat");
try {
CarbonHttpRequest carbonHttpRequest = new CarbonHttpRequest(
"GET", requestUri, requestUrl);
String uri = request.getRequestLine().getUri();
// setting the parameters for nhttp transport
int pos = uri.indexOf("?");
if (pos != -1) {
StringTokenizer st = new StringTokenizer(uri.substring(pos + 1), "&");
while (st.hasMoreTokens()) {
String param = st.nextToken();
pos = param.indexOf("=");
if (pos != -1) {
carbonHttpRequest.setParameter(
param.substring(0, pos), param.substring(pos + 1));
} else {
carbonHttpRequest.setParameter(param, null);
}
}
}
carbonHttpRequest.setContextPath(cfgCtx.getServiceContextPath());
carbonHttpRequest.setQueryString(queryString);
CarbonHttpResponse carbonHttpResponse = new CarbonHttpResponse(
temporaryData.getOutputStream());
try {
PrivilegedCarbonContext.startTenantFlow();
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(TenantAxisUtils.
getTenantDomain(requestUrl), true);
(getRequestProcessors.get(item)).process(carbonHttpRequest,
carbonHttpResponse, cfgCtx);
} finally {
PrivilegedCarbonContext.endTenantFlow();
}
// adding headers
Map<String, String> responseHeaderMap = carbonHttpResponse.getHeaders();
for (Object key : responseHeaderMap.keySet()) {
Object value = responseHeaderMap.get(key);
response.addHeader(key.toString(), value.toString());
}
// setting status code
response.setStatusCode(carbonHttpResponse.getStatusCode());
// setting error codes
if (carbonHttpResponse.isError()) {
if (carbonHttpResponse.getStatusMessage() != null) {
response.setStatusLine(response.getProtocolVersion(),
carbonHttpResponse.getStatusCode(),
carbonHttpResponse.getStatusMessage());
} else {
response.setStatusLine(response.getProtocolVersion(),
carbonHttpResponse.getStatusCode());
}
}
if (carbonHttpResponse.isRedirect()) {
response.addHeader("Location", carbonHttpResponse.getRedirect());
response.setStatusLine(response.getProtocolVersion(), 302);
}
SourceContext.updateState(conn, ProtocolState.WSDL_RESPONSE_DONE);
try{
temporaryData.writeTo(outputStream);
}catch (Exception e) {
e.printStackTrace();
}
try {
outputStream.flush();
outputStream.close();
} catch (Exception ignored) {}
} finally {
temporaryData.release();
sourceHandler.commitResponseHideExceptions(conn, response);
}
}
public void init(ConfigurationContext configurationContext, SourceHandler sourceHandler)
throws AxisFault {
//super.init(configurationContext, sourceHandler);
this.cfgCtx = configurationContext;
this.sourceHandler = sourceHandler;
if (cfgCtx.getProperty("GETRequestProcessorMap") != null) {
getRequestProcessors = (Map<String, org.wso2.carbon.core.transports.HttpGetRequestProcessor>)
cfgCtx.getProperty("GETRequestProcessorMap");
} else {
populateGetRequestProcessors();
}
}
public void process(HttpRequest request, HttpResponse response,
MessageContext messageContext,
NHttpServerConnection conn,
OutputStream outputStream, boolean b) {
boolean isRequestHandled = false;
String uri = request.getRequestLine().getUri();
String servicePath = cfgCtx.getServiceContextPath();
if (!servicePath.startsWith("/")) {
servicePath = "/" + servicePath;
}
String serviceName = getServiceName(request);
boolean loadBalancer = Boolean.parseBoolean(System.getProperty("wso2.loadbalancer", "false"));
// Handle browser request to get favicon while requesting for wsdl
if (uri.equals("/favicon.ico")) {
response.setStatusCode(HttpStatus.SC_MOVED_PERMANENTLY);
response.addHeader("Location", "http://wso2.org/favicon.ico");
SourceContext.updateState(conn, ProtocolState.WSDL_RESPONSE_DONE);
try {
outputStream.flush();
outputStream.close();
} catch (Exception ignore) {
}
sourceHandler.commitResponseHideExceptions(conn, response);
isRequestHandled = true ;
} else if(uri.startsWith(servicePath) &&
(serviceName == null || serviceName.length() == 0)){
//check if service listing request is blocked
if (isServiceListBlocked(uri)) {
response.setStatusCode(HttpStatus.SC_FORBIDDEN);
sourceHandler.commitResponseHideExceptions(conn, response);
} else {
generateServicesList(response, conn, outputStream, servicePath);
messageContext.setProperty("WSDL_GEN_HANDLED", true);
}
try {
outputStream.flush();
outputStream.close();
} catch (IOException ignore) {
}
isRequestHandled = true ;
} else {
int pos = uri.indexOf('?');
if (pos != -1) {
String queryString = uri.substring(pos + 1);
String requestUri = uri.substring(0, pos);
String requestUrl = uri;
if (requestUri.indexOf("://") == -1) {
HttpInetConnection inetConn = (HttpInetConnection) conn;
String hostName = "localhost";
ServerConfiguration serverConfig = ServerConfiguration.getInstance();
if (serverConfig.getFirstProperty("HostName") != null) {
hostName = serverConfig.getFirstProperty("HostName");
}
requestUrl = "http://" +
hostName + ":" + inetConn.getLocalPort() + requestUri;
}
String contextPath = cfgCtx.getServiceContextPath();
int beginIndex = -1;
if (requestUri.indexOf(contextPath) != -1) {
beginIndex = requestUri.indexOf(contextPath) + contextPath.length() + 1;
}
/**
* This reverseProxyMode was introduce to avoid LB exposing it's own services when invoked through rest call.
* For a soap call this works well. But for a rest call this does not work as intended. in LB it has to set system property "reverseProxyMode"
*
*/
boolean reverseProxyMode = Boolean.parseBoolean(System.getProperty("reverseProxyMode"));
AxisService axisService = null;
if (!reverseProxyMode) {
if (!(beginIndex < 0 || beginIndex > requestUri.length())) {
serviceName = requestUri.substring(beginIndex);
axisService = cfgCtx.getAxisConfiguration().getServiceForActivation(serviceName);
}
if (axisService == null && !loadBalancer && serviceName != null) {
// Try to see whether the service is available in a tenant
try {
//Removing the below code segment due to empty stack exception coming from kernel
//Current context does not need to be destroyed at this level.
//PrivilegedCarbonContext.destroyCurrentContext();
String tenantDomain = TenantAxisUtils.getTenantDomain(uri);
PrivilegedCarbonContext.startTenantFlow();
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true);
axisService = TenantAxisUtils.getAxisService(serviceName, cfgCtx);
} catch (AxisFault axisFault) {
log.error("Error while retrieving Axis Service for Service name: "+serviceName,
axisFault);
} finally {
PrivilegedCarbonContext.endTenantFlow();
}
}
}
if (queryString != null) {
for (String item : getRequestProcessors.keySet()) {
if (queryString.indexOf(item) == 0 &&
(queryString.equals(item) ||
queryString.indexOf("&") == item.length() ||
queryString.indexOf("=") == item.length())) {
if (axisService == null) {
try {
String tenantDomain = TenantAxisUtils.getTenantDomain(uri);
PrivilegedCarbonContext.startTenantFlow();
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true);
//check for APIs since no axis2 service found
if (!RequestProcessorDispatcherUtil.isDispatchToApiGetProcessor(requestUri, cfgCtx)) {
continue;
}
} finally {
PrivilegedCarbonContext.endTenantFlow();
}
}
try {
processWithGetProcessor(request, response, requestUri,
requestUrl, queryString,
item, outputStream, conn);
messageContext.setProperty("WSDL_GEN_HANDLED", true);
} catch (Exception e) {
handleBrowserException(response, conn, outputStream,
"Error processing request", e);
}
isRequestHandled = true;
break;
}
}
}
}
}
if (!isRequestHandled) {
//processGetAndDelete(request, response, messageContext, conn, outputStream, "GET", b);
messageContext.setProperty(PassThroughConstants.REST_GET_DELETE_INVOKE, true);
}
}
/**
* Generates the services list.
*
* @param response HttpResponse
* @param conn NHttpServerConnection
* @param os OutputStream
* @param servicePath service path of the service
*/
protected void generateServicesList(HttpResponse response,
NHttpServerConnection conn,
OutputStream os, String servicePath) {
try {
byte[] bytes = getServicesHTML(
servicePath.endsWith("/") ? "" : servicePath + "/").getBytes();
response.addHeader(CONTENT_TYPE, TEXT_HTML);
SourceContext.updateState(conn, ProtocolState.WSDL_RESPONSE_DONE);
sourceHandler.commitResponseHideExceptions(conn, response);
os.write(bytes);
} catch (IOException e) {
handleBrowserException(response, conn, os,
"Error generating services list", e);
}
}
/**
* Returns the HTML text for the list of services deployed.
* This can be delegated to another Class as well
* where it will handle more options of GET messages.
*
* @param prefix to be used for the Service names
* @return the HTML to be displayed as a String
*/
protected String getServicesHTML(String prefix) {
Map services = cfgCtx.getAxisConfiguration().getServices();
Hashtable erroneousServices = cfgCtx.getAxisConfiguration().getFaultyServices();
boolean servicesFound = false;
StringBuffer resultBuf = new StringBuffer();
resultBuf.append("<html><head><title>Axis2: Services</title></head>" + "<body>");
if ((services != null) && !services.isEmpty()) {
servicesFound = true;
resultBuf.append("<h2>" + "Deployed services" + "</h2>");
for (Object service : services.values()) {
AxisService axisService = (AxisService) service;
Parameter isHiddenService = axisService.getParameter(
NhttpConstants.HIDDEN_SERVICE_PARAM_NAME);
Parameter isAdminService = axisService.getParameter("adminService");
boolean isClientSide = axisService.isClientSide();
boolean isSkippedService = (isHiddenService != null &&
JavaUtils.isTrueExplicitly(isHiddenService.getValue())) || (isAdminService != null &&
JavaUtils.isTrueExplicitly(isAdminService.getValue())) || isClientSide;
if (axisService.getName().startsWith("__") || isSkippedService) {
continue; // skip private services
}
Iterator iterator = axisService.getOperations();
resultBuf.append("<h3><a href=\"").append(prefix).append(axisService.getName()).append(
"?wsdl\">").append(axisService.getName()).append("</a></h3>");
if (iterator.hasNext()) {
resultBuf.append("Available operations <ul>");
for (; iterator.hasNext();) {
AxisOperation axisOperation = (AxisOperation) iterator.next();
resultBuf.append("<li>").append(
axisOperation.getName().getLocalPart()).append("</li>");
}
resultBuf.append("</ul>");
} else {
resultBuf.append("No operations specified for this service");
}
}
}
if ((erroneousServices != null) && !erroneousServices.isEmpty()) {
servicesFound = true;
resultBuf.append("<hr><h2><font color=\"blue\">Faulty Services</font></h2>");
Enumeration faultyservices = erroneousServices.keys();
while (faultyservices.hasMoreElements()) {
String faultyserviceName = (String) faultyservices.nextElement();
resultBuf.append("<h3><font color=\"blue\">").append(
faultyserviceName).append("</font></h3>");
}
}
if (!servicesFound) {
resultBuf.append("<h2>There are no services deployed</h2>");
}
resultBuf.append("</body></html>");
return resultBuf.toString();
}
/**
* Is the incoming URI is requesting service list and http.block_service_list=true in
* nhttp.properties
* @param incomingURI incoming URI
* @return whether to proceed with incomingURI
*/
protected boolean isServiceListBlocked(String incomingURI) {
String isBlocked = NHttpConfiguration.getInstance().isServiceListBlocked();
return (("/services").equals(incomingURI) || ("/services" + "/").equals(incomingURI)) &&
Boolean.parseBoolean(isBlocked);
}
/**
* Handles browser exception.
*
* @param response HttpResponse
* @param conn NHttpServerConnection
* @param os OutputStream
* @param msg message
* @param e Exception
*/
protected void handleBrowserException(HttpResponse response,
NHttpServerConnection conn, OutputStream os,
String msg, Exception e) {
if (e == null) {
log.error(msg);
} else {
log.error(msg, e);
}
if (!response.containsHeader(HTTP.TRANSFER_ENCODING)) {
response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
response.setReasonPhrase(msg);
//response.addHeader(CONTENT_TYPE, TEXT_HTML);
//serverHandler.commitResponseHideExceptions(conn, response);
try {
os.write(msg.getBytes());
os.close();
} catch (IOException ignore) {
}
}
if (conn != null) {
try {
conn.shutdown();
} catch (IOException ignore) {
}
}
}
/**
* Returns the service name.
*
* @param request HttpRequest
* @return service name as a String
*/
protected String getServiceName(HttpRequest request) {
String uri = request.getRequestLine().getUri();
String servicePath = cfgCtx.getServiceContextPath();
if (!servicePath.startsWith("/")) {
servicePath = "/" + servicePath;
}
String serviceName = null;
if (uri.startsWith(servicePath)) {
serviceName = uri.substring(servicePath.length());
if (serviceName.startsWith("/")) {
serviceName = serviceName.substring(1);
}
if (serviceName.indexOf("?") != -1) {
serviceName = serviceName.substring(0, serviceName.indexOf("?"));
}
} else {
// this may be a custom URI
String incomingURI = request.getRequestLine().getUri();
Map serviceURIMap = (Map) cfgCtx.getProperty(NhttpConstants.EPR_TO_SERVICE_NAME_MAP);
if (serviceURIMap != null) {
Set keySet = serviceURIMap.keySet();
for (Object key : keySet) {
if (incomingURI.toLowerCase().contains(((String) key).toLowerCase())) {
return (String) serviceURIMap.get(key);
}
}
}
}
if (serviceName != null) {
int opnStart = serviceName.indexOf("/");
if (opnStart != -1) {
serviceName = serviceName.substring(0, opnStart);
}
}
return serviceName;
}
public void handleException(String msg, Exception e) throws AxisFault {
log.error(msg, e);
throw new AxisFault(msg, e);
}
}