All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.servicemix.http.endpoints.HttpConsumerEndpoint Maven / Gradle / Ivy

/*
 * 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.servicemix.http.endpoints;

import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.jbi.management.DeploymentException;
import javax.jbi.messaging.ExchangeStatus;
import javax.jbi.messaging.Fault;
import javax.jbi.messaging.MessageExchange;
import javax.jbi.messaging.NormalizedMessage;
import javax.jbi.servicedesc.ServiceEndpoint;
import javax.security.auth.Subject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.servicemix.http.exception.HttpTimeoutException;
import org.apache.servicemix.http.exception.LateResponseException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.apache.servicemix.common.DefaultComponent;
import org.apache.servicemix.common.ServiceUnit;
import org.apache.servicemix.common.JbiConstants;
import org.apache.servicemix.common.endpoints.ConsumerEndpoint;
import org.apache.servicemix.http.ContextManager;
import org.apache.servicemix.http.HttpComponent;
import org.apache.servicemix.http.HttpEndpointType;
import org.apache.servicemix.http.HttpProcessor;
import org.apache.servicemix.http.SslParameters;
import org.apache.servicemix.http.jetty.JaasJettyPrincipal;
import org.apache.servicemix.jbi.jaxp.SourceTransformer;
import org.mortbay.jetty.RetryRequest;
import org.mortbay.util.ajax.Continuation;
import org.mortbay.util.ajax.ContinuationSupport;

import static org.apache.servicemix.http.jetty.ContinuationHelper.isNewContinuation;

/**
 * Plain HTTP consumer endpoint. This endpoint can be used to handle plain HTTP request (without SOAP) or to be able to
 * process the request in a non standard way. For HTTP requests, a WSDL2 HTTP binding can be used.
 *
 * @author gnodet
 * @since 3.2
 * @org.apache.xbean.XBean element="consumer"
 */
public class HttpConsumerEndpoint extends ConsumerEndpoint implements HttpProcessor, HttpEndpointType {

    public static final String MAIN_WSDL = "main.wsdl";
    private String authMethod;
    private SslParameters ssl;
    private String locationURI;
    private HttpConsumerMarshaler marshaler;
    private long timeout; // 0 => default to the timeout configured on component
    private URI defaultMep = JbiConstants.IN_OUT;
    private Map resources = new HashMap();
    private Map continuations = new ConcurrentHashMap();
    private Map mutexes = new ConcurrentHashMap();
    private Object httpContext;
    private boolean started = false;
    private LateResponseStrategy lateResponseStrategy = LateResponseStrategy.error;
    private boolean rewriteSoapAddress = false;

    public HttpConsumerEndpoint() {
        super();
    }

    public HttpConsumerEndpoint(DefaultComponent component, ServiceEndpoint endpoint) {
        super(component, endpoint);
    }

    public HttpConsumerEndpoint(ServiceUnit serviceUnit, QName service, String endpoint) {
        super(serviceUnit, service, endpoint);
    }

    /**
     * Returns the URI at which the endpoint listens for new requests.
     *
     * @return a string representing the endpoint's URI
     */
    public String getLocationURI() {
        return locationURI;
    }

    /**
     * Sets the URI at which an endpoint listens for requests.
     *
     * @param locationURI a string representing the URI
     * @org.apache.xbean.Property description="the URI at which the endpoint listens for requests"
     */
    public void setLocationURI(String locationURI) {
        this.locationURI = locationURI;
    }

    /**
     * Returns the timeout value for an HTTP endpoint.
     *
     * @return the timeout specified in milliseconds
     */
    public long getTimeout() {
        return timeout;
    }

    /**
     * Specifies the timeout value for an HTTP consumer endpoint. The timeout is specified in milliseconds. The default value is 0
     * which means that the endpoint will never timeout.
     *
     * @org.apache.xbean.Property description="the timeout is specified in milliseconds. The default value is 0 which means that the endpoint will never timeout."
     * @param timeout the length time, in milliseconds, to wait before timing out
     */
    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    /**
     * @return the marshaler
     */
    public HttpConsumerMarshaler getMarshaler() {
        return marshaler;
    }

    /**
     * Sets the class used to marshal messages.
     *
     * @param marshaler the marshaler to set
     * @org.apache.xbean.Property description="the bean used to marshal HTTP messages. The default is a DefaultHttpConsumerMarshaler."
     */
    public void setMarshaler(HttpConsumerMarshaler marshaler) {
        this.marshaler = marshaler;
    }

