no.ntnu.okse.protocol.wsn.WSNotificationServer.java Source code

Java tutorial

Introduction

Here is the source code for no.ntnu.okse.protocol.wsn.WSNotificationServer.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Norwegian Defence Research Establishment / NTNU
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package no.ntnu.okse.protocol.wsn;

import com.google.common.io.ByteStreams;
import no.ntnu.okse.Application;
import no.ntnu.okse.core.messaging.Message;
import no.ntnu.okse.core.subscription.SubscriptionService;
import no.ntnu.okse.protocol.AbstractProtocolServer;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.InputStreamContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.ntnunotif.wsnu.base.internal.ServiceConnection;
import org.ntnunotif.wsnu.base.net.NuNamespaceContextResolver;
import org.ntnunotif.wsnu.base.util.InternalMessage;
import org.ntnunotif.wsnu.base.util.RequestInformation;
import org.oasis_open.docs.wsn.b_2.NotificationMessageHolderType;
import org.oasis_open.docs.wsn.b_2.Notify;
import org.oasis_open.docs.wsn.b_2.TopicExpressionType;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import java.io.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Created by Aleksander Skraastad (myth) on 3/12/15.
 * <p>
 * okse is licenced under the MIT licence.
 */
public class WSNotificationServer extends AbstractProtocolServer {

    // Runstate variables
    private static boolean _invoked, _running;

    // Path to internal configuration file on classpath
    private static final String wsnInternalConfigFile = "/config/wsnserver.xml";

    // Internal Default Values
    private static final String DEFAULT_HOST = "0.0.0.0";
    private static final int DEFAULT_PORT = 61000;
    private static final Long DEFAULT_CONNECTION_TIMEOUT = 5L;
    private static final Integer DEFAULT_HTTP_CLIENT_DISPATCHER_POOL_SIZE = 50;
    private static final String DEFAULT_MESSAGE_CONTENT_WRAPPER_NAME = "Content";

    // Flag and defaults for operation behind NAT
    private static boolean behindNAT = false;
    private static String publicWANHost = "0.0.0.0";
    private static Integer publicWANPort = 61000;

    // HTTP Client fields
    private static Long connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
    private static Integer clientPoolSize = DEFAULT_HTTP_CLIENT_DISPATCHER_POOL_SIZE;

    // The singleton containing the WSNotificationServer instance
    private static WSNotificationServer _singleton;

    // Non-XMl Content Wrapper Name
    private static String contentWrapperElementName = DEFAULT_MESSAGE_CONTENT_WRAPPER_NAME;

    // Instance fields
    private Server _server;
    private WSNRequestParser _requestParser;
    private WSNCommandProxy _commandProxy;
    private final ArrayList<Connector> _connectors = new ArrayList();
    private HttpClient _client;
    private HttpHandler _handler;
    private HashSet<ServiceConnection> _services;
    private ExecutorService clientPool;
    private Properties config;

    /**
     * Empty constructor, uses internal defaults or provided from config file
     */
    private WSNotificationServer() {
        // Check config file
        config = Application.readConfigurationFiles();
        String configHost = config.getProperty("WSN_HOST", DEFAULT_HOST);
        Integer configPort = null;
        try {
            configPort = Integer.parseInt(config.getProperty("WSN_PORT", Integer.toString(DEFAULT_PORT)));
        } catch (NumberFormatException e) {
            log.error("Failed to parse WSN Port from config file! Using default: " + DEFAULT_PORT);
        }

        // Call init with what the results were
        this.init(configHost, configPort);

        _running = false;
        _invoked = true;
    }

    /**
     * Constructor that takes in a port that the WSNServer jetty instance should
     * listen to.
     * <p>
     *
     * @param host A string representing the host the WSNServer should bind to
     * @param port An integer representing the port the WSNServer should bind to.
     */
    private WSNotificationServer(String host, Integer port) {
        // Check config file
        config = Application.readConfigurationFiles();
        this.init(host, port);
    }

