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.registry; 023 024import de.novanic.eventservice.client.event.filter.EventFilter; 025import de.novanic.eventservice.client.event.Event; 026import de.novanic.eventservice.client.event.DomainEvent; 027import de.novanic.eventservice.client.event.domain.Domain; 028import de.novanic.eventservice.client.event.domain.DomainFactory; 029import de.novanic.eventservice.config.EventServiceConfiguration; 030import de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent; 031import de.novanic.eventservice.client.event.listener.unlisten.UnlistenEventListener; 032import de.novanic.eventservice.logger.ServerLogger; 033import de.novanic.eventservice.logger.ServerLoggerFactory; 034import de.novanic.eventservice.service.EventServiceException; 035import de.novanic.eventservice.service.connection.strategy.connector.ConnectionStrategyServerConnector; 036import de.novanic.eventservice.service.registry.user.*; 037import de.novanic.eventservice.service.registry.domain.ListenDomainAccessor; 038import de.novanic.eventservice.service.UserTimeoutListener; 039import de.novanic.eventservice.event.listener.unlisten.UnlistenEventFilter; 040 041import java.util.*; 042 043/** 044 * The EventRegistry handles the users/clients and the events per domain. Users can be registered for a domain/context 045 * to receive events for the according domain. 046 * User specific events can be handled domain-less, when the user is registered. 047 * The EventRegistry is used by {@link de.novanic.eventservice.service.EventServiceImpl}. 048 * 049 * <br>The client id is required, because the connection to every client must be kept open. 050 * 051 * @see de.novanic.eventservice.service.EventServiceImpl 052 * 053 * @author sstrohschein 054 * <br>Date: 05.06.2008 055 * <br>Time: 19:12:35 056 */ 057public class DefaultEventRegistry implements EventRegistry, ListenDomainAccessor 058{ 059 private static final ServerLogger LOG = ServerLoggerFactory.getServerLogger(DefaultEventRegistry.class.getName()); 060 061 private final EventServiceConfiguration myConfiguration; 062 private final DomainUserMapping myDomainUserMapping; 063 private final UserManager myUserManager; 064 private final UserActivityScheduler myUserActivityScheduler; 065 066 /** 067 * Creates a new EventRegistry with a configuration ({@link de.novanic.eventservice.config.EventServiceConfiguration}). 068 * The {@link EventRegistryFactory} should be used instead of calling that constructor directly. 069 * @param aConfiguration configuration 070 * @see de.novanic.eventservice.service.registry.EventRegistryFactory#getEventRegistry() 071 */ 072 protected DefaultEventRegistry(EventServiceConfiguration aConfiguration) { 073 myConfiguration = aConfiguration; 074 myDomainUserMapping = new DomainUserMapping(); 075 myUserManager = UserManagerFactory.getInstance().getUserManager(aConfiguration); 076 myUserActivityScheduler = myUserManager.getUserActivityScheduler(); 077 myUserActivityScheduler.addTimeoutListener(new TimeoutListener()); 078 myUserManager.activateUserActivityScheduler(); 079 080 LOG.info("Configuration changed - " + aConfiguration.toString()); 081 } 082 083 /** 084 * Checks if the user is registered for any domain. 085 * @param aUserId the user to check 086 * @return true if registered, false if not registered 087 */ 088 public boolean isUserRegistered(String aUserId) { 089 UserInfo theUserInfo = getUserInfo(aUserId); 090 return isUserRegistered(theUserInfo); 091 } 092 093 /** 094 * Checks if the user is registered for any domain. 095 * @param aUserInfo the user to check 096 * @return true if registered, false if not registered 097 */ 098 private boolean isUserRegistered(UserInfo aUserInfo) { 099 return aUserInfo != null && myUserManager.isUserContained(aUserInfo); 100 } 101 102 /** 103 * Checks if the user is registered for the corresponding domain. 104 * @param aDomain the domain to check 105 * @param aUserId the user to check 106 * @return true if registered, false if not registered 107 */ 108 public boolean isUserRegistered(Domain aDomain, String aUserId) { 109 UserInfo theUserInfo = getUserInfo(aUserId); 110 return isUserRegistered(aDomain, theUserInfo); 111 } 112 113 /** 114 * Checks if the user is registered for the corresponding domain. 115 * @param aDomain the domain to check 116 * @param aUserInfo the user to check 117 * @return true if registered, false if not registered 118 */ 119 private boolean isUserRegistered(Domain aDomain, UserInfo aUserInfo) { 120 return aDomain != null && aUserInfo != null && myDomainUserMapping.isUserContained(aDomain, aUserInfo); 121 } 122 123 /** 124 * Registers a user for listening for the corresponding domain. From now all events for the domain are recognized and 125 * will be returned when listen ({@link DefaultEventRegistry#listen(de.novanic.eventservice.service.connection.strategy.connector.ConnectionStrategyServerConnector , String)}) is called. The {@link de.novanic.eventservice.client.event.filter.EventFilter} 126 * is optional and can be NULL. 127 * @param aDomain the domain to listen 128 * @param aUserId the user to register 129 * @param anEventFilter EventFilter to filter the domain events (optional, can be NULL) 130 */ 131 public void registerUser(final Domain aDomain, final String aUserId, EventFilter anEventFilter) { 132 //create UserInfo 133 UserInfo theUserInfo = myUserManager.addUser(aUserId); 134 135 //register UserInfo for the Domain 136 if(aDomain != null) { 137 myDomainUserMapping.addUser(aDomain, theUserInfo); 138 139 LOG.debug("User \"" + aUserId + "\" registered for domain \"" + aDomain + "\"."); 140 141 //set EventFilter 142 setEventFilter(aDomain, theUserInfo, anEventFilter); 143 } else { 144 LOG.debug("User \"" + aUserId + "\" registered."); 145 } 146 } 147 148 /** 149 * The {@link de.novanic.eventservice.client.event.filter.EventFilter} for a user domain combination can be set or 150 * changed with that method. The {@link de.novanic.eventservice.client.event.filter.EventFilter} can be removed 151 * with the method {@link de.novanic.eventservice.service.registry.EventRegistry#removeEventFilter(de.novanic.eventservice.client.event.domain.Domain, String)} 152 * or when that method is called with NULL as the {@link de.novanic.eventservice.client.event.filter.EventFilter} 153 * parameter value. 154 * @param aDomain domain 155 * @param aUserId user 156 * @param anEventFilter new EventFilter 157 */ 158 public void setEventFilter(final Domain aDomain, final String aUserId, EventFilter anEventFilter) { 159 UserInfo theUserInfo = getUserInfo(aUserId); 160 setEventFilter(aDomain, theUserInfo, anEventFilter); 161 } 162 163 /** 164 * The {@link de.novanic.eventservice.client.event.filter.EventFilter} for a user domain combination can be set or 165 * changed with that method. The {@link de.novanic.eventservice.client.event.filter.EventFilter} can be removed 166 * with the method {@link de.novanic.eventservice.service.registry.EventRegistry#removeEventFilter(de.novanic.eventservice.client.event.domain.Domain, String)} 167 * or when that method is called with NULL as the {@link de.novanic.eventservice.client.event.filter.EventFilter} 168 * parameter value. 169 * @param aDomain domain 170 * @param aUserInfo user 171 * @param anEventFilter new {@link de.novanic.eventservice.client.event.filter.EventFilter} 172 */ 173 private void setEventFilter(final Domain aDomain, final UserInfo aUserInfo, EventFilter anEventFilter) { 174 if(aUserInfo != null) { 175 if(anEventFilter != null) { 176 LOG.debug(aUserInfo.getUserId() + ": EventFilter changed for domain \"" + aDomain + "\"."); 177 aUserInfo.setEventFilter(aDomain, anEventFilter); 178 } else { 179 if(aUserInfo.removeEventFilter(aDomain)) { 180 LOG.debug(aUserInfo.getUserId() + ": EventFilter removed from domain \"" + aDomain + "\"."); 181 } 182 } 183 } 184 } 185 186 /** 187 * Returns the EventFilter for the user domain combination. 188 * @param aDomain domain 189 * @param aUserId user 190 * @return EventFilter for the domain 191 */ 192 public EventFilter getEventFilter(Domain aDomain, String aUserId) { 193 UserInfo theUserInfo = getUserInfo(aUserId); 194 if(theUserInfo != null) { 195 return theUserInfo.getEventFilter(aDomain); 196 } 197 return null; 198 } 199 200 /** 201 * EventFilters can be removed for a user domain combination with that method. 202 * @param aDomain domain 203 * @param aUserId user 204 */ 205 public void removeEventFilter(final Domain aDomain, final String aUserId) { 206 setEventFilter(aDomain, aUserId, null); 207 } 208 209 /** 210 * The listen method returns all events for the user (events for all domains where the user is registered and user 211 * specific events). If no events are available, the method waits a defined time before the events are returned. 212 * The listen method is designed for the EventService functionality. The client side calls the method with a defined 213 * interval to receive all events. If the client don't call the method in the interval, the user will be removed 214 * from the EventRegistry. The timeout time and the min. and max. waiting time can be configured by 215 * @param aServerEventListener {@link de.novanic.eventservice.service.connection.strategy.connector.ConnectionStrategyServerConnector} for the listening method 216 * @param aUserId user 217 * @return list of events 218 */ 219 public List<DomainEvent> listen(ConnectionStrategyServerConnector aServerEventListener, String aUserId) { 220 UserInfo theUserInfo = getUserInfo(aUserId); 221 LOG.debug(aUserId + ": listen (UserInfo " + theUserInfo + ")."); 222 if(theUserInfo != null) { 223 myUserActivityScheduler.reportUserActivity(theUserInfo); 224 try { 225 return aServerEventListener.listen(theUserInfo); 226 } catch(EventServiceException e) { 227 LOG.error("Error on listening for user \"" + theUserInfo + "\" with \"" + aServerEventListener.getClass().getName() + "\"!", e); 228 } finally { 229 myUserActivityScheduler.reportUserActivity(theUserInfo); 230 } 231 } 232 return null; 233 } 234 235 /** 236 * This method causes a stop of listening for a domain ({@link DefaultEventRegistry#listen(de.novanic.eventservice.service.connection.strategy.connector.ConnectionStrategyServerConnector , String)}). 237 * @param aDomain domain to stop listening 238 * @param aUserId user 239 */ 240 public void unlisten(Domain aDomain, String aUserId) { 241 UserInfo theUserInfo = getUserInfo(aUserId); 242 if(theUserInfo != null) { 243 if(aDomain != null) { 244 LOG.debug(aUserId + ": unlisten (domain \"" + aDomain + "\")."); 245 if(isUserRegistered(aDomain, theUserInfo)) { 246 Set<Domain> theDomains = new HashSet<Domain>(1); 247 theDomains.add(aDomain); 248 addEvent(DomainFactory.UNLISTEN_DOMAIN, produceUnlistenEvent(theUserInfo, theDomains, false)); 249 } 250 removeUser(aDomain, theUserInfo); 251 } else { 252 //An unlisten for the NULL-domain should only have an effect when the user isn't still registered for a domain. 253 if(!myDomainUserMapping.isUserContained(theUserInfo)) { 254 unlisten(theUserInfo, false); 255 } 256 } 257 } 258 } 259 260 /** 261 * This method causes a stop of listening for all domains ({@link DefaultEventRegistry#listen(de.novanic.eventservice.service.connection.strategy.connector.ConnectionStrategyServerConnector , String)}). 262 * @param aUserId user 263 */ 264 public void unlisten(String aUserId) { 265 UserInfo theUserInfo = getUserInfo(aUserId); 266 unlisten(theUserInfo, false); 267 } 268 269 /** 270 * This method causes a stop of listening for all domains ({@link DefaultEventRegistry#listen(de.novanic.eventservice.service.connection.strategy.connector.ConnectionStrategyServerConnector , String)}). 271 * @param aUserInfo user 272 * @param isTimeout reason for the unlistening (timeout or leave of a specific domain) 273 */ 274 private void unlisten(UserInfo aUserInfo, boolean isTimeout) { 275 if(aUserInfo != null) { 276 final String theUserId = aUserInfo.getUserId(); 277 LOG.debug(theUserId + ": unlisten."); 278 Set<Domain> theDomains = myDomainUserMapping.getDomains(aUserInfo); 279 addEvent(DomainFactory.UNLISTEN_DOMAIN, produceUnlistenEvent(aUserInfo, theDomains, isTimeout)); 280 removeUser(aUserInfo); 281 } 282 } 283 284 /** 285 * Removes the user from a domain. 286 * @param aDomain domain 287 * @param aUserInfo user 288 * @return true when the user is removed from the domain, otherwise false 289 */ 290 private boolean removeUser(Domain aDomain, UserInfo aUserInfo) { 291 boolean isUserRemoved = myDomainUserMapping.removeUser(aDomain, aUserInfo); 292 if(isUserRemoved) { 293 LOG.debug("User \"" + aUserInfo + "\" removed from domain \"" + aDomain + "\"."); 294 } 295 296 if(!myDomainUserMapping.isUserContained(aUserInfo)) { 297 myUserManager.removeUser(aUserInfo.getUserId()); 298 } else { 299 //remove the EventFilter if the user isn't removed completely 300 aUserInfo.removeEventFilter(aDomain); 301 } 302 303 return isUserRemoved; 304 } 305 306 /** 307 * Removes a user from all domains. 308 * @param aUserInfo user 309 */ 310 private void removeUser(UserInfo aUserInfo) { 311 myDomainUserMapping.removeUser(aUserInfo); 312 if(myUserManager.removeUser(aUserInfo.getUserId()) != null) { 313 LOG.debug("User \"" + aUserInfo + "\" removed."); 314 } 315 } 316 317 /** 318 * Returns all domains where the user is registered to. 319 * @param aUserId user id 320 * @return domains where the user is registered to 321 */ 322 public Set<Domain> getListenDomains(String aUserId) { 323 UserInfo theUserInfo = getUserInfo(aUserId); 324 return getListenDomains(theUserInfo); 325 } 326 327 /** 328 * Returns all domains where the user is registered to. 329 * @param aUserInfo user 330 * @return domains where the user is registered to 331 */ 332 private Set<Domain> getListenDomains(UserInfo aUserInfo) { 333 return myDomainUserMapping.getDomains(aUserInfo); 334 } 335 336 /** 337 * Returns all registered/activated domains. 338 * @return all registered/activated domains 339 */ 340 public Set<Domain> getListenDomains() { 341 return myDomainUserMapping.getDomains(); 342 } 343 344 /** 345 * Returns all registered users/clients. 346 * To get only the registered users/client of a specific {@link de.novanic.eventservice.client.event.domain.Domain}, 347 * the method {@link de.novanic.eventservice.service.registry.EventRegistry#getRegisteredUserIds(de.novanic.eventservice.client.event.domain.Domain)} 348 * can be used instead. 349 * @return registered users/clients 350 */ 351 public Set<String> getRegisteredUserIds() { 352 return getUserIds(myUserManager.getUsers()); 353 } 354 355 /** 356 * Returns all registered users/client of a specific {@link de.novanic.eventservice.client.event.domain.Domain}. 357 * To get all the registered users/client (of all domains), the method {@link EventRegistry#getRegisteredUserIds()} 358 * can be used instead. 359 * @param aDomain domain 360 * @return registered users/client of the specific domain 361 */ 362 public Set<String> getRegisteredUserIds(Domain aDomain) { 363 return getUserIds(myDomainUserMapping.getUsers(aDomain)); 364 } 365 366 /** 367 * Adds an event to a domain. 368 * @param aDomain domain for the event 369 * @param anEvent event to add 370 */ 371 public void addEvent(Domain aDomain, Event anEvent) { 372 LOG.debug("Event \"" + anEvent + "\" added to domain \"" + aDomain + "\"."); 373 final Set<UserInfo> theDomainUsers = myDomainUserMapping.getUsers(aDomain); 374 //if the domain doesn't exist/no users assigned, no users must be notified for the event... 375 if(theDomainUsers != null) { 376 for(UserInfo theUserInfo: theDomainUsers) { 377 addEvent(aDomain, theUserInfo, anEvent); 378 } 379 } 380 } 381 382 /** 383 * Adds an event directly to a user. The user must be registered to any domain. 384 * @param aUserId user 385 * @param anEvent event 386 */ 387 public void addEventUserSpecific(String aUserId, Event anEvent) { 388 UserInfo theUserInfo = getUserInfo(aUserId); 389 addEventUserSpecific(theUserInfo, anEvent); 390 } 391 392 /** 393 * Adds an event directly to a user. The user must be registered to any domain. 394 * @param aUserInfo user 395 * @param anEvent event 396 */ 397 private void addEventUserSpecific(UserInfo aUserInfo, Event anEvent) { 398 if(aUserInfo != null) { 399 LOG.debug("User specific event \"" + anEvent + "\" added to client id \"" + aUserInfo + "\"."); 400 addEvent(DomainFactory.USER_SPECIFIC_DOMAIN, aUserInfo, anEvent); 401 } 402 } 403 404 /** 405 * Registers an {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} which is triggered on a 406 * timeout or when a user/client leaves a {@link de.novanic.eventservice.client.event.domain.Domain}. An 407 * {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} is hold at the server side and can 408 * contain custom data. Other users/clients can use the custom data when the event is for example triggered by a timeout. 409 * @param aUserId user to register the {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} to 410 * @param anUnlistenScope scope of the unlisten events to receive 411 * @param anUnlistenEvent {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} which should 412 * be transferred to other users/clients when a timeout occurs or a domain is leaved. 413 */ 414 public void registerUnlistenEvent(String aUserId, UnlistenEventListener.Scope anUnlistenScope, UnlistenEvent anUnlistenEvent) { 415 registerUser(DomainFactory.UNLISTEN_DOMAIN, aUserId, new UnlistenEventFilter(this, aUserId, anUnlistenScope)); 416 UserInfo theUserInfo = getUserInfo(aUserId); 417 theUserInfo.setUnlistenEvent(anUnlistenEvent); 418 } 419 420 /** 421 * Returns the initialized {@link de.novanic.eventservice.config.EventServiceConfiguration} 422 * @return configuration {@link de.novanic.eventservice.config.EventServiceConfiguration} 423 */ 424 public EventServiceConfiguration getConfiguration() { 425 return myConfiguration; 426 } 427 428 /** 429 * Adds an event to a user in a domain. 430 * @param aDomain domain for the event 431 * @param aUserInfo user 432 * @param anEvent event to add 433 */ 434 private void addEvent(Domain aDomain, UserInfo aUserInfo, Event anEvent) { 435 if(isEventValid(anEvent, aUserInfo.getEventFilter(aDomain))) { 436 aUserInfo.addEvent(aDomain, anEvent); 437 LOG.debug(anEvent + " for user \"" + aUserInfo + "\"."); 438 } 439 } 440 441 /** 442 * Checks if the EventFilter recognizes the event as valid. When no EventFilter is available (NULL), 443 * the event is always valid. 444 * @param anEvent event 445 * @param anEventFilter EventFilter to check the event 446 * @return true when the event is valid, false when the event isn't valid (filtered by the EventFilter) 447 */ 448 private boolean isEventValid(Event anEvent, EventFilter anEventFilter) { 449 return (anEventFilter == null || !(anEventFilter.match(anEvent))); 450 } 451 452 /** 453 * Determines the UserInfo with the user id. 454 * @param aUserId user 455 * @return UserInfo according to the user id 456 */ 457 private UserInfo getUserInfo(final String aUserId) { 458 return myUserManager.getUser(aUserId); 459 } 460 461 /** 462 * Initializes an {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} with required information 463 * like the unlistened domain, user id of the unlistened user/client and the reason for the {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent}. 464 * @param aUserInfo user 465 * @param aDomains unlistened domains 466 * @param isTimeout true when the unlisten event is caused by a timeout, false when the unlisten event is caused by a regular unlisten call 467 * @return produced {@link de.novanic.eventservice.client.event.listener.unlisten.UnlistenEvent} 468 */ 469 private UnlistenEvent produceUnlistenEvent(UserInfo aUserInfo, Set<Domain> aDomains, boolean isTimeout) { 470 final UnlistenEvent theUnlistenEvent = aUserInfo.getUnlistenEvent(); 471 theUnlistenEvent.setUserId(aUserInfo.getUserId()); 472 theUnlistenEvent.setDomains(aDomains); 473 theUnlistenEvent.setTimeout(isTimeout); 474 return theUnlistenEvent; 475 } 476 477 /** 478 * Generates a set of user ids on the basis of {@link de.novanic.eventservice.service.registry.user.UserInfo} objects 479 * @param aUserInfoSet set of users ({@link de.novanic.eventservice.service.registry.user.UserInfo}) 480 * @return set of user ids 481 */ 482 private Set<String> getUserIds(Collection<UserInfo> aUserInfoSet) { 483 if(aUserInfoSet == null) { 484 return new HashSet<String>(0); 485 } 486 487 Set<String> theUserIdSet = new HashSet<String>(aUserInfoSet.size()); 488 for(UserInfo theUserInfo: aUserInfoSet) { 489 theUserIdSet.add(theUserInfo.getUserId()); 490 } 491 return theUserIdSet; 492 } 493 494 /** 495 * TimeoutListener to clean up inactive users/clients. The timeout is checked with 496 * {@link de.novanic.eventservice.service.registry.user.UserActivityScheduler}. 497 */ 498 private class TimeoutListener implements UserTimeoutListener 499 { 500 /** 501 * The method onTimeout is called when a timeout is recognized for the user. 502 * It causes a unlisten call ({@link de.novanic.eventservice.service.registry.DefaultEventRegistry#unlisten(String)}) 503 * to clean up the inactive user/client. 504 * @param aUserInfo the inactive user 505 */ 506 public void onTimeout(UserInfo aUserInfo) { 507 LOG.debug(aUserInfo.getUserId() + ": timeout."); 508 unlisten(aUserInfo, true); 509 } 510 } 511}