<commit_before>/**
 * File:   MulticastBroadcaster.java
 * Author: <NAME>
 * Date:   28 juin 2013
 */
package org.cohorte.remote.multicast;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.felix.ipojo.annotations.Bind;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Property;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.apache.felix.ipojo.annotations.ServiceController;
import org.apache.felix.ipojo.annotations.Validate;
import org.cohorte.remote.IRemoteServiceBroadcaster;
import org.cohorte.remote.IRemoteServiceEventListener;
import org.cohorte.remote.IRemoteServiceRepository;
import org.cohorte.remote.IRemoteServicesConstants;
import org.cohorte.remote.beans.RemoteServiceEvent;
import org.cohorte.remote.beans.RemoteServiceEvent.ServiceEventType;
import org.cohorte.remote.beans.RemoteServiceRegistration;
import org.cohorte.remote.multicast.beans.PelixEndpointDescription;
import org.cohorte.remote.multicast.beans.PelixMulticastPacket;
import org.cohorte.remote.multicast.utils.IPacketListener;
import org.cohorte.remote.multicast.utils.MulticastHandler;
import org.cohorte.remote.utilities.RSUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.osgi.framework.BundleContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.log.LogService;

/**
 * Multicast/servlet remote service discovery, compatible with the Pelix remote
 * services implementation
 * 
 * @author <NAME>
 */
