# PassThroughNHttpGetProcessor.java

    /*
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements. See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership. The ASF 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.passthru.api;

    import org.apache.axis2.AxisFault;
    import org.apache.axis2.addressing.EndpointReference;
    import org.apache.axis2.context.ConfigurationContext;
    import org.apache.axis2.context.MessageContext;
    import org.apache.axis2.description.AxisService;
    import org.apache.axis2.wsdl.WSDLConstants;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    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.nio.entity.ContentOutputStream;
    import org.apache.http.nio.util.SimpleOutputBuffer;
    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.apache.ws.commons.schema.XmlSchema;

    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.net.InetAddress;
    import java.net.NetworkInterface;
    import java.net.SocketException;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    import java.util.StringTokenizer;

    public class PassThroughNHttpGetProcessor implements HttpGetRequestProcessor {

    	private static final Log log = LogFactory.getLog(PassThroughNHttpGetProcessor.class);

    	private static final String LOCATION = "Location";
    	private static final String CONTENT_TYPE = "Content-Type";
    	private static final String TEXT_HTML = "text/html";
    	private static final String TEXT_XML = "text/xml";
    	private static final String PASS_THROUGH_RESPONSE_SOURCE_BUFFER = "synapse.response-source-buffer";
    	private static final String GET_REQUEST_HANDLED = "GET_REQUEST_HANDLED";

    	protected ConfigurationContext cfgCtx;

    	protected SourceHandler sourceHandler;

    	public void init(ConfigurationContext cfgCtx, SourceHandler handler)
    			throws AxisFault {

    		this.cfgCtx = cfgCtx;
    		this.sourceHandler = handler;
    	}

    	public void process(HttpRequest request, HttpResponse response,
    			MessageContext msgContext, NHttpServerConnection conn,
    			OutputStream ostream, boolean isRestDispatching) {

    		String uri = request.getRequestLine().getUri();
    		String serviceName = getServiceName(request);

    		Map<String, String> parameters = new HashMap<String, String>();
    		int pos = uri.indexOf("?");
    		if (pos != -1) {
    			msgContext.setTo(new EndpointReference(uri.substring(0, pos)));
    			StringTokenizer st = new StringTokenizer(uri.substring(pos + 1),
                                                         "&");
    			while (st.hasMoreTokens()) {
    				String param = st.nextToken();
    				pos = param.indexOf("=");
    				if (pos != -1) {
    					parameters.put(param.substring(0, pos),
    							param.substring(pos + 1));
    				} else {
    					parameters.put(param, null);
    				}
    			}
    		} else {
    			msgContext.setTo(new EndpointReference(uri));
    		}

            SimpleOutputBuffer outputBuffer = (SimpleOutputBuffer) conn.getContext().getAttribute(
                    PASS_THROUGH_RESPONSE_SOURCE_BUFFER);
            ContentOutputStream os = new ContentOutputStream(outputBuffer);

    		if (isServiceListBlocked(uri)) {
                sendResponseAndFinish(response, HttpStatus.SC_FORBIDDEN, conn, os, msgContext);
    		} else if (uri.equals("/favicon.ico")) {
    			response.addHeader(LOCATION, "http://ws.apache.org/favicon.ico");
                sendResponseAndFinish(response, HttpStatus.SC_MOVED_PERMANENTLY, conn, os, msgContext);
    		} else if (serviceName != null && parameters.containsKey("wsdl")) {
    			generateWsdl(response, msgContext, conn, os, serviceName, parameters);
    		} else if (serviceName != null && parameters.containsKey("wsdl2")) {
    			generateWsdl2(response, msgContext, conn, os, serviceName);
    		} else if (serviceName != null && parameters.containsKey("xsd")) {
    			generateXsd(response, msgContext, conn, os, serviceName, parameters);
    		} else {
    			msgContext.setProperty(PassThroughConstants.REST_GET_DELETE_INVOKE, true);
    		}
    	}

        private void sendResponseAndFinish(HttpResponse response, int status,
                                           NHttpServerConnection conn, OutputStream os,
                                           MessageContext msgContext) {
            response.setStatusCode(status);
            SourceContext.updateState(conn, ProtocolState.WSDL_RESPONSE_DONE);
            sourceHandler.commitResponseHideExceptions(conn, response);
            closeOutputStream(os);
            msgContext.setProperty(GET_REQUEST_HANDLED, Boolean.TRUE);
        }

        private void sendResponseAndFinish(HttpResponse response, byte[] data,
                                           NHttpServerConnection conn, OutputStream os,
                                           MessageContext msgContext) throws IOException {
            SourceContext.updateState(conn, ProtocolState.WSDL_RESPONSE_DONE);
            sourceHandler.commitResponseHideExceptions(conn, response);
            write(conn, os, data);
            closeOutputStream(os);
            msgContext.setProperty(GET_REQUEST_HANDLED, Boolean.TRUE);
        }

    	private void closeOutputStream(OutputStream os) {
    		try {
    			os.flush();
    			os.close();
    		} catch (IOException ignore) {
    		}
    	}

    	/**
    	 * Generate WSDL.
    	 *
    	 * @param response
    	 *            HttpResponse
    	 * @param msgContext
    	 *            MessageContext
    	 * @param conn
    	 *            NHttpServerConnection
    	 * @param os
    	 *            OutputStream
    	 * @param serviceName
    	 *            service name
    	 * @param parameters
    	 *            parameters
    	 */
    	protected void generateWsdl(HttpResponse response,
    			MessageContext msgContext, NHttpServerConnection conn,
    			OutputStream os, String serviceName,
    			Map<String, String> parameters) {
    		AxisService service = cfgCtx.getAxisConfiguration().getServices().get(serviceName);
    		if (service != null) {
    			try {
    				ByteArrayOutputStream output = new ByteArrayOutputStream();
    				String parameterValue = parameters.get("wsdl");
    				if (parameterValue == null) {
    					service.printWSDL(output, getIpAddress());
    				} else {
    					// here the parameter value should be the wsdl file name
    					service.printUserWSDL(output, parameterValue);
    				}

                    response.addHeader(CONTENT_TYPE, TEXT_XML);
                    sendResponseAndFinish(response, output.toByteArray(), conn, os, msgContext);

    			} catch (Exception e) {
    				handleBrowserException(response, msgContext, conn, os,
    						"Error generating ?wsdl output for service : " + serviceName, e);
    			}
    		} else {
                if (log.isDebugEnabled()) {
                    log.debug("Unable to find service: " + serviceName + " for WSDL generation.");
                }
    			msgContext.setProperty(PassThroughConstants.REST_GET_DELETE_INVOKE, true);
    		}
    	}

    	/**
    	 * Generate WSDL2.
    	 *
    	 * @param response
    	 *            HttpResponse
    	 * @param msgContext
    	 *            MessageContext
    	 * @param conn
    	 *            NHttpServerConnection
    	 * @param os
    	 *            OutputStream
    	 * @param serviceName
    	 *            service name
    	 */
    	protected void generateWsdl2(HttpResponse response,
    			MessageContext msgContext, NHttpServerConnection conn,
    			OutputStream os, String serviceName) {
    		AxisService service = cfgCtx.getAxisConfiguration().getServices()
                                        .get(serviceName);
    		if (service != null) {
    			String parameterValue = (String) service
    					.getParameterValue("serviceType");
    			if ("proxy".equals(parameterValue)
    					&& !isWSDLProvidedForProxyService(service)) {
    				handleBrowserException(response, msgContext, conn, os,
    						"No WSDL was provided for the Service " + serviceName
    								+ ". A WSDL cannot be generated.", null);
    			}
    			try {
    				ByteArrayOutputStream output = new ByteArrayOutputStream();
    				service.printWSDL2(output, getIpAddress());
    				response.addHeader(CONTENT_TYPE, TEXT_XML);
                    sendResponseAndFinish(response, output.toByteArray(), conn, os, msgContext);
    			} catch (Exception e) {
    				handleBrowserException(response, msgContext, conn, os,
    						"Error generating ?wsdl2 output for service : "
    								+ serviceName, e);
    			}
    		} else {
    			msgContext.setProperty(PassThroughConstants.REST_GET_DELETE_INVOKE, true);
    		}
    	}

    	/**
    	 * 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.contains("?")) {
    				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;
    	}

    	/**
    	 * Generates Schema.
    	 *
    	 * @param response
    	 *            HttpResponse
    	 * @param msgContext
    	 *            Current MessageContext
    	 * @param conn
    	 *            NHttpServerConnection
    	 * @param os
    	 *            OutputStream
    	 * @param serviceName
    	 *            service name
    	 * @param parameters
    	 *            url parameters
    	 */
    	protected void generateXsd(HttpResponse response,
    			MessageContext msgContext, NHttpServerConnection conn,
    			OutputStream os, String serviceName,
    			Map<String, String> parameters) {
    		if (parameters.get("xsd") == null || "".equals(parameters.get("xsd"))) {
    			AxisService service = cfgCtx.getAxisConfiguration().getServices()
                                            .get(serviceName);
    			if (service != null) {
    				try {
    					ByteArrayOutputStream output = new ByteArrayOutputStream();
    					service.printSchema(output);
    					response.addHeader(CONTENT_TYPE, TEXT_XML);
                        sendResponseAndFinish(response, output.toByteArray(), conn, os, msgContext);
    				} catch (Exception e) {
    					handleBrowserException(response, msgContext, conn, os,
    							"Error generating ?xsd output for service : "
    									+ serviceName, e);
    				}
    			} else {
    				msgContext.setProperty(PassThroughConstants.REST_GET_DELETE_INVOKE, true);
    			}

    		} else {
    			// cater for named XSDs - check for the xsd name
    			String schemaName = parameters.get("xsd");
    			AxisService service = cfgCtx.getAxisConfiguration().getServices()
                                            .get(serviceName);

    			if (service != null) {
    				// run the population logic just to be sure
    				service.populateSchemaMappings();
    				// write out the correct schema
    				Map schemaTable = service.getSchemaMappingTable();
    				XmlSchema schema = (XmlSchema) schemaTable.get(schemaName);
    				if (schema == null) {
    					int dotIndex = schemaName.indexOf('.');
    					if (dotIndex > 0) {
    						String schemaKey = schemaName.substring(0, dotIndex);
    						schema = (XmlSchema) schemaTable.get(schemaKey);
    					}
    				}
    				// schema found - write it to the stream
    				if (schema != null) {
    					try {
    						ByteArrayOutputStream output = new ByteArrayOutputStream();
    						schema.write(output);
    						response.addHeader(CONTENT_TYPE, TEXT_XML);
    						sourceHandler.commitResponseHideExceptions(conn,
    								response);
                            write(conn, os, output.toByteArray());
    						closeOutputStream(os);
                            msgContext.setProperty(GET_REQUEST_HANDLED, Boolean.TRUE);
    					} catch (Exception e) {
    						handleBrowserException(response, msgContext, conn, os,
    								"Error generating named ?xsd output for service : "
    										+ serviceName, e);
    					}

    				} else {
    					// no schema available by that name - send 404
    					response.setStatusCode(HttpStatus.SC_NOT_FOUND);
    					closeOutputStream(os);
                        msgContext.setProperty(GET_REQUEST_HANDLED, Boolean.TRUE);
    				}
    			} else {
    				msgContext.setProperty(PassThroughConstants.REST_GET_DELETE_INVOKE, true);
    			}
    		}
    	}

    	/**
         * Handles browser exception.
         *
         * @param response HttpResponse
         * @param conn     NHttpServerConnection
         * @param os       OutputStream
         * @param msg      message
         * @param e        Exception
         */
        protected void handleBrowserException(HttpResponse response, MessageContext msgContext,
                                              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);
                sourceHandler.commitResponseHideExceptions(conn, response);
                try {
                    write(conn, os, msg.getBytes());
                    os.close();
                } catch (IOException ignore) {
                }
            }

            if (conn != null) {
                try {
                    conn.shutdown();
                } catch (IOException ignore) {
                }
            }
            msgContext.setProperty(GET_REQUEST_HANDLED, Boolean.TRUE);
        }

    	/**
    	 * 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);
    	}

        /**
         * Checks whether a wsdl is provided for a proxy service.
         *
         * @param service AxisService
         * @return whether the wsdl is provided or not
         */
        protected boolean isWSDLProvidedForProxyService(AxisService service) {
            boolean isWSDLProvided = false;
            if (service.getParameterValue(WSDLConstants.WSDL_4_J_DEFINITION) != null ||
                    service.getParameterValue(WSDLConstants.WSDL_20_DESCRIPTION) != null) {
                isWSDLProvided = true;
            }
            return isWSDLProvided;
        }

    	   /**
         * Whatever this method returns as the IP is ignored by the actual http/s listener when
         * its getServiceEPR is invoked. This was originally copied from axis2
         *
         * @return Returns String.
         * @throws java.net.SocketException if the socket can not be accessed
         */
        protected static String getIpAddress() throws SocketException {
            Enumeration e = NetworkInterface.getNetworkInterfaces();
            String address = "127.0.0.1";

            while (e.hasMoreElements()) {
                NetworkInterface networkInterface = (NetworkInterface) e.nextElement();
                Enumeration addresses = networkInterface.getInetAddresses();

                while (addresses.hasMoreElements()) {
                    InetAddress ip = (InetAddress) addresses.nextElement();
                    if (!ip.isLoopbackAddress() && isIP(ip.getHostAddress())) {
                        return ip.getHostAddress();
                    }
                }
            }
            return address;
        }

        protected static boolean isIP(String hostAddress) {
            return hostAddress.split("[.]").length == 4;
        }

        private void write(NHttpServerConnection conn, OutputStream os,
                           byte[] data) throws IOException {
            synchronized (conn.getContext()) {
                // The SimpleOutputBuffer on which this output stream is based is not thread safe.
                // Explicit synchronization required.
                // Do not worry about running out of buffer space.
                // SimpleOutputBuffer expands to fit the data.
                os.write(data);
            }
        }

    }