org.geoserver.wps.remote.plugin.XMPPClient.java Source code

Java tutorial

Introduction

Here is the source code for org.geoserver.wps.remote.plugin.XMPPClient.java

Source

/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geoserver.wps.remote.plugin;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.SSLContext;

import org.apache.commons.io.IOUtils;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.ssl.SSLContexts;
import org.geoserver.ows.Dispatcher;
import org.geoserver.ows.Request;
import org.geoserver.ows.URLMangler.URLType;
import org.geoserver.ows.util.RequestUtils;
import org.geoserver.ows.util.ResponseUtils;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.platform.resource.Resources;
import org.geoserver.wps.process.RawData;
import org.geoserver.wps.process.ResourceRawData;
import org.geoserver.wps.process.StreamRawData;
import org.geoserver.wps.process.StringRawData;
import org.geoserver.wps.remote.RemoteMachineDescriptor;
import org.geoserver.wps.remote.RemoteProcessClient;
import org.geoserver.wps.remote.RemoteProcessClientListener;
import org.geoserver.wps.remote.RemoteProcessFactoryConfiguration;
import org.geoserver.wps.remote.RemoteProcessFactoryConfigurationWatcher;
import org.geoserver.wps.remote.RemoteProcessFactoryListener;
import org.geoserver.wps.remote.RemoteRequestDescriptor;
import org.geotools.feature.NameImpl;
import org.geotools.util.logging.Logging;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManager;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.bosh.BOSHConfiguration;
import org.jivesoftware.smack.bosh.XMPPBOSHConnection;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Type;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.muc.DiscussionHistory;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.opengis.feature.type.Name;
import org.opengis.util.ProgressListener;

import net.razorvine.pickle.Opcodes;
import net.razorvine.pickle.PickleException;
import net.razorvine.pickle.PickleUtils;
import net.razorvine.pickle.Pickler;
import net.razorvine.pickle.Unpickler;

/**
 * XMPP implementation of the {@link RemoteProcessClient}
 * 
 * @author Alessio Fabiani, GeoSolutions
 * 
 */
public class XMPPClient extends RemoteProcessClient {

    /** The LOGGER */
    public static final Logger LOGGER = Logging.getLogger(XMPPClient.class.getPackage().getName());

    private static final int DEFAULT_PACKET_REPLY_TIMEOUT = 500; // millis

    /** The XMPP Server endpoint */
    private String server;

    /** The XMPP Server port */
    private int port;

    /**
     * XMPP specific parameters and properties
     */
    private XMPPConnection connection;

    private ConnectionConfiguration config;

    private ChatManager chatManager;

    private PacketListener packetListener;

    private ServiceDiscoveryManager discoStu;

    private Map<String, Chat> openChat = Collections.synchronizedMap(new HashMap<String, Chat>());

    private String domain;

    private String bus;

    private String managementChannelUser;

    private String managementChannelPassword;

    protected String managementChannel;

    /**
     * Private structures
     */
    protected List<String> serviceChannels;

    /*
     * protected Map<String, List<String>> occupantsList = Collections .synchronizedMap(new HashMap<String, List<String>>());
     */

    protected List<Name> registeredServices = Collections.synchronizedList(new ArrayList<Name>());

    protected List<MultiUserChat> mucServiceChannels = new ArrayList<MultiUserChat>();

    protected MultiUserChat mucManagementChannel;

    /** Primitive type name -> class map. */
    public static final Map<String, Object> PRIMITIVE_NAME_TYPE_MAP = new HashMap<String, Object>();

    /** Default Thresholds indicating overloaded resources */
    private static double DEFAULT_CPU_PERCENT_THRESHOLD = 82.0;

    private static double DEFAULT_MEM_PERCENT_THRESHOLD = 82.0;

    /** Setup the primitives map. */
    static enum CType {
        SIMPLE, COMPLEX
    }

    /**
     * 
     * STATIC MAP of the available mime-types.
     * 
     * Those are the available key-strigns which the remote client can specify on the XMPP message in order to declare which kind of output objects it
     * is able to produce.
     * 
     * 
     */