    /**
     * Factory method providing an instance of WSNotificationServer, adhering to the
     * singleton pattern. (Using default port from config file.)
     * <p>
     *
     * @return: The WSNotification instance.
     */
    public static WSNotificationServer getInstance() {
        if (!_invoked)
            _singleton = new WSNotificationServer();
        return _singleton;
    }

    /**
     * Factory method providing an instance of WSNotificationServer, adhering to the
     * singleton pattern. This method allows overriding of host and port defined in the
     * config file.
     *
     * @param host A string representing the hostname the server should bind to
     * @param port An integer representing the port WSNServer should bind to.
     * @return: The WSNotification instance.
     */
    public static WSNotificationServer getInstance(String host, Integer port) {
        if (!_invoked)
            _singleton = new WSNotificationServer(host, port);
        return _singleton;
    }

    /**
     * Initialization method that reads the wsnserver.xml configuration file and constructs
     * a jetty server instance.
     *
     * @param port: An integer representing the port WSNServer should bind to.
     */
    protected void init(String host, Integer port) {

        log = Logger.getLogger(WSNotificationServer.class.getName());

        // Set the servertype
        protocolServerType = "WSNotification";

        // Declare HttpClient field
        _client = null;

        // Attempt to fetch connection timeout from settings, otherwise use 5 seconds as default
        try {
            connectionTimeout = Long
                    .parseLong(config.getProperty("WSN_CONNECTION_TIMEOUT", connectionTimeout.toString()));
        } catch (NumberFormatException e) {
            log.error("Failed to parse WSN Connection Timeout, using default: " + connectionTimeout);
        }

        // Attempt to fetch the HTTP Client pool size from settings, otherwise use default
        try {
            clientPoolSize = Integer.parseInt(config.getProperty("WSN_POOL_SIZE",
                    Integer.toString(DEFAULT_HTTP_CLIENT_DISPATCHER_POOL_SIZE)));
        } catch (NumberFormatException e) {
            log.error("Failed to parse WSN Client pool size from config file! Using default: "
                    + DEFAULT_HTTP_CLIENT_DISPATCHER_POOL_SIZE);
        }
        clientPool = Executors.newFixedThreadPool(clientPoolSize);

        // If a default message content wrapper name is specified in config, set it, otherwise use default
        contentWrapperElementName = config.getProperty("WSN_MESSAGE_CONTENT_ELEMENT_NAME",
                DEFAULT_MESSAGE_CONTENT_WRAPPER_NAME);

        if (contentWrapperElementName.contains("<") || contentWrapperElementName.contains(">")) {
            log.warn("Non-XML message payload element wrapper name cannot contain XML element characters (< or >),"
                    + " using default: " + DEFAULT_MESSAGE_CONTENT_WRAPPER_NAME);
            contentWrapperElementName = DEFAULT_MESSAGE_CONTENT_WRAPPER_NAME;
        }

        // If we have host or port provided, set them, otherwise use internal defaults
        this.port = port == null ? DEFAULT_PORT : port;
        this.host = host == null ? DEFAULT_HOST : host;

        /* Check if config file specifies that we are behind NAT, and update the provided WAN IP and PORT */
        // Check for use NAT flag
        if (config.getProperty("WSN_USES_NAT", "false").equalsIgnoreCase("true"))
            behindNAT = true;
        else
            behindNAT = false;

        // Check for WAN_HOST
        publicWANHost = config.getProperty("WSN_WAN_HOST", publicWANHost);

        // Check for WAN_PORT
        try {
            publicWANPort = Integer.parseInt(config.getProperty("WSN_WAN_PORT", publicWANPort.toString()));
        } catch (NumberFormatException e) {
            log.error("Failed to parse WSN WAN Port, using default: " + publicWANPort);
        }

        // Declare configResource (Fetched from classpath as a Resource from system)
        Resource configResource;
        try {
            // Try to parse the configFile for WSNServer to set up the Server instance
            configResource = Resource.newSystemResource(wsnInternalConfigFile);
            XmlConfiguration config = new XmlConfiguration(configResource.getInputStream());
            this._server = (Server) config.configure();
            // Remove the xmlxonfig connector
            this._server.removeConnector(this._server.getConnectors()[0]);

            // Add a the serverconnector
            log.debug("Adding WSNServer connector");
            this.addStandardConnector(this.host, this.port);

            // Initialize the RequestParser for WSNotification
            this._requestParser = new WSNRequestParser(this);

            // Initialize the collection of ServiceConnections
            this._services = new HashSet<>();

            // Initialize and set the HTTPHandler for the Server instance
            HttpHandler handler = new WSNotificationServer.HttpHandler();
            this._server.setHandler(handler);

            log.debug("XMLConfig complete, server instanciated.");

        } catch (Exception e) {
            log.error("Unable to start WSNotificationServer: " + e.getMessage());
        }
    }

