package ch.unifr.pai.twice.comm.serverPush.client; /* * Copyright 2013 Oliver Schmid * 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. */ import java.io.Serializable; import java.util.List; import org.atmosphere.gwt.client.AtmosphereClient; import org.atmosphere.gwt.client.AtmosphereGWTSerializer; import org.atmosphere.gwt.client.AtmosphereListener; import ch.unifr.pai.twice.authentication.client.security.MessagingException; import ch.unifr.pai.twice.authentication.client.security.TWICESecurityManager; import ch.unifr.pai.twice.comm.serverPush.shared.Constants; import ch.unifr.pai.twice.comm.serverPush.shared.PingEvent; import ch.unifr.pai.twice.comm.serverPush.shared.PingEvent.PingEventHandler; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.rpc.StatusCodeException; import com.google.web.bindery.event.shared.Event; import com.google.web.bindery.event.shared.Event.Type; import com.google.web.bindery.event.shared.HandlerRegistration; import com.google.web.bindery.event.shared.SimpleEventBus; /** * An extension of the standard GWT event bus which transparently handles remote events and includes security mechanisms such as encryption and signature * validation. The implementation also handles the establishment of consistant event orders between the distributed systems. * * * * @author Oliver Schmid * */ public class ServerPushEventBus extends SimpleEventBus { // CONFIGURABLE: public ServerPushEventBus() { super(); start(); } /** * The security manager to be applied. The implementation of this manager can easily be replaced through deferred binding. */ private final TWICESecurityManager security = GWT.create(TWICESecurityManager.class); /** * Helper class to handle the remote eventing mechanism */ private final RemoteEventing remoteEventing = new RemoteEventing() { /* * (non-Javadoc) * @see ch.unifr.pai.twice.comm.serverPush.client.RemoteEventing#sendMessage(java.lang.String) */ @Override protected void sendMessage(String message) { atmosphereClient.post(message); } /* * (non-Javadoc) * @see ch.unifr.pai.twice.comm.serverPush.client.RemoteEventing#fireEventLocally(com.google.web.bindery.event.shared.Event) */ @Override protected void fireEventLocally(Event<?> e) { ServerPushEventBus.super.fireEvent(e); } /* * (non-Javadoc) * @see ch.unifr.pai.twice.comm.serverPush.client.RemoteEventing#fireEventFromSourceLocally(com.google.web.bindery.event.shared.Event, java.lang.Object) */ @Override protected void fireEventFromSourceLocally(Event<?> e, Object source) { ServerPushEventBus.super.fireEventFromSource(e, source); } /* * (non-Javadoc) * @see ch.unifr.pai.twice.comm.serverPush.client.RemoteEventing#getSecurityManager() */ @Override protected TWICESecurityManager getSecurityManager() { return security; } }; /** * The atmosphere client that establishes a bi-directional communication with the server */ private AtmosphereClient atmosphereClient; /** * Fires the event to the remote instances as well * * @see com.google.web.bindery.event.shared.SimpleEventBus#fireEventFromSource(com.google.web.bindery.event.shared.Event, java.lang.Object) */ @Override public void fireEventFromSource(Event<?> event, Object source) { remoteEventing.fireEventFromSource(event, source); } /* * (non-Javadoc) * @see com.google.web.bindery.event.shared.SimpleEventBus#fireEvent(com.google.web.bindery.event.shared.Event) */ @Override public void fireEvent(Event<?> event) { fireEventFromSource(event, null); } /** * Deserializer for the remote events */ private final RemoteEventDeserializer deserializer = GWT.create(RemoteEventDeserializer.class); /** * Processes the received network message and translates it based on the security mechanism (decryption) and the deserializer * * @param message */ private void handleRemoteMessage(Serializable message) { String messageStr; try { messageStr = security.decryptMessage(message.toString()); RemoteEvent<?> event = deserializer.deserialize(messageStr, security); if (event != null) { GWT.log("Remote event timestamp: " + event.getTimestamp()); remoteEventing.fireEventInOrder(event, event.getSourceObject(), false); } } catch (MessagingException e) { // Do not interpret errorenous messages by default e.printStackTrace(); } } /** * Starts the server event bus (establishes the communication and starts to listen for remote events). */ private void start() { if (atmosphereClient != null) atmosphereClient.stop(); AtmosphereGWTSerializer serializer = GWT.create(AtmosphereEventWrapperSerializer.class); atmosphereClient = new AtmosphereClient(GWT.getHostPageBaseURL() + Constants.BASEPATH + Constants.ATMOSPHERE, serializer, new AtmosphereListener() { @Override public void onConnected(int heartbeat, int connectionID) { GWT.log("comet.connected [" + heartbeat + ", " + connectionID + "]"); } @Override public void onBeforeDisconnected() { GWT.log("comet.beforeDisconnected"); } @Override public void onDisconnected() { GWT.log("comet.disconnected"); } @Override public void onError(Throwable exception, boolean connected) { int statuscode = -1; if (exception instanceof StatusCodeException) { statuscode = ((StatusCodeException) exception).getStatusCode(); } GWT.log("comet.error [connected=" + connected + "] (" + statuscode + ")" + exception.getMessage()); } @Override public void onHeartbeat() { GWT.log("comet.heartbeat [" + atmosphereClient.getConnectionID() + "]"); } @Override public void onRefresh() { GWT.log("comet.refresh [" + atmosphereClient.getConnectionID() + "]"); } @Override public void onAfterRefresh() { GWT.log("comet.afterrefresh [" + atmosphereClient.getConnectionID() + "]"); } @Override public void onMessage(List<?> messages) { for (Object message : messages) { GWT.log("received message: " + message); if (message instanceof AtmosphereEventWrapper) handleRemoteMessage(((AtmosphereEventWrapper) message).getSerializedEvent()); else if (message instanceof Serializable) handleRemoteMessage((Serializable) message); } } }); this.atmosphereClient.start(); addHandler(PingEvent.TYPE, new PingEventHandler() { /** * A special event - if a blocking event arrives at the server, the server triggers a PingEvent to all clients. If the client receives such a ping * event, a response has to be sent immediately. This way the client confirms, that there are no other events in its queue and that the blocking * event can further be processed by the server * * @param event */ @Override public void onEvent(PingEvent event) { PingEvent e = GWT.create(PingEvent.class); e.setTimestamp(remoteEventing.getEstimatedServerTime(null)); AtmosphereEventWrapper wrapper = new AtmosphereEventWrapper(); wrapper.setEvent(e, security); GWT.log("Send ping"); atmosphereClient.post(wrapper); } }); } /* * (non-Javadoc) * @see com.google.web.bindery.event.shared.SimpleEventBus#addHandlerToSource(com.google.web.bindery.event.shared.Event.Type, java.lang.Object, * java.lang.Object) */ @Override public <H> HandlerRegistration addHandlerToSource(Type<H> type, Object source, H handler) { if (source instanceof RemoteWidget) { source = ((RemoteWidget) source).getEventSource(); } return super.addHandlerToSource(type, source, handler); } }