org.apache.synapse.rest.API.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.synapse.rest.API.java

Source

/*
*  Copyright (c) 2005-2010, 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.rest;

import org.apache.axis2.Constants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.protocol.HTTP;
import org.apache.synapse.ManagedLifecycle;
import org.apache.synapse.Mediator;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseConstants;
import org.apache.synapse.transport.customlogsetter.CustomLogSetter;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.rest.dispatch.DispatcherHelper;
import org.apache.synapse.rest.dispatch.RESTDispatcher;
import org.apache.synapse.rest.version.DefaultStrategy;
import org.apache.synapse.rest.version.URLBasedVersionStrategy;
import org.apache.synapse.rest.version.VersionStrategy;
import org.apache.synapse.config.xml.rest.VersionStrategyFactory;
import org.apache.synapse.transport.nhttp.NhttpConstants;

import java.util.*;

public class API extends AbstractRESTProcessor implements ManagedLifecycle {

    private String host;
    private int port = -1;
    private String context;
    private Map<String, Resource> resources = new LinkedHashMap<String, Resource>();
    private List<Handler> handlers = new ArrayList<Handler>();

    private int protocol = RESTConstants.PROTOCOL_HTTP_AND_HTTPS;

    private VersionStrategy versionStrategy = new DefaultStrategy(this);

    private String fileName;

    private Log apiLog;
    private static final Log trace = LogFactory.getLog(SynapseConstants.TRACE_LOGGER);

    private int traceState = SynapseConstants.TRACING_UNSET;

    private String artifactContainerName;

    private boolean isEdited = false;

    public API(String name, String context) {
        super(name);
        setContext(context);
    }

    public void setContext(String context) {
        if (!context.startsWith("/")) {
            handleException("API context must begin with '/' character");
        }
        this.context = RESTUtils.trimTrailingSlashes(context);
        apiLog = LogFactory.getLog(SynapseConstants.API_LOGGER_PREFIX + name);

    }

    public void setArtifactContainerName(String name) {
        artifactContainerName = name;
    }

    public String getArtifactContainerName() {
        return artifactContainerName;
    }

    public boolean isEdited() {
        return isEdited;
    }

    public void setIsEdited(boolean isEdited) {
        this.isEdited = isEdited;
    }

    public void setLogSetterValue() {
        CustomLogSetter.getInstance().setLogAppender(artifactContainerName);
    }

    /**
     * Get the fully qualified name of this API
     *
     * @return returns the key combination for API NAME + VERSION
     */
    public String getName() {
        // check if a versioning strategy exists
        if (versionStrategy.getVersion() != null && !"".equals(versionStrategy.getVersion())) {
            return name + ":v" + versionStrategy.getVersion();
        }
        return name;
    }

    public int getProtocol() {
        return protocol;
    }

    public void setProtocol(int protocol) {
        this.protocol = protocol;
    }

    public String getAPIName() {
        return name;
    }

    public String getVersion() {
        return versionStrategy.getVersion();
    }

    public String getContext() {
        return context;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public int getTraceState() {
        return traceState;
    }

    public void setTraceState(int traceState) {
        this.traceState = traceState;
    }

    public void addResource(Resource resource) {
        DispatcherHelper dispatcherHelper = resource.getDispatcherHelper();
        if (dispatcherHelper != null) {
            String mapping = dispatcherHelper.getString();
            for (Resource r : resources.values()) {
                DispatcherHelper helper = r.getDispatcherHelper();
                if (helper != null && helper.getString().equals(mapping) && resourceMatches(resource, r)) {
                    handleException("Two resources cannot have the same path mapping and methods");
                }
            }
        } else {
            for (Resource r : resources.values()) {
                DispatcherHelper helper = r.getDispatcherHelper();
                if (helper == null) {
                    handleException("Only one resource can be designated as default");
                }
            }
        }
        resources.put(resource.getName(), resource);
    }

    private boolean resourceMatches(Resource r1, Resource r2) {
        String[] methods1 = r1.getMethods();
        String[] methods2 = r2.getMethods();
        for (String m1 : methods1) {
            for (String m2 : methods2) {
                if (m1.equals(m2)) {
                    return true;
                }
            }
        }
        return false;
    }

    public Resource[] getResources() {
        return resources.values().toArray(new Resource[resources.size()]);
    }

    public void addHandler(Handler handler) {
        handlers.add(handler);
    }

    public Handler[] getHandlers() {
        return handlers.toArray(new Handler[handlers.size()]);
    }

    boolean canProcess(MessageContext synCtx) {
        if (synCtx.isResponse()) {
            String apiName = (String) synCtx.getProperty(RESTConstants.SYNAPSE_REST_API);
            String version = synCtx.getProperty(RESTConstants.SYNAPSE_REST_API_VERSION) == null ? ""
                    : (String) synCtx.getProperty(RESTConstants.SYNAPSE_REST_API_VERSION);
            //if api name is not matching OR versions are different
            if (!getName().equals(apiName) || !versionStrategy.getVersion().equals(version)) {
                return false;
            }
        } else {
            String path = RESTUtils.getFullRequestPath(synCtx);
            if (!path.startsWith(context + "/") && !path.startsWith(context + "?") && !context.equals(path)
                    && !"/".equals(context)) {
                auditDebug("API context: " + context + " does not match request URI: " + path);
                return false;
            }

            if (!versionStrategy.isMatchingVersion(synCtx)) {
                return false;
            }

            org.apache.axis2.context.MessageContext msgCtx = ((Axis2MessageContext) synCtx)
                    .getAxis2MessageContext();

            if (host != null || port != -1) {
                String hostHeader = getHostHeader(msgCtx);
                if (hostHeader != null) {
                    if (host != null && !host.equals(extractHostName(hostHeader))) {
                        auditDebug("API host: " + host + " does not match host information " + "in the request: "
                                + hostHeader);
                        return false;
                    }

                    if (port != -1 && port != extractPortNumber(hostHeader, msgCtx.getIncomingTransportName())) {
                        auditDebug("API port: " + port + " does not match port information " + "in the request: "
                                + hostHeader);
                        return false;
                    }
                } else {
                    auditDebug("Host information not available on the message");
                    return false;
                }
            }
            if (protocol == RESTConstants.PROTOCOL_HTTP_ONLY
                    && !Constants.TRANSPORT_HTTP.equals(msgCtx.getIncomingTransportName())) {
                if (log.isDebugEnabled()) {
                    log.debug("Protocol information does not match - Expected HTTP");
                }
                synCtx.setProperty(SynapseConstants.TRANSPORT_DENIED, new Boolean(true));
                synCtx.setProperty(SynapseConstants.IN_TRANSPORT, msgCtx.getTransportIn().getName());
                log.warn("Trying to access API : " + name + " on restricted transport chanel ["
                        + msgCtx.getTransportIn().getName() + "]");
                return false;
            }

            if (protocol == RESTConstants.PROTOCOL_HTTPS_ONLY
                    && !Constants.TRANSPORT_HTTPS.equals(msgCtx.getIncomingTransportName())) {
                if (log.isDebugEnabled()) {
                    log.debug("Protocol information does not match - Expected HTTPS");
                }
                synCtx.setProperty(SynapseConstants.TRANSPORT_DENIED, new Boolean(true));
                synCtx.setProperty(SynapseConstants.IN_TRANSPORT, msgCtx.getTransportIn().getName());
                log.warn("Trying to access API : " + name + " on restricted transport chanel ["
                        + msgCtx.getTransportIn().getName() + "]");
                return false;
            }
        }

        return true;
    }

    void process(MessageContext synCtx) {
        auditDebug("Processing message with ID: " + synCtx.getMessageID() + " through the " + "API: " + name);

        synCtx.setProperty(RESTConstants.SYNAPSE_REST_API, getName());
        synCtx.setProperty(RESTConstants.SYNAPSE_REST_API_VERSION, versionStrategy.getVersion());
        synCtx.setProperty(RESTConstants.REST_API_CONTEXT, context);
        synCtx.setProperty(RESTConstants.SYNAPSE_REST_API_VERSION_STRATEGY, versionStrategy.getVersionType());

        // get API log for this message and attach to the message context
        ((Axis2MessageContext) synCtx).setServiceLog(apiLog);

        // Calculate REST_URL_POSTFIX from full request path
        String restURLPostfix = (String) synCtx.getProperty(RESTConstants.REST_FULL_REQUEST_PATH);
        if (!synCtx.isResponse() && restURLPostfix != null) { // Skip for response path
            if (!restURLPostfix.startsWith("/")) {
                restURLPostfix = "/" + restURLPostfix;
            }
            if (restURLPostfix.startsWith(context)) {
                restURLPostfix = restURLPostfix.substring(context.length());
            }
            if (versionStrategy instanceof URLBasedVersionStrategy) {
                String version = versionStrategy.getVersion();
                if (restURLPostfix.startsWith(version)) {
                    restURLPostfix = restURLPostfix.substring(version.length());
                } else if (restURLPostfix.startsWith("/" + version)) {
                    restURLPostfix = restURLPostfix.substring(version.length() + 1);
                }
            }
            ((Axis2MessageContext) synCtx).getAxis2MessageContext().setProperty(NhttpConstants.REST_URL_POSTFIX,
                    restURLPostfix);
        }

        for (Handler handler : handlers) {
            auditDebug("Processing message with ID: " + synCtx.getMessageID() + " through " + "handler: "
                    + handler.getClass().getName());

            boolean proceed;
            if (synCtx.isResponse()) {
                proceed = handler.handleResponse(synCtx);
            } else {
                proceed = handler.handleRequest(synCtx);
            }

            if (!proceed) {
                return;
            }
        }

        if (synCtx.isResponse()) {
            String resourceName = (String) synCtx.getProperty(RESTConstants.SYNAPSE_RESOURCE);
            if (resourceName != null) {
                Resource resource = resources.get(resourceName);
                if (resource != null) {
                    resource.process(synCtx);
                }
            } else if (log.isDebugEnabled()) {
                auditDebug("No resource information on the response: " + synCtx.getMessageID());
            }
            return;
        }

        String path = RESTUtils.getFullRequestPath(synCtx);
        String subPath;
        if (versionStrategy.getVersionType().equals(VersionStrategyFactory.TYPE_URL)) {
            //for URL based
            //request --> http://{host:port}/context/version/path/to/resource
            subPath = path.substring(context.length() + versionStrategy.getVersion().length() + 1);
        } else {
            subPath = path.substring(context.length());
        }
        if ("".equals(subPath)) {
            subPath = "/";
        }
        synCtx.setProperty(RESTConstants.REST_SUB_REQUEST_PATH, subPath);

        org.apache.axis2.context.MessageContext msgCtx = ((Axis2MessageContext) synCtx).getAxis2MessageContext();
        String hostHeader = getHostHeader(msgCtx);
        if (hostHeader != null) {
            synCtx.setProperty(RESTConstants.REST_URL_PREFIX,
                    msgCtx.getIncomingTransportName() + "://" + hostHeader);
        }

        Set<Resource> acceptableResources = new LinkedHashSet<Resource>();
        for (Resource r : resources.values()) {
            if (r.canProcess(synCtx)) {
                acceptableResources.add(r);
            }
        }

        boolean processed = false;
        if (!acceptableResources.isEmpty()) {
            for (RESTDispatcher dispatcher : RESTUtils.getDispatchers()) {
                Resource resource = dispatcher.findResource(synCtx, acceptableResources);
                if (resource != null) {
                    resource.process(synCtx);
                    processed = true;
                    break;
                }
            }
        }

        if (!processed) {
            auditDebug("No matching resource was found for the request: " + synCtx.getMessageID());
            Mediator sequence = synCtx.getSequence(RESTConstants.NO_MATCHING_RESOURCE_HANDLER);
            if (sequence != null) {
                sequence.mediate(synCtx);
            }
        }
    }

    private String getHostHeader(org.apache.axis2.context.MessageContext msgCtx) {
        Map transportHeaders = (Map) msgCtx.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
        String hostHeader = null;
        if (transportHeaders != null) {
            hostHeader = (String) transportHeaders.get(HTTP.TARGET_HOST);
        }

        if (hostHeader == null) {
            hostHeader = (String) msgCtx.getProperty(NhttpConstants.SERVICE_PREFIX);
        }
        return hostHeader;
    }

    private String extractHostName(String hostHeader) {
        int index = hostHeader.indexOf(':');
        if (index != -1) {
            return hostHeader.substring(0, index);
        } else {
            return hostHeader;
        }
    }

    private int extractPortNumber(String hostHeader, String transport) {
        int index = hostHeader.indexOf(':');
        if (index != -1) {
            return Integer.parseInt(hostHeader.substring(index + 1));
        } else if (Constants.TRANSPORT_HTTP.equals(transport)) {
            return 80;
        } else {
            return 443;
        }
    }

    public void init(SynapseEnvironment se) {
        if (resources.isEmpty()) {
            handleException("The API: " + getName() + " has been configured without " + "any resource definitions");
        }

        auditInfo("Initializing API: " + getName());
        for (Resource resource : resources.values()) {
            resource.init(se);
        }

        for (Handler handler : handlers) {
            if (handler instanceof ManagedLifecycle) {
                ((ManagedLifecycle) handler).init(se);
            }
        }
    }

    public void destroy() {
        auditInfo("Destroying API: " + getName());
        for (Resource resource : resources.values()) {
            resource.destroy();
        }

        for (Handler handler : handlers) {
            if (handler instanceof ManagedLifecycle) {
                ((ManagedLifecycle) handler).destroy();
            }
        }
    }

    public VersionStrategy getVersionStrategy() {
        return versionStrategy;
    }

    public void setVersionStrategy(VersionStrategy versionStrategy) {
        this.versionStrategy = versionStrategy;
    }

    public Resource getResource(String resourceName) {
        return resources.get(resourceName);
    }

    private boolean trace() {
        return traceState == SynapseConstants.TRACING_ON;
    }

    /**
     * Write to the general log, as well as any API specific logs the audit message at INFO
     * @param message the INFO level audit message
     */
    private void auditInfo(String message) {
        log.info(message);
        apiLog.info(message);

        //TODO - Implement 'trace' attribute support in API configuration.
        if (trace()) {
            trace.info(message);
        }
    }

    /**
     * Write to the general log, as well as any API specific logs the audit message at DEBUG
     * @param message the DEBUG level audit message
     */
    private void auditDebug(String message) {
        if (log.isDebugEnabled()) {
            log.debug(message);
            apiLog.debug(message);

            //TODO - Implement 'trace' attribute support in API configuration.
            if (trace()) {
                trace.debug(message);
            }

        }

    }
}