| /* |
| * 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). |
| *<p> |
| * 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 |
| * <em>notifiers</em>--the value. Each registration request consists of: |
| *<ul> |
| *<li>The name argument of the registration. |
| *<li>The filter (default is "(objectclass=*)"). |
| *<li>The search controls (default is null SearchControls). |
| *<li>The events that the listener is interested in. This is determined by |
| * finding out which <tt>NamingListener</tt> interface the listener supports. |
| *</ul> |
| *<p> |
| *A notifier (<tt>NamingEventNotifier</tt>) 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 <tt>NamingListener</tt> interfaces. |
| *<p> |
| *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.] |
| *<p> |
| * |
| *When a context no longer needs this EventSupport, it should invoke |
| *cleanup() on it. |
| *<p> |
| *<h4>Registration</h4> |
| *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. |
| * |
| *<h4>Deregistration</h4> |
| *When a deregistration request is made, this class attemps 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. |
| * |
| *<h4>Event Dispatching</h4> |
| *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 <tt>fireXXXEvent</tt> on this class with the information and list of |
| *listeners. This causes an event and the list of listeners to be added |
| *to the <em>event queue</em>. |
| *This class maintains an event queue and a dispatching thread that dequeues |
| *events from the queue and dispatches them to the listeners. |
| * |
| *<h4>Synchronization</h4> |
| *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<NotifierArgs, NamingEventNotifier> notifiers = |
| new Hashtable<>(11); |
| |
| /** |
| * List of unsolicited notification listeners. |
| */ |
| private Vector<UnsolicitedNotificationListener> unsolicited = null; |
| |
| /** |
| * Constructs EventSupport for ctx. |
| * <em>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 <tt>l</tt> to list of listeners interested in <tt>nm</tt>. |
| */ |
| /* |
| * 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 <tt>l</tt> to list of listeners interested in <tt>nm</tt> |
| * 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 <tt>l</tt> 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 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<? extends NamingListener> 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<NamingListener> v = |
| (Vector<NamingListener>)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(). |
| } |