    static {
        // ----
        PRIMITIVE_NAME_TYPE_MAP.put("string", new Object[] { String.class, CType.SIMPLE, null, "text/plain" });
        PRIMITIVE_NAME_TYPE_MAP.put("url", new Object[] { String.class, CType.SIMPLE, null, "text/plain" });
        PRIMITIVE_NAME_TYPE_MAP.put("boolean", new Object[] { Boolean.TYPE, CType.SIMPLE, Boolean.TRUE, "" });
        PRIMITIVE_NAME_TYPE_MAP.put("byte", new Object[] { Byte.TYPE, CType.SIMPLE, null, "" });
        PRIMITIVE_NAME_TYPE_MAP.put("char", new Object[] { Character.TYPE, CType.SIMPLE, null, "text/plain" });
        PRIMITIVE_NAME_TYPE_MAP.put("short", new Object[] { Short.TYPE, CType.SIMPLE, null, "" });
        PRIMITIVE_NAME_TYPE_MAP.put("int", new Object[] { Integer.TYPE, CType.SIMPLE, null, "" });
        PRIMITIVE_NAME_TYPE_MAP.put("long", new Object[] { Long.TYPE, CType.SIMPLE, null, "" });
        PRIMITIVE_NAME_TYPE_MAP.put("float", new Object[] { Float.TYPE, CType.SIMPLE, null, "" });
        PRIMITIVE_NAME_TYPE_MAP.put("double", new Object[] { Double.TYPE, CType.SIMPLE, null, "" });
        PRIMITIVE_NAME_TYPE_MAP.put("datetime", new Object[] { Date.class, CType.SIMPLE, null, "" });

        // Complex and Raw data types
        // ----
        PRIMITIVE_NAME_TYPE_MAP.put("application/xml", new Object[] { RawData.class, CType.COMPLEX,
                new StringRawData("", "application/xml"), "application/xml,text/xml", ".xml" });
        PRIMITIVE_NAME_TYPE_MAP.put("text/xml", new Object[] { RawData.class, CType.COMPLEX,
                new StringRawData("", "text/xml"), "application/xml,text/xml", ".xml" });

        // ----
        PRIMITIVE_NAME_TYPE_MAP.put("text/xml;subtype", new Object[] { RawData.class, CType.COMPLEX,
                new StringRawData("", "application/gml-3.1.1"),
                "application/xml,application/gml-3.1.1,application/gml-2.1.2,text/xml; subtype=gml/3.1.1,text/xml; subtype=gml/2.1.2",
                ".xml" });
        PRIMITIVE_NAME_TYPE_MAP.put("text/xml;subtype=gml/3.1.1", PRIMITIVE_NAME_TYPE_MAP.get("text/xml;subtype"));
        PRIMITIVE_NAME_TYPE_MAP.put("text/xml;subtype=gml/2.1.2", PRIMITIVE_NAME_TYPE_MAP.get("text/xml;subtype"));
        PRIMITIVE_NAME_TYPE_MAP.put("application/gml-3.1.1", PRIMITIVE_NAME_TYPE_MAP.get("text/xml;subtype"));
        PRIMITIVE_NAME_TYPE_MAP.put("application/gml-2.1.2", PRIMITIVE_NAME_TYPE_MAP.get("text/xml;subtype"));

        // ----
        PRIMITIVE_NAME_TYPE_MAP.put("application/json", new Object[] { RawData.class, CType.COMPLEX,
                new StringRawData("", "application/vnd.geo+json"), "application/vnd.geo+json", ".json" });

        // ----
        PRIMITIVE_NAME_TYPE_MAP.put("application/owc", new Object[] { RawData.class, CType.COMPLEX,
                new StringRawData("", "application/vnd.geo+json"), "application/vnd.geo+json", ".json" });

        // ----
        PRIMITIVE_NAME_TYPE_MAP.put("image/geotiff", new Object[] { RawData.class, CType.COMPLEX,
                new ResourceRawData(null, "image/geotiff", "tif"), "image/geotiff,image/tiff", ".tif" });
        PRIMITIVE_NAME_TYPE_MAP.put("image/geotiff;stream", new Object[] { RawData.class, CType.COMPLEX,
                new StreamRawData("image/geotiff", null, "tif"), "image/geotiff,image/tiff", ".tif" });

        // ----
        PRIMITIVE_NAME_TYPE_MAP.put("application/zip", new Object[] { RawData.class, CType.COMPLEX,
                new ResourceRawData(null, "application/zip", "zip"), "application/zip", ".zip" });
        PRIMITIVE_NAME_TYPE_MAP.put("application/zip;stream", new Object[] { RawData.class, CType.COMPLEX,
                new StreamRawData("application/zip", null, "zip"), "application/zip", ".zip" });

        // ----
        PRIMITIVE_NAME_TYPE_MAP.put("application/x-netcdf", new Object[] { RawData.class, CType.COMPLEX,
                new ResourceRawData(null, "application/x-netcdf", "nc"), "application/x-netcdf", ".nc" });
        PRIMITIVE_NAME_TYPE_MAP.put("application/x-netcdf;stream", new Object[] { RawData.class, CType.COMPLEX,
                new StreamRawData("application/x-netcdf", null, "nc"), "application/x-netcdf", ".nc" });

        // ----
        PRIMITIVE_NAME_TYPE_MAP.put("video/mp4", new Object[] { RawData.class, CType.COMPLEX,
                new ResourceRawData(null, "video/mp4", "mp4"), "video/mp4", ".mp4" });
        PRIMITIVE_NAME_TYPE_MAP.put("video/mp4;stream", new Object[] { RawData.class, CType.COMPLEX,
                new StreamRawData("video/mp4", null, "mp4"), "video/mp4", ".mp4" });
    }

    /**
     * Default Constructor
     * 
     * @param remoteProcessFactoryConfigurationWatcher
     * @param enabled
     */
    public XMPPClient(RemoteProcessFactoryConfigurationWatcher remoteProcessFactoryConfigurationWatcher,
            boolean enabled, int priority) {
        super(remoteProcessFactoryConfigurationWatcher, enabled, priority);
        this.server = getConfiguration().get("xmpp_server");
        this.port = Integer.parseInt(getConfiguration().get("xmpp_port"));
        this.domain = getConfiguration().get("xmpp_domain");
        this.bus = getConfiguration().get("xmpp_bus");
        this.managementChannelUser = getConfiguration().get("xmpp_management_channel_user");
        this.managementChannelPassword = getConfiguration().get("xmpp_management_channel_pwd");
        this.managementChannel = getConfiguration().get("xmpp_management_channel");

        this.serviceChannels = new ArrayList<String>();

        String[] serviceNamespaces = getConfiguration().get("xmpp_service_channels").split(",");
        for (int sc = 0; sc < serviceNamespaces.length; sc++) {
            this.serviceChannels.add(serviceNamespaces[sc].trim());
        }
    }

