/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.core.util.event; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import org.olat.core.gui.control.Controller; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.ThreadLocalUserActivityLoggerInstaller; import org.olat.core.logging.activity.UserActivityLoggerImpl; import org.olat.core.util.event.businfo.BusListenerInfo; /** * abstract class for common services of the system bus * @author Felix Jost */ public abstract class AbstractEventBus implements EventBus { private final Map<String, EventAgency> infocenter; private final Map<String, EventAgency> typeInfocenter; private final OLog log = Tracing.createLoggerFor(this.getClass()); public AbstractEventBus() { infocenter = new HashMap<String, EventAgency>(); typeInfocenter = new HashMap<String, EventAgency>(); } @Override public void registerFor(GenericEventListener gel, Identity identity, OLATResourceable ores) { final Long oresId = ores.getResourceableId(); final String typeName = ores.getResourceableTypeName(); synchronized (infocenter) { EventAgency ea = null; if (oresId == null) { // return the eventagency which listens to all events with the type of // the ores ea = typeInfocenter.get(typeName); if (ea == null) { // we are the first listener -> create an agency ea = new EventAgency(); typeInfocenter.put(typeName, ea); } } else { // type and id String oresStr = typeName + "::" + oresId; ea = infocenter.get(oresStr); if (ea == null) { // we are the first listener ea = new EventAgency(); infocenter.put(oresStr, ea); } } ea.addListener(gel, identity); } } @Override public void deregisterFor(GenericEventListener gel, OLATResourceable ores) { final Long oresId = ores.getResourceableId(); final String typeName = ores.getResourceableTypeName(); synchronized (infocenter) { if (oresId == null) { EventAgency ea = typeInfocenter.get(typeName); if (ea != null) { ea.removeListener(gel); if(ea.getListenerCount() == 0) { typeInfocenter.remove(typeName); } } } else { // type and id String oresStr = typeName + "::" + oresId; EventAgency ea = infocenter.get(oresStr); if (ea != null) { ea.removeListener(gel); if(ea.getListenerCount() == 0) { typeInfocenter.remove(typeName); } } } } } public abstract int getListeningIdentityCntFor(OLATResourceable ores); public abstract void fireEventToListenersOf(MultiUserEvent event, OLATResourceable ores); /** * fires events to registered listeners of generic events. * To see all events set this class and also DefaultController and Component to debug. * @param event * @param ores */ protected final void doFire(final MultiUserEvent event, final OLATResourceable ores) { final Long oresId = ores.getResourceableId(); final String typeName = ores.getResourceableTypeName(); GenericEventListener[] listenersArr = null; GenericEventListener[] listenersTypeArr = null; synchronized (infocenter) { if (oresId != null) { String oresStr = typeName + "::" + oresId; EventAgency ea = infocenter.get(oresStr); if (ea != null) { listenersArr = ea.getListeners(); } } EventAgency ea = typeInfocenter.get(typeName); if (ea != null) { listenersTypeArr = ea.getListeners(); } } doFire(event, listenersArr); doFire(event, listenersTypeArr); } private final void doFire(final MultiUserEvent event, final GenericEventListener[] liArr) { if(liArr == null) return; for (int i = 0; i < liArr.length; i++) { try { final GenericEventListener listener = liArr[i]; //make sure GenericEvents are only sent when controller is not yet disposed if (listener instanceof Controller) { Controller dCtrl = (Controller)listener; if (!dCtrl.isDisposed()) { ThreadLocalUserActivityLoggerInstaller.runWithUserActivityLogger(new Runnable() { @Override public void run() { listener.event(event); } }, UserActivityLoggerImpl.newLoggerForEventBus(dCtrl)); } } else if(listener != null) { if(log.isDebug()){ log.debug("fireEvent: Non-Controller: "+listener); } //is there a need to differ the events sent on one VM and in cluster mode? ThreadLocalUserActivityLoggerInstaller.runWithUserActivityLogger(new Runnable() { @Override public void run() { listener.event(event); } }, ThreadLocalUserActivityLoggerInstaller.createEmptyUserActivityLogger()); } } catch (RuntimeException e) { log.error("Error while sending generic event: "+liArr[i], e); } } } protected final BusListenerInfo createBusListenerInfo() { BusListenerInfo bii = new BusListenerInfo(); synchronized(infocenter) { // o_clusterOK by:fj: extract quickly so that we can later serialize and send across the wire. data affects only one vm. // for all types: the name of the type + "::"+ the id (integer) is used as key List<String> infocenterKeys = new ArrayList<>(infocenter.keySet()); for (String derivedOres: infocenterKeys) { EventAgency ea = infocenter.get(derivedOres); int cnt = ea.getListenerCount(); // only add those with at least one current listener. Telling that a resource has no listeners is unneeded since we update // the whole table on each clusterInfoEvent (cluster:: could be improved by only sending the delta of listeners) if (cnt > 0) { bii.addEntry(derivedOres, cnt); } else { infocenter.remove(derivedOres); } } infocenterKeys = null; // for all types: the name of the type is used as key List<String> typeInfocenterKeys = new ArrayList<>(typeInfocenter.keySet()); for (String derivedOres: typeInfocenterKeys) { EventAgency ea = typeInfocenter.get(derivedOres); int cnt = ea.getListenerCount(); if (cnt > 0) { bii.addEntry(derivedOres, cnt); } else { typeInfocenter.remove(derivedOres); } } } return bii; } protected final int getLocalListeningIdentityCntFor(OLATResourceable ores) { int cnt = 0; final Long oresId = ores.getResourceableId(); final String typeName = ores.getResourceableTypeName(); synchronized (infocenter) { EventAgency ea = null; if (oresId == null) { ea = typeInfocenter.get(typeName); } else { // type and id String oresStr = typeName + "::" + oresId; ea = infocenter.get(oresStr); } if (ea != null) { cnt = ea.getListenerCount(); } } return cnt; } /** * Description: <br> * The listeners map is not synchronized, but need to be. The synchronization * is done by the infocenter map. Make sure that you always access instances of * this class in a synchronized(infocenter). * * @author Felix Jost * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ private static class EventAgency { private WeakHashMap<GenericEventListener, String> listeners = new WeakHashMap<GenericEventListener, String>(); /** * @param event */ GenericEventListener[] getListeners() { return listeners.keySet().toArray(new GenericEventListener[listeners.size()]); } /** * impl note: the underlying impl takes a weakHashMap, so unused entries are * cleared. an instance may not be added twice (make no sense anyway), since we * are using a map, not a list. * * @param gel the instance which wants to listen to events. * @param identity the identity belonging to the listener, or null if there is * none (e.g. the LockManager = the 'System') */ void addListener(GenericEventListener gel, Identity identity) { if (!listeners.containsKey(gel)) { String identityName = (identity != null? identity.getName() : null); listeners.put(gel, identityName); } } /** * @param gel */ void removeListener(GenericEventListener gel) { listeners.remove(gel); } /** * * @return the current number of listeners listening to this channel/eventagency */ int getListenerCount() { return listeners.size(); } } }