    /**
     * The primary boot method for starting a WSNServer instance. Will only perform actions if the
     * server instance is not already running.
     * <p>
     * Initializes a HttpClient, and starts it. Also adds predefined connectors to the jetty server
     * instance. Constructs a new serverThread and starts the jetty server instance in this new thread.
     * </p>
     */
    public void boot() {

        log.info("Booting WSNServer.");

        if (!_running) {
            try {
                // Initialize a plain HttpClient
                this._client = new HttpClient();
                // Turn off following HTTP 30x redirects for the client
                this._client.setFollowRedirects(false);
                this._client.start();
                log.info("Started WSNServer HTTPClient");

                // For all registered connectors in WSNotificationServer, add these to the Jetty Server
                this._connectors.stream().forEach(c -> this._server.addConnector(c));

                /* OKSE custom WS-Nu web services */

                // Initialize the CommandProxy
                WSNCommandProxy broker = new WSNCommandProxy();
                _commandProxy = broker;
                // Initialize the WSN SubscriptionManager and PublisherRegistrationManager
                WSNSubscriptionManager subscriptionManager = new WSNSubscriptionManager();
                WSNRegistrationManager registrationManager = new WSNRegistrationManager();
                // Add listener support from the OKSE SubscriptionService
                SubscriptionService.getInstance().addSubscriptionChangeListener(subscriptionManager);
                SubscriptionService.getInstance().addPublisherChangeListener(registrationManager);

                // QuickBuild the broker
                broker.quickBuild("broker", this._requestParser);
                // QuickBuild the WSN SubManager
                subscriptionManager.quickBuild("subscriptionManager", this._requestParser);
                subscriptionManager.initCoreSubscriptionService(SubscriptionService.getInstance());
                // QuickBuild the WSN PubRegManager
                registrationManager.quickBuild("registrationManager", this._requestParser);
                registrationManager.initCoreSubscriptionService(SubscriptionService.getInstance());
                // Register the WSN managers to the command proxy (proxied broker)
                broker.setSubscriptionManager(subscriptionManager);
                broker.setRegistrationManager(registrationManager);

                // Create a new thread for the Jetty Server to run in
                this._serverThread = new Thread(() -> {
                    this.run();
                });
                this._serverThread.setName("WSNServer");
                // Start the Jetty Server
                this._serverThread.start();
                WSNotificationServer._running = true;
                log.info("WSNServer Thread started successfully.");
            } catch (Exception e) {
                totalErrors.incrementAndGet();
                log.trace(e.getStackTrace());
            }
        }
    }

    /**
     * This interface method should contain the main run loop initialization
     */
    @Override
    public void run() {
        try {
            WSNotificationServer.this._server.start();
            WSNotificationServer.this._server.join();

        } catch (Exception serverError) {
            totalErrors.incrementAndGet();
            log.trace(serverError.getStackTrace());
        }
    }

    /**
     * Fetch the HashSet containing all WebServices registered to the protocol server
     *
     * @return A HashSet of ServiceConnections for all the registered web services.
     */
    public HashSet<ServiceConnection> getServices() {
        return _services;
    }