    @Override
    public void init() throws Exception {

        // Initializes the XMPP Client and starts the communication. It also
        // register GeoServer as "manager" to the service channels on the MUC
        // (Multi
        // User Channel) Rooms
        LOGGER.info(String.format("Initializing connection to server %1$s port %2$d", server, port));

        int packetReplyTimeout = DEFAULT_PACKET_REPLY_TIMEOUT;
        if (getConfiguration().get("xmpp_packet_reply_timeout") != null) {
            packetReplyTimeout = Integer.parseInt(getConfiguration().get("xmpp_packet_reply_timeout"));
        }
        SmackConfiguration.setDefaultPacketReplyTimeout(packetReplyTimeout);

        config = new ConnectionConfiguration(server, port);

        checkSecured(getConfiguration());

        // Trust own CA and all self-signed certs
        SSLContext sslcontext = null;
        if (this.certificateFile != null && this.certificatePassword != null) {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            FileInputStream instream = new FileInputStream(this.certificateFile);
            try {
                trustStore.load(instream, this.certificatePassword.toCharArray());
            } finally {
                instream.close();
            }

            sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();

        }

        if (sslcontext != null) {
            // config.setSASLAuthenticationEnabled(false);
            config.setSecurityMode(SecurityMode.enabled);
            config.setCustomSSLContext(sslcontext);
        } else {
            config.setSecurityMode(SecurityMode.disabled);
        }

        // Actually performs the connection to the XMPP Server
        for (int testConn = 0; testConn < 5; testConn++) {
            try {
                // Try first the TCP Endpoint
                connection = new XMPPTCPConnection(config);
                connection.connect();
                break;
            } catch (NoResponseException e) {
                connection = null;
                if (testConn >= 5) {
                    LOGGER.warning(
                            "No XMPP TCP Endpoint available or could not get any response from the Server. Falling back to BOSH Endpoint.");
                } else {
                    LOGGER.log(Level.WARNING, "Tentative #" + (testConn + 1)
                            + " - Error while trying to connect to XMPP TCP Endpoint.", e);
                    Thread.sleep(500);
                }
            }
        }

        if (connection == null || !connection.isConnected()) {
            for (int testConn = 0; testConn < 5; testConn++) {
                try {
                    // Falling back to BOSH Endpoint
                    BOSHConfiguration boshConfig = new BOSHConfiguration((sslcontext != null), server, port, null,
                            getConfiguration().get("xmpp_domain"));

                    if (sslcontext != null) {
                        // boshConfig.setSASLAuthenticationEnabled(false);
                        boshConfig.setSecurityMode(SecurityMode.enabled);
                        boshConfig.setCustomSSLContext(sslcontext);
                    } else {
                        boshConfig.setSecurityMode(SecurityMode.disabled);
                    }

                    connection = new XMPPBOSHConnection(boshConfig);
                    connection.connect();
                    break;
                } catch (NoResponseException e) {
                    connection = null;
                    if (testConn >= 5) {
                        LOGGER.warning(
                                "No XMPP BOSH Endpoint available or could not get any response from the Server. The XMPP Client won't be available.");
                    } else {
                        LOGGER.log(Level.WARNING, "Tentative #" + (testConn + 1)
                                + " - Error while trying to connect to XMPP BOSH Endpoint.", e);
                        Thread.sleep(500);
                    }
                }
            }
        }

        LOGGER.info("Connected: " + connection.isConnected());

        // Check if the connection to the XMPP server is successful; the login
        // and registration is not yet performed at this time
        if (connection.isConnected()) {
            chatManager = ChatManager.getInstanceFor(connection);
            discoStu = ServiceDiscoveryManager.getInstanceFor(connection);

            // Add features to our XMPP client
            discoProperties();

            // Performs login with "admin" user credentials
            performLogin(getConfiguration().get("xmpp_manager_username"),
                    getConfiguration().get("xmpp_manager_password"));

            // Start "ping" task in order to maintain alive the connection
            startPingTask();

            // Send invitation to the registered endpoints
            sendInvitations();

            //
            getEndpointsLoadAverages();

            //
            checkPendingRequests();
        } else {
            setEnabled(false);
            LOGGER.warning("Not connected! The XMPP client has been disabled.");
        }
    }

    private void checkSecured(RemoteProcessFactoryConfiguration configuration) {
        final String xmppServerEmbeddedSecure = configuration.get("xmpp_server_embedded_secure");
        final String xmppServerEmbeddedCertFile = configuration.get("xmpp_server_embedded_certificate_file");
        final String xmppServerEmbeddedCertPwd = configuration.get("xmpp_server_embedded_certificate_password");

        if (xmppServerEmbeddedSecure != null && Boolean.valueOf(xmppServerEmbeddedSecure.trim())) {
            // Override XML properties
            if (xmppServerEmbeddedCertFile != null && xmppServerEmbeddedCertPwd != null) {
                final org.geoserver.platform.resource.Resource certFileResource = Resources
                        .fromURL(xmppServerEmbeddedCertFile.trim());
                if (certFileResource != null) {
                    this.certificateFile = certFileResource.file();
                    this.certificatePassword = xmppServerEmbeddedCertPwd.trim();
                } else {
                    // Get the Resource loader
                    GeoServerResourceLoader loader = GeoServerExtensions.bean(GeoServerResourceLoader.class);
                    try {
                        // Copy the default property file into the data directory
                        //URL url = RemoteProcessFactoryConfigurationWatcher.class.getResource(xmppServerEmbeddedCertFile.trim());
                        //if (url != null) {
                        this.certificateFile = loader.createFile(xmppServerEmbeddedCertFile.trim());
                        loader.copyFromClassPath(xmppServerEmbeddedCertFile.trim(),
                                this.certificateFile/*,
                                                    RemoteProcessFactoryConfigurationWatcher.class*/);
                        //}
                        this.certificateFile = loader.find(xmppServerEmbeddedCertFile.trim());
                        this.certificatePassword = xmppServerEmbeddedCertPwd.trim();
                    } catch (IOException e) {
                        if (LOGGER.isLoggable(Level.WARNING)) {
                            LOGGER.log(Level.WARNING, e.getMessage(), e);
                        }
                    }
                }
            }
        }
    }

    @Override
    public String execute(Name serviceName, Map<String, Object> input, Map<String, Object> metadata,
            ProgressListener monitor) throws Exception {

        // Check for a free machine...

        LOGGER.info("XMPPClient::execute - searching available remote process machine for service [" + serviceName
                + "]");

        /**
         * Sanity Checks
         */
        if (metadata == null) {
            throw new Exception("Could not send a Request Message to the Remote XMPP Client!");
        }

        /**
         * Collecting Request Info and Inputs
         */
        // Extract the process inputs
        final Object fixedInputs = getFixedInputs(input);

        // Generate a unique pID to be used to identify the endpoint
        final String pid = md5Java(serviceName.getNamespaceURI() + "." + serviceName.getLocalPart()
                + System.nanoTime() + byteArrayToURLString(pickle(fixedInputs)));

        // Try to retrieve the base URL from the request and send to the
        // endpoint as parameter
        Request request = Dispatcher.REQUEST.get();
        metadata.put("request", request);
        String baseURL = getGeoServer().getGlobal().getSettings().getProxyBaseUrl();

        try {
            if (baseURL == null) {
                baseURL = RequestUtils.baseURL(request.getHttpRequest());
            }

            baseURL = ResponseUtils.buildURL(baseURL, "/", null, URLType.SERVICE);
        } catch (Exception e) {
            LOGGER.warning("Could not acquire the GeoServer Base URL!");
        }

        // Build and send the REUQEST message

        /**
         * topic = request id = pid baseURL = geoserver url message = <pickled WPS inputs>
         */

        final String msg = "topic=request&id=" + pid + "&baseURL=" + baseURL + "&message="
                + byteArrayToURLString(pickle(fixedInputs));

        /**
         * Looking for an available remote processing node
         */

        final String serviceJID = getFlattestMachine(serviceName);

        if (serviceJID != null) {
            /**
             * We have a JID to an available processing node; send the request message to it...
             */

            // Update Metadata
            metadata.put("serviceJID", serviceJID);

            LOGGER.info("XMPPClient::execute - extracting the PID for the service JID [" + serviceJID
                    + "] with inputs [" + fixedInputs + "]");

            sendMessage(serviceJID, msg);
        } else {
            /**
             * We could not find a suitable processing node to serve the request; queue it for later checks...
             */

            pendingRequests.add(new RemoteRequestDescriptor(serviceName, input, metadata, pid, baseURL));
        }

        return pid;
    }

