package com.google.sitebricks.channel; import java.util.HashMap; import java.util.Map; import java.util.Set; import com.google.common.collect.ImmutableSet; import com.google.inject.TypeLiteral; import com.google.inject.name.Names; import com.google.inject.servlet.ServletModule; /** * Install to set up sitebricks channel support for websocket (or other available * transports, such as Google AppEngine's Channel API). * * @author dhanji@gmail.com (Dhanji R. Prasanna) */ public abstract class ChannelModule extends ServletModule { static final String CHANNEL_URL_NAME = "__SB:CHANNEL_URL"; private final String channelUrl; private static final Set<String> SUPPORTED_SYSTEMS = ImmutableSet.of( "jetty-websocket", "jetty-continuation" ); private final Map<String, ObserverWrapper> handlers = new HashMap<String, ObserverWrapper>(); public ChannelModule(String channelUrl) { if (channelUrl == null || channelUrl.isEmpty() || !channelUrl.startsWith("/")) addError("ChannelModule URL must begin with '/' but found: " + channelUrl); else if (channelUrl.endsWith("/") && channelUrl.length() > 1) channelUrl = channelUrl.substring(0, channelUrl.length() - 1); this.channelUrl = channelUrl; } @Override protected final void configureServlets() { configureChannels(); bind(Switchboard.class).to(ChannelSwitchboard.class); bind(new TypeLiteral<Map<String, ObserverWrapper>>() {}) .annotatedWith(Handlers.class) .toInstance(handlers); bindConstant().annotatedWith(Names.named(CHANNEL_URL_NAME)).to(channelUrl); // Appengine? if (System.getProperty("com.google.appengine.runtime.version") != null) { serve(channelUrl).with(AppengineRoutingServlet.class); } else { // Test if Jetty-websocket is on classpath. boolean websocket = true; try { // Jetty 9 Class.forName("org.eclipse.jetty.websocket.servlet.WebSocketServlet"); // Jetty 8 //Class.forName("org.eclipse.jetty.websocket.WebSocketServlet"); serve(channelUrl + "/websocket").with(WebSocketRoutingServlet.class); } catch (ClassNotFoundException cnfe) { System.out.println("NO WEBSOCKET!!!!"); websocket = false; } // Also try Jetty-continuations. try { Class.forName("org.eclipse.jetty.continuation.ContinuationSupport"); serve(channelUrl + "/async").with(ContinuationRoutingServlet.class); } catch (ClassNotFoundException cnfe2) { // Give up? if (!websocket) addError("No available browser-socket subsystems were found on the classpath. " + "Currently supported are: " + SUPPORTED_SYSTEMS); } serve(channelUrl + CometJSServlet.SOCKET_URL_PATTERN).with(CometJSServlet.class); serve(channelUrl + CometJSServlet.JQUERY_URL_PATTERN).with(CometJSServlet.class); } } protected final ChannelObserverBinder process(final String event) { return new ChannelObserverBinder() { @Override public void with(Class<?> clazz) { handlers.put(event, new ObserverWrapper(event, clazz, binder())); } }; } protected final ChannelObserverBinder processAll() { return new ChannelObserverBinder() { @Override public void with(Class<?> clazz) { ObserverWrapper observer = new ObserverWrapper(ObserverWrapper.DEFAULT, clazz, binder()); requestInjection(observer); handlers.put(ObserverWrapper.DEFAULT, observer); } }; } protected abstract void configureChannels(); public static interface ChannelObserverBinder { void with(Class<?> clazz); } }