    /**
     * This method stops the execution of the WSNotificationServer instance.
     */
    @Override
    public void stopServer() {
        try {
            log.info("Stopping WSNServer...");
            // Removing all subscribers
            _commandProxy.getAllRecipients().forEach(s -> {
                _commandProxy.getProxySubscriptionManager().removeSubscriber(s);
            });
            // Removing all publishers
            _commandProxy.getProxyRegistrationManager().getAllPublishers().forEach(p -> {
                _commandProxy.getProxyRegistrationManager().removePublisher(p);
            });

            // Stop the HTTP Client
            this._client.stop();
            // Stop the ServerConnector
            this._server.stop();
            this._serverThread = null;
            // Reset flags
            this._singleton = null;
            this._invoked = false;
            log.info("WSNServer Client and ServerThread stopped");
        } catch (Exception e) {
            totalErrors.incrementAndGet();
            log.trace(e.getStackTrace());
        }
    }

    /**
     * Fetches the specified String representation of the Protocol that this ProtocolServer handles.
     *
     * @return A string representing the name of the protocol that this ProtocolServer handles.
     */
    @Override
    public String getProtocolServerType() {
        return protocolServerType;
    }

    /**
     * Support method to allow other classes in the wsn package to increment total messages received
     */
    protected void incrementTotalMessagesReceived() {
        totalMessagesReceived.incrementAndGet();
    }

    /**
     * Retrieve the default element name for non-XML messages that are to be wrapped in a soap enveloped
     * WSNotification Notify element. This element will be the first and only child of the Message element.
     *
     * @return The default name of the content wrapper element
     */
    public static String getMessageContentWrapperElementName() {
        return contentWrapperElementName;
    }

    /**
     * This interface method must take in an instance of Message, which contains the appropriate references
     * and flags needed to distribute the message to consumers. Implementation specific details can vary from
     * protocol to protocol, but the end result of a method call to sendMessage is that the message is delivered,
     * or an error is logged.
     *
     * @param message An instance of Message containing the required data to distribute a message.
     */
    @Override
    public void sendMessage(Message message) {
        log.debug("WSNServer received message for distribution");
        if (!message.getOriginProtocol().equals(protocolServerType) || message.getAttribute("duplicate") != null) {
            log.debug("The message originated from other protocol than WSNotification");

            WSNTools.NotifyWithContext notifywrapper = WSNTools.buildNotifyWithContext(message.getMessage(),
                    message.getTopic(), null, null);
            // If it contained XML, we need to create properly marshalled jaxb node structure
            if (message.getMessage().contains("<") || message.getMessage().contains(">")) {
                // Unmarshal from raw XML
                Notify notify = WSNTools.createNotify(message);
                // If it was malformed, or maybe just a message containing < or >, build it as generic content element
                if (notify == null) {
                    WSNTools.injectMessageContentIntoNotify(
                            WSNTools.buildGenericContentElement(message.getMessage()), notifywrapper.notify);
                    // Else inject the unmarshalled XML nodes into the Notify message attribute
                } else {
                    WSNTools.injectMessageContentIntoNotify(WSNTools.extractMessageContentFromNotify(notify),
                            notifywrapper.notify);
                }
            }

            /*
            Start to resolve recipients. The reason we cannot re-use the WSNCommandProxy's
            sendNotification method is that it will inject the message to the MessageService for relay
            thus creating duplicate messages.
             */

            NuNamespaceContextResolver namespaceContextResolver = notifywrapper.nuNamespaceContextResolver;

            // bind namespaces to topics
            for (NotificationMessageHolderType holderType : notifywrapper.notify.getNotificationMessage()) {

                // Extract the topic
                TopicExpressionType topic = holderType.getTopic();

                if (holderType.getTopic() != null) {
                    NuNamespaceContextResolver.NuResolvedNamespaceContext context = namespaceContextResolver
                            .resolveNamespaceContext(topic);

                    if (context == null) {
                        continue;
                    }

                    context.getAllPrefixes().forEach(prefix -> {
                        // check if this is the default xmlns attribute
                        if (!prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
                            // add namespace context to the expression node
                            topic.getOtherAttributes().put(new QName("xmlns:" + prefix),
                                    context.getNamespaceURI(prefix));
                        }
                    });
                }
            }

            // For all valid recipients
            for (String recipient : _commandProxy.getAllRecipients()) {

                // If the subscription has expired, continue
                if (_commandProxy.getProxySubscriptionManager().getSubscriber(recipient).hasExpired())
                    continue;

                // Filter do filter handling, if any
                Notify toSend = _commandProxy.getRecipientFilteredNotify(recipient, notifywrapper.notify,
                        namespaceContextResolver);

                // If any message was left to send, send it
                if (toSend != null) {
                    InternalMessage outMessage = new InternalMessage(InternalMessage.STATUS_OK
                            | InternalMessage.STATUS_HAS_MESSAGE | InternalMessage.STATUS_ENDPOINTREF_IS_SET,
                            toSend);
                    // Update the requestinformation
                    outMessage.getRequestInformation()
                            .setEndpointReference(_commandProxy.getEndpointReferenceOfRecipient(recipient));

                    // Check if the subscriber has requested raw message format
                    // If the recipient has requested UseRaw, remove Notify payload wrapping
                    if (_commandProxy.getProxySubscriptionManager().getSubscriber(recipient)
                            .getAttribute(WSNSubscriptionManager.WSN_USERAW_TOKEN) != null) {

                        Object content = WSNTools.extractMessageContentFromNotify(toSend);
                        // Update the InternalMessage with the content of the NotificationMessage
                        outMessage.setMessage(content);
                    }

                    // Pass it along to the request parser wrapped as a thread pool executed job
                    clientPool.execute(() -> _requestParser.acceptLocalMessage(outMessage));
                }
            }
        } else {
            log.debug("Message originated from WSN protocol, already processed");
        }
    }