    /**
     * Utility method to extract the process inputs accordingly to whatever declared from the endpoint.
     * 
     * @param input
     *
     * @throws IOException
     */
    private Object getFixedInputs(Map<String, Object> input) throws IOException {
        Map<String, Object> fixedInputs = new HashMap<String, Object>();

        for (Entry<String, Object> entry : input.entrySet()) {
            final String key = entry.getKey();
            final Object value = entry.getValue();

            Object fixedValue = value;
            if (value instanceof RawData) {
                fixedValue = IOUtils.toString(((RawData) value).getInputStream(), "UTF-8");
            } else if (value instanceof List) {
                List<Object> values = (List<Object>) value;

                if (values != null && values.size() > 0 && values.get(0) instanceof RawData) {
                    fixedValue = new ArrayList<String>();

                    for (Object o : values) {
                        ((List<String>) fixedValue).add(IOUtils.toString(((RawData) o).getInputStream(), "UTF-8"));
                    }
                }
            }

            fixedInputs.put(key, fixedValue);
        }

        return fixedInputs;
    }

    /**
     * Add features to our XMPP client We do support Data forms, XHTML-IM, Service Discovery
     */
    private void discoProperties() {
        discoStu.addFeature("http://jabber.org/protocol/xhtml-im");
        discoStu.addFeature("jabber:x:data");
        discoStu.addFeature("http://jabber.org/protocol/disco#info");
        discoStu.addFeature("jabber:iq:privacy");
        discoStu.addFeature("http://jabber.org/protocol/si");
        discoStu.addFeature("http://jabber.org/protocol/bytestreams");
        discoStu.addFeature("http://jabber.org/protocol/ibb");
    }

    /**
     * Logins as manager to the XMPP Server and registers to the service channels management chat rooms
     * 
     * @param username
     * @param password
     */
    public void performLogin(String username, String password) throws Exception {
        if (connection != null && connection.isConnected()) {
            connection.login(username, password, getResource(username));

            // Create a MultiUserChat using a XMPPConnection for a room

            // User joins the new room using a password and specifying
            // the amount of history to receive. In this example we are
            // requesting the last 5 messages.
            DiscussionHistory history = new DiscussionHistory();
            history.setMaxStanzas(5);

            mucManagementChannel = new MultiUserChat(connection, managementChannel + "@" + bus + "." + domain);
            try {
                mucManagementChannel.join(getJID(username), managementChannelPassword); /*
                                                                                        * , history, connection. getPacketReplyTimeout());
                                                                                        */
            } catch (Exception e) {
                mucManagementChannel.join(username, managementChannelPassword);
            }

            for (String channel : serviceChannels) {
                MultiUserChat serviceChannel = new MultiUserChat(connection, channel + "@" + bus + "." + domain);
                try {
                    serviceChannel.join(getJID(username), managementChannelPassword); /*
                                                                                       * , history, connection. getPacketReplyTimeout());
                                                                                       */
                } catch (Exception e) {
                    serviceChannel.join(username, managementChannelPassword);
                }

                mucServiceChannels.add(serviceChannel);
            }

            //
            setStatus(true, "Orchestrator Active");

            //
            setupListeners();
        }
    }

    /**
     * Generate the XMPP JID
     * 
     * @param username
     *
     */
    private String getJID(String username) {
        // final String id = md5Java(username + "@" + this.domain + "/" + System.nanoTime());
        return username + "@" + this.domain;
    }

    /**
     * Generate a unique Server JID Resource
     * 
     * @param username
     *
     */
    private String getResource(String username) {
        final String id = md5Java(username + "@" + this.domain + "/" + System.nanoTime());
        try {
            return /* this.domain + "/" + */id + "@" + InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            return /* this.domain + "/" + */id + "@geoserver";
        }
    }

    /**
     * Declare the status on the XMPP Chat
     * 
     * @param available
     * @param status
     */
    public void setStatus(boolean available, String status) throws Exception {
        Presence.Type type = available ? Type.available : Type.unavailable;
        Presence presence = new Presence(type);

        presence.setStatus(status);
        connection.sendPacket(presence);
    }

    /**
     * Destroy the connection
     */
    public void destroy() throws Exception {
        if (connection != null && connection.isConnected()) {
            stopPingTask();
            connection.disconnect();
        }
    }

    /**
     * 
     * 
     * @param user
     * @param name
     */
    public void createEntry(String user, String name) throws Exception {
        LOGGER.fine(String.format("Creating entry for buddy '%1$s' with name %2$s", user, name));
        Roster roster = connection.getRoster();
        roster.createEntry(user, name, null);
    }

    /**
     * This handles the chat listener. We can't simply listen to chats for some reason, and instead have to grab the chats from the packets. The other
     * listeners work properly in SMACK
     */
    public void setupListeners() {
        /*
         * This is the actual code that handles what happens with XMPP users
         */
        packetListener = new XMPPPacketListener(this);

        // PacketFilter filter = new MessageTypeFilter(Message.Type.chat);
        connection.addPacketListener(packetListener, null);
    }

    /**
     * Conversation setup!
     * 
     * Messages should be moved here once we get this working properly
     */
    public Chat setupChat(final String origin) {
        synchronized (openChat) {
            if (openChat.get(origin) != null) {
                return openChat.get(origin);
            }

            MessageListener listener = new MessageListener() {
                public void processMessage(Chat chat, Message message) {
                    // TODO: Fix this so that this actually does something!
                }
            };

            Chat chat = chatManager.createChat(origin, listener);
            openChat.put(origin, chat);
            return chat;
        }
    }

    /**
     * This is the code that handles HTML messages
     */
    public void sendMessage(String person, String message) {
        synchronized (openChat) {
            Chat chat = openChat.get(person);
            if (chat == null)
                chat = setupChat(person);
            try {
                chat.sendMessage(message);
            } catch (XMPPException e) {
                LOGGER.log(Level.SEVERE, "xmppClient._ReceiveError", e);
            } catch (NotConnectedException e) {
                LOGGER.log(Level.SEVERE, "xmppClient._ReceiveError", e);
            }
        }
    }

