1/*
2 * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.jndi.ldap;
27
28import java.util.Hashtable;
29import java.util.Vector;
30import java.util.EventObject;
31import java.util.Iterator;
32import java.util.Map;
33
34import javax.naming.*;
35import javax.naming.event.*;
36import javax.naming.directory.SearchControls;
37import javax.naming.ldap.UnsolicitedNotificationListener;
38import javax.naming.ldap.UnsolicitedNotificationEvent;
39import javax.naming.ldap.UnsolicitedNotification;
40
41/**
42 * This is a utility class that can be used by a context that supports
43 * event notification.  You can use an instance of this class as a member field
44 * of your context and delegate various work to it.
45 * It is currently structured so that each context should have its own
46 * EventSupport (instead of static version shared by all contexts
47 * of a service provider).
48 *<p>
49 * This class supports two types of listeners: those that register for
50 * NamingEvents, and those for UnsolicitedNotificationEvents (they can be mixed
51 * into the same listener).
52 * For NamingEvent listeners, it maintains a hashtable that maps
53 * registration requests--the key--to
54 * <em>notifiers</em>--the value. Each registration request consists of:
55 *<ul>
56 *<li>The name argument of the registration.
57 *<li>The filter (default is "(objectclass=*)").
58 *<li>The search controls (default is null SearchControls).
59 *<li>The events that the listener is interested in. This is determined by
60 * finding out which {@code NamingListener} interface the listener supports.
61 *</ul>
62 *<p>
63 *A notifier ({@code NamingEventNotifier}) is a worker thread that is responsible
64 *for gathering information for generating events requested by its listeners.
65 *Each notifier maintains its own list of listeners; these listeners have
66 *all made the same registration request (at different times) and implements
67 *the same {@code NamingListener} interfaces.
68 *<p>
69 *For unsolicited listeners, this class maintains a vector, unsolicited.
70 *When an unsolicited listener is registered, this class adds itself
71 *to the context's LdapClient. When LdapClient receives an unsolicited
72 *notification, it notifies this EventSupport to fire an event to the
73 *the listeners. Special handling in LdapClient is done for the DISCONNECT
74 *notification. [It results in the EventSupport firing also a
75 *NamingExceptionEvent to the unsolicited listeners.]
76 *<p>
77 *
78 *When a context no longer needs this EventSupport, it should invoke
79 *cleanup() on it.
80 *<p>
81 *<h4>Registration</h4>
82 *When a registration request is made, this class attempts to find an
83 *existing notifier that's already working on the request. If one is
84 *found, the listener is added to the notifier's list. If one is not found,
85 *a new notifier is created for the listener.
86 *
87 *<h4>Deregistration</h4>
88 *When a deregistration request is made, this class attempts to find its
89 *corresponding notifier. If the notifier is found, the listener is removed
90 *from the notifier's list. If the listener is the last listener on the list,
91 *the notifier's thread is terminated and removed from this class's hashtable.
92 *Nothing happens if the notifier is not found.
93 *
94 *<h4>Event Dispatching</h4>
95 *The notifiers are responsible for gather information for generating events
96 *requested by their respective listeners. When a notifier gets sufficient
97 *information to generate an event, it creates invokes the
98 *appropriate {@code fireXXXEvent} on this class with the information and list of
99 *listeners. This causes an event and the list of listeners to be added
100 *to the <em>event queue</em>.
101 *This class maintains an event queue and a dispatching thread that dequeues
102 *events from the queue and dispatches them to the listeners.
103 *
104 *<h4>Synchronization</h4>
105 *This class is used by the main thread (LdapCtx) to add/remove listeners.
106 *It is also used asynchronously by NamingEventNotifiers threads and
107 *the context's Connection thread. It is used by the notifier threads to
108 *queue events and to update the notifiers list when the notifiers exit.
109 *It is used by the Connection thread to fire unsolicited notifications.
110 *Methods that access/update the 'unsolicited' and 'notifiers' lists are
111 *thread-safe.
112 *
113 * @author Rosanna Lee
114 */
115final class EventSupport {
116    final static private boolean debug = false;
117
118    private LdapCtx ctx;
119
120    /**
121     * NamingEventNotifiers; hashed by search arguments;
122     */
123    private Hashtable<NotifierArgs, NamingEventNotifier> notifiers =
124            new Hashtable<>(11);
125
126    /**
127     * List of unsolicited notification listeners.
128     */
129    private Vector<UnsolicitedNotificationListener> unsolicited = null;
130
131    /**
132     * Constructs EventSupport for ctx.
133     * <em>Do we need to record the name of the target context?
134     * Or can we assume that EventSupport is called on a resolved
135     * context? Do we need other add/remove-NamingListener methods?
136     * package private;
137     */
138    EventSupport(LdapCtx ctx) {
139        this.ctx = ctx;
140    }
141
142    /**
143     * Adds {@code l} to list of listeners interested in {@code nm}.
144     */
145    /*
146     * Make the add/removeNamingListeners synchronized to:
147     * 1. protect usage of 'unsolicited', which may be read by
148     *    the Connection thread when dispatching unsolicited notification.
149     * 2. ensure that NamingEventNotifier thread's access to 'notifiers'
150     *    is safe
151     */
152    synchronized void addNamingListener(String nm, int scope,
153        NamingListener l) throws NamingException {
154
155        if (l instanceof ObjectChangeListener ||
156            l instanceof NamespaceChangeListener) {
157            NotifierArgs args = new NotifierArgs(nm, scope, l);
158
159            NamingEventNotifier notifier = notifiers.get(args);
160            if (notifier == null) {
161                notifier = new NamingEventNotifier(this, ctx, args, l);
162                notifiers.put(args, notifier);
163            } else {
164                notifier.addNamingListener(l);
165            }
166        }
167        if (l instanceof UnsolicitedNotificationListener) {
168            // Add listener to this's list of unsolicited notifiers
169            if (unsolicited == null) {
170                unsolicited = new Vector<>(3);
171            }
172
173            unsolicited.addElement((UnsolicitedNotificationListener)l);
174        }
175    }
176
177    /**
178     * Adds {@code l} to list of listeners interested in {@code nm}
179     * and filter.
180     */
181    synchronized void addNamingListener(String nm, String filter,
182        SearchControls ctls, NamingListener l) throws NamingException {
183
184        if (l instanceof ObjectChangeListener ||
185            l instanceof NamespaceChangeListener) {
186            NotifierArgs args = new NotifierArgs(nm, filter, ctls, l);
187
188            NamingEventNotifier notifier = notifiers.get(args);
189            if (notifier == null) {
190                notifier = new NamingEventNotifier(this, ctx, args, l);
191                notifiers.put(args, notifier);
192            } else {
193                notifier.addNamingListener(l);
194            }
195        }
196        if (l instanceof UnsolicitedNotificationListener) {
197            // Add listener to this's list of unsolicited notifiers
198            if (unsolicited == null) {
199                unsolicited = new Vector<>(3);
200            }
201            unsolicited.addElement((UnsolicitedNotificationListener)l);
202        }
203    }
204
205    /**
206     * Removes {@code l} from all notifiers in this context.
207     */
208    synchronized void removeNamingListener(NamingListener l) {
209        if (debug) {
210            System.err.println("EventSupport removing listener");
211        }
212        // Go through list of notifiers, remove 'l' from each.
213        // If 'l' is notifier's only listener, remove notifier too.
214        Iterator<NamingEventNotifier> iterator = notifiers.values().iterator();
215        while (iterator.hasNext()) {
216            NamingEventNotifier notifier = iterator.next();
217            if (notifier != null) {
218                if (debug) {
219                    System.err.println("EventSupport removing listener from notifier");
220                }
221                notifier.removeNamingListener(l);
222                if (!notifier.hasNamingListeners()) {
223                    if (debug) {
224                        System.err.println("EventSupport stopping notifier");
225                    }
226                    notifier.stop();
227                    iterator.remove();
228                }
229            }
230        }
231        // Remove from list of unsolicited notifier
232        if (debug) {
233            System.err.println("EventSupport removing unsolicited: " + unsolicited);
234        }
235        if (unsolicited != null) {
236            unsolicited.removeElement(l);
237        }
238    }
239
240    synchronized boolean hasUnsolicited() {
241        return (unsolicited != null && unsolicited.size() > 0);
242    }
243
244    /**
245      * package private;
246      * Called by NamingEventNotifier to remove itself when it encounters
247      * a NamingException.
248      */
249    synchronized void removeDeadNotifier(NotifierArgs info) {
250        if (debug) {
251            System.err.println("EventSupport.removeDeadNotifier: " + info.name);
252        }
253        notifiers.remove(info);
254    }
255
256    /**
257     * Fire an event to unsolicited listeners.
258     * package private;
259     * Called by LdapCtx when its clnt receives an unsolicited notification.
260     */
261    synchronized void fireUnsolicited(Object obj) {
262        if (debug) {
263            System.err.println("EventSupport.fireUnsolicited: " + obj + " "
264                + unsolicited);
265        }
266        if (unsolicited == null || unsolicited.size() == 0) {
267            // This shouldn't really happen, but might in case
268            // there is a timing problem that removes a listener
269            // before a fired event reaches here.
270            return;
271        }
272
273        if (obj instanceof UnsolicitedNotification) {
274
275            // Fire UnsolicitedNotification to unsolicited listeners
276
277            UnsolicitedNotificationEvent evt =
278                new UnsolicitedNotificationEvent(ctx, (UnsolicitedNotification)obj);
279            queueEvent(evt, unsolicited);
280
281        } else if (obj instanceof NamingException) {
282
283            // Fire NamingExceptionEvent to unsolicited listeners.
284
285            NamingExceptionEvent evt =
286                new NamingExceptionEvent(ctx, (NamingException)obj);
287            queueEvent(evt, unsolicited);
288
289            // When an exception occurs, the unsolicited listeners
290            // are automatically deregistered.
291            // When LdapClient.processUnsolicited() fires a NamingException,
292            // it will update its listener list so we don't have to.
293            // Likewise for LdapCtx.
294
295            unsolicited = null;
296        }
297    }
298
299    /**
300     * Stops notifier threads that are collecting event data and
301     * stops the event queue from dispatching events.
302     * Package private; used by LdapCtx.
303     */
304    synchronized void cleanup() {
305        if (debug) System.err.println("EventSupport clean up");
306        if (notifiers != null) {
307            for (NamingEventNotifier notifier : notifiers.values()) {
308                notifier.stop();
309            }
310            notifiers = null;
311        }
312        if (eventQueue != null) {
313            eventQueue.stop();
314            eventQueue = null;
315        }
316        // %%% Should we fire NamingExceptionEvents to unsolicited listeners?
317    }
318
319    /*
320     * The queue of events to be delivered.
321     */
322    private EventQueue eventQueue;
323
324    /**
325     * Add the event and vector of listeners to the queue to be delivered.
326     * An event dispatcher thread dequeues events from the queue and dispatches
327     * them to the registered listeners.
328     * Package private; used by NamingEventNotifier to fire events
329     */
330    synchronized void queueEvent(EventObject event,
331                                 Vector<? extends NamingListener> vector) {
332        if (eventQueue == null)
333            eventQueue = new EventQueue();
334
335        /*
336         * Copy the vector in order to freeze the state of the set
337         * of EventListeners the event should be delivered to prior
338         * to delivery.  This ensures that any changes made to the
339         * Vector from a target listener's method during the delivery
340         * of this event will not take effect until after the event is
341         * delivered.
342         */
343        @SuppressWarnings("unchecked") // clone()
344        Vector<NamingListener> v =
345                (Vector<NamingListener>)vector.clone();
346        eventQueue.enqueue(event, v);
347    }
348
349    // No finalize() needed because EventSupport is always owned by
350    // an LdapCtx. LdapCtx's finalize() and close() always call cleanup() so
351    // there is no need for EventSupport to have a finalize().
352}
353