    /**
     * Fetches the complete URI of this ProtocolServer
     *
     * @return A string representing the complete URI of this ProtocolServer
     */
    public String getURI() {
        // Check if we are behind NAT
        if (behindNAT) {
            return "http://" + publicWANHost + ":" + publicWANPort;
        }
        // If somehow URI could not be retrieved
        if (_singleton._server.getURI() == null) {
            _singleton.log.warn("Failed to fetch URI of server");
            return "http://" + DEFAULT_HOST + ":" + DEFAULT_PORT;
        }
        // Return the server connectors registered host and port
        return "http://" + _singleton._server.getURI().getHost() + ":"
                + (_singleton._server.getURI().getPort() > -1 ? _singleton._server.getURI().getPort()
                        : DEFAULT_PORT);
    }

    /**
     * Returns the public WAN Host if behindNAT is true. If behindNAT is false, the value of host is returned.
     *
     * @return The public WAN Host
     */
    public String getPublicWANHost() {
        if (behindNAT)
            return publicWANHost;
        return host;
    }

    /**
     * Returns the public WAN Port if behindNAT is true. If behindNAT is false, the value of port is returned.
     *
     * @return The public WAN Port
     */
    public Integer getPublicWANPort() {
        if (behindNAT)
            return publicWANPort;
        return port;
    }

    /**
     * Registers the specified ServiceConnection to the ProtocolServer
     *
     * @param webServiceConnector: The ServiceConnection you wish to register.
     */
    public synchronized void registerService(ServiceConnection webServiceConnector) {
        _services.add(webServiceConnector);
    }

    /**
     * Unregisters the specified ServiceConnection from the ProtocolServer
     *
     * @param webServiceConnector: The ServiceConnection you wish to remove.
     */
    public synchronized void removeService(ServiceConnection webServiceConnector) {
        _services.remove(webServiceConnector);
    }