    /**
     * Close the XMPP connection
     * 
     * @throws NotConnectedException
     */
    public void disconnect() throws NotConnectedException {
        connection.disconnect();
    }

    /**
     * Utility method to extract the Service Name from the XMPP JID
     * 
     * @param person
     * 
     *
     */
    public static NameImpl extractServiceName(String person) throws Exception {
        String occupantFlatName = null;
        if (person.lastIndexOf("@") < person.indexOf("/")) {
            occupantFlatName = person.substring(person.indexOf("/") + 1);
        } else {
            occupantFlatName = person.substring(person.indexOf("/") + 1);
            occupantFlatName = occupantFlatName.substring(0, occupantFlatName.indexOf("@"));
        }

        if (occupantFlatName.indexOf(".") > 0) {
            final String serviceName[] = occupantFlatName.split("\\.");
            return new NameImpl(serviceName[0], serviceName[1]);
        } else {
            final String channel = person.substring(0, person.indexOf("@"));
            return new NameImpl(channel, occupantFlatName);
        }
    }

    /**
     * Send an invitation to the new logged in member
     * 
     */
    protected void sendInvitations() throws Exception {
        synchronized (registeredServices) {
            for (MultiUserChat mucServiceChannel : mucServiceChannels) {
                for (String occupant : mucServiceChannel.getOccupants()) {
                    final Name serviceName = extractServiceName(occupant);

                    // send invitation and register source JID
                    String[] serviceJIDParts = occupant.split("/");
                    if (serviceJIDParts.length == 3
                            && (serviceJIDParts[2].startsWith("master") || serviceJIDParts[2].indexOf("@") < 0)) {
                        sendMessage(occupant, "topic=invite");
                    }
                    // register service on listeners
                    if (!registeredServices.contains(serviceName)) {
                        registeredServices.add(serviceName);
                    }
                }
            }
        }
    }

    /**
     * Scan Remote Processing Machines availability and average load
     * 
     * @throws Exception
     */
    protected void getEndpointsLoadAverages() throws Exception {
        synchronized (registeredProcessingMachines) {
            List<String> nodeJIDs = new ArrayList<String>();
            for (RemoteMachineDescriptor node : registeredProcessingMachines) {
                nodeJIDs.add(node.getNodeJID());
                //node.setAvailable(false);
            }

            for (MultiUserChat mucServiceChannel : mucServiceChannels) {
                for (String occupant : mucServiceChannel.getOccupants()) {
                    if (!nodeJIDs.contains(occupant)) {
                        registeredProcessingMachines.add(new RemoteMachineDescriptor(occupant,
                                extractServiceName(occupant), false, 0.0, 0.0));
                    }

                    // send invitation and register source JID
                    String[] serviceJIDParts = occupant.split("/");
                    if (serviceJIDParts.length == 3
                            && (serviceJIDParts[2].startsWith("master") || serviceJIDParts[2].indexOf("@") < 0)) {
                        sendMessage(occupant, "topic=getloadavg");
                    }
                }
            }
        }
    }

    /**
     * Scan pending requests queue; try to find a free remote node suitable for processing or abort the request if expired.
     * 
     * @throws Exception
     */
    protected void checkPendingRequests() throws Exception {
        synchronized (pendingRequests) {
            for (RemoteRequestDescriptor request : pendingRequests) {

                // Check if the request is still valid
                final String pid = request.getPid();
                boolean isRequestValid = false;
                for (RemoteProcessClientListener process : getRemoteClientListeners()) {
                    if (process.getPID().equals(pid)) {
                        isRequestValid = true;
                        break;
                    }
                }

                if (!isRequestValid) {
                    // Remove the request from the queue
                    pendingRequests.remove(request);
                    continue;
                }

                // Check if the request can be executed by a remote processing node
                final Name serviceName = request.getServicename();
                final String serviceJID = getFlattestMachine(serviceName);

                if (serviceJID != null) {
                    // Extract the process inputs
                    final Object fixedInputs = getFixedInputs(request.getInput());

                    // Build and send the REUQEST message
                    /**
                     * topic = request id = pid baseURL = geoserver url message = <pickled WPS inputs>
                     */
                    final String msg = "topic=request&id=" + pid + "&baseURL=" + request.getBaseURL() + "&message="
                            + byteArrayToURLString(pickle(fixedInputs));

                    /**
                     * We have a JID to an available processing node; send the request message to it...
                     */

                    // Update Metadata
                    request.getMetadata().put("serviceJID", serviceJID);

                    LOGGER.info("XMPPClient::execute - extracting the PID for the service JID [" + serviceJID
                            + "] with inputs [" + fixedInputs + "]");

                    sendMessage(serviceJID, msg);

                    // Remove the request from the queue
                    pendingRequests.remove(request);
                    continue;
                }
            }
        }
    }

    /**
     * A new member joined one of the service chat-rooms; send an invitation and see if it is a remote service. If so, register it
     * 
     * @param p
     * 
     */
    protected void handleMemberJoin(Presence p) throws Exception {
        synchronized (registeredServices) {
            LOGGER.finer("Member " + p.getFrom() + " joined the chat.");
            final Name serviceName = extractServiceName(p.getFrom());

            // send invitation and register source JID
            String[] serviceJIDParts = p.getFrom().split("/");

            if (serviceJIDParts.length == 3
                    && (serviceJIDParts[2].startsWith("master") || serviceJIDParts[2].indexOf("@") < 0)) {
                sendMessage(p.getFrom(), "topic=invite");
            }

            if (!registeredServices.contains(serviceName)) {
                registeredServices.add(serviceName);
            }
        }
    }

    /**
     * A member leaved one of the service chat-rooms; lets remove the service declaration and de-register it
     * 
     * @param p
     * 
     */
    protected void handleMemberLeave(Packet p) throws Exception {
        final Name serviceName = extractServiceName(p.getFrom());

        LOGGER.finer("Member " + p.getFrom() + " leaved the chat.");
        if (registeredServices.contains(serviceName)) {
            registeredServices.remove(serviceName);
        }

        for (RemoteProcessFactoryListener listener : getRemoteFactoryListeners()) {
            listener.deregisterProcess(serviceName);
        }
    }

