/* * 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.axiom.util.UIDGenerator; import org.apache.axis2.Constants; import org.apache.http.HttpHeaders; 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.SynapseException; import org.apache.synapse.aspects.AspectConfigurable; import org.apache.synapse.aspects.AspectConfiguration; import org.apache.synapse.aspects.ComponentType; import org.apache.synapse.aspects.flow.statistics.StatisticIdentityGenerator; import org.apache.synapse.aspects.flow.statistics.collectors.CloseEventCollector; import org.apache.synapse.aspects.flow.statistics.collectors.OpenEventCollector; import org.apache.synapse.aspects.flow.statistics.collectors.RuntimeStatisticCollector; import org.apache.synapse.aspects.flow.statistics.data.artifact.ArtifactHolder; import org.apache.synapse.core.SynapseEnvironment; import org.apache.synapse.core.axis2.Axis2MessageContext; import org.apache.synapse.core.axis2.Axis2Sender; import org.apache.synapse.mediators.MediatorFaultHandler; import org.apache.synapse.mediators.base.SequenceMediator; import org.apache.synapse.rest.dispatch.DispatcherHelper; import org.apache.synapse.transport.nhttp.NhttpConstants; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.HashSet; import java.util.Map; import java.util.Set; public class Resource extends AbstractRESTProcessor implements ManagedLifecycle, AspectConfigurable { /** * List of HTTP methods applicable on this method. Empty list means all methods * are applicable. */ private Set<String> methods = new HashSet<String>(4); private String contentType; private String userAgent; private int protocol = RESTConstants.PROTOCOL_HTTP_AND_HTTPS; AspectConfiguration aspectConfiguration; /** * In-lined sequence to be executed upon receiving messages */ private SequenceMediator inSequence; private SequenceMediator outSequence; private SequenceMediator faultSequence; /** * Identifier of the sequence to be executed upon receiving a message */ private String inSequenceKey; private String outSequenceKey; private String faultSequenceKey; /** * DispatcherHelper instance which is used to determine whether a particular resource * should be dispatched to this resource or not */ private DispatcherHelper dispatcherHelper; public Resource() { super(UIDGenerator.generateUID()); } protected String getName() { return name; } public SequenceMediator getInSequence() { return inSequence; } public void setInSequence(SequenceMediator inSequence) { this.inSequence = inSequence; } public SequenceMediator getOutSequence() { return outSequence; } public void setOutSequence(SequenceMediator outSequence) { this.outSequence = outSequence; } public String getInSequenceKey() { return inSequenceKey; } public void setInSequenceKey(String inSequenceKey) { this.inSequenceKey = inSequenceKey; } public String getOutSequenceKey() { return outSequenceKey; } public void setOutSequenceKey(String outSequenceKey) { this.outSequenceKey = outSequenceKey; } public SequenceMediator getFaultSequence() { return faultSequence; } public void setFaultSequence(SequenceMediator faultSequence) { this.faultSequence = faultSequence; } public String getFaultSequenceKey() { return faultSequenceKey; } public void setFaultSequenceKey(String faultSequenceKey) { this.faultSequenceKey = faultSequenceKey; } public boolean addMethod(String method) { for (RESTConstants.METHODS allowedMethod : RESTConstants.METHODS.values()) { if (allowedMethod.name().equals(method)) { methods.add(method); return true; } } return false; } public String[] getMethods() { return methods.toArray(new String[methods.size()]); } /** * Helper method to check whether API supports the incoming HTTP method. * * @param method * @return true if support false otherwise. */ public boolean hasMatchingMethod(String method) { if (RESTConstants.METHOD_OPTIONS.equals(method)) { return true; // OPTIONS requests are always welcome } else if (!methods.isEmpty()) { if (!methods.contains(method)) { if (log.isDebugEnabled()) { log.debug("HTTP method does not match"); } return false; } } return true; } public DispatcherHelper getDispatcherHelper() { return dispatcherHelper; } public void setDispatcherHelper(DispatcherHelper dispatcherHelper) { this.dispatcherHelper = dispatcherHelper; } public String getContentType() { return contentType; } public void setContentType(String contentType) { if (contentType.indexOf('/') == -1 || contentType.split("/").length != 2) { throw new SynapseException("Invalid content type: " + contentType); } this.contentType = contentType; } public String getUserAgent() { return userAgent; } public void setUserAgent(String userAgent) { this.userAgent = userAgent; } public int getProtocol() { return protocol; } public void setProtocol(int protocol) { this.protocol = protocol; } @Override boolean canProcess(MessageContext synCtx) { if (synCtx.isResponse()) { return true; } org.apache.axis2.context.MessageContext msgCtx = ((Axis2MessageContext) synCtx). getAxis2MessageContext(); if (protocol == RESTConstants.PROTOCOL_HTTP_ONLY && !Constants.TRANSPORT_HTTP.equals(msgCtx.getIncomingTransportName())) { if (log.isDebugEnabled()) { log.debug("Protocol information does not match - Expected HTTP"); } 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"); } return false; } String method = (String) msgCtx.getProperty(Constants.Configuration.HTTP_METHOD); synCtx.setProperty(RESTConstants.REST_METHOD, method); if (RESTConstants.METHOD_OPTIONS.equals(method)) { return true; // OPTIONS requests are always welcome } else if (!methods.isEmpty()) { if (!methods.contains(method)) { if (log.isDebugEnabled()) { log.debug("HTTP method does not match"); } return false; } } Map transportHeaders = (Map) msgCtx.getProperty( org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS); if ((contentType != null || userAgent != null) && transportHeaders == null) { if (log.isDebugEnabled()) { log.debug("Transport headers not available on the message"); } return false; } boolean hasPayload = !Boolean.TRUE.equals(msgCtx.getProperty(NhttpConstants.NO_ENTITY_BODY)); if (contentType != null && hasPayload) { String type = (String) transportHeaders.get(HTTP.CONTENT_TYPE); if (!contentType.equals(type)) { if (log.isDebugEnabled()) { log.debug("Content type does not match - Expected: " + contentType + ", " + "Found: " + type); } return false; } } if (userAgent != null) { String agent = (String) transportHeaders.get(HTTP.USER_AGENT); if (agent == null || !agent.matches(this.userAgent)) { if (log.isDebugEnabled()) { log.debug("User agent does not match - Expected: " + userAgent + ", " + "Found: " + agent); } return false; } } return true; } void process(MessageContext synCtx) { Integer statisticReportingIndex = null; boolean isStatisticsEnabled = RuntimeStatisticCollector.isStatisticsEnabled(); if (!synCtx.isResponse()) { if (getDispatcherHelper() != null) { synCtx.setProperty(RESTConstants.REST_URL_PATTERN, getDispatcherHelper().getString()); } } if (isStatisticsEnabled) { statisticReportingIndex = OpenEventCollector.reportChildEntryEvent(synCtx, getResourceName(synCtx, name), ComponentType.RESOURCE, getAspectConfiguration(), true); } if (log.isDebugEnabled()) { log.debug("Processing message with ID: " + synCtx.getMessageID() + " through the " + "resource: " + name); } if (!synCtx.isResponse()) { String method = (String) synCtx.getProperty(RESTConstants.REST_METHOD); if (RESTConstants.METHOD_OPTIONS.equals(method) && sendOptions(synCtx)) { if (isStatisticsEnabled) { CloseEventCollector.closeEntryEvent(synCtx, getResourceName(synCtx, name), ComponentType.RESOURCE, statisticReportingIndex, true); } return; } synCtx.setProperty(RESTConstants.SYNAPSE_RESOURCE, name); String path = RESTUtils.getFullRequestPath(synCtx); int queryIndex = path.indexOf('?'); if (queryIndex != -1) { String query = path.substring(queryIndex + 1); String[] entries = query.split("&"); for (String entry : entries) { int index = entry.indexOf('='); if (index != -1) { try { String name = entry.substring(0, index); String value = URLDecoder.decode(entry.substring(index + 1), RESTConstants.DEFAULT_ENCODING); synCtx.setProperty(RESTConstants.REST_QUERY_PARAM_PREFIX + name, value); } catch (UnsupportedEncodingException ignored) { } } } } } SequenceMediator sequence = synCtx.isResponse() ? outSequence : inSequence; if (sequence != null) { registerFaultHandler(synCtx); sequence.mediate(synCtx); if (isStatisticsEnabled) { CloseEventCollector.closeEntryEvent(synCtx, getResourceName(synCtx, name), ComponentType.RESOURCE, statisticReportingIndex, true); } return; } String sequenceKey = synCtx.isResponse() ? outSequenceKey : inSequenceKey; if (sequenceKey != null) { registerFaultHandler(synCtx); Mediator referredSequence = synCtx.getSequence(sequenceKey); if (referredSequence != null) { referredSequence.mediate(synCtx); } else { throw new SynapseException("Specified sequence: " + sequenceKey + " cannot " + "be found"); } if (isStatisticsEnabled) { CloseEventCollector.closeEntryEvent(synCtx, getResourceName(synCtx, name), ComponentType.RESOURCE, statisticReportingIndex, true); } return; } // Neither a sequence nor a sequence key has been specified. If this message is a // response, simply send it back to the client. if (synCtx.isResponse()) { if (log.isDebugEnabled()) { log.debug("No out-sequence configured. Sending the response back."); } registerFaultHandler(synCtx); Axis2Sender.sendBack(synCtx); } else if (log.isDebugEnabled()) { log.debug("No in-sequence configured. Dropping the request."); } if (isStatisticsEnabled) { CloseEventCollector .closeEntryEvent(synCtx, getResourceName(synCtx, name), ComponentType.RESOURCE, statisticReportingIndex, true); } } public void registerFaultHandler(MessageContext synCtx) { if (faultSequence != null) { synCtx.pushFaultHandler(new MediatorFaultHandler(faultSequence)); } else if (faultSequenceKey != null) { Mediator faultSequence = synCtx.getSequence(faultSequenceKey); if (faultSequence != null) { synCtx.pushFaultHandler(new MediatorFaultHandler(faultSequence)); } else { synCtx.pushFaultHandler(new MediatorFaultHandler(synCtx.getFaultSequence())); } } else { synCtx.pushFaultHandler(new MediatorFaultHandler(synCtx.getFaultSequence())); } } private boolean sendOptions(MessageContext synCtx) { org.apache.axis2.context.MessageContext msgCtx = ((Axis2MessageContext) synCtx). getAxis2MessageContext(); Map<String,String> transportHeaders = (Map<String,String>) msgCtx.getProperty( org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS); if (methods.contains(RESTConstants.METHOD_OPTIONS)) { // Resource should mediate the OPTIONS request String maxForwardsHeader = transportHeaders.get(HttpHeaders.MAX_FORWARDS); if (maxForwardsHeader != null) { int maxForwards = Integer.parseInt(maxForwardsHeader); if (maxForwards == 0) { // Resource should respond to the OPTIONS request synCtx.setResponse(true); synCtx.setTo(null); transportHeaders.put(HttpHeaders.ALLOW, getSupportedMethods()); Axis2Sender.sendBack(synCtx); return true; } else { transportHeaders.put(HttpHeaders.MAX_FORWARDS, String.valueOf(maxForwards - 1)); } } return false; } else { // Resource should respond to the OPTIONS request synCtx.setResponse(true); synCtx.setTo(null); transportHeaders.put(HttpHeaders.ALLOW, getSupportedMethods()); Axis2Sender.sendBack(synCtx); return true; } } private String getSupportedMethods() { StringBuilder value = new StringBuilder(""); if (methods.isEmpty()) { value.append(RESTConstants.REST_ALL_SUPPORTED_METHODS); } else { for (String method : methods) { if (RESTConstants.METHOD_OPTIONS.equals(method)) { continue; } if (value.length() > 0) { value.append(", "); } value.append(method); } } return value.toString(); } public void init(SynapseEnvironment se) { if (log.isDebugEnabled()) { log.debug("Initializing resource with ID: " + name); } if (inSequence != null) { inSequence.init(se); } if (outSequence != null) { outSequence.init(se); } if (faultSequence != null) { faultSequence.init(se); } } public void destroy() { if (log.isDebugEnabled()) { log.debug("Destroying resource with ID: " + name); } if (inSequence != null && inSequence.isInitialized()) { inSequence.destroy(); } if (outSequence != null && outSequence.isInitialized()) { outSequence.destroy(); } if (faultSequence != null && faultSequence.isInitialized()) { faultSequence.destroy(); } } private static String getResourceName(MessageContext messageContext, String resourceId) { Object synapseRestApi = messageContext.getProperty(RESTConstants.REST_API_CONTEXT); Object restUrlPattern = messageContext.getProperty(RESTConstants.REST_URL_PATTERN); if (synapseRestApi != null) { String textualStringName; if (restUrlPattern != null) { textualStringName = (String) synapseRestApi + restUrlPattern; } else { textualStringName = (String) synapseRestApi; } return textualStringName; } return resourceId; } @Override public void configure(AspectConfiguration aspectConfiguration) { this.aspectConfiguration = aspectConfiguration; } @Override public AspectConfiguration getAspectConfiguration() { return aspectConfiguration; } public void setComponentStatisticsId(ArtifactHolder holder) { StatisticIdentityGenerator.reportingBranchingEvents(holder); if (aspectConfiguration == null) { aspectConfiguration = new AspectConfiguration(name); } String resourceId = StatisticIdentityGenerator.getIdForComponent(getResourceClassName(), ComponentType.RESOURCE, holder); aspectConfiguration.setUniqueId(resourceId); String childId = null; if (inSequenceKey != null) { childId = StatisticIdentityGenerator.getIdReferencingComponent(inSequenceKey, ComponentType.SEQUENCE, holder); StatisticIdentityGenerator.reportingEndEvent(childId, ComponentType.SEQUENCE, holder); } if (inSequence != null) { inSequence.setComponentStatisticsId(holder); } if (outSequenceKey != null) { childId = StatisticIdentityGenerator.getIdReferencingComponent(outSequenceKey, ComponentType.SEQUENCE, holder); StatisticIdentityGenerator.reportingEndEvent(childId, ComponentType.SEQUENCE, holder); } if (outSequence != null) { outSequence.setComponentStatisticsId(holder); } if (faultSequenceKey != null) { childId = StatisticIdentityGenerator.getIdReferencingComponent(faultSequenceKey, ComponentType.SEQUENCE, holder); StatisticIdentityGenerator.reportingEndEvent(childId, ComponentType.SEQUENCE, holder); } if (faultSequence != null) { faultSequence.setComponentStatisticsId(holder); } StatisticIdentityGenerator.reportingEndEvent(resourceId, ComponentType.RESOURCE, holder); } /** * Returns the name of the class of Resource. * @return Resource class name. */ public String getResourceClassName(){ String cls = getClass().getName(); return cls.substring(cls.lastIndexOf(".") + 1); } }