<commit_before>/*
 * 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 java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;

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.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.util.InternalMessage;
import org.ntnunotif.wsnu.base.util.RequestInformation;
import org.ntnunotif.wsnu.services.general.WsnUtilities;
import org.ntnunotif.wsnu.services.implementations.notificationbroker.NotificationBrokerImpl;
import org.ntnunotif.wsnu.services.implementations.publisherregistrationmanager.SimplePublisherRegistrationManager;
import org.ntnunotif.wsnu.services.implementations.subscriptionmanager.SimpleSubscriptionManager;
import org.oasis_open.docs.wsn.b_2.NotificationMessageHolderType;
import org.oasis_open.docs.wsn.b_2.Notify;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created by <NAME> (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 configuration file on classpath
    private static final String configurationFile = "/config/wsnserver.xml";

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

    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;

    /**
     * Empty constructor, uses defaults from jetty configuration file for WSNServer
     */
    private WSNotificationServer() {
        this.init(null);
        _running = false;
        _invoked = true;
    }

    /**
     * Constructor that takes in a port that the WSNServer jetty instance should
     * listen to.
     * <p>
     * @param port: An integer representing the port the WSNServer should bind to.
     */
    private WSNotificationServer(Integer port) {
        this.init(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 (WSNotificationServer._invoked) return _singleton;
        else {
            _singleton = new WSNotificationServer();
            WSNotificationServer._invoked = true;

            return _singleton;
        }
    }

    /**
     * Factory method providing an instance of WSNotificationServer, adhering to the
     * singleton pattern. (Using default port from config file.
     *
     * <p>Note: This factory method will ignore the port argument if an instance is already
     * running. In that case, one must stop the WSNServer instance and invoke it again to
     * specify a different port.</p>
     *
     * @param port: An integer representing the port WSNServer should bind to.
     *
     * @return: The WSNotification instance.
     */
    public static WSNotificationServer getInstance(Integer port) {
        if (WSNotificationServer._invoked) return _singleton;
        else {
            _singleton = new WSNotificationServer(port);
            WSNotificationServer._invoked = true;
            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(Integer port) {

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

        // Set the servertype
        protocolServerType = "WSNotification";

        // Declare HttpClient field
        _client = null;

        // 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(configurationFile);
            XmlConfiguration config = new XmlConfiguration(configResource.getInputStream());
            this._server = (Server)config.configure();

            if (port != null) {
                this.addStandardConnector("0.0.0.0", port);
                this._server.setConnectors((Connector[]) this._connectors.toArray());
            }

            // 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 */
                WSNCommandProxy broker = new WSNCommandProxy();
                _commandProxy = broker;
                WSNSubscriptionManager subscriptionManager = new WSNSubscriptionManager();
                WSNRegistrationManager registrationManager = new WSNRegistrationManager();
                SubscriptionService.getInstance().addSubscriptionChangeListener(subscriptionManager);
                SubscriptionService.getInstance().addPublisherChangeListener(registrationManager);

                broker.quickBuild("broker", this._requestParser);
                subscriptionManager.quickBuild("subscriptionManager", this._requestParser);
                subscriptionManager.initCoreSubscriptionService(SubscriptionService.getInstance());
                registrationManager.quickBuild("registrationManager", this._requestParser);
                registrationManager.initCoreSubscriptionService(SubscriptionService.getInstance());
                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++;
                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++;
            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...");
            this._client.stop();
            this._server.stop();
            log.info("WSNServer Client and ServerThread stopped");
        } catch (Exception e) {
            totalErrors++;
            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;
    }

    /**
     * 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 recieved message for distribution");
        if (!message.getOriginProtocol().equals(protocolServerType)) {
            log.debug("The message originated from other protocol than WSNotification");

            /* HERE BE FUCKING DRAGONS */

            // Create wrapper provided the message, subscriptionref, publisherref and dialect
            Notify notify = WSNTools.generateNotificationMessage(
                    message,    // OKSE Message object
                    message.getAttribute(WSNSubscriptionManager.WSN_ENDPOINT_TOKEN), //
                    "http://herp.derp/snake",
                    WSNTools._ConcreteTopicExpression
            );

            // Pass it on to the broker proxy for processing
            _commandProxy.sendNotification(notify);
        }
    }

    /**
     * Fetches the complete URI of this ProtocolServer
     * @return A string representing the complete URI of this ProtocolServer
     */
    public static String getURI(){
        if(_singleton._server.getURI() == null){
            return "http://0.0.0.0:8080";<commit_msg>Fix duplicate message propagation in WSNotificationServer sendMessage()<commit_after>/*
 * 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 java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;

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.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.ntnunotif.wsnu.services.general.WsnUtilities;
import org.ntnunotif.wsnu.services.implementations.notificationbroker.NotificationBrokerImpl;
import org.ntnunotif.wsnu.services.implementations.publisherregistrationmanager.SimplePublisherRegistrationManager;
import org.ntnunotif.wsnu.services.implementations.subscriptionmanager.SimpleSubscriptionManager;
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;

/**
 * Created by <NAME> (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 configuration file on classpath
    private static final String configurationFile = "/config/wsnserver.xml";

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

    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;

    /**
     * Empty constructor, uses defaults from jetty configuration file for WSNServer
     */
    private WSNotificationServer() {
        this.init(null);
        _running = false;
        _invoked = true;
    }

    /**
     * Constructor that takes in a port that the WSNServer jetty instance should
     * listen to.
     * <p>
     * @param port: An integer representing the port the WSNServer should bind to.
     */
    private WSNotificationServer(Integer port) {
        this.init(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 (WSNotificationServer._invoked) return _singleton;
        else {
            _singleton = new WSNotificationServer();
            WSNotificationServer._invoked = true;

            return _singleton;
        }
    }

    /**
     * Factory method providing an instance of WSNotificationServer, adhering to the
     * singleton pattern. (Using default port from config file.
     *
     * <p>Note: This factory method will ignore the port argument if an instance is already
     * running. In that case, one must stop the WSNServer instance and invoke it again to
     * specify a different port.</p>
     *
     * @param port: An integer representing the port WSNServer should bind to.
     *
     * @return: The WSNotification instance.
     */
    public static WSNotificationServer getInstance(Integer port) {
        if (WSNotificationServer._invoked) return _singleton;
        else {
            _singleton = new WSNotificationServer(port);
            WSNotificationServer._invoked = true;
            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(Integer port) {

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

        // Set the servertype
        protocolServerType = "WSNotification";

        // Declare HttpClient field
        _client = null;

        // 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(configurationFile);
            XmlConfiguration config = new XmlConfiguration(configResource.getInputStream());
            this._server = (Server)config.configure();

            if (port != null) {
                this.addStandardConnector("0.0.0.0", port);
                this._server.setConnectors((Connector[]) this._connectors.toArray());
            }

            // 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 */
                WSNCommandProxy broker = new WSNCommandProxy();
                _commandProxy = broker;
                WSNSubscriptionManager subscriptionManager = new WSNSubscriptionManager();
                WSNRegistrationManager registrationManager = new WSNRegistrationManager();
                SubscriptionService.getInstance().addSubscriptionChangeListener(subscriptionManager);
                SubscriptionService.getInstance().addPublisherChangeListener(registrationManager);

                broker.quickBuild("broker", this._requestParser);
                subscriptionManager.quickBuild("subscriptionManager", this._requestParser);
                subscriptionManager.initCoreSubscriptionService(SubscriptionService.getInstance());
                registrationManager.quickBuild("registrationManager", this._requestParser);
                registrationManager.initCoreSubscriptionService(SubscriptionService.getInstance());
                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++;
                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++;
            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...");
            this._client.stop();
            this._server.stop();
            log.info("WSNServer Client and ServerThread stopped");
        } catch (Exception e) {
            totalErrors++;
            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;
    }

    /**
     * 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 recieved message for distribution");
        if (!message.getOriginProtocol().equals(protocolServerType)) {
            log.debug("The message originated from other protocol than WSNotification");

            // Create wrapper provided the message, subscriptionref, publisherref and dialect
            Notify notify = WSNTools.generateNotificationMessage(
                    message,    // OKSE Message object
                    message.getAttribute(WSNSubscriptionManager.WSN_ENDPOINT_TOKEN), // Returns publisherKey found
                    null, // We do not yet know the recipient endpoints
                    WSNTools._ConcreteTopicExpression
            );

            /*
                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 = new NuNamespaceContextResolver();

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

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

            // Remember current message with context
            Notify currentMessage = notify;
            NuNamespaceContextResolver currentMessageNamespaceContextResolver = namespaceContextResolver;

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

                // Filter do filter handling, if any
                Notify toSend = _commandProxy.getRecipientFilteredNotify(recipient, 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));
                    // Pass it along to the requestparser
                    _requestParser.acceptLocalMessage(outMessage);
                }
            }
        }
    }

    /**
     * Fetches the complete URI of this ProtocolServer
     * @return A string representing the complete URI of this ProtocolServer
     */
    public static String getURI(){
        if(_singleton._server.getURI() == null){
            return "http://0.0.0.0:8080";
{'fraction_non_alphanumeric': 0.07616800054723305, 'fraction_numerical': 0.0018127094876530542, 'mean_word_length': 2.564862228724701, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 32, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 10}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1564800', 'n_tokens_mistral': 7171, 'n_tokens_neox': 6870, 'n_words': 2206}