@Component(name = "cohorte-remote-broadcast-multicast")
@Provides(specifications = IRemoteServiceBroadcaster.class)
// @Instantiate(name = "cohorte-remote-multicast")
public class MulticastBroadcaster implements IRemoteServiceBroadcaster,
        IPacketListener {

    /** UTF-8 charset name */
    private static final String CHARSET_UTF8 = "UTF-8";

    /** HTTP 200 OK */
    private static final int HTTP_OK = 200;

    /** HTTP service port property */
    private static final String HTTP_SERVICE_PORT = "org.osgi.service.http.port";

    /** HTTPService dependency ID */
    private static final String IPOJO_ID_HTTP = "http.service";

    /** The bundle context */
    private final BundleContext pBundleContext;

    /** The HTTP server port */
    private int pHttpPort;

    /** The HTTP service */
    @Requires(id = IPOJO_ID_HTTP, filter = "(" + HTTP_SERVICE_PORT + "=*)")
    private HttpService pHttpService;

    /** End point UID -&gt; Remote Service Registration */
    private final Map<String, RemoteServiceRegistration> pImportedEndpoints = new LinkedHashMap<String, RemoteServiceRegistration>();

    /** The isolate UID */
    private String pIsolateUID;

    /** The logger */
    @Requires
    private LogService pLogger;

    /** The multicast socket */
    private MulticastHandler pMulticast;

    /** The multicast group */
    @Property(name = "multicast.group", value = "172.16.31.10")
    private String pMulticastGroup;

    /** The multicast port */
    @Property(name = "multicast.port", value = "42000")
    private int pMulticastPort;

    /** Remote service events listeners */
    @Requires(optional = true)
    private IRemoteServiceEventListener[] pRemoteEventsListeners;

    /** The remote service repository */
    @Requires
    private IRemoteServiceRepository pRepository;

    /** The service controller: active only if the validation succeeded */
    @ServiceController
    private boolean pServiceController;

    /** The registry servlet */
    private RegistryServlet pServlet;

    /** The servlet registration path */
    @Property(name = "servlet.path", value = "/pelix-dispatcher")
    private String pServletPath;

    /**
     * Sets up members
     * 
     * @param aBundleContext
     *            The bundle context
     */
    public MulticastBroadcaster(final BundleContext aBundleContext) {

        pBundleContext = aBundleContext;
    }

    /**
     * HTTP service ready
     * 
     * @param aHttpService
     *            The bound service
     * @param aServiceProperties
     *            The HTTP service properties
     */
    @Bind(id = IPOJO_ID_HTTP)
    private void bindHttpService(final HttpService aHttpService,
            final Map<?, ?> aServiceProperties) {

        final Object rawPort = aServiceProperties.get(HTTP_SERVICE_PORT);

        if (rawPort instanceof Number) {
            // Get the integer
            pHttpPort = ((Number) rawPort).intValue();

        } else if (rawPort instanceof CharSequence) {
            // Parse the string
            pHttpPort = Integer.parseInt(rawPort.toString());

        } else {
            // Unknown port type
            pLogger.log(LogService.LOG_WARNING, "Couldn't read access port "
                    + rawPort);
            pHttpPort = -1;
        }

        pLogger.log(LogService.LOG_INFO,
                "Multicast Remote Service broadcaster bound to port "
                        + pHttpPort);
    }

    /**
     * Returns the response of a HTTP server, or throws an exception
     * 
     * @param aAddress
     *            Server address
     * @param aPort
     *            Server port
     * @param aPath
     *            Request URI
     * @return The raw response of the server
     */
    private String grabData(final InetAddress aAddress, final int aPort,
            final String aPath) {

        // Forge the URL
        final URL url;
        try {
            url = new URL("http", aAddress.getHostAddress(), aPort, aPath);

        } catch (final MalformedURLException ex) {
            pLogger.log(LogService.LOG_ERROR,
                    "Couldn't forge the URL to access: " + aAddress + " : "
                            + aPort + " - " + aPath, ex);
            return null;
        }

        // Open the connection
        HttpURLConnection httpConnection = null;
        try {
            httpConnection = (HttpURLConnection) url.openConnection();
            httpConnection.connect();

            // Flush the request
            final int responseCode = httpConnection.getResponseCode();
            if (responseCode != HTTP_OK) {
                // Incorrect answer
                pLogger.log(LogService.LOG_WARNING, "Error: " + url
                        + " responded with code " + responseCode);
                return null;
            }

            // Get the response content
            final byte[] rawResult = RSUtils.inputStreamToBytes(httpConnection
                    .getInputStream());

            // Construct corresponding string
            return new String(rawResult);

        } catch (final IOException ex) {
            // Connection error
            pLogger.log(LogService.LOG_ERROR,
                    "Error requesting information from " + url.toString(), ex);

        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }

        return null;
    }

    /**
     * @param aAddress
     * @param aPort
     * @param aPath
     * @param aEndpointUID
     * @return The description of the given Pelix end point
     */
    private RemoteServiceRegistration grabEndpoint(final InetAddress aAddress,
            final int aPort, final String aPath, final String aEndpointUID) {

        // Get the raw servlet result
        final String rawResponse = grabData(aAddress, aPort, aPath
                + "/endpoint/" + aEndpointUID);
        if (rawResponse == null || rawResponse.isEmpty()) {
            // No response
            pLogger.log(LogService.LOG_WARNING, "No response from the server "
                    + aAddress + " for end point " + aEndpointUID);
            return null;
        }

        try {
            // Parse it
            final JSONObject rawEndpoint = new JSONObject(rawResponse);

            // Convert the result
            final PelixEndpointDescription endpoint = new PelixEndpointDescription(
                    rawEndpoint);
            endpoint.setServerAddress(aAddress.getHostAddress());

            return endpoint.toRegistration();

        } catch (final JSONException ex) {
            // Invalid response
            pLogger.log(LogService.LOG_WARNING,
                    "Invalid response from the server " + aAddress
                            + " for end point " + aEndpointUID + "\n"
                            + rawResponse, ex);

        } catch (final MalformedURLException ex) {
            // Invalid end point URL
            pLogger.log(LogService.LOG_WARNING, "Invalid end point URL for "
                    + aEndpointUID + "\n" + rawResponse, ex);
        }

        return null;
    }

    /**
     * @param aAddress
     * @param aPort
     * @param aPath
     * @return The list of the end points of the given isolate
     */
    private List<RemoteServiceRegistration> grabEndpoints(
            final InetAddress aAddress, final int aPort, final String aPath) {

        // Get the raw servlet result
        final String rawResponse = grabData(aAddress, aPort, aPath
                + "/endpoints");
        if (rawResponse == null || rawResponse.isEmpty()) {
            // No response
            pLogger.log(LogService.LOG_WARNING, "No response from the server "
                    + aAddress + " for endpoints/");
            return null;
        }

        // Prepare the result list
        final List<RemoteServiceRegistration> registrations = new LinkedList<RemoteServiceRegistration>();

        // Parse it
        try {
            final JSONArray endpointsArray = new JSONArray(rawResponse);

            for (int i = 0; i < endpointsArray.length(); i++) {
                // Get the next end point
                final JSONObject rawEndpoint = endpointsArray.getJSONObject(i);

                try {
                    // Convert it
                    final PelixEndpointDescription endpoint = new PelixEndpointDescription(
                            rawEndpoint);
                    endpoint.setServerAddress(aAddress.getHostAddress());
                    registrations.add(endpoint.toRegistration());

                } catch (final JSONException ex) {
                    // Conversion error: just log
                    pLogger.log(LogService.LOG_WARNING,
                            "Error parsing an end point:\n" + rawEndpoint, ex);

                } catch (final MalformedURLException ex) {
                    // Conversion error: just log
                    pLogger.log(LogService.LOG_WARNING,
                            "Invalid end point URL:\n" + rawEndpoint, ex);
                }
            }

        } catch (final JSONException ex) {
            // Invalid response
            pLogger.log(LogService.LOG_WARNING,
                    "Invalid response from the server " + aAddress
                            + " for endpoints/\n" + rawResponse, ex);
            // Return null in case of error
            return null;
        }

        return registrations;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.cohorte.remote.multicast.utils.IPacketListener#handleError(java.lang
     * .Exception)
     */
    @Override
    public boolean handleError(final Exception aException) {

        // Log the error
        pLogger.log(LogService.LOG_ERROR,
                "Error reading a packet from the multicast handler", aException);

        // Continue if the exception is not "important"
        return !(aException instanceof SocketException || aException instanceof NullPointerException);
    }

    /**
     * Handles a remote service event packet
     * 
     * @param aEndpointPacket
     *            Received end point packet
     * @param aAddress
     *            Sender address
     * @param aPort
     *            Sender port
     */
    private void handleEvent(final PelixMulticastPacket aEndpointPacket,
            final InetAddress aAddress, final int aPort) {

        final String event = aEndpointPacket.getEvent();
        final String endpointUID = aEndpointPacket.getUID();

        if (IPacketConstants.EVENT_ADD.equals(event)) {
            // Store the new service
            handleEventAdd(endpointUID, aEndpointPacket, aAddress);

        } else if (IPacketConstants.EVENT_REMOVE.equals(event)) {
            // Remove it
            handleEventRemove(endpointUID);

        } else if (IPacketConstants.EVENT_UPDATE.equals(event)) {
            // Update it
            handleEventUpdate(endpointUID, aEndpointPacket);

        } else {
            // Unknown...
            pLogger.log(LogService.LOG_WARNING,
                    "Unknown kind of remote service event: " + event);
        }
    }

    /**
     * Handles a registered end point event
     * 
     * @param aEndpointUID
     *            The UID of the registered end point
     * @param aEndpointPacket
     *            Received packet content
     * @param aAddress
     *            Sender address
     */
    private void handleEventAdd(final String aEndpointUID,
            final PelixMulticastPacket aEndpointPacket,
            final InetAddress aAddress) {

        // Check if the end point is already known
        if (pImportedEndpoints.containsKey(aEndpointUID)) {
            // Nothing to do
            return;
        }

        // Get the access to the dispatcher servlet
        final String path = aEndpointPacket.getAccessPath();
        final int port = aEndpointPacket.getAccessPort();

        // Grab the end point
        final RemoteServiceRegistration registration = grabEndpoint(aAddress,
                port, path, aEndpointUID);

        if (registration != null) {
            // Store the service registration
            pImportedEndpoints.put(aEndpointUID, registration);

            // Notify listeners
            final RemoteServiceEvent remoteEvent = new RemoteServiceEvent(
                    ServiceEventType.REGISTERED, registration);
            for (final IRemoteServiceEventListener listener : pRemoteEventsListeners) {
                listener.handleRemoteEvent(remoteEvent);
            }
        }
    }

    /**
     * Handles an unregistered end point event
     * 
     * @param aEndpointUID
     *            The UID of the unregistered end point
     */
    private void handleEventRemove(final String aEndpointUID) {

        // Find the associated registration
        final RemoteServiceRegistration registration = pImportedEndpoints
                .remove(aEndpointUID);
        if (registration == null) {
            pLogger.log(LogService.LOG_DEBUG, "Unknown end point UID: "
                    + aEndpointUID);
            return;
        }

        // Notify listeners
        final RemoteServiceEvent remoteEvent = new RemoteServiceEvent(
                ServiceEventType.UNREGISTERED, registration);
        for (final IRemoteServiceEventListener listener : pRemoteEventsListeners) {
            listener.handleRemoteEvent(remoteEvent);
        }
    }

    /**
     * Handles a registered end point event
     * 
     * @param aEndpointUID
     *            The UID of the registered end point
     * @param aEndpointPacket
     *            Received packet content
     */
    private void handleEventUpdate(final String aEndpointUID,
            final PelixMulticastPacket aEndpointPacket) {

        // Find the associated registration
        final RemoteServiceRegistration registration = pImportedEndpoints
                .remove(aEndpointUID);
        if (registration == null) {
            pLogger.log(LogService.LOG_DEBUG, "Unknown end point UID: "
                    + aEndpointUID);
            return;
        }

        // New service properties (they will be filtered by listeners)
        final Map<String, Object> newProperties = aEndpointPacket
                .getNewProperties();

        // Update the registration
        registration.setServiceProperties(newProperties);

        // Notify listeners
        final RemoteServiceEvent remoteEvent = new RemoteServiceEvent(
                ServiceEventType.MODIFIED, registration);
        for (final IRemoteServiceEventListener listener : pRemoteEventsListeners) {
            listener.handleRemoteEvent(remoteEvent);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.cohorte.remote.multicast.utils.IPacketListener#handlePacket(java.
     * net.DatagramPacket)
     */
    @Override
    public void handlePacket(final DatagramPacket aPacket) {

        // Read the packet content
        final String content;
        try {
            content = new String(aPacket.getData(), CHARSET_UTF8);

        } catch (final UnsupportedEncodingException ex) {
            pLogger.log(LogService.LOG_ERROR,
                    "Error reading multicast packet data", ex);
            return;
        }

        // Parse it from JSON
        final PelixMulticastPacket endpointPacket;
        try {
            final JSONObject parsed = new JSONObject(content);
            endpointPacket = new PelixMulticastPacket(parsed);

        } catch (final JSONException ex) {
            // Log
            pLogger.log(LogService.LOG_ERROR,
                    "Error parsing a multicast packet", ex);
            return;
        }

        // Avoid handling our own packets
        if (endpointPacket.isFromSender(pIsolateUID)) {
            return;
        }

        // Get the kind of event
        final String event = endpointPacket.getEvent();

        // Dispatch...
        if (IPacketConstants.EVENT_DISCOVERY.equals(event)) {
            // Discovery request: send a packet back
            sendDiscovered(aPacket.getAddress(), aPacket.getPort());

        } else if (IPacketConstants.EVENT_DISCOVERY.equals(event)) {
            // Discovered: grab end points
            final List<RemoteServiceRegistration> endpoints = grabEndpoints(
                    aPacket.getAddress(), endpointPacket.getAccessPort(),
                    endpointPacket.getAccessPath());

            // Notify listeners
            for (final RemoteServiceRegistration registration : endpoints) {

                final RemoteServiceEvent regEvent = new RemoteServiceEvent(
                        ServiceEventType.REGISTERED, registration);
                for (final IRemoteServiceEventListener listener : pRemoteEventsListeners) {<commit_msg>Corrected a typo in the multicast broadcaster<commit_after>/**
 * File:   MulticastBroadcaster.java
 * Author: <NAME>
 * Date:   28 juin 2013
 */
package org.cohorte.remote.multicast;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.felix.ipojo.annotations.Bind;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Property;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.apache.felix.ipojo.annotations.ServiceController;
import org.apache.felix.ipojo.annotations.Validate;
import org.cohorte.remote.IRemoteServiceBroadcaster;
import org.cohorte.remote.IRemoteServiceEventListener;
import org.cohorte.remote.IRemoteServiceRepository;
import org.cohorte.remote.IRemoteServicesConstants;
import org.cohorte.remote.beans.RemoteServiceEvent;
import org.cohorte.remote.beans.RemoteServiceEvent.ServiceEventType;
import org.cohorte.remote.beans.RemoteServiceRegistration;
import org.cohorte.remote.multicast.beans.PelixEndpointDescription;
import org.cohorte.remote.multicast.beans.PelixMulticastPacket;
import org.cohorte.remote.multicast.utils.IPacketListener;
import org.cohorte.remote.multicast.utils.MulticastHandler;
import org.cohorte.remote.utilities.RSUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.osgi.framework.BundleContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.log.LogService;

/**
 * Multicast/servlet remote service discovery, compatible with the Pelix remote
 * services implementation
 * 
 * @author <NAME>
 */
@Component(name = "cohorte-remote-broadcast-multicast")
@Provides(specifications = IRemoteServiceBroadcaster.class)
// @Instantiate(name = "cohorte-remote-multicast")
public class MulticastBroadcaster implements IRemoteServiceBroadcaster,
        IPacketListener {

    /** UTF-8 charset name */
    private static final String CHARSET_UTF8 = "UTF-8";

    /** HTTP 200 OK */
    private static final int HTTP_OK = 200;

    /** HTTP service port property */
    private static final String HTTP_SERVICE_PORT = "org.osgi.service.http.port";

    /** HTTPService dependency ID */
    private static final String IPOJO_ID_HTTP = "http.service";

    /** The bundle context */
    private final BundleContext pBundleContext;

    /** The HTTP server port */
    private int pHttpPort;

    /** The HTTP service */
    @Requires(id = IPOJO_ID_HTTP, filter = "(" + HTTP_SERVICE_PORT + "=*)")
    private HttpService pHttpService;

    /** End point UID -&gt; Remote Service Registration */
    private final Map<String, RemoteServiceRegistration> pImportedEndpoints = new LinkedHashMap<String, RemoteServiceRegistration>();

    /** The isolate UID */
    private String pIsolateUID;

    /** The logger */
    @Requires
    private LogService pLogger;

    /** The multicast socket */
    private MulticastHandler pMulticast;

    /** The multicast group */
    @Property(name = "multicast.group", value = "172.16.31.10")
    private String pMulticastGroup;

    /** The multicast port */
    @Property(name = "multicast.port", value = "42000")
    private int pMulticastPort;

    /** Remote service events listeners */
    @Requires(optional = true)
    private IRemoteServiceEventListener[] pRemoteEventsListeners;

    /** The remote service repository */
    @Requires
    private IRemoteServiceRepository pRepository;

    /** The service controller: active only if the validation succeeded */
    @ServiceController
    private boolean pServiceController;

    /** The registry servlet */
    private RegistryServlet pServlet;

    /** The servlet registration path */
    @Property(name = "servlet.path", value = "/pelix-dispatcher")
    private String pServletPath;

    /**
     * Sets up members
     * 
     * @param aBundleContext
     *            The bundle context
     */
    public MulticastBroadcaster(final BundleContext aBundleContext) {

        pBundleContext = aBundleContext;
    }

    /**
     * HTTP service ready
     * 
     * @param aHttpService
     *            The bound service
     * @param aServiceProperties
     *            The HTTP service properties
     */
    @Bind(id = IPOJO_ID_HTTP)
    private void bindHttpService(final HttpService aHttpService,
            final Map<?, ?> aServiceProperties) {

        final Object rawPort = aServiceProperties.get(HTTP_SERVICE_PORT);

        if (rawPort instanceof Number) {
            // Get the integer
            pHttpPort = ((Number) rawPort).intValue();

        } else if (rawPort instanceof CharSequence) {
            // Parse the string
            pHttpPort = Integer.parseInt(rawPort.toString());

        } else {
            // Unknown port type
            pLogger.log(LogService.LOG_WARNING, "Couldn't read access port "
                    + rawPort);
            pHttpPort = -1;
        }

        pLogger.log(LogService.LOG_INFO,
                "Multicast Remote Service broadcaster bound to port "
                        + pHttpPort);
    }

    /**
     * Returns the response of a HTTP server, or throws an exception
     * 
     * @param aAddress
     *            Server address
     * @param aPort
     *            Server port
     * @param aPath
     *            Request URI
     * @return The raw response of the server
     */
    private String grabData(final InetAddress aAddress, final int aPort,
            final String aPath) {

        // Forge the URL
        final URL url;
        try {
            url = new URL("http", aAddress.getHostAddress(), aPort, aPath);

        } catch (final MalformedURLException ex) {
            pLogger.log(LogService.LOG_ERROR,
                    "Couldn't forge the URL to access: " + aAddress + " : "
                            + aPort + " - " + aPath, ex);
            return null;
        }

        // Open the connection
        HttpURLConnection httpConnection = null;
        try {
            httpConnection = (HttpURLConnection) url.openConnection();
            httpConnection.connect();

            // Flush the request
            final int responseCode = httpConnection.getResponseCode();
            if (responseCode != HTTP_OK) {
                // Incorrect answer
                pLogger.log(LogService.LOG_WARNING, "Error: " + url
                        + " responded with code " + responseCode);
                return null;
            }

            // Get the response content
            final byte[] rawResult = RSUtils.inputStreamToBytes(httpConnection
                    .getInputStream());

            // Construct corresponding string
            return new String(rawResult);

        } catch (final IOException ex) {
            // Connection error
            pLogger.log(LogService.LOG_ERROR,
                    "Error requesting information from " + url.toString(), ex);

        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }

        return null;
    }

    /**
     * @param aAddress
     * @param aPort
     * @param aPath
     * @param aEndpointUID
     * @return The description of the given Pelix end point
     */
    private RemoteServiceRegistration grabEndpoint(final InetAddress aAddress,
            final int aPort, final String aPath, final String aEndpointUID) {

        // Get the raw servlet result
        final String rawResponse = grabData(aAddress, aPort, aPath
                + "/endpoint/" + aEndpointUID);
        if (rawResponse == null || rawResponse.isEmpty()) {
            // No response
            pLogger.log(LogService.LOG_WARNING, "No response from the server "
                    + aAddress + " for end point " + aEndpointUID);
            return null;
        }

        try {
            // Parse it
            final JSONObject rawEndpoint = new JSONObject(rawResponse);

            // Convert the result
            final PelixEndpointDescription endpoint = new PelixEndpointDescription(
                    rawEndpoint);
            endpoint.setServerAddress(aAddress.getHostAddress());

            return endpoint.toRegistration();

        } catch (final JSONException ex) {
            // Invalid response
            pLogger.log(LogService.LOG_WARNING,
                    "Invalid response from the server " + aAddress
                            + " for end point " + aEndpointUID + "\n"
                            + rawResponse, ex);

        } catch (final MalformedURLException ex) {
            // Invalid end point URL
            pLogger.log(LogService.LOG_WARNING, "Invalid end point URL for "
                    + aEndpointUID + "\n" + rawResponse, ex);
        }

        return null;
    }

    /**
     * @param aAddress
     * @param aPort
     * @param aPath
     * @return The list of the end points of the given isolate
     */
    private List<RemoteServiceRegistration> grabEndpoints(
            final InetAddress aAddress, final int aPort, final String aPath) {

        // Get the raw servlet result
        final String rawResponse = grabData(aAddress, aPort, aPath
                + "/endpoints");
        if (rawResponse == null || rawResponse.isEmpty()) {
            // No response
            pLogger.log(LogService.LOG_WARNING, "No response from the server "
                    + aAddress + " for endpoints/");
            return null;
        }

        // Prepare the result list
        final List<RemoteServiceRegistration> registrations = new LinkedList<RemoteServiceRegistration>();

        // Parse it
        try {
            final JSONArray endpointsArray = new JSONArray(rawResponse);

            for (int i = 0; i < endpointsArray.length(); i++) {
                // Get the next end point
                final JSONObject rawEndpoint = endpointsArray.getJSONObject(i);

                try {
                    // Convert it
                    final PelixEndpointDescription endpoint = new PelixEndpointDescription(
                            rawEndpoint);
                    endpoint.setServerAddress(aAddress.getHostAddress());
                    registrations.add(endpoint.toRegistration());

                } catch (final JSONException ex) {
                    // Conversion error: just log
                    pLogger.log(LogService.LOG_WARNING,
                            "Error parsing an end point:\n" + rawEndpoint, ex);

                } catch (final MalformedURLException ex) {
                    // Conversion error: just log
                    pLogger.log(LogService.LOG_WARNING,
                            "Invalid end point URL:\n" + rawEndpoint, ex);
                }
            }

        } catch (final JSONException ex) {
            // Invalid response
            pLogger.log(LogService.LOG_WARNING,
                    "Invalid response from the server " + aAddress
                            + " for endpoints/\n" + rawResponse, ex);
            // Return null in case of error
            return null;
        }

        return registrations;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.cohorte.remote.multicast.utils.IPacketListener#handleError(java.lang
     * .Exception)
     */
    @Override
    public boolean handleError(final Exception aException) {

        // Log the error
        pLogger.log(LogService.LOG_ERROR,
                "Error reading a packet from the multicast handler", aException);

        // Continue if the exception is not "important"
        return !(aException instanceof SocketException || aException instanceof NullPointerException);
    }

    /**
     * Handles a remote service event packet
     * 
     * @param aEndpointPacket
     *            Received end point packet
     * @param aAddress
     *            Sender address
     * @param aPort
     *            Sender port
     */
    private void handleEvent(final PelixMulticastPacket aEndpointPacket,
            final InetAddress aAddress, final int aPort) {

        final String event = aEndpointPacket.getEvent();
        final String endpointUID = aEndpointPacket.getUID();

        if (IPacketConstants.EVENT_ADD.equals(event)) {
            // Store the new service
            handleEventAdd(endpointUID, aEndpointPacket, aAddress);

        } else if (IPacketConstants.EVENT_REMOVE.equals(event)) {
            // Remove it
            handleEventRemove(endpointUID);

        } else if (IPacketConstants.EVENT_UPDATE.equals(event)) {
            // Update it
            handleEventUpdate(endpointUID, aEndpointPacket);

        } else {
            // Unknown...
            pLogger.log(LogService.LOG_WARNING,
                    "Unknown kind of remote service event: " + event);
        }
    }

    /**
     * Handles a registered end point event
     * 
     * @param aEndpointUID
     *            The UID of the registered end point
     * @param aEndpointPacket
     *            Received packet content
     * @param aAddress
     *            Sender address
     */
    private void handleEventAdd(final String aEndpointUID,
            final PelixMulticastPacket aEndpointPacket,
            final InetAddress aAddress) {

        // Check if the end point is already known
        if (pImportedEndpoints.containsKey(aEndpointUID)) {
            // Nothing to do
            return;
        }

        // Get the access to the dispatcher servlet
        final String path = aEndpointPacket.getAccessPath();
        final int port = aEndpointPacket.getAccessPort();

        // Grab the end point
        final RemoteServiceRegistration registration = grabEndpoint(aAddress,
                port, path, aEndpointUID);

        if (registration != null) {
            // Store the service registration
            pImportedEndpoints.put(aEndpointUID, registration);

            // Notify listeners
            final RemoteServiceEvent remoteEvent = new RemoteServiceEvent(
                    ServiceEventType.REGISTERED, registration);
            for (final IRemoteServiceEventListener listener : pRemoteEventsListeners) {
                listener.handleRemoteEvent(remoteEvent);
            }
        }
    }

    /**
     * Handles an unregistered end point event
     * 
     * @param aEndpointUID
     *            The UID of the unregistered end point
     */
    private void handleEventRemove(final String aEndpointUID) {

        // Find the associated registration
        final RemoteServiceRegistration registration = pImportedEndpoints
                .remove(aEndpointUID);
        if (registration == null) {
            pLogger.log(LogService.LOG_DEBUG, "Unknown end point UID: "
                    + aEndpointUID);
            return;
        }

        // Notify listeners
        final RemoteServiceEvent remoteEvent = new RemoteServiceEvent(
                ServiceEventType.UNREGISTERED, registration);
        for (final IRemoteServiceEventListener listener : pRemoteEventsListeners) {
            listener.handleRemoteEvent(remoteEvent);
        }
    }

    /**
     * Handles a registered end point event
     * 
     * @param aEndpointUID
     *            The UID of the registered end point
     * @param aEndpointPacket
     *            Received packet content
     */
    private void handleEventUpdate(final String aEndpointUID,
            final PelixMulticastPacket aEndpointPacket) {

        // Find the associated registration
        final RemoteServiceRegistration registration = pImportedEndpoints
                .remove(aEndpointUID);
        if (registration == null) {
            pLogger.log(LogService.LOG_DEBUG, "Unknown end point UID: "
                    + aEndpointUID);
            return;
        }

        // New service properties (they will be filtered by listeners)
        final Map<String, Object> newProperties = aEndpointPacket
                .getNewProperties();

        // Update the registration
        registration.setServiceProperties(newProperties);

        // Notify listeners
        final RemoteServiceEvent remoteEvent = new RemoteServiceEvent(
                ServiceEventType.MODIFIED, registration);
        for (final IRemoteServiceEventListener listener : pRemoteEventsListeners) {
            listener.handleRemoteEvent(remoteEvent);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.cohorte.remote.multicast.utils.IPacketListener#handlePacket(java.
     * net.DatagramPacket)
     */
    @Override
    public void handlePacket(final DatagramPacket aPacket) {

        // Read the packet content
        final String content;
        try {
            content = new String(aPacket.getData(), CHARSET_UTF8);

        } catch (final UnsupportedEncodingException ex) {
            pLogger.log(LogService.LOG_ERROR,
                    "Error reading multicast packet data", ex);
            return;
        }

        // Parse it from JSON
        final PelixMulticastPacket endpointPacket;
        try {
            final JSONObject parsed = new JSONObject(content);
            endpointPacket = new PelixMulticastPacket(parsed);

        } catch (final JSONException ex) {
            // Log
            pLogger.log(LogService.LOG_ERROR,
                    "Error parsing a multicast packet", ex);
            return;
        }

        // Avoid handling our own packets
        if (endpointPacket.isFromSender(pIsolateUID)) {
            return;
        }

        // Get the kind of event
        final String event = endpointPacket.getEvent();

        // Dispatch...
        if (IPacketConstants.EVENT_DISCOVERY.equals(event)) {
            // Discovery request: send a packet back
            sendDiscovered(aPacket.getAddress(), aPacket.getPort());

        } else if (IPacketConstants.EVENT_DISCOVERED.equals(event)) {
            // Discovered: grab end points
            final List<RemoteServiceRegistration> endpoints = grabEndpoints(
                    aPacket.getAddress(), endpointPacket.getAccessPort(),
                    endpointPacket.getAccessPath());

            // Notify listeners
            for (final RemoteServiceRegistration registration : endpoints) {

                final RemoteServiceEvent regEvent = new RemoteServiceEvent(
                        ServiceEventType.REGISTERED, registration);
                for (final IRemoteServiceEventListener listener : pRemoteEventsListeners) {
{'fraction_non_alphanumeric': 0.08222310862917165, 'fraction_numerical': 0.0017019013429065283, 'mean_word_length': 2.057149825217462, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7666600', 'n_tokens_mistral': 9677, 'n_tokens_neox': 9242, 'n_words': 2496}