/**
* 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 org.jboss.aerogear.simplepush.protocol.impl.json.JsonUtil.fromJson;
import static org.jboss.aerogear.simplepush.protocol.impl.json.JsonUtil.toJson;
import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsConfig;
import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsSessionContext;
import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsService;
import io.netty.util.concurrent.ScheduledFuture;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.jboss.aerogear.simplepush.protocol.Ack;
import org.jboss.aerogear.simplepush.protocol.AckMessage;
import org.jboss.aerogear.simplepush.protocol.HelloResponse;
import org.jboss.aerogear.simplepush.protocol.MessageType;
import org.jboss.aerogear.simplepush.protocol.RegisterResponse;
import org.jboss.aerogear.simplepush.protocol.UnregisterMessage;
import org.jboss.aerogear.simplepush.protocol.UnregisterResponse;
import org.jboss.aerogear.simplepush.protocol.impl.AckMessageImpl;
import org.jboss.aerogear.simplepush.protocol.impl.HelloMessageImpl;
import org.jboss.aerogear.simplepush.protocol.impl.NotificationMessageImpl;
import org.jboss.aerogear.simplepush.protocol.impl.PingMessageImpl;
import org.jboss.aerogear.simplepush.protocol.impl.RegisterMessageImpl;
import org.jboss.aerogear.simplepush.protocol.impl.UnregisterMessageImpl;
import org.jboss.aerogear.simplepush.protocol.impl.json.JsonUtil;
import org.jboss.aerogear.simplepush.server.SimplePushServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SimplePush server implementation using SockJS.
*/
public class SimplePushSockJSService implements SockJsService {
private final Logger logger = LoggerFactory.getLogger(SimplePushSockJSService.class);
private final UserAgents userAgents = UserAgents.getInstance();
private final SockJsConfig sockjsConfig;
private final SimplePushServer simplePushServer;
private String uaid;
private SockJsSessionContext session;
private ScheduledFuture<?> ackJobFuture;
/**
* Sole constructor.
*
* @param sockjsConfig the SockJS {@link SockJsConfig} for this service.
* @param simplePushServer the {@link SimplePushServer} that this instance will use.
*/
public SimplePushSockJSService(final SockJsConfig sockjsConfig, final SimplePushServer simplePushServer) {
this.sockjsConfig = sockjsConfig;
this.simplePushServer = simplePushServer;
}
@Override
public SockJsConfig config() {
return sockjsConfig;
}
@Override
public void onOpen(final SockJsSessionContext session) {
logger.info("SimplePushSockJSServer onOpen");
this.session = session;
}
@Override
@SuppressWarnings("incomplete-switch")
public void onMessage(final String message) throws Exception {
final MessageType messageType = JsonUtil.parseFrame(message);
logger.info("messageType: " + messageType.getMessageType());
switch (messageType.getMessageType()) {
case HELLO:
if (!checkHandshakeCompleted(uaid)) {
final HelloResponse response = simplePushServer.handleHandshake(fromJson(message, HelloMessageImpl.class));
session.send(toJson(response));
uaid = response.getUAID();
userAgents.add(uaid, session);
processUnacked(uaid, session, 0);
logger.info("UserAgent [" + uaid + "] handshake done");
}
break;
case REGISTER:
if (checkHandshakeCompleted(uaid)) {
final RegisterResponse response = simplePushServer.handleRegister(fromJson(message, RegisterMessageImpl.class), uaid);
session.send(toJson(response));
logger.info("UserAgent [" + uaid + "] Registered[" + response.getChannelId() + "]");
}
break;
case UNREGISTER:
if (checkHandshakeCompleted(uaid)) {
final UnregisterMessage unregister = fromJson(message, UnregisterMessageImpl.class);
final UnregisterResponse response = simplePushServer.handleUnregister(unregister, uaid);
session.send(toJson(response));
logger.info("UserAgent [" + uaid + "] Unregistered[" + response.getChannelId() + "]");
}
break;
case ACK:
if (checkHandshakeCompleted(uaid)) {
final AckMessage ack = fromJson(message, AckMessageImpl.class);
simplePushServer.handleAcknowledgement(ack, uaid);
processUnacked(uaid, session, simplePushServer.config().acknowledmentInterval());
}
break;
case PING:
session.send(PingMessageImpl.JSON);
break;
}
userAgents.updateAccessedTime(uaid);
}
private void processUnacked(final String uaid, final SockJsSessionContext session, final long delay) {
final Set<Ack> unacked = simplePushServer.getUnacknowledged(uaid);
if (unacked.isEmpty()) {
if (ackJobFuture != null && !ackJobFuture.isCancelled()) {
ackJobFuture.cancel(false);
logger.info("Cancelled Re-Acknowledger job");
}
} else if (ackJobFuture == null) {
ackJobFuture = session.getContext().executor().scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
final Set<Ack> unacked = simplePushServer.getUnacknowledged(uaid);
logger.info("Resending " + unacked);
session.send(toJson(new NotificationMessageImpl(unacked)));
}
},
delay,
simplePushServer.config().acknowledmentInterval(),
TimeUnit.MILLISECONDS);
}
}
private boolean checkHandshakeCompleted(final String uaid) {
if (uaid == null) {
logger.debug("Hello frame has not been sent");
return false;
}
if (!userAgents.contains(uaid)) {
this.uaid = uaid;
return false;
}
return true;
}
@Override
public void onClose() {
logger.info("SimplePushSockJSServer onClose");
if (ackJobFuture != null) {
ackJobFuture.cancel(true);
}
}
}