001/* 002 * GWTEventService 003 * Copyright (c) 2011 and beyond, strawbill UG (haftungsbeschr?nkt) 004 * 005 * This is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Lesser General Public License as 007 * published by the Free Software Foundation; either version 3 of 008 * the License, or (at your option) any later version. 009 * Other licensing for GWTEventService may also be possible on request. 010 * Please view the license.txt of the project for more information. 011 * 012 * This software is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this software; if not, write to the Free 019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 021 */ 022package de.novanic.eventservice.client.event; 023 024import com.google.gwt.user.client.rpc.AsyncCallback; 025 026import java.util.List; 027 028import com.google.gwt.user.client.rpc.StatusCodeException; 029import de.novanic.eventservice.client.config.ConfigurationTransferableDependentFactory; 030import de.novanic.eventservice.client.config.EventServiceConfigurationTransferable; 031import de.novanic.eventservice.client.connection.callback.AsyncCallbackWrapper; 032import de.novanic.eventservice.client.connection.strategy.connector.ConnectionStrategyClientConnector; 033import de.novanic.eventservice.client.event.listener.EventNotification; 034import de.novanic.eventservice.client.connection.strategy.connector.RemoteEventConnector; 035import de.novanic.eventservice.client.logger.ClientLogger; 036import de.novanic.eventservice.client.logger.ClientLoggerFactory; 037import de.novanic.eventservice.client.event.domain.Domain; 038import de.novanic.eventservice.client.event.domain.DomainFactory; 039import de.novanic.eventservice.client.event.filter.EventFilter; 040import de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent; 041import de.novanic.eventservice.client.event.listener.unlisten.DefaultUnlistenEvent; 042import de.novanic.eventservice.client.event.listener.unlisten.UnlistenEventListener; 043 044/** 045 * RemoteEventConnector should handle the connections between client- and the server side. 046 * 047 * @author sstrohschein 048 * <br>Date: 14.10.2008 049 * <br>Time: 11:34:45 050 */ 051public abstract class DefaultRemoteEventConnector implements RemoteEventConnector 052{ 053 private static final ClientLogger LOG = ClientLoggerFactory.getClientLogger(); 054 055 private boolean isActive; 056 private EventServiceConfigurationTransferable myConfiguration; 057 private ConnectionStrategyClientConnector myConnectionStrategyClientConnector; 058 private UnlistenEvent myUnlistenEvent; 059 private int myErrorCount; 060 061 /** 062 * Initializes the listen method implementation with a {@link de.novanic.eventservice.client.connection.strategy.connector.ConnectionStrategyClientConnector} from the configuration. 063 * That is required to specify the listen / connection strategy. The connection strategy can't be changed, when the listening has already started / an listener was added. 064 * @param aConfiguration configuration 065 */ 066 public synchronized ConnectionStrategyClientConnector initListen(EventServiceConfigurationTransferable aConfiguration) { 067 if(isActive) { 068 throw new RemoteEventServiceRuntimeException("Invalid attempt to change the connection strategy after listening was started!"); 069 } 070 if(aConfiguration == null) { 071 throw new RemoteEventServiceRuntimeException("Invalid attempt to initialize the listening without a configuration!"); 072 } 073 myConfiguration = aConfiguration; 074 ConfigurationTransferableDependentFactory theConfigDependentFactory = ConfigurationTransferableDependentFactory.getInstance(aConfiguration); 075 return (myConnectionStrategyClientConnector = theConfigDependentFactory.getConnectionStrategyClientConnector()); 076 } 077 078 /** 079 * Deactivates the connector for all domains (no events can be got from the domains). 080 */ 081 public synchronized void deactivate() { 082 if(isActive) { 083 isActive = false; 084 myConnectionStrategyClientConnector.deactivate(); 085 LOG.log("RemoteEventConnector deactivated."); 086 } 087 } 088 089 /** 090 * Checks if the connector is active (listening). 091 * @return true when active/listening, otherwise false 092 */ 093 public boolean isActive() { 094 return isActive; 095 } 096 097 /** 098 * Activates the connector for the domain. An {@link de.novanic.eventservice.client.event.filter.EventFilter} 099 * to filter events on the server side is optional. 100 * @param aDomain domain to activate 101 * @param anEventFilter EventFilter to filter the events on the server side (optional) 102 * @param anEventNotification supports the notification about incoming events 103 * @param aCallback callback 104 */ 105 public void activate(Domain aDomain, EventFilter anEventFilter, EventNotification anEventNotification, AsyncCallback<Void> aCallback) { 106 LOG.log("Activate RemoteEventConnector for domain \"" + aDomain + "\"."); 107 activateStart(aDomain, anEventFilter, new ActivationCallback<Void>(anEventNotification, aCallback)); 108 } 109 110 /** 111 * Registers an {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} to the server side which 112 * will be triggered when a timeout or unlisten/deactivation for a domain occurs. 113 * The UnlistenEvent will also be hold at the client side to trigger on local timeouts (for e.g. connection errors). 114 * @param anUnlistenScope scope of the unlisten events to receive 115 * @param anUnlistenEvent {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} which can contain custom data 116 * @param aCallback callback 117 */ 118 public void registerUnlistenEvent(UnlistenEventListener.Scope anUnlistenScope, UnlistenEvent anUnlistenEvent, AsyncCallback<Void> aCallback) { 119 myUnlistenEvent = anUnlistenEvent; 120 } 121 122 /** 123 * Activates the connector for the domain. An {@link de.novanic.eventservice.client.event.filter.EventFilter} 124 * to filter events on the server side is optional. 125 * @param aDomain domain to activate 126 * @param anEventFilter EventFilter to filter the events on the server side (optional) 127 * @param aCallback callback 128 */ 129 protected abstract void activateStart(Domain aDomain, EventFilter anEventFilter, AsyncCallback<Void> aCallback); 130 131 /** 132 * Creates the {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} for local timeouts. 133 * @param anEventNotification {@link de.novanic.eventservice.client.event.listener.EventNotification} for the triggered 134 * {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} 135 */ 136 private void fireUnlistenEvent(EventNotification anEventNotification) { 137 if(myUnlistenEvent == null) { 138 myUnlistenEvent = new DefaultUnlistenEvent(); 139 } 140 myUnlistenEvent.setTimeout(false); 141 myUnlistenEvent.setLocal(true); 142 final DomainEvent theUnlistenDomainEvent = new DefaultDomainEvent(myUnlistenEvent, DomainFactory.UNLISTEN_DOMAIN); 143 anEventNotification.onNotify(theUnlistenDomainEvent); 144 } 145 146 /** 147 * Callback to activate listening of RemoteEventConnector. 148 */ 149 private final class ActivationCallback<T> extends AsyncCallbackWrapper<T> 150 { 151 private final EventNotification myEventNotification; 152 153 private ActivationCallback(EventNotification anEventNotification, AsyncCallback<T> aCallback) { 154 super(aCallback); 155 myEventNotification = anEventNotification; 156 } 157 158 /** 159 * Starts listening to get events from the server side. 160 * @param aResult unused 161 * @see de.novanic.eventservice.client.event.GWTRemoteEventConnector.ListenEventCallback#callListen() 162 */ 163 public void onSuccess(T aResult) { 164 if(!isActive) { 165 if(myConnectionStrategyClientConnector != null) { 166 LOG.log("RemoteEventConnector activated."); 167 isActive = true; 168 final ListenEventCallback theListenEventCallback = new ListenEventCallback(myEventNotification); 169 theListenEventCallback.callListen(); 170 } else { 171 throw new RemoteEventServiceRuntimeException("No connection strategy was set at the start of listening!"); 172 } 173 } 174 super.onSuccess(aResult); 175 } 176 177 public void onFailure(Throwable aThrowable) { 178 LOG.error("Error on register client for domain!", aThrowable); 179 fireUnlistenEvent(myEventNotification); 180 super.onFailure(aThrowable); 181 } 182 } 183 184 /** 185 * The ListenEventCallback is used to produce the listen cycle. It is attached as callback for the listen server call. 186 */ 187 private final class ListenEventCallback implements AsyncCallback<List<DomainEvent>> 188 { 189 private EventNotification myEventNotification; 190 191 private ListenEventCallback(EventNotification anEventNotification) { 192 myEventNotification = anEventNotification; 193 } 194 195 /** 196 * When an error occurs while listening for events, reconnect attempts are started (when configured) and an unlisten event 197 * will be processed when the connection error could not be solved (to clean-up the client side). 198 * @param aThrowable 199 */ 200 public void onFailure(Throwable aThrowable) { 201 if((aThrowable instanceof StatusCodeException) && !isNotableStatusCode((StatusCodeException)aThrowable)) { 202 //Status code 0 is not handled as an error. Some browsers send this status code when the user leaves the site/application. 203 //The module is unloaded in this case and it has no negative effects to the application. Therefore it isn't a notable error. 204 LOG.log("The current connection was terminated with status code " + ((StatusCodeException)aThrowable).getStatusCode() + '.'); 205 fireUnlistenEvent(myEventNotification); //client side clean-up 206 } else { 207 LOG.error("Error on processing event!", aThrowable); 208 if(++myErrorCount > myConfiguration.getReconnectAttemptCount()) { 209 fireUnlistenEvent(myEventNotification); //client side clean-up 210 } else { 211 LOG.log("Reconnecting after error..."); 212 callListen(); 213 } 214 } 215 } 216 217 /** 218 * Calls listen on the server side and put itself as the callback to produces the listen cycle as long as the 219 * RemoteEventConnector is active. 220 * @param anEvents) events to process 221 * @see de.novanic.eventservice.client.event.GWTRemoteEventConnector.ListenEventCallback#callListen() 222 */ 223 public void onSuccess(List<DomainEvent> anEvents) { 224 myErrorCount = 0; 225 if(anEvents != null) { 226 for(DomainEvent theEvent: anEvents) { 227 myEventNotification.onNotify(theEvent); 228 } 229 callListen(); 230 } else { 231 //if the remote service doesn't know the client, all listeners will be removed and the connection becomes inactive 232 deactivate(); 233 myEventNotification.onAbort(); 234 } 235 } 236 237 /** 238 * Calls listen at the server side to receive events. 239 */ 240 public synchronized void callListen() { 241 if(isActive) { 242 //after getting an event, register itself to listen for the next events 243 myConnectionStrategyClientConnector.listen(myEventNotification, this); 244 } 245 } 246 247 /** 248 * Checks if the status code is a valid error code. For example status code 0 is rather an informational status code 249 * instead of a notable error status code. 250 * @param aStatusCodeException {@link StatusCodeException} which occurred with the status code 251 * @return true when it is a notable error status code, otherwise false 252 */ 253 private boolean isNotableStatusCode(StatusCodeException aStatusCodeException) { 254 return aStatusCodeException.getStatusCode() != 0; 255 } 256 } 257}