    /**
     * Returns a string describing the authentication scheme being used by an endpoint.
     *
     * @return a string representing the authentication method used by an endpoint
     */
    public String getAuthMethod() {
        return authMethod;
    }

    /**
     * Specifies the authentication method used by a secure endpoint. The authentication method is a string naming the scheme used
     * for authenticating users.
     *
     * @param authMethod a string naming the authentication scheme a secure endpoint should use
     * @org.apache.xbean.Property description="a string naming the scheme used for authenticating users"
     */
    public void setAuthMethod(String authMethod) {
        this.authMethod = authMethod;
    }

    /**
     * @return the sslParameters
     */
    public SslParameters getSsl() {
        return ssl;
    }

    /**
     * Sets the properties used to configure SSL for the endpoint.
     *
     * @param ssl an SslParameters object containing the SSL properties
     * @org.apache.xbean.Property description="a bean containing the SSL configuration properties"
     */
    public void setSsl(SslParameters ssl) {
        this.ssl = ssl;
    }

    /**
     * Returns a URI representing the default message exachange pattern(MEP) used by an endpoint.
     *
     * @return a URI representing an endpoint's default MEP
     */
    public URI getDefaultMep() {
        return defaultMep;
    }

    /**
     * Sets the default message exchange pattern(MEP) for an endpoint. The default MEP is specified as a URI and the default is
     * JbiConstants.IN_OUT.
     *
     * @param defaultMep a URI representing the default MEP of the endpoint
     * @org.apache.xbean.Property description="a URI representing the endpoint's default MEP. The default is JbiConstants.IN_OUT."
     */
    public void setDefaultMep(URI defaultMep) {
        this.defaultMep = defaultMep;
    }

    public String getLateResponseStrategy() {
        return lateResponseStrategy.name();
    }

