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.service;
023
024import com.google.gwt.user.server.rpc.RemoteServiceServlet;
025
026import java.io.IOException;
027import java.util.List;
028import java.util.Set;
029
030import de.novanic.eventservice.config.ConfigurationDependentFactory;
031import de.novanic.eventservice.client.config.EventServiceConfigurationTransferable;
032import de.novanic.eventservice.client.config.RemoteEventServiceConfigurationTransferable;
033import de.novanic.eventservice.service.connection.strategy.connector.ConnectionStrategyServerConnector;
034import de.novanic.eventservice.client.event.service.EventService;
035import de.novanic.eventservice.client.event.filter.EventFilter;
036import de.novanic.eventservice.client.event.Event;
037import de.novanic.eventservice.client.event.DomainEvent;
038import de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent;
039import de.novanic.eventservice.client.event.listener.unlisten.UnlistenEventListener;
040import de.novanic.eventservice.client.event.domain.Domain;
041import de.novanic.eventservice.config.EventServiceConfiguration;
042import de.novanic.eventservice.service.connection.id.SessionConnectionIdGenerator;
043import de.novanic.eventservice.service.connection.strategy.connector.streaming.StreamingServerConnector;
044import de.novanic.eventservice.service.registry.EventRegistry;
045import de.novanic.eventservice.service.registry.EventRegistryFactory;
046import de.novanic.eventservice.logger.ServerLogger;
047import de.novanic.eventservice.logger.ServerLoggerFactory;
048import de.novanic.eventservice.config.EventServiceConfigurationFactory;
049import de.novanic.eventservice.config.level.ConfigLevelFactory;
050import de.novanic.eventservice.config.loader.WebDescriptorConfigurationLoader;
051
052import javax.servlet.ServletException;
053import javax.servlet.ServletConfig;
054import javax.servlet.ServletOutputStream;
055import javax.servlet.http.HttpServletRequest;
056import javax.servlet.http.HttpServletResponse;
057
058/**
059 * {@link de.novanic.eventservice.client.event.service.EventService} is the server side interface to register listen
060 * requests for domains and to add events.
061 *
062 * @author sstrohschein
063 * <br>Date: 05.06.2008
064 * <br>Time: 19:12:17
065 */
066public class EventServiceImpl extends RemoteServiceServlet implements EventService
067{
068    private static final ServerLogger LOG = ServerLoggerFactory.getServerLogger(EventServiceImpl.class.getName());
069    private EventRegistry myEventRegistry;
070    private ConfigurationDependentFactory myConfigurationDependentFactory;
071
072    /**
073     * The init method should be called automatically before the servlet can be used and should called only one time.
074     * That method initialized the {@link de.novanic.eventservice.service.registry.EventRegistry}.
075     * @param aConfig servlet configuration
076     * @throws ServletException
077     */
078    public void init(ServletConfig aConfig) throws ServletException {
079        super.init(aConfig);
080        myEventRegistry = initEventRegistry(aConfig);
081        EventServiceConfiguration theConfiguration = myEventRegistry.getConfiguration();
082        myConfigurationDependentFactory = ConfigurationDependentFactory.getInstance(theConfiguration);
083    }
084
085    @Override
086    public void destroy() {
087        super.destroy();
088        EventRegistryFactory.getInstance().resetEventRegistry();
089    }
090
091    /**
092     * The GET method is used to stream data to the clients.
093     * @param aRequest request
094     * @param aResponse response (with the stream)
095     * @throws ServletException
096     * @throws IOException
097     */
098    protected void doGet(HttpServletRequest aRequest, HttpServletResponse aResponse) throws ServletException, IOException {
099        ConnectionStrategyServerConnector theConnectionStrategyServerConnector = myConfigurationDependentFactory.getConnectionStrategyServerConnector();
100        if(theConnectionStrategyServerConnector instanceof StreamingServerConnector) {
101            final String theClientId = getClientId(aRequest);
102            StreamingServerConnector theStreamingServerConnector = (StreamingServerConnector)theConnectionStrategyServerConnector;
103            try {
104                //The streaming server connector has to be cloned, because it isn't stateless (a prepare method is required).
105                theStreamingServerConnector = (StreamingServerConnector)theStreamingServerConnector.clone();
106                theStreamingServerConnector.prepare(aResponse);
107                listen(theStreamingServerConnector, theClientId);
108            } catch(EventServiceException e) {
109                throw new ServletException("Error on streaming events to the client\"" + theClientId + "\"!", e);
110            } catch(CloneNotSupportedException e) {
111                throw new ServletException("Error on cloning \"" + StreamingServerConnector.class.getName() + "\" for client \"" + theClientId + "\"!", e);
112            } finally {
113                ServletOutputStream theServletOutputStream = aResponse.getOutputStream();
114                theServletOutputStream.close();
115            }
116        }
117    }
118
119    /**
120     * Initializes the {@link de.novanic.eventservice.client.event.service.EventService}.
121     * @return EventServiceConfigurationTransferable a transferable configuration for the client side
122     */
123    public EventServiceConfigurationTransferable initEventService() {
124        final String theClientId = generateClientId();
125        final EventServiceConfiguration theConfiguration = myEventRegistry.getConfiguration();
126
127        String theClientIdTransferable;
128        if(SessionConnectionIdGenerator.class.getName().equals(theConfiguration.getConnectionIdGeneratorClassName())) {
129            theClientIdTransferable = null;
130        } else {
131            theClientIdTransferable = theClientId;
132        }
133        LOG.info("Client \"" + theClientId + "\" initialized.");
134        return new RemoteEventServiceConfigurationTransferable(theConfiguration.getMinWaitingTime(), theConfiguration.getMaxWaitingTime(),
135                theConfiguration.getTimeoutTime(), theConfiguration.getReconnectAttemptCount(), theClientIdTransferable, theConfiguration.getConnectionStrategyClientConnectorClassName());
136    }
137
138    /**
139     * Register listen for a domain.
140     * @param aDomain domain to listen to
141     */
142    public void register(Domain aDomain) {
143        register(aDomain, null);
144    }
145
146    /**
147     * Register listen for a domain.
148     * @param aDomain domain to listen to
149     * @param anEventFilter EventFilter to filter events
150     */
151    public void register(Domain aDomain, EventFilter anEventFilter) {
152        final String theClientId = getClientId();
153        myEventRegistry.registerUser(aDomain, theClientId, anEventFilter);
154    }
155
156    /**
157     * Register listen for a domain.
158     * @param aDomains domains to listen to
159     */
160    public void register(Set<Domain> aDomains) {
161        for(Domain aDomain : aDomains) {
162            register(aDomain);
163        }
164    }
165
166    /**
167     * Register listen for domains.
168     * @param aDomains domains to listen to
169     * @param anEventFilter EventFilter to filter events (applied to all domains)
170     */
171    public void register(Set<Domain> aDomains, EventFilter anEventFilter) {
172        for(Domain aDomain : aDomains) {
173            register(aDomain, anEventFilter);
174        }
175    }
176
177    /**
178     * Registers an {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} which is triggered on a
179     * timeout or when a user/client leaves a {@link de.novanic.eventservice.client.event.domain.Domain}. An
180     * {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} is hold at the server side and can
181     * contain custom data. Other users/clients can use the custom data when the event is for example triggered by a timeout.
182     * @param anUnlistenScope scope of the unlisten events to receive
183     * @param anUnlistenEvent {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} which should
184     * be transferred to other users/clients when a timeout occurs or a domain is leaved.
185     */
186    public void registerUnlistenEvent(UnlistenEventListener.Scope anUnlistenScope, UnlistenEvent anUnlistenEvent) {
187        final String theClientId = getClientId();
188        myEventRegistry.registerUnlistenEvent(theClientId, anUnlistenScope, anUnlistenEvent);
189    }
190
191    /**
192     * Registers an {@link EventFilter} for the domain.
193     * @param aDomain domain to register the EventFilter to
194     * @param anEventFilter EventFilter to filter events for the domain
195     */
196    public void registerEventFilter(Domain aDomain, EventFilter anEventFilter) {
197        final String theClientId = getClientId();
198        myEventRegistry.setEventFilter(aDomain, theClientId, anEventFilter);
199    }
200
201    /**
202     * Deregisters the {@link EventFilter} of the domain.
203     * @param aDomain domain to drop the EventFilters from
204     */
205    public void deregisterEventFilter(Domain aDomain) {
206        final String theClientId = getClientId();
207        myEventRegistry.removeEventFilter(aDomain, theClientId);
208    }
209
210    /**
211     * Returns the EventFilter for the user domain combination.
212     * @param aDomain domain
213     * @return EventFilter for the domain
214     */
215    public EventFilter getEventFilter(Domain aDomain) {
216        final String theClientId = getClientId();
217        return myEventRegistry.getEventFilter(aDomain, theClientId);
218    }
219
220    /**
221     * The listen method returns all events for the user (events for all domains where the user is registered and user
222     * specific events). If no events are available, the method waits a defined time before the events are returned.
223     * The client side calls the method with a defined interval to receive all events. If the client doesn't call the
224     * method in that interval, the user will be removed from the EventRegistry. The timeout time and the waiting time
225     * can be configured with {@link de.novanic.eventservice.config.EventServiceConfiguration}.
226     * The default listening method is long-polling, but that can be changed with changing the connection strategy.
227     * The connection strategy can be configured with {@link de.novanic.eventservice.config.ConfigParameter#CONNECTION_STRATEGY_CLIENT_CONNECTOR}
228     * for the client side part / connector and {@link de.novanic.eventservice.config.ConfigParameter#CONNECTION_STRATEGY_SERVER_CONNECTOR}.
229     * @return list of events
230     */
231    public List<DomainEvent> listen() {
232        final String theClientId = getClientId();
233        ConnectionStrategyServerConnector theConnectionStrategyServerConnector = myConfigurationDependentFactory.getConnectionStrategyServerConnector();
234        LOG.debug("Listen (client id \"" + theClientId + "\").");
235        return listen(theConnectionStrategyServerConnector, theClientId);
236    }
237
238    /**
239     * The listen method returns all events for the user (events for all domains where the user is registered and user
240     * specific events). If no events are available, the method waits a defined time before the events are returned.
241     * The client side calls the method with a defined interval to receive all events. If the client doesn't call the
242     * method in that interval, the user will be removed from the EventRegistry. The timeout time and the waiting time
243     * can be configured with {@link de.novanic.eventservice.config.EventServiceConfiguration}.
244     * @param aServerEventListener {@link de.novanic.eventservice.service.connection.strategy.connector.ConnectionStrategyServerConnector} for the listening method
245     * @param aClientId client / user
246     * @return list of events
247     */
248    private List<DomainEvent> listen(ConnectionStrategyServerConnector aServerEventListener, String aClientId) {
249        LOG.debug("Listen (client id \"" + aClientId + "\").");
250        return myEventRegistry.listen(aServerEventListener, aClientId);
251    }    
252
253    /**
254     * Unlisten for events (for the current user) in all domains (deregisters the user from all domains).
255     */
256    public void unlisten() {
257        final String theClientId = getClientId();
258        LOG.debug("Unlisten (client id \"" + theClientId + "\").");
259        myEventRegistry.unlisten(theClientId);
260        LOG.debug("Unlisten finished (client id \"" + theClientId + "\").");
261    }
262
263    /**
264     * Unlisten for events (for the current user) in the domain and deregisters the user from the domain.
265     * @param aDomain domain to unlisten
266     */
267    public void unlisten(Domain aDomain) {
268        final String theClientId = getClientId();
269        LOG.debug("Unlisten (client id \"" + theClientId + "\").");
270        myEventRegistry.unlisten(aDomain, theClientId);
271        LOG.debug("Unlisten finished (client id \"" + theClientId + "\").");
272    }
273
274    /**
275     * Unlisten for events (for the current user) in the domains and deregisters the user from the domains.
276     * @param aDomains set of domains to unlisten
277     */
278    public void unlisten(Set<Domain> aDomains) {
279        for(Domain theDomain: aDomains) {
280            unlisten(theDomain);
281        }
282    }
283
284    /**
285     * Checks if the user is registered for event listening.
286     * @param aDomain domain to check
287     * @return true when the user is registered for listening, otherwise false
288     */
289    public boolean isUserRegistered(Domain aDomain) {
290        final String theClientId = getClientId();
291        return myEventRegistry.isUserRegistered(aDomain, theClientId);
292    }
293
294    /**
295     * Adds an event for all users in the domain.
296     * @param aDomain domain to add the event
297     * @param anEvent event to add
298     */
299    public void addEvent(Domain aDomain, Event anEvent) {
300        myEventRegistry.addEvent(aDomain, anEvent);
301    }
302
303    /**
304     * Adds an event only for the current user.
305     * @param anEvent event to add to the user
306     */
307    public void addEventUserSpecific(Event anEvent) {
308        final String theClientId = getClientId();
309        myEventRegistry.addEventUserSpecific(theClientId, anEvent);
310    }
311
312    /**
313     * Returns the domain names, where the user is listening to
314     * @return collection of domain names
315     */
316    public Set<Domain> getActiveListenDomains() {
317        final String theClientId = getClientId();
318        return myEventRegistry.getListenDomains(theClientId);
319    }
320
321    /**
322     * Registers the {@link de.novanic.eventservice.config.loader.WebDescriptorConfigurationLoader},
323     * loads the first available configuration (with {@link de.novanic.eventservice.config.EventServiceConfigurationFactory})
324     * and initializes the {@link de.novanic.eventservice.service.registry.EventRegistry}.
325     * @param aConfig servlet configuration
326     * @return initialized {@link de.novanic.eventservice.service.registry.EventRegistry}
327     */
328    private EventRegistry initEventRegistry(ServletConfig aConfig) {
329        final WebDescriptorConfigurationLoader theWebDescriptorConfigurationLoader = new WebDescriptorConfigurationLoader(aConfig);
330        final EventServiceConfigurationFactory theEventServiceConfigurationFactory = EventServiceConfigurationFactory.getInstance();
331        theEventServiceConfigurationFactory.addConfigurationLoader(ConfigLevelFactory.DEFAULT, theWebDescriptorConfigurationLoader);
332
333        final EventRegistryFactory theEventRegistryFactory = EventRegistryFactory.getInstance();
334        EventRegistry theEventRegistry = theEventRegistryFactory.getEventRegistry();
335
336        if(theWebDescriptorConfigurationLoader.isAvailable()) {
337            theEventServiceConfigurationFactory.loadEventServiceConfiguration();
338        }
339        return theEventRegistry;
340    }
341
342    /**
343     * Returns the client id.
344     * @return client id
345     */
346    protected String getClientId() {
347        return getClientId(getThreadLocalRequest());
348    }
349
350    /**
351     * Returns the client id.
352     * @param aRequest request
353     * @return client id
354     */
355    protected String getClientId(HttpServletRequest aRequest) {
356        return myConfigurationDependentFactory.getConnectionIdGenerator().getConnectionId(aRequest);
357    }
358
359    /**
360     * Generates and returns a new client id.
361     * @return client id
362     */
363    protected String generateClientId() {
364        return myConfigurationDependentFactory.getConnectionIdGenerator().generateConnectionId(getThreadLocalRequest());
365    }
366
367    /**
368     * This method is overridden because applications with various GWT versions got a {@link SecurityException}
369     * @throws SecurityException
370     */
371    @Override
372    protected void checkPermutationStrongName() throws SecurityException {}
373}