org.eclipse.leshan.standalone.servlet.EventServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.leshan.standalone.servlet.EventServlet.java

Source

/*******************************************************************************
 * Copyright (c) 2013-2015 Sierra Wireless and others.
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 * 
 * The Eclipse Public License is available at
 *    http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 *    http://www.eclipse.org/org/documents/edl-v10.html.
 * 
 * Contributors:
 *     Sierra Wireless - initial API and implementation
 *******************************************************************************/
package org.eclipse.leshan.standalone.servlet;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;

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

import org.eclipse.californium.core.network.Endpoint;
import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationListener;
import org.eclipse.jetty.continuation.ContinuationSupport;
import org.eclipse.jetty.util.ConcurrentHashSet;
import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.server.californium.impl.LeshanServer;
import org.eclipse.leshan.server.client.Client;
import org.eclipse.leshan.server.client.ClientRegistryListener;
import org.eclipse.leshan.server.observation.Observation;
import org.eclipse.leshan.server.observation.ObservationRegistryListener;
import org.eclipse.leshan.standalone.servlet.json.ClientSerializer;
import org.eclipse.leshan.standalone.servlet.json.LwM2mNodeSerializer;
import org.eclipse.leshan.standalone.servlet.log.CoapMessage;
import org.eclipse.leshan.standalone.servlet.log.CoapMessageListener;
import org.eclipse.leshan.standalone.servlet.log.CoapMessageTracer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class EventServlet extends HttpServlet {

    private static final String EVENT_DEREGISTRATION = "DEREGISTRATION";

    private static final String EVENT_UPDATED = "UPDATED";

    private static final String EVENT_REGISTRATION = "REGISTRATION";

    private static final String EVENT_NOTIFICATION = "NOTIFICATION";

    private static final String EVENT_COAP_LOG = "COAPLOG";

    private static final String QUERY_PARAM_ENDPOINT = "ep";

    private static final long serialVersionUID = 1L;

    private static final Logger LOG = LoggerFactory.getLogger(EventServlet.class);

    private final Gson gson;

    private final byte[] EVENT = "event: ".getBytes();

    private final byte[] DATA = "data: ".getBytes();

    private final byte[] VOID = ": ".getBytes();

    private static final byte[] TERMINATION = new byte[] { '\r', '\n' };

    private final Set<Continuation> continuations = new ConcurrentHashSet<>();

    private final CoapMessageTracer coapMessageTracer;

    private final ClientRegistryListener clientRegistryListener = new ClientRegistryListener() {

        @Override
        public void registered(Client client) {
            String jClient = EventServlet.this.gson.toJson(client);
            sendEvent(EVENT_REGISTRATION, jClient, client.getEndpoint());
        }

        @Override
        public void updated(Client clientUpdated) {
            String jClient = EventServlet.this.gson.toJson(clientUpdated);
            sendEvent(EVENT_UPDATED, jClient, clientUpdated.getEndpoint());
        };

        @Override
        public void unregistered(Client client) {
            String jClient = EventServlet.this.gson.toJson(client);
            sendEvent(EVENT_DEREGISTRATION, jClient, client.getEndpoint());
        }
    };

    private final ObservationRegistryListener observationRegistryListener = new ObservationRegistryListener() {

        @Override
        public void cancelled(Observation observation) {
        }

        @Override
        public void newValue(Observation observation, LwM2mNode value) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Received notification from [{}] containing value [{}]", observation.getPath(),
                        value.toString());
            }
            String data = new StringBuffer("{\"ep\":\"").append(observation.getClient().getEndpoint())
                    .append("\",\"res\":\"").append(observation.getPath().toString()).append("\",\"val\":")
                    .append(gson.toJson(value)).append("}").toString();

            sendEvent(EVENT_NOTIFICATION, data, observation.getClient().getEndpoint());
        }

        @Override
        public void newObservation(Observation observation) {
        }
    };

    public EventServlet(LeshanServer server) {
        server.getClientRegistry().addListener(this.clientRegistryListener);
        server.getObservationRegistry().addListener(this.observationRegistryListener);

        // add an interceptor to each endpoint to trace all CoAP messages
        coapMessageTracer = new CoapMessageTracer(server.getClientRegistry());
        for (Endpoint endpoint : server.getCoapServer().getEndpoints()) {
            endpoint.addInterceptor(coapMessageTracer);
        }

        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeHierarchyAdapter(Client.class, new ClientSerializer());
        gsonBuilder.registerTypeHierarchyAdapter(LwM2mNode.class, new LwM2mNodeSerializer());
        gsonBuilder.setDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
        this.gson = gsonBuilder.create();
    }

    private synchronized void sendEvent(String event, String data, String endpoint) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Dispatching {} event from endpoint {}", event, endpoint);
        }

        Collection<Continuation> disconnected = new ArrayList<>();

        for (Continuation c : continuations) {
            Object endpointAttribute = c.getAttribute(QUERY_PARAM_ENDPOINT);
            if (endpointAttribute == null || endpointAttribute.equals(endpoint)) {
                try {
                    OutputStream output = c.getServletResponse().getOutputStream();
                    output.write(EVENT);
                    output.write(event.getBytes("UTF-8"));
                    output.write(TERMINATION);
                    output.write(DATA);
                    output.write(data.getBytes("UTF-8"));
                    output.write(TERMINATION);
                    output.write(TERMINATION);
                    output.flush();
                    c.getServletResponse().flushBuffer();
                } catch (IOException e) {
                    LOG.debug("Disconnected SSE client");
                    disconnected.add(c);
                }
            }
        }
        if (!disconnected.isEmpty()) {
            continuations.removeAll(disconnected);
            cleanCoapListener(endpoint);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/event-stream");
        OutputStream output = resp.getOutputStream();
        output.write(VOID);
        output.write("waiting for events".getBytes());
        output.write(TERMINATION);
        output.flush();
        resp.flushBuffer();

        Continuation c = ContinuationSupport.getContinuation(req);
        c.setTimeout(0);
        c.addContinuationListener(new ContinuationListener() {

            @Override
            public void onTimeout(Continuation continuation) {
                LOG.debug("continuation closed");
                continuation.complete();
            }

            @Override
            public void onComplete(Continuation continuation) {
                LOG.debug("continuation completed");
                continuations.remove(continuation);

                String endpoint = (String) continuation.getAttribute(QUERY_PARAM_ENDPOINT);
                if (endpoint != null) {
                    cleanCoapListener(endpoint);
                }
            }
        });

        String endpoint = req.getParameter(QUERY_PARAM_ENDPOINT);
        if (endpoint != null) {
            // mark continuation as notification listener for endpoint
            c.setAttribute(QUERY_PARAM_ENDPOINT, endpoint);
            coapMessageTracer.addListener(endpoint, new ClientCoapListener(endpoint));

        }
        synchronized (this) {
            continuations.add(c);
            c.suspend(resp);
        }
    }

    class ClientCoapListener implements CoapMessageListener {

        private final String endpoint;

        ClientCoapListener(String endpoint) {
            this.endpoint = endpoint;
        }

        @Override
        public void trace(CoapMessage message) {
            String coapLog = EventServlet.this.gson.toJson(message);
            sendEvent(EVENT_COAP_LOG, coapLog, endpoint);
        }

    }

    private void cleanCoapListener(String endpoint) {
        // remove the listener if there is no more continuation for this endpoint
        for (Continuation c : continuations) {
            String cEndpoint = (String) c.getAttribute(QUERY_PARAM_ENDPOINT);
            if (cEndpoint != null && cEndpoint.equals(endpoint)) {
                // still used
                return;
            }
        }
        coapMessageTracer.removeListener(endpoint);
    }
}