    /**
     * Add a standard serverconnector to the server instance.
     *
     * @param address The IP address you wish to bind the serverconnector to
     * @param port    The port you with to bind the serverconnector to
     */
    public void addStandardConnector(String address, int port) {
        ServerConnector connector = new ServerConnector(_server);
        connector.setHost(address);
        if (port == 80) {
            log.warn("You have requested to use port 80. This will not work unless you are running as root."
                    + "Are you running as root? You shouldn't. Reroute port 80 to 8080 instead.");
        }
        connector.setPort(port);
        _connectors.add(connector);
        _server.addConnector(connector);
    }

    /**
     * Add a predefined serverconnector to the server instance.
     *
     * @param connector A jetty ServerConnector
     */
    public void addConnector(Connector connector) {
        _connectors.add(connector);
        this._server.addConnector(connector);
    }

    /**
     * Fetch the WSNRequestParser object
     *
     * @return WSNRequestParser
     */
    public WSNRequestParser getRequestParser() {
        return this._requestParser;
    }

    // This is the HTTP Handler that the WSNServer uses to process all incoming requests
    private class HttpHandler extends AbstractHandler {

        @Override
        public void handle(String target, Request baseRequest, HttpServletRequest request,
                HttpServletResponse response) throws IOException, ServletException {

            log.debug("HttpHandle invoked on target: " + target);

            // Do some stats.
            totalRequests.incrementAndGet();

            boolean isChunked = false;

            Enumeration headerNames = request.getHeaderNames();

            log.debug("Checking headers...");

            // Check the request headers, check for chunked encoding
            while (headerNames.hasMoreElements()) {
                String outMessage = (String) headerNames.nextElement();
                Enumeration returnMessage = request.getHeaders(outMessage);

                while (returnMessage.hasMoreElements()) {
                    String inputStream = (String) returnMessage.nextElement();
                    if (outMessage.equals("Transfer-Encoding") && inputStream.equals("chunked")) {
                        log.debug("Found Transfer-Encoding was chunked.");
                        isChunked = true;
                    }
                }
            }

            log.debug("Accepted message, trying to instantiate WSNu InternalMessage");

            // Get message content, if any
            InternalMessage outgoingMessage;
            if (request.getContentLength() > 0) {
                InputStream inputStream = request.getInputStream();
                outgoingMessage = new InternalMessage(
                        InternalMessage.STATUS_OK | InternalMessage.STATUS_HAS_MESSAGE, inputStream);
            } else if (isChunked) {
                InputStream chunkedInputStream = request.getInputStream();
                StringWriter swriter = new StringWriter();
                IOUtils.copy(chunkedInputStream, swriter);
                String rawRequest = swriter.toString();
                log.debug(rawRequest);
                outgoingMessage = new InternalMessage(
                        InternalMessage.STATUS_OK | InternalMessage.STATUS_HAS_MESSAGE,
                        new ByteArrayInputStream(rawRequest.getBytes()));
            } else {
                outgoingMessage = new InternalMessage(InternalMessage.STATUS_OK, null);
            }

            log.debug("WSNInternalMessage: " + outgoingMessage);

            // Update the request information object
            outgoingMessage.getRequestInformation().setEndpointReference(request.getRemoteHost());
            outgoingMessage.getRequestInformation().setRequestURL(request.getRequestURI());
            outgoingMessage.getRequestInformation().setParameters(request.getParameterMap());

            log.debug("EndpointReference: " + outgoingMessage.getRequestInformation().getEndpointReference());
            log.debug("Request URI: " + outgoingMessage.getRequestInformation().getRequestURL());

            log.debug("Forwarding message to requestParser...");

            // Push the outgoingMessage to the request parser. Based on the status flags of the return message
            // we should know what has happened, and which response we should send.
            InternalMessage returnMessage = null;
            try {
                returnMessage = WSNotificationServer.this._requestParser.parseMessage(outgoingMessage,
                        response.getOutputStream());
            } catch (Exception e) {
                log.error("Uncaught exception: " + e.getMessage());
                log.trace(e.getStackTrace());
            }

            // Improper response from WSNRequestParser! FC WHAT DO?
            if (returnMessage == null) {
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
                totalErrors.incrementAndGet();
                baseRequest.setHandled(true);
                returnMessage = new InternalMessage(InternalMessage.STATUS_FAULT_INTERNAL_ERROR, null);
            }

            /* Handle possible errors */
            if ((returnMessage.statusCode & InternalMessage.STATUS_FAULT) > 0) {

                /* Have we got an error message to return? */
                if ((returnMessage.statusCode & InternalMessage.STATUS_HAS_MESSAGE) > 0) {
                    response.setContentType("application/soap+xml;charset=utf-8");

                    // Declare input and output streams
                    InputStream inputStream = (InputStream) returnMessage.getMessage();
                    OutputStream outputStream = response.getOutputStream();

                    // Pipe the data from input to output stream
                    ByteStreams.copy(inputStream, outputStream);

                    // Set proper HTTP status, flush the output stream and set the handled flag
                    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
                    outputStream.flush();
                    baseRequest.setHandled(true);
                    totalErrors.incrementAndGet();

                    return;
                }

                /* If no valid destination was found for the request (Endpoint non-existant) */
                if ((returnMessage.statusCode & InternalMessage.STATUS_FAULT_INVALID_DESTINATION) > 0) {
                    response.setStatus(HttpStatus.NOT_FOUND_404);
                    baseRequest.setHandled(true);
                    totalBadRequests.incrementAndGet();

                    return;

                    /* If there was an internal server error */
                } else if ((returnMessage.statusCode & InternalMessage.STATUS_FAULT_INTERNAL_ERROR) > 0) {
                    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
                    baseRequest.setHandled(true);
                    totalErrors.incrementAndGet();

                    return;

                    /* If there was syntactical errors or otherwise malformed request content */
                } else if ((returnMessage.statusCode & InternalMessage.STATUS_FAULT_INVALID_PAYLOAD) > 0) {
                    response.setStatus(HttpStatus.BAD_REQUEST_400);
                    baseRequest.setHandled(true);
                    totalBadRequests.incrementAndGet();

                    return;

                    /* If the requested method or access to endpoint is forbidden */
                } else if ((returnMessage.statusCode & InternalMessage.STATUS_FAULT_ACCESS_NOT_ALLOWED) > 0) {
                    response.setStatus(HttpStatus.FORBIDDEN_403);
                    baseRequest.setHandled(true);
                    totalBadRequests.incrementAndGet();

                    return;
                }

                /*
                Otherwise, there has been an exception of some sort with no message attached,
                and we will reply with a server error
                */
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
                baseRequest.setHandled(true);
                totalErrors.incrementAndGet();

                // Check if we have status=OK and also we have a message
            } else if (((InternalMessage.STATUS_OK & returnMessage.statusCode) > 0)
                    && (InternalMessage.STATUS_HAS_MESSAGE & returnMessage.statusCode) > 0) {

                /* Liar liar pants on fire */
                if (returnMessage.getMessage() == null) {

                    log.error(
                            "The HAS_RETURNING_MESSAGE flag was checked, but there was no returning message content");
                    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
                    baseRequest.setHandled(true);
                    totalErrors.incrementAndGet();

                    return;
                }

                // Prepare the response content type
                response.setContentType("application/soap+xml;charset=utf-8");

                // Allocate the input and output streams
                InputStream inputStream = (InputStream) returnMessage.getMessage();
                OutputStream outputStream = response.getOutputStream();

                /* Copy the contents of the input stream into the output stream */
                ByteStreams.copy(inputStream, outputStream);

                /* Set proper OK status and flush out the stream for response to be sent */
                response.setStatus(HttpStatus.OK_200);
                outputStream.flush();

                baseRequest.setHandled(true);

                /* Everything is fine, and nothing is expected */
            } else if ((InternalMessage.STATUS_OK & returnMessage.statusCode) > 0) {

                response.setStatus(HttpStatus.OK_200);
                baseRequest.setHandled(true);

            } else {
                // We obviously should never land in this block, hence we set the 500 status.
                log.error(
                        "HandleMessage: The message returned to the WSNotificationServer was not flagged with either STATUS_OK or"
                                + "STATUS_FAULT. Please set either of these flags at all points");

                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
                baseRequest.setHandled(true);
                totalErrors.incrementAndGet();

            }
        }
    }

