org.jboss.aerogear.simplepush.server.netty.NotificationHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.aerogear.simplepush.server.netty.NotificationHandler.java

Source

/**
 * JBoss, Home of Professional Open Source
 * Copyright Red Hat, Inc., and individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.aerogear.simplepush.server.netty;

import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpMethod.PUT;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static io.netty.util.CharsetUtil.UTF_8;
import static org.jboss.aerogear.simplepush.protocol.impl.json.JsonUtil.toJson;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.ReferenceCountUtil;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsSessionContext;
import org.jboss.aerogear.simplepush.protocol.impl.NotificationMessageImpl;
import org.jboss.aerogear.simplepush.server.Notification;
import org.jboss.aerogear.simplepush.server.SimplePushServer;
import org.jboss.aerogear.simplepush.server.datastore.ChannelNotFoundException;
import org.jboss.aerogear.simplepush.server.datastore.VersionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Handles HTTP PUT 'notification' request for the SimplePush server.
 */
public class NotificationHandler extends SimpleChannelInboundHandler<Object> {

    private final UserAgents userAgents = UserAgents.getInstance();
    private final Logger logger = LoggerFactory.getLogger(NotificationHandler.class);

    private final SimplePushServer simplePushServer;
    private final ExecutorService executorServer;

    public NotificationHandler(final SimplePushServer simplePushServer) {
        this.simplePushServer = simplePushServer;
        executorServer = Executors.newFixedThreadPool(simplePushServer.config().notifierMaxThreads());
    }

    @Override
    public void messageReceived(final ChannelHandlerContext ctx, final Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            final FullHttpRequest request = (FullHttpRequest) msg;
            final String requestUri = request.getUri();
            logger.debug(requestUri);
            if (requestUri.startsWith(simplePushServer.config().endpointPrefix())) {
                handleHttpRequest(ctx, request);
            } else {
                ctx.fireChannelRead(ReferenceCountUtil.retain(msg));
            }
        } else {
            ctx.fireChannelRead(ReferenceCountUtil.retain(msg));
        }
    }

    private void handleHttpRequest(final ChannelHandlerContext ctx, final FullHttpRequest req) throws Exception {
        if (isHttpRequestValid(req, ctx.channel())) {
            executorServer.submit(new Notifier(req.getUri(), req.content()));
            sendHttpResponse(OK, req, ctx.channel());
        }
    }

    private boolean isHttpRequestValid(final FullHttpRequest request, final Channel channel) {
        if (!request.getDecoderResult().isSuccess()) {
            sendHttpResponse(BAD_REQUEST, request, channel);
            return false;
        }
        if (request.getMethod() != PUT && request.getMethod() != GET) {
            sendHttpResponse(FORBIDDEN, request, channel);
            return false;
        }
        return true;
    }

    private void sendHttpResponse(final HttpResponseStatus status, final FullHttpRequest request,
            final Channel channel) {
        final ByteBuf content = Unpooled.copiedBuffer(status.reasonPhrase(), UTF_8);
        final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, content);
        response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, content.readableBytes());
        response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8");
        channel.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private class Notifier implements Callable<Void> {

        private final String endpoint;
        private final ByteBuf payload;

        private Notifier(final String requestUri, final ByteBuf payload) {
            this.endpoint = requestUri.substring(requestUri.lastIndexOf('/') + 1);
            this.payload = payload;
            this.payload.retain();
        }

        @Override
        public Void call() throws Exception {
            try {
                final Notification notification = simplePushServer.handleNotification(endpoint,
                        payload.toString(UTF_8));
                final String uaid = notification.uaid();
                final SockJsSessionContext session = userAgents.get(uaid).context();
                if (logger.isDebugEnabled()) {
                    logger.debug("Sending notification for UAID [ " + notification.uaid() + "] "
                            + toJson(new NotificationMessageImpl(notification.ack())));
                }
                session.send(toJson(new NotificationMessageImpl(notification.ack())));
                userAgents.updateAccessedTime(uaid);
            } catch (final ChannelNotFoundException e) {
                logger.debug("Could not find channel for [" + endpoint + "]");
            } catch (final VersionException e) {
                logger.debug(e.getMessage());
            } finally {
                payload.release();
            }
            return null;
        }
    }

}