    /**
     * Find the service by name with the smallest amount of processes running, channel is decoded in service name
     * 
     * e.g. debug.foo@bar/service@localhost
     * 
     * @param service name
     * 
     * @param candidateServiceJID
     * 
     *
     */
    private String getFlattestMachine(Name serviceName) {
        // The candidate remote processing node
        RemoteMachineDescriptor candidateNode = null;

        /** Thresholds indicating overloaded resources */
        Double cpuLoadPercThreshold = DEFAULT_CPU_PERCENT_THRESHOLD;
        Double vmemPercThreshold = DEFAULT_MEM_PERCENT_THRESHOLD;

        if (getConfiguration().get("xmpp_cpu_perc_threshold") != null) {
            cpuLoadPercThreshold = Double.valueOf(getConfiguration().get("xmpp_cpu_perc_threshold"));
        }

        if (getConfiguration().get("xmpp_mem_perc_threshold") != null) {
            vmemPercThreshold = Double.valueOf(getConfiguration().get("xmpp_mem_perc_threshold"));
        }

        synchronized (registeredProcessingMachines) {
            LOGGER.info("XMPPClient::getFlattestMachine - scanning the connected remote services...");

            for (RemoteMachineDescriptor node : registeredProcessingMachines) {
                if (node.getAvailable() && node.getServiceName().equals(serviceName)) {

                    if (node.getLoadAverage() >= cpuLoadPercThreshold
                            || node.getMemPercUsed() >= vmemPercThreshold) {
                        continue;
                    }

                    if (candidateNode == null || (node.getLoadAverage() <= candidateNode.getLoadAverage()
                            && (node.getLoadAverage() < candidateNode.getLoadAverage()
                                    || node.getMemPercUsed() < candidateNode.getMemPercUsed()))) {
                        candidateNode = node;
                    }
                }
            }
        }

        // Return the candidate remote processing node JID or null
        if (candidateNode != null) {
            return candidateNode.getNodeJID();
        }

        return null;
    }

    /**
     * Keep connection alive and check for network changes by sending ping packets
     */

    Thread pingThread;

    private static int ping_task_generation = 1;

    void startPingTask() {
        // Schedule a ping task to run.
        PingTask task = new PingTask();
        pingThread = new Thread(task);
        task.setThread(pingThread);
        pingThread.setDaemon(true);
        pingThread.setName("XmppConnection Pinger " + ping_task_generation);
        ping_task_generation++;
        pingThread.start();
    }

    void stopPingTask() {
        pingThread = null;
    }

    class PingTask implements Runnable {

        private static final long DEFAULT_INITIAL_PING_DELAY = 20000;

        private static final long DEFAULT_PING_INTERVAL = 30000;

        private static final long DEFAULT_PING_TIMEOUT = 10000;

        private long delay;

        private long timeout;

        private long start_delay;

        private Thread thread;

        /**
         * 
         */
        public PingTask() {
            this.delay = DEFAULT_PING_INTERVAL;
            if (getConfiguration().get("xmpp_connection_ping_interval") != null) {
                this.delay = Long.parseLong(getConfiguration().get("xmpp_connection_ping_interval"));
            }

            this.timeout = DEFAULT_PING_TIMEOUT;
            if (getConfiguration().get("xmpp_connection_ping_timeout") != null) {
                this.timeout = Long.parseLong(getConfiguration().get("xmpp_connection_ping_timeout"));
            }

            this.start_delay = DEFAULT_INITIAL_PING_DELAY;
            if (getConfiguration().get("xmpp_connection_ping_initial_delay") != null) {
                this.start_delay = Long.parseLong(getConfiguration().get("xmpp_connection_ping_initial_delay"));
            }
        }

        /**
         * 
         * @param thread
         */
        protected void setThread(Thread thread) {
            this.thread = thread;
        }

        /**
         * 
         *
         * @throws NotConnectedException
         */
        private boolean sendPing() throws NotConnectedException {
            IQ req = new IQ() {
                public String getChildElementXML() {
                    return "<ping xmlns='urn:xmpp:ping'/>";
                }
            };
            req.setType(IQ.Type.GET);
            PacketFilter filter = new AndFilter(new PacketIDFilter(req.getPacketID()),
                    new PacketTypeFilter(IQ.class));
            PacketCollector collector = connection.createPacketCollector(filter);
            connection.sendPacket(req);
            IQ result = (IQ) collector.nextResult(timeout);
            if (result == null) {
                LOGGER.warning("ping timeout");
                return false;
            }
            collector.cancel();
            return true;
        }

        /**
         * 
         */
        public void run() {
            try {
                // Sleep before sending first heartbeat. This will give time to
                // properly finish logging in.
                Thread.sleep(start_delay);
            } catch (InterruptedException ie) {
                // Do nothing
            }
            while (connection != null && pingThread == thread) {
                if (connection.isConnected() && connection.isAuthenticated()) {
                    LOGGER.log(Level.FINER, "ping");
                    try {
                        if (!sendPing()) {
                            LOGGER.severe("ping failed - close connection");
                            try {
                                connection.disconnect();
                            } catch (NotConnectedException e) {
                                LOGGER.log(Level.SEVERE, e.getMessage(), e);
                            }
                        } else {
                            //
                            getEndpointsLoadAverages();

                            //
                            checkPendingRequests();
                        }
                    } catch (NotConnectedException e) {
                        LOGGER.log(Level.SEVERE, e.getMessage(), e);
                    } catch (Exception e) {
                        LOGGER.log(Level.SEVERE, e.getMessage(), e);
                    }
                } else {
                    // Try to reconnect...
                    LOGGER.log(Level.FINER, "Try to reconnect...");
                    try {
                        connection.connect();

                        LOGGER.info("Connected: " + connection.isConnected());

                        // check if the connection to the XMPP server is
                        // successful; the login and registration is not yet
                        // performed at this time
                        if (connection.isConnected()) {
                            chatManager = ChatManager.getInstanceFor(connection);
                            discoStu = ServiceDiscoveryManager.getInstanceFor(connection);

                            //
                            discoProperties();

                            //
                            performLogin(getConfiguration().get("xmpp_manager_username"),
                                    getConfiguration().get("xmpp_manager_password"));

                            //
                            startPingTask();

                            //
                            sendInvitations();

                            //
                            getEndpointsLoadAverages();

                            //
                            checkPendingRequests();
                        } else {
                            setEnabled(false);
                        }
                    } catch (Exception e) {
                        LOGGER.log(Level.WARNING, "XMPP Could not reconnect!", e);
                    }
                }
                try {
                    // Sleep until we should write the next keep-alive.
                    Thread.sleep(delay);
                } catch (InterruptedException ie) {
                    // Do nothing
                }
            }
            LOGGER.log(Level.FINER, "pinger exit");
        }
    }