    public InternalMessage sendMessage(InternalMessage message) {

        // Fetch the requestInformation from the message, and extract the endpoint
        RequestInformation requestInformation = message.getRequestInformation();
        String endpoint = requestInformation.getEndpointReference();

        /* If we have nowhere to send the message */
        if (endpoint == null) {
            log.error("Endpoint reference not set");
            totalErrors.incrementAndGet();
            return new InternalMessage(InternalMessage.STATUS_FAULT, null);
        }

        /* Create the actual http-request*/
        org.eclipse.jetty.client.api.Request request = _client
                .newRequest(requestInformation.getEndpointReference());
        request.timeout(connectionTimeout, TimeUnit.SECONDS);

        /* Try to send the message */
        try {
            /* Raw request */
            if ((message.statusCode & InternalMessage.STATUS_HAS_MESSAGE) == 0) {

                request.method(HttpMethod.GET);

                log.debug("Sending message without content to " + requestInformation.getEndpointReference());
                ContentResponse response = request.send();
                totalRequests.incrementAndGet();

                return new InternalMessage(InternalMessage.STATUS_OK | InternalMessage.STATUS_HAS_MESSAGE,
                        response.getContentAsString());
                /* Request with message */
            } else {

                // Set proper request method
                request.method(HttpMethod.POST);

                // If the statusflag has set a message and it is not an input stream
                if ((message.statusCode & InternalMessage.STATUS_MESSAGE_IS_INPUTSTREAM) == 0) {
                    log.error("sendMessage(): " + "The message contained something else than an inputStream."
                            + "Please convert your message to an InputStream before calling this methbod.");

                    return new InternalMessage(
                            InternalMessage.STATUS_FAULT | InternalMessage.STATUS_FAULT_INVALID_PAYLOAD, null);

                } else {

                    // Check if we should have had a message, but there was none
                    if (message.getMessage() == null) {
                        log.error("No content was found to send");
                        totalErrors.incrementAndGet();
                        return new InternalMessage(
                                InternalMessage.STATUS_FAULT | InternalMessage.STATUS_FAULT_INVALID_PAYLOAD, null);
                    }

                    // Send the request to the specified endpoint reference
                    log.info("Sending message with content to " + requestInformation.getEndpointReference());
                    InputStream msg = (InputStream) message.getMessage();
                    request.content(new InputStreamContentProvider(msg), "application/soap+xml; charset=utf-8");

                    ContentResponse response = request.send();
                    totalMessagesSent.incrementAndGet();

                    // Check what HTTP status we received, if is not A-OK, flag the internalmessage as fault
                    // and make the response content the message of the InternalMessage returned
                    if (!HttpStatus.isSuccess(response.getStatus())) {
                        totalBadRequests.incrementAndGet();
                        return new InternalMessage(
                                InternalMessage.STATUS_FAULT | InternalMessage.STATUS_HAS_MESSAGE,
                                response.getContentAsString());
                    } else {
                        return new InternalMessage(InternalMessage.STATUS_OK | InternalMessage.STATUS_HAS_MESSAGE,
                                response.getContentAsString());
                    }
                }
            }
        } catch (ClassCastException e) {
            log.error("sendMessage(): The message contained something else than an inputStream."
                    + "Please convert your message to an InputStream before calling this method.");
            totalErrors.incrementAndGet();

            return new InternalMessage(InternalMessage.STATUS_FAULT | InternalMessage.STATUS_FAULT_INVALID_PAYLOAD,
                    null);

        } catch (Exception e) {
            totalErrors.incrementAndGet();
            e.printStackTrace();
            log.error("sendMessage(): Unable to establish connection: " + e.getMessage());
            return new InternalMessage(InternalMessage.STATUS_FAULT_INTERNAL_ERROR, null);
        }
    }
}