# DefaultHttpGetProcessor.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.nhttp;

    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.AxisOperation;
    import org.apache.axis2.description.AxisService;
    import org.apache.axis2.description.Parameter;
    import org.apache.axis2.engine.AxisEngine;
    import org.apache.axis2.transport.http.HTTPTransportReceiver;
    import org.apache.axis2.util.JavaUtils;
    import org.apache.axis2.util.MessageContextBuilder;
    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.protocol.HTTP;
    import org.apache.synapse.transport.nhttp.util.RESTUtil;
    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.*;

    /**
     * Default http Get processor implementation for Synapse.
     */
    public class DefaultHttpGetProcessor implements HttpGetRequestProcessor {
        private static final Log log = LogFactory.getLog(DefaultHttpGetProcessor.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";

        protected ConfigurationContext cfgCtx;

        protected ServerHandler serverHandler;

        public void init(ConfigurationContext cfgCtx, ServerHandler serverHandler) throws AxisFault {
            this.cfgCtx = cfgCtx;
            this.serverHandler = serverHandler;
        }

        /**
         * Process the HTTP GET request.
         *
         * @param request    The HttpRequest
         * @param response   The HttpResponse
         * @param msgContext The MessageContext
         * @param conn       The NHttpServerConnection
         * @param os         The OutputStream
         */
        public void process(HttpRequest request,
                            HttpResponse response,
                            MessageContext msgContext,
                            NHttpServerConnection conn,
                            OutputStream os,
                            boolean isRestDispatching) {

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

            String servicePath = cfgCtx.getServiceContextPath();
            if (!servicePath.startsWith("/")) {
                servicePath = "/" + servicePath;
            }

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

            if (isServiceListBlocked(uri)) {
                response.setStatusCode(HttpStatus.SC_FORBIDDEN);
                serverHandler.commitResponseHideExceptions(conn,  response);
            } else if (uri.equals("/favicon.ico")) {
                response.setStatusCode(HttpStatus.SC_MOVED_PERMANENTLY);
                response.addHeader(LOCATION, "http://wso2.org/favicon.ico");
                serverHandler.commitResponseHideExceptions(conn, response);

    //        } else if (!uri.startsWith(servicePath)) {
    //            response.setStatusCode(HttpStatus.SC_MOVED_PERMANENTLY);
    //            response.addHeader(LOCATION, servicePath + "/");
    //            serverHandler.commitResponseHideExceptions(conn, response);

            } else if (serviceName != null && parameters.containsKey("wsdl")) {
                generateWsdl(request, response, msgContext,
                        conn, os, serviceName, parameters, isRestDispatching);
                return;
            } else if (serviceName != null && parameters.containsKey("wsdl2")) {
                generateWsdl2(request, response, msgContext,
                        conn, os, serviceName, isRestDispatching);
                return;
            } else if (serviceName != null && parameters.containsKey("xsd")) {
                generateXsd(request, response, msgContext, conn, os, serviceName, parameters, isRestDispatching);
                return;
            } else if (serviceName != null && parameters.containsKey("info")) {
                generateServiceDetailsPage(response, conn, os, serviceName);
            } else if (uri.startsWith(servicePath) &&
                    (serviceName == null || serviceName.length() == 0)) {
                generateServicesList(response, conn, os, servicePath);
            } else {
                processGetAndDelete(request, response, msgContext,
                        conn, os, "GET", isRestDispatching);
                return;
            }

            // make sure that the output stream is flushed and closed properly
            closeOutputStream(os);
        }

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

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

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

        /**
         * 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);
                serverHandler.commitResponseHideExceptions(conn, response);
                os.write(bytes);

            } catch (IOException e) {
                handleBrowserException(response, conn, os,
                        "Error generating services list", e);
            }
        }

        /**
         * Generates service details page.
         *
         * @param response    HttpResponse
         * @param conn        NHttpServerConnection
         * @param os          OutputStream
         * @param serviceName service name
         */
        protected void generateServiceDetailsPage(HttpResponse response,
                                                  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, conn, os,
                            "No WSDL was provided for the Service " + serviceName +
                                    ". A WSDL cannot be generated.", null);
                }
                try {
                    byte[] bytes =
                            HTTPTransportReceiver.printServiceHTML(serviceName, cfgCtx).getBytes();
                    response.addHeader(CONTENT_TYPE, TEXT_HTML);
                    serverHandler.commitResponseHideExceptions(conn, response);
                    os.write(bytes);

                } catch (IOException e) {
                    handleBrowserException(response, conn, os,
                            "Error generating service details page for : " + serviceName, e);
                }
            } else {
                handleBrowserException(response, conn, os,
                        "Invalid service : " + serviceName, null);
            }
        }

        /**
         * Generates Schema.
         *
         * @param response    HttpResponse
         * @param conn        NHttpServerConnection
         * @param os          OutputStream
         * @param serviceName service name
         * @param parameters  url parameters
         */
        protected void generateXsd(HttpRequest request, HttpResponse response,
                                   MessageContext messageCtx, NHttpServerConnection conn,
                                   OutputStream os, String serviceName,
                                   Map<String, String> parameters, boolean isRestDispatching) {
            if (parameters.get("xsd") == null || "".equals(parameters.get("xsd"))) {
                AxisService service = cfgCtx.getAxisConfiguration()
                        .getServices().get(serviceName);
                if (service != null) {
                    try {
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        service.printSchema(baos);
                        response.addHeader(CONTENT_TYPE, TEXT_XML);
                        serverHandler.commitResponseHideExceptions(conn, response);
                        os.write(baos.toByteArray());
                        closeOutputStream(os);

                    } catch (Exception e) {
                        handleBrowserException(response, conn, os,
                                "Error generating ?xsd output for service : " + serviceName, e);
                    }
                } else {
                    processGetAndDelete(request, response, messageCtx, conn, os,
                            serviceName, isRestDispatching);
                }

            } 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 baos = new ByteArrayOutputStream();
                            schema.write(baos);
                            response.addHeader(CONTENT_TYPE, TEXT_XML);
                            serverHandler.commitResponseHideExceptions(conn, response);
                            os.write(baos.toByteArray());
                            closeOutputStream(os);
                        } catch (Exception e) {
                            handleBrowserException(response, 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);
                    }
                } else {
                    processGetAndDelete(request, response, messageCtx, conn, os,
                            serviceName, isRestDispatching);
                }
            }
        }

        /**
         * Generate WSDL2.
         *
         * @param request           HttpRequest
         * @param response          HttpResponse
         * @param msgContext        MessageContext
         * @param conn              NHttpServerConnection
         * @param os                OutputStream
         * @param serviceName       service name
         * @param isRestDispatching weather nhttp should do rest dispatching
         */
        protected void generateWsdl2(HttpRequest request, HttpResponse response,
                                     MessageContext msgContext,
                                     NHttpServerConnection conn,
                                     OutputStream os, String serviceName, boolean isRestDispatching) {
            AxisService service = cfgCtx.getAxisConfiguration().
                    getServices().get(serviceName);
            if (service != null) {
                String parameterValue = (String) service.getParameterValue("serviceType");
                if ("proxy".equals(parameterValue) && !isWSDLProvidedForProxyService(service)) {
                    handleBrowserException(response, conn, os,
                            "No WSDL was provided for the Service " + serviceName +
                                    ". A WSDL cannot be generated.", null);
                }
                try {
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    service.printWSDL2(baos, getIpAddress());
                    response.addHeader(CONTENT_TYPE, TEXT_XML);
                    serverHandler.commitResponseHideExceptions(conn, response);
                    os.write(baos.toByteArray());
                    closeOutputStream(os);

                } catch (Exception e) {
                    handleBrowserException(response, conn, os,
                            "Error generating ?wsdl2 output for service : " + serviceName, e);
                }
            } else {
                processGetAndDelete(request, response, msgContext,
                        conn, os, "GET", isRestDispatching);
            }
        }

        /**
         * Generate WSDL.
         *
         * @param request           HttpRequest
         * @param response          HttpResponse
         * @param msgContext        MessageContext
         * @param conn              NHttpServerConnection
         * @param os                OutputStream
         * @param serviceName       service name
         * @param parameters        parameters
         * @param isRestDispatching if restDispatching is on
         */
        protected void generateWsdl(HttpRequest request, HttpResponse response,
                                    MessageContext msgContext,
                                    NHttpServerConnection conn,
                                    OutputStream os, String serviceName,
                                    Map<String, String> parameters, boolean isRestDispatching) {
            AxisService service = cfgCtx.getAxisConfiguration().
                    getServices().get(serviceName);
            if (service != null) {
                try {
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    String parameterValue = parameters.get("wsdl");
                    if (parameterValue == null) {
                        service.printWSDL(baos, getIpAddress());
                    } else {
                        // here the parameter value should be the wsdl file name
                        service.printUserWSDL(baos, parameterValue);
                    }
                    response.addHeader(CONTENT_TYPE, TEXT_XML);
                    serverHandler.commitResponseHideExceptions(conn, response);
                    os.write(baos.toByteArray());
                    closeOutputStream(os);

                } catch (Exception e) {
                    handleBrowserException(response, conn, os,
                            "Error generating ?wsdl output for service : " + serviceName, e);
                }
            } else {
                processGetAndDelete(request, response, msgContext,
                        conn, os, "GET", isRestDispatching);
            }
        }

        /**
         * Calls the RESTUtil to process GET and DELETE Request
         *
         * @param request           HttpRequest
         * @param response          HttpResponse
         * @param msgContext        MessageContext
         * @param conn              NHttpServerConnection
         * @param os                OutputStream
         * @param method            HTTP method, either GET or DELETE
         * @param isRestDispatching weather transport should do rest dispatching
         */
        protected void processGetAndDelete(HttpRequest request, HttpResponse response,
                                           MessageContext msgContext,
                                           NHttpServerConnection conn, OutputStream os,
                                           String method, boolean isRestDispatching) {
            try {
                RESTUtil.processGetAndDeleteRequest(
                        msgContext, os, request.getRequestLine().getUri(),
                        request.getFirstHeader(HTTP.CONTENT_TYPE), method, isRestDispatching);
                // do not let the output stream close (as by default below) since
                // we are serving this GET/DELETE request through the Synapse engine
            } catch (AxisFault axisFault) {
                handleException(response, msgContext, conn, os,
                        "Error processing " + method + " request for: " +
                                request.getRequestLine().getUri(), axisFault);
            }

        }

        /**
         * Handles exception.
         *
         * @param response   HttpResponse
         * @param msgContext MessageContext
         * @param conn       NHttpServerConnection
         * @param os         OutputStream
         * @param msg        message
         * @param e          Exception
         */
        protected void handleException(HttpResponse response, MessageContext msgContext,
                                       NHttpServerConnection conn,
                                       OutputStream os, String msg, Exception e) {
            if (e == null) {
                log.error(msg);
            } else {
                log.error(msg, e);
            }

            if (e == null) {
                e = new Exception(msg);
            }

            try {
                MessageContext faultContext = MessageContextBuilder.createFaultMessageContext(
                        msgContext, e);
                AxisEngine.sendFault(faultContext);

            } catch (Exception ex) {
                response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
                response.addHeader(CONTENT_TYPE, TEXT_XML);
                serverHandler.commitResponseHideExceptions(conn, response);

                try {
                    os.write(msg.getBytes());
                    if (ex != null) {
                        os.write(ex.getMessage().getBytes());
                    }
                } catch (IOException ignore) {
                }

                if (conn != null) {
                    try {
                        conn.shutdown();
                    } catch (IOException ignore) {
                    }
                }
            }
        }

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

        /**
         * 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 netface = (NetworkInterface) e.nextElement();
                Enumeration addresses = netface.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;
        }

        /**
         * 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 parameter = axisService.getParameter(
                            NhttpConstants.HIDDEN_SERVICE_PARAM_NAME);
                    if (axisService.getName().startsWith("__") ||
                            (parameter != null && JavaUtils.isTrueExplicitly(parameter.getValue()))) {
                        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();
        }

    }