    /**
     * Utility method to "pickle" (compress) the input parameters to be attached to the XMPP message
     * 
     * @param unpickled
     *
     * @throws PickleException
     * @throws IOException
     */
    static byte[] pickle(Object unpickled) throws PickleException, IOException {
        Pickler p = new Pickler();
        return p.dumps(unpickled);
    }

    /**
     * Utility method to "un-pickle" (decompress) the input parameters attached to the XMPP message
     * 
     * @param strdata
     *
     * @throws PickleException
     * @throws IOException
     */
    static Object unPickle(String strdata) throws PickleException, IOException {
        return unPickle(PickleUtils.str2bytes(strdata));
    }

    /**
     * Utility method to "un-pickle" (decompress) the input parameters attached to the XMPP message
     * 
     * @param data
     *
     * @throws PickleException
     * @throws IOException
     */
    static Object unPickle(byte[] data) throws PickleException, IOException {
        Unpickler u = new Unpickler();
        Object o = u.loads(data);
        u.close();
        return o;
    }

    /**
     * Utility method to get bytes out of a String
     * 
     * @param s
     *
     * @throws IOException
     */
    static byte[] toBytes(String s) throws IOException {
        try {
            byte[] bytes = PickleUtils.str2bytes(s);
            byte[] result = new byte[bytes.length + 3];
            result[0] = (byte) Opcodes.PROTO;
            result[1] = 2;
            result[result.length - 1] = (byte) Opcodes.STOP;
            System.arraycopy(bytes, 0, result, 2, bytes.length);
            return result;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Utility method to get bytes out of a short array
     * 
     * @param shorts
     *
     */
    static byte[] toBytes(short[] shorts) {
        byte[] result = new byte[shorts.length + 3];
        result[0] = (byte) Opcodes.PROTO;
        result[1] = 2;
        result[result.length - 1] = (byte) Opcodes.STOP;
        for (int i = 0; i < shorts.length; ++i) {
            result[i + 2] = (byte) shorts[i];
        }
        return result;
    }

    /**
     * Utility method to generate a unique md5
     * 
     * @param message
     *
     */
    public static String md5Java(String message) {
        String digest = null;
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] hash = md.digest(message.getBytes("UTF-8"));
            // converting byte array to Hexadecimal String
            StringBuilder sb = new StringBuilder(2 * hash.length);
            for (byte b : hash) {
                sb.append(String.format("%02x", b & 0xff));
            }
            digest = sb.substring(0, 15).toString();
        } catch (UnsupportedEncodingException ex) {
            LOGGER.log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            LOGGER.log(Level.SEVERE, null, ex);
        }
        return digest;
    }

    /**
     * Convert a byte array to a URL encoded string
     * 
     * @param in byte[]
     * @return String
     */
    public static String byteArrayToURLString(byte in[]) {
        byte ch = 0x00;
        int i = 0;
        if (in == null || in.length <= 0)
            return null;

        String pseudo[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" };
        StringBuffer out = new StringBuffer(in.length * 2);

        while (i < in.length) {
            // First check to see if we need ASCII or HEX
            if ((in[i] >= '0' && in[i] <= '9') || (in[i] >= 'a' && in[i] <= 'z') || (in[i] >= 'A' && in[i] <= 'Z')
                    || in[i] == '$' || in[i] == '-' || in[i] == '_' || in[i] == '.' || in[i] == '!') {
                out.append((char) in[i]);
                i++;
            } else {
                out.append('%');
                ch = (byte) (in[i] & 0xF0); // Strip off high nibble
                ch = (byte) (ch >>> 4); // shift the bits down
                ch = (byte) (ch & 0x0F); // must do this is high order bit is
                // on!
                out.append(pseudo[(int) ch]); // convert the nibble to a
                // String Character
                ch = (byte) (in[i] & 0x0F); // Strip off low nibble
                out.append(pseudo[(int) ch]); // convert the nibble to a
                // String Character
                i++;
            }
        }

        String rslt = new String(out);

        return rslt;
    }

    /**
     * Convert a list of Strings from an Interator into an array of Classes (the Strings are taken as classnames).
     * 
     * @param it A java.util.Iterator pointing to a Collection of Strings
     * @param cl The ClassLoader to use
     * 
     * @return Array of Classes
     * 
     * @throws ClassNotFoundException When a class could not be loaded from the specified ClassLoader
     */
    public final static Class<?>[] convertToJavaClasses(Iterator<String> it, ClassLoader cl)
            throws ClassNotFoundException {
        ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
        while (it.hasNext()) {
            classes.add(convertToJavaClass(it.next(), cl, null).getClazz());
        }
        return classes.toArray(new Class[classes.size()]);
    }

    /**
     * Convert a given String into the appropriate Class.
     * 
     * @param name Name of class
     * @param cl ClassLoader to use
     * @param object
     * @param sample
     * 
     * @return The class for the given name
     * 
     * @throws ClassNotFoundException When the class could not be found by the specified ClassLoader
     */
    final static ParameterTemplate convertToJavaClass(String name, ClassLoader cl, Object defaultValue)
            throws ClassNotFoundException {
        int arraySize = 0;
        while (name.endsWith("[]")) {
            name = name.substring(0, name.length() - 2);
            arraySize++;
        }

        // Retrieve the Class of the parameter through the mapping
        String mimeTypes = "";
        Class c = null;
        if (name.equalsIgnoreCase("complex") || name.equalsIgnoreCase("complex")) {
            // Is it a complex/raw data type?
            c = RawData.class;
        } else if (PRIMITIVE_NAME_TYPE_MAP.get(name) != null) {
            // Check for a primitive type
            c = (Class) ((Object[]) PRIMITIVE_NAME_TYPE_MAP.get(name))[0];
        }

        if (c == null) {
            // No primitive, try to load it from the given ClassLoader
            try {
                c = cl.loadClass(name);
            } catch (ClassNotFoundException cnfe) {
                throw new ClassNotFoundException("Parameter class not found: " + name);
            }
        }

        // if we have an array get the array class
        if (arraySize > 0) {
            int[] dims = new int[arraySize];
            for (int i = 0; i < arraySize; i++) {
                dims[i] = 1;
            }
            c = Array.newInstance(c, dims).getClass();
        }

        // Set the default value or the sample object
        Object sample;
        if (defaultValue != null && CType.SIMPLE.equals(((Object[]) PRIMITIVE_NAME_TYPE_MAP.get(name))[1])) {
            sample = defaultValue;
        } else if (CType.COMPLEX.equals(((Object[]) PRIMITIVE_NAME_TYPE_MAP.get(name))[1])
                && ((Object[]) PRIMITIVE_NAME_TYPE_MAP.get(name)).length > 2) {
            sample = ((Object[]) PRIMITIVE_NAME_TYPE_MAP.get(name))[2];
        } else {
            sample = null;
        }

        if (PRIMITIVE_NAME_TYPE_MAP.get(name) != null)
            mimeTypes = (String) ((Object[]) PRIMITIVE_NAME_TYPE_MAP.get(name))[3];

        return new ParameterTemplate(c, sample, mimeTypes);
    }
}

/**
 * Actual implementation of a "PacketListener".
 * 
 * Listen to the service channels and handles the "registration" and "de-registration" of the available services (alias available WPS Processes)
 * 
 * @author Alessio Fabiani, GeoSolutions
 * 
 */
class XMPPPacketListener implements PacketListener {

    /** The LOGGER */
    public static final Logger LOGGER = Logging.getLogger(XMPPPacketListener.class.getPackage().getName());

    private XMPPClient xmppClient;

    public XMPPPacketListener(XMPPClient xmppClient) {
        this.xmppClient = xmppClient;
    }

    @Override
    public void processPacket(Packet packet) {
        if (packet instanceof Presence) {
            Presence p = (Presence) packet;

            try {
                if (p.isAvailable()) {
                    if (p.getFrom().indexOf("@") > 0) {

                        /**
                         * Manage the channel occupants list
                         */
                        final String channel = p.getFrom().substring(0, p.getFrom().indexOf("@"));
                        /*
                         * if (xmppClient.occupantsList.get(channel) == null) { xmppClient.occupantsList.put(channel, new ArrayList<String>()); } if
                         * (xmppClient.occupantsList.get(channel) != null) { if (!xmppClient.occupantsList.get(channel).contains(p. getFrom()))
                         * xmppClient.occupantsList.get(channel).add(p.getFrom() ); }
                         */

                        if (xmppClient.serviceChannels.contains(channel))
                            xmppClient.handleMemberJoin(p);
                    }
                } else if (!p.isAvailable()) {
                    if (p.getFrom().indexOf("@") > 0 && p.getFrom().indexOf("/master") > 0) {
                        boolean mustDeregisterService = true;

                        final String channel = p.getFrom().substring(0, p.getFrom().indexOf("@"));
                        final NameImpl serviceName = xmppClient.extractServiceName(p.getFrom());

                        for (MultiUserChat mucServiceChannel : xmppClient.mucServiceChannels) {
                            if (mucServiceChannel.getRoom().startsWith(channel)) {
                                for (String occupant : mucServiceChannel.getOccupants()) {
                                    if (!occupant.equals(p.getFrom())) {
                                        final Name occupantServiceName = xmppClient.extractServiceName(occupant);

                                        // send invitation and register source
                                        // JID
                                        String[] serviceJIDParts = occupant.split("/");
                                        if (serviceJIDParts.length == 3 && (serviceJIDParts[2].startsWith("master")
                                                || serviceJIDParts[2].indexOf("@") < 0)) {
                                            if (serviceName.equals(occupantServiceName)) {
                                                mustDeregisterService = false;
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        if (mustDeregisterService && xmppClient.serviceChannels.contains(channel))
                            xmppClient.handleMemberLeave(p);
                    }
                }
            } catch (Exception e) {
                LOGGER.log(Level.WARNING, e.getMessage(), e);
            }
        } else if (packet instanceof Message) {
            Message message = (Message) packet;
            String origin = message.getFrom().split("/")[0];
            Chat chat = xmppClient.setupChat(origin);

            if (message.getBody() != null) {
                LOGGER.fine("ReceivedMessage('" + message.getBody() + "','" + origin + "','" + message.getPacketID()
                        + "');");

                Map<String, String> signalArgs = new HashMap<String, String>();
                try {
                    String[] messageParts = message.getBody().split("&");
                    for (String mp : messageParts) {
                        String[] signalArg = mp.split("=");
                        signalArgs.put(signalArg[0], signalArg[1]);
                    }
                } catch (Exception e) {
                    LOGGER.log(Level.WARNING, "Wrong message! [" + message.getBody() + "]");
                    signalArgs.clear();
                }

                if (!signalArgs.isEmpty() && signalArgs.containsKey("topic")) {

                    for (XMPPMessage xmppMessage : GeoServerExtensions.extensions(XMPPMessage.class)) {
                        if (xmppMessage.canHandle(signalArgs)) {
                            xmppMessage.handleSignal(xmppClient, packet, message, signalArgs);
                        }
                    }

                }
            }
        }
    }
}

/**
 * Just an utility class helping us to convert a parameter to a Java class.
 * 
 * @author Alessio Fabiani, GeoSolutions
 * 
 */
class ParameterTemplate {

    private final Class<?> clazz;

    private final Object defaultValue;

    private final Map<String, String> meta = new HashMap<String, String>();

    /**
     * @param clazz
     * @param defaultValue
     */
    public ParameterTemplate(Class<?> clazz, Object defaultValue, String mimeTypes) {
        this.clazz = clazz;
        this.defaultValue = defaultValue;
        this.meta.put("mimeTypes", mimeTypes);
        if (mimeTypes != null && mimeTypes.split(",").length > 0) {
            this.meta.put("chosenMimeType", mimeTypes.split(",")[0]);
        }
    }

    /**
     * @return the clazz
     */
    public Class<?> getClazz() {
        return clazz;
    }

    /**
     * @return the defaultValue
     */
    public Object getDefaultValue() {
        return defaultValue;
    }

    public Map<String, String> getMeta() {
        return meta;
    }

}