    /**
     * Set the strategy to be used for handling a late response from the ESB (i.e. a response that arrives after the HTTP request has timed out).
     * Defaults to error
     *
     * 
    *
  • error will terminate the exchange with an ERROR status and log an exception for the late response
  • *
  • warning will end the exchange with a DONE status and log a warning for the late response instead
  • *
* * @param value */ public void setLateResponseStrategy(String value) { this.lateResponseStrategy = LateResponseStrategy.valueOf(value); } public boolean isRewriteSoapAddress() { return rewriteSoapAddress; } /** * Toggles the rewriting of the soap address based on the request info. *

* When active, the soap address in the wsdl will be updated according * to the protocol, host and port of the request. This is useful when * listening on 0.0.0.0 or when behind a NAT (or reverse-proxy in some * cases).
* This function only works on the main wsdl, not in imported wsdl-parts. * This means the service with its port must be declared in the main * wsdl. *

* By default it is activated. *

* @param value */ public void setRewriteSoapAddress(boolean value) { this.rewriteSoapAddress = value; } public void activate() throws Exception { super.activate(); loadStaticResources(); httpContext = getServerManager().createContext(locationURI, this); } public void deactivate() throws Exception { getServerManager().remove(httpContext); httpContext = null; super.deactivate(); } public void start() throws Exception { super.start(); started = true; } public void stop() throws Exception { started = false; super.stop(); } /* * Process the reponse message exchange */ public void process(MessageExchange exchange) throws Exception { final String id = exchange.getExchangeId(); final Object mutex = mutexes.get(id); // if the mutex is no longer available, the HTTP request timed out before the message exchange got handled by the ESB if (mutex == null) { handleLateResponse(exchange); return; } // Synchronize on the mutex object while we're tinkering with the continuation object synchronized (mutex) { final Continuation continuation = continuations.get(id); if (continuation != null && continuation.isPending()) { logger.debug("Resuming continuation for exchange: {}", id); // in case of the JMS/JCA flow, you might have a different instance of the message exchange here continuation.setObject(exchange); continuation.resume(); // if the continuation could no longer be resumed, the HTTP request might have timed out before the message // exchange got handled by the ESB if (!continuation.isResumed()) { handleLateResponse(exchange); } } else { // it the continuation is no longer available or no longer pending, the HTTP request has time out before // the message exchange got handled by the ESB handleLateResponse(exchange); } } } /* * Process the HTTP request/response - this method gets invoked: * - when a new HTTP request is received * - when a suspended HTTP request is being resumed * (either because the exchange was received or because the request timed out) */ public void process(HttpServletRequest request, HttpServletResponse response) throws Exception { MessageExchange exchange = null; try { // Handle WSDLs, XSDs if (handleStaticResource(request, response)) { return; } // configure the timeout long to = this.timeout; if (to == 0) { to = ((HttpComponent) getServiceUnit().getComponent()).getConfiguration().getConsumerProcessorSuspendTime(); } final Continuation continuation = ContinuationSupport.getContinuation(request, null); exchange = (MessageExchange) continuation.getObject(); final Object mutex = getOrCreateMutex(exchange); // Synchronize on the mutex object while we're tinkering with the continuation object synchronized (mutex) { if (isNewContinuation(continuation)) { logger.debug("Receiving HTTP request: {}", request); // send back HTTP status 503 (Not Avaialble) to reject any new requests if the endpoint is not started if (!started) { response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Endpoint is stopped"); return; } // Create the exchange exchange = createExchange(request); final String id = exchange.getExchangeId(); // Put the exchange into the continuation for later retrieval and store the mutex/continuation objects continuation.setObject(exchange); mutexes.put(id, mutex); continuations.put(id, continuation); // Send the exchange and then suspend the HTTP request until the message exchange gets answered send(exchange); // Right after this if-block, we will try suspending the continuation // If a SelectConnector is being used, the call to suspend will throw a RetryRequest // This will free the thread - this method will be invoked again when the continuation gets resumed logger.debug("Suspending continuation for exchange: {}", id); } else { logger.debug("Resuming HTTP request: {}", request); } boolean istimeout = !continuation.suspend(to); // Continuation is being ended (either because the message exchange was handled or the continuation timed out) // Cleaning up the stored objects for this continuation now exchange = doEndContinuation(continuation); // Throw exception when HTTP time-out has occurred if (istimeout) { throw new HttpTimeoutException(exchange); } } // message exchange has been completed, so we're ready to send back an HTTP response now handleResponse(exchange, request, response); } catch (RetryRequest e) { // retrow the RetryRequest to allow Jetty to re-invoke this method when the continuation is being resumed throw e; } catch (Exception e) { sendError(exchange, e, request, response); } } /* * Handle the HTTP response based on the information in the message exchange we received */ private void handleResponse(MessageExchange exchange, HttpServletRequest request, HttpServletResponse response) throws Exception { if (exchange.getStatus() == ExchangeStatus.ERROR) { Exception e = exchange.getError(); if (e == null) { e = new Exception("Unknown error (exchange aborted ?)"); } throw e; } else if (exchange.getStatus() == ExchangeStatus.ACTIVE) { try { Fault fault = exchange.getFault(); if (fault != null) { sendFault(exchange, fault, request, response); } else { NormalizedMessage outMsg = exchange.getMessage("out"); if (outMsg != null) { sendOut(exchange, outMsg, request, response); } } done(exchange); } catch (Exception e) { fail(exchange, e); throw e; } } else if (exchange.getStatus() == ExchangeStatus.DONE) { // This happens when there is no response to send back sendAccepted(exchange, request, response); } } /* * Handle a message exchange that is being received after the corresponding HTTP request has timed out */ private void handleLateResponse(MessageExchange exchange) throws Exception { // if the exchange is no longer active by now, something else probably went wrong in the meanwhile if (exchange.getStatus() == ExchangeStatus.ACTIVE) { if (lateResponseStrategy == LateResponseStrategy.error) { // ends the exchange in ERROR fail(exchange, new LateResponseException(exchange)); } else { // let's log the exception message text, but end the exchange with DONE logger.warn(LateResponseException.createMessage(exchange)); done(exchange); } } } /* * Get or create an object that can be used for synchronizing code blocks for a given exchange */ private Object getOrCreateMutex(MessageExchange exchange) { Object result = null; // let's try to find the object that corresponds to the exchange first if (exchange != null) { result = mutexes.get(exchange.getExchangeId()); } // no luck finding an existing object, let's create a new one if (result == null) { result = new Object(); } return result; } /* * End the continuation by removing all objects stored on/for the continuation * and returning the MessageExchange that was represented by the continuation */ private MessageExchange doEndContinuation(Continuation continuation) { final MessageExchange exchange = (MessageExchange) continuation.getObject(); final String id = exchange.getExchangeId(); continuation.setObject(null); mutexes.remove(id); continuations.remove(id); return exchange; } protected void loadStaticResources() throws Exception { } /** * Handle static resources * * @param request the http request * @param response the http response * @return true if the request has been handled * @throws IOException * @throws ServletException */ protected boolean handleStaticResource(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { if (!"GET".equals(request.getMethod())) { return false; } String query = request.getQueryString(); if (query != null && query.trim().equalsIgnoreCase("wsdl") && getResource(MAIN_WSDL) != null) { String uri = request.getRequestURI(); if (!uri.endsWith("/")) { uri += "/"; } uri += MAIN_WSDL; response.sendRedirect(uri); return true; } String path = request.getPathInfo(); if (path.indexOf('/') >= 0) { path = path.substring(path.indexOf('/') + 1); } Object res; if (rewriteSoapAddress && path.equals(MAIN_WSDL) && getResource(path) instanceof Document) { // determine the location based on the request String location = getLocationURI(); try { URL listUrl = new URL(getLocationURI()); URL requestUrl = new URL(request.getRequestURL().toString()); URL acceptUri = new URL(requestUrl.getProtocol(), requestUrl.getHost(), requestUrl.getPort(), listUrl.getFile()); location = acceptUri.toExternalForm(); //Update the location for this request Document copy = (Document) ((Document) getResource(path)).cloneNode(true); updateSoapLocations(location, copy.getElementsByTagNameNS("http://schemas.xmlsoap.org/wsdl/soap12/", "address")); updateSoapLocations(location, copy.getElementsByTagNameNS("http://schemas.xmlsoap.org/wsdl/soap/", "address")); res = copy; } catch (Exception e) { logger.warn("Could not update soap location, using default", e); res = getResource(path); } } else { res = getResource(path); } if (res == null) { return false; } if (res instanceof Node) { response.setStatus(200); response.setContentType("text/xml"); try { new SourceTransformer().toResult(new DOMSource((Node) res), new StreamResult(response.getOutputStream())); } catch (TransformerException e) { throw new ServletException("Error while sending xml resource", e); } } else if (res != null) { // TODO: handle other static resources ... throw new ServletException("Unable to serialize resource"); } else { return false; } return true; } protected Object getResource(String path) { return resources.get(path); } protected void addResource(String path, Object resource) { resources.put(path, resource); } protected ContextManager getServerManager() { HttpComponent comp = (HttpComponent) getServiceUnit().getComponent(); return comp.getServer(); } public MessageExchange createExchange(HttpServletRequest request) throws Exception { MessageExchange me = marshaler.createExchange(request, getContext()); if (me.getEndpoint() == null) { configureExchangeTarget(me); } // If the user has been authenticated, put these informations on // the in NormalizedMessage. if (request.getUserPrincipal() instanceof JaasJettyPrincipal) { Subject subject = ((JaasJettyPrincipal) request.getUserPrincipal()).getSubject(); me.getMessage("in").setSecuritySubject(subject); } return me; } public void sendAccepted(MessageExchange exchange, HttpServletRequest request, HttpServletResponse response) throws Exception { marshaler.sendAccepted(exchange, request, response); } public void sendError(MessageExchange exchange, Exception error, HttpServletRequest request, HttpServletResponse response) throws Exception { marshaler.sendError(exchange, error, request, response); } public void sendFault(MessageExchange exchange, Fault fault, HttpServletRequest request, HttpServletResponse response) throws Exception { marshaler.sendFault(exchange, fault, request, response); } public void sendOut(MessageExchange exchange, NormalizedMessage outMsg, HttpServletRequest request, HttpServletResponse response) throws Exception { marshaler.sendOut(exchange, outMsg, request, response); } public void validate() throws DeploymentException { super.validate(); if (locationURI == null || locationURI.trim().length() < 1) { throw new DeploymentException("The location URI is mandatory."); } if (endpoint != null && endpoint.contains(":")) { throw new DeploymentException("Endpoint name contains ':'. This character is not allowed as it can provide invalid WSDL."); } if (marshaler == null) { marshaler = new DefaultHttpConsumerMarshaler(); } if (marshaler instanceof DefaultHttpConsumerMarshaler) { ((DefaultHttpConsumerMarshaler) marshaler).setDefaultMep(getDefaultMep()); } } protected void updateSoapLocations(String location, NodeList addresses) { int i = 0; while (i < addresses.getLength()) { Element address = (Element) addresses.item(i); if (getLocationURI().equals(address.getAttribute("location"))) { address.setAttribute("location", location); } i++; } } /** * Determines how the HTTP consumer endpoint should handle a late response from the NMR */ protected enum LateResponseStrategy { /** * Terminate the exchange with an ERROR status and log an exception for the late response */ error, /** * End the exchange with a DONE status and log a warning for the late response */ warning } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy