/* * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.jndi.ldap; import java.util.Hashtable; import java.util.Vector; import java.util.EventObject; import javax.naming.*; import javax.naming.event.*; import javax.naming.directory.SearchControls; import javax.naming.ldap.UnsolicitedNotificationListener; import javax.naming.ldap.UnsolicitedNotificationEvent; import javax.naming.ldap.UnsolicitedNotification; /** * This is a utility class that can be used by a context that supports * event notification. You can use an instance of this class as a member field * of your context and delegate various work to it. * It is currently structured so that each context should have its own * EventSupport (instead of static version shared by all contexts * of a service provider). *

* This class supports two types of listeners: those that register for * NamingEvents, and those for UnsolicitedNotificationEvents (they can be mixed * into the same listener). * For NamingEvent listeners, it maintains a hashtable that maps * registration requests--the key--to * notifiers--the value. Each registration request consists of: *

*

*A notifier ({@code NamingEventNotifier}) is a worker thread that is responsible *for gathering information for generating events requested by its listeners. *Each notifier maintains its own list of listeners; these listeners have *all made the same registration request (at different times) and implements *the same {@code NamingListener} interfaces. *

*For unsolicited listeners, this class maintains a vector, unsolicited. *When an unsolicited listener is registered, this class adds itself *to the context's LdapClient. When LdapClient receives an unsolicited *notification, it notifies this EventSupport to fire an event to the *the listeners. Special handling in LdapClient is done for the DISCONNECT *notification. [It results in the EventSupport firing also a *NamingExceptionEvent to the unsolicited listeners.] *

* *When a context no longer needs this EventSupport, it should invoke *cleanup() on it. *

*

Registration

*When a registration request is made, this class attempts to find an *existing notifier that's already working on the request. If one is *found, the listener is added to the notifier's list. If one is not found, *a new notifier is created for the listener. * *

Deregistration

*When a deregistration request is made, this class attempts to find its *corresponding notifier. If the notifier is found, the listener is removed *from the notifier's list. If the listener is the last listener on the list, *the notifier's thread is terminated and removed from this class's hashtable. *Nothing happens if the notifier is not found. * *

Event Dispatching

*The notifiers are responsible for gather information for generating events *requested by their respective listeners. When a notifier gets sufficient *information to generate an event, it creates invokes the *appropriate {@code fireXXXEvent} on this class with the information and list of *listeners. This causes an event and the list of listeners to be added *to the event queue. *This class maintains an event queue and a dispatching thread that dequeues *events from the queue and dispatches them to the listeners. * *

Synchronization

*This class is used by the main thread (LdapCtx) to add/remove listeners. *It is also used asynchronously by NamingEventNotifiers threads and *the context's Connection thread. It is used by the notifier threads to *queue events and to update the notifiers list when the notifiers exit. *It is used by the Connection thread to fire unsolicited notifications. *Methods that access/update the 'unsolicited' and 'notifiers' lists are *thread-safe. * * @author Rosanna Lee */ final class EventSupport { final static private boolean debug = false; private LdapCtx ctx; /** * NamingEventNotifiers; hashed by search arguments; */ private Hashtable notifiers = new Hashtable<>(11); /** * List of unsolicited notification listeners. */ private Vector unsolicited = null; /** * Constructs EventSupport for ctx. * Do we need to record the name of the target context? * Or can we assume that EventSupport is called on a resolved * context? Do we need other add/remove-NamingListener methods? * package private; */ EventSupport(LdapCtx ctx) { this.ctx = ctx; } /** * Adds {@code l} to list of listeners interested in {@code nm}. */ /* * Make the add/removeNamingListeners synchronized to: * 1. protect usage of 'unsolicited', which may be read by * the Connection thread when dispatching unsolicited notification. * 2. ensure that NamingEventNotifier thread's access to 'notifiers' * is safe */ synchronized void addNamingListener(String nm, int scope, NamingListener l) throws NamingException { if (l instanceof ObjectChangeListener || l instanceof NamespaceChangeListener) { NotifierArgs args = new NotifierArgs(nm, scope, l); NamingEventNotifier notifier = notifiers.get(args); if (notifier == null) { notifier = new NamingEventNotifier(this, ctx, args, l); notifiers.put(args, notifier); } else { notifier.addNamingListener(l); } } if (l instanceof UnsolicitedNotificationListener) { // Add listener to this's list of unsolicited notifiers if (unsolicited == null) { unsolicited = new Vector<>(3); } unsolicited.addElement((UnsolicitedNotificationListener)l); } } /** * Adds {@code l} to list of listeners interested in {@code nm} * and filter. */ synchronized void addNamingListener(String nm, String filter, SearchControls ctls, NamingListener l) throws NamingException { if (l instanceof ObjectChangeListener || l instanceof NamespaceChangeListener) { NotifierArgs args = new NotifierArgs(nm, filter, ctls, l); NamingEventNotifier notifier = notifiers.get(args); if (notifier == null) { notifier = new NamingEventNotifier(this, ctx, args, l); notifiers.put(args, notifier); } else { notifier.addNamingListener(l); } } if (l instanceof UnsolicitedNotificationListener) { // Add listener to this's list of unsolicited notifiers if (unsolicited == null) { unsolicited = new Vector<>(3); } unsolicited.addElement((UnsolicitedNotificationListener)l); } } /** * Removes {@code l} from all notifiers in this context. */ synchronized void removeNamingListener(NamingListener l) { if (debug) System.err.println("EventSupport removing listener"); // Go through list of notifiers, remove 'l' from each. // If 'l' is notifier's only listener, remove notifier too. for (NamingEventNotifier notifier : notifiers.values()) { if (notifier != null) { if (debug) System.err.println("EventSupport removing listener from notifier"); notifier.removeNamingListener(l); if (!notifier.hasNamingListeners()) { if (debug) System.err.println("EventSupport stopping notifier"); notifier.stop(); notifiers.remove(notifier.info); } } } // Remove from list of unsolicited notifier if (debug) System.err.println("EventSupport removing unsolicited: " + unsolicited); if (unsolicited != null) { unsolicited.removeElement(l); } } synchronized boolean hasUnsolicited() { return (unsolicited != null && unsolicited.size() > 0); } /** * package private; * Called by NamingEventNotifier to remove itself when it encounters * a NamingException. */ synchronized void removeDeadNotifier(NotifierArgs info) { if (debug) { System.err.println("EventSupport.removeDeadNotifier: " + info.name); } notifiers.remove(info); } /** * Fire an event to unsolicited listeners. * package private; * Called by LdapCtx when its clnt receives an unsolicited notification. */ synchronized void fireUnsolicited(Object obj) { if (debug) { System.err.println("EventSupport.fireUnsolicited: " + obj + " " + unsolicited); } if (unsolicited == null || unsolicited.size() == 0) { // This shouldn't really happen, but might in case // there is a timing problem that removes a listener // before a fired event reaches here. return; } if (obj instanceof UnsolicitedNotification) { // Fire UnsolicitedNotification to unsolicited listeners UnsolicitedNotificationEvent evt = new UnsolicitedNotificationEvent(ctx, (UnsolicitedNotification)obj); queueEvent(evt, unsolicited); } else if (obj instanceof NamingException) { // Fire NamingExceptionEvent to unsolicited listeners. NamingExceptionEvent evt = new NamingExceptionEvent(ctx, (NamingException)obj); queueEvent(evt, unsolicited); // When an exception occurs, the unsolicited listeners // are automatically deregistered. // When LdapClient.processUnsolicited() fires a NamingException, // it will update its listener list so we don't have to. // Likewise for LdapCtx. unsolicited = null; } } /** * Stops notifier threads that are collecting event data and * stops the event queue from dispatching events. * Package private; used by LdapCtx. */ synchronized void cleanup() { if (debug) System.err.println("EventSupport clean up"); if (notifiers != null) { for (NamingEventNotifier notifier : notifiers.values()) { notifier.stop(); } notifiers = null; } if (eventQueue != null) { eventQueue.stop(); eventQueue = null; } // %%% Should we fire NamingExceptionEvents to unsolicited listeners? } /* * The queue of events to be delivered. */ private EventQueue eventQueue; /** * Add the event and vector of listeners to the queue to be delivered. * An event dispatcher thread dequeues events from the queue and dispatches * them to the registered listeners. * Package private; used by NamingEventNotifier to fire events */ synchronized void queueEvent(EventObject event, Vector vector) { if (eventQueue == null) eventQueue = new EventQueue(); /* * Copy the vector in order to freeze the state of the set * of EventListeners the event should be delivered to prior * to delivery. This ensures that any changes made to the * Vector from a target listener's method during the delivery * of this event will not take effect until after the event is * delivered. */ @SuppressWarnings("unchecked") // clone() Vector v = (Vector)vector.clone(); eventQueue.enqueue(event, v); } // No finalize() needed because EventSupport is always owned by // an LdapCtx. LdapCtx's finalize() and close() always call cleanup() so // there is no need for EventSupport to have a finalize(). }