1 /*
2  * Copyright (c) 1999, 2011, 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 
26 package com.sun.jndi.ldap;
27 
28 import javax.naming.*;
29 import javax.naming.directory.*;
30 import javax.naming.event.*;
31 import javax.naming.ldap.*;
32 import javax.naming.ldap.LdapName;
33 
34 import java.util.Vector;
35 import com.sun.jndi.toolkit.ctx.Continuation;
36 
37 /**
38   * Gathers information to generate events by using the Persistent Search
39   * control.
40   *<p>
41   * This class maintains a list of listeners all interested in the same
42   * "search" request. It creates a thread that does the persistent search
43   * and blocks, collecting the results of the search.
44   * For each result that it receives from the search, it fires the
45   * corresponding event to its listeners. If an exception is encountered,
46   * it fires a NamingExceptionEvent.
47   *
48   * @author Rosanna Lee
49   */
50 final class NamingEventNotifier implements Runnable {
51     private final static boolean debug = false;
52 
53     private Vector<NamingListener> namingListeners;
54     private Thread worker;
55     private LdapCtx context;
56     private EventContext eventSrc;
57     private EventSupport support;
58     private NamingEnumeration<SearchResult> results;
59 
60     // package private; used by EventSupport to remove it
61     NotifierArgs info;
62 
NamingEventNotifier(EventSupport support, LdapCtx ctx, NotifierArgs info, NamingListener firstListener)63     NamingEventNotifier(EventSupport support, LdapCtx ctx, NotifierArgs info,
64         NamingListener firstListener) throws NamingException {
65         this.info = info;
66         this.support = support;
67 
68         Control psearch;
69         try {
70             psearch = new PersistentSearchControl(
71                 info.mask,
72                 true /* no info about original entry(s) */,
73                 true /* additional info about changes */,
74                 Control.CRITICAL);
75         } catch (java.io.IOException e) {
76             NamingException ne = new NamingException(
77                 "Problem creating persistent search control");
78             ne.setRootCause(e);
79             throw ne;
80         }
81 
82         // Add psearch control to existing list
83         context = (LdapCtx)ctx.newInstance(new Control[]{psearch});
84         eventSrc = ctx;
85 
86         namingListeners = new Vector<>();
87         namingListeners.addElement(firstListener);
88 
89         worker = Obj.helper.createThread(this);
90         worker.setDaemon(true);  // not a user thread
91         worker.start();
92     }
93 
94     // package private; used by EventSupport; namingListener already synchronized
addNamingListener(NamingListener l)95     void addNamingListener(NamingListener l) {
96         namingListeners.addElement(l);
97     }
98 
99     // package private; used by EventSupport; namingListener already synchronized
removeNamingListener(NamingListener l)100     void removeNamingListener(NamingListener l) {
101         namingListeners.removeElement(l);
102     }
103 
104     // package private; used by EventSupport; namingListener already synchronized
hasNamingListeners()105     boolean hasNamingListeners() {
106         return namingListeners.size() > 0;
107     }
108 
109     /**
110      * Execute "persistent search".
111      * For each result, create the appropriate NamingEvent and
112      * queue to be dispatched to listeners.
113      */
run()114     public void run() {
115         try {
116             Continuation cont = new Continuation();
117             cont.setError(this, info.name);
118             Name nm = (info.name == null || info.name.equals("")) ?
119                 new CompositeName() : new CompositeName().add(info.name);
120 
121             results = context.searchAux(nm, info.filter, info.controls,
122                 true, false, cont);
123 
124             // Change root of search results so that it will generate
125             // names relative to the event context instead of that
126             // named by nm
127             ((LdapSearchEnumeration)(NamingEnumeration)results)
128                     .setStartName(context.currentParsedDN);
129 
130             SearchResult si;
131             Control[] respctls;
132             EntryChangeResponseControl ec;
133             long changeNum;
134 
135             while (results.hasMore()) {
136                 si = results.next();
137                 respctls = (si instanceof HasControls) ?
138                     ((HasControls) si).getControls() : null;
139 
140                 if (debug) {
141                     System.err.println("notifier: " + si);
142                     System.err.println("respCtls: " + respctls);
143                 }
144 
145                 // Just process ECs; ignore all the rest
146                 if (respctls != null) {
147                     for (int i = 0; i < respctls.length; i++) {
148                         // %%% Should be checking OID instead of class
149                         // %%% in case using someone else's  EC ctl
150                         if (respctls[i] instanceof EntryChangeResponseControl) {
151                             ec = (EntryChangeResponseControl)respctls[i];
152                             changeNum = ec.getChangeNumber();
153                             switch (ec.getChangeType()) {
154                             case EntryChangeResponseControl.ADD:
155                                 fireObjectAdded(si, changeNum);
156                                 break;
157                             case EntryChangeResponseControl.DELETE:
158                                 fireObjectRemoved(si, changeNum);
159                                 break;
160                             case EntryChangeResponseControl.MODIFY:
161                                 fireObjectChanged(si, changeNum);
162                                 break;
163                             case EntryChangeResponseControl.RENAME:
164                                 fireObjectRenamed(si, ec.getPreviousDN(),
165                                     changeNum);
166                                 break;
167                             }
168                         }
169                         break;
170                     }
171                 }
172             }
173         } catch (InterruptedNamingException e) {
174             if (debug) System.err.println("NamingEventNotifier Interrupted");
175         } catch (NamingException e) {
176             // Fire event to notify NamingExceptionEvent listeners
177             fireNamingException(e);
178 
179             // This notifier is no longer valid
180             support.removeDeadNotifier(info);
181         } finally {
182             cleanup();
183         }
184         if (debug) System.err.println("NamingEventNotifier finished");
185     }
186 
cleanup()187     private void cleanup() {
188         if (debug) System.err.println("NamingEventNotifier cleanup");
189 
190         try {
191             if (results != null) {
192                 if (debug) System.err.println("NamingEventNotifier enum closing");
193                 results.close(); // this will abandon the search
194                 results = null;
195             }
196             if (context != null) {
197                 if (debug) System.err.println("NamingEventNotifier ctx closing");
198                 context.close();
199                 context = null;
200             }
201         } catch (NamingException e) {}
202     }
203 
204     /**
205      * Stop the dispatcher so we can be destroyed.
206      * package private; used by EventSupport
207      */
stop()208     void stop() {
209         if (debug) System.err.println("NamingEventNotifier being stopping");
210         if (worker != null) {
211             worker.interrupt(); // kill our thread
212             worker = null;
213         }
214     }
215 
216     /**
217      * Fire an "object added" event to registered NamingListeners.
218      */
fireObjectAdded(Binding newBd, long changeID)219     private void fireObjectAdded(Binding newBd, long changeID) {
220         if (namingListeners == null || namingListeners.size() == 0)
221             return;
222 
223         NamingEvent e = new NamingEvent(eventSrc, NamingEvent.OBJECT_ADDED,
224             newBd, null, changeID);
225         support.queueEvent(e, namingListeners);
226     }
227 
228     /**
229      * Fire an "object removed" event to registered NamingListeners.
230      */
fireObjectRemoved(Binding oldBd, long changeID)231     private void fireObjectRemoved(Binding oldBd, long changeID) {
232         if (namingListeners == null || namingListeners.size() == 0)
233             return;
234 
235         NamingEvent e = new NamingEvent(eventSrc, NamingEvent.OBJECT_REMOVED,
236             null, oldBd, changeID);
237         support.queueEvent(e, namingListeners);
238     }
239 
240     /**
241      * Fires an "object changed" event to registered NamingListeners.
242      */
fireObjectChanged(Binding newBd, long changeID)243     private void fireObjectChanged(Binding newBd, long changeID) {
244         if (namingListeners == null || namingListeners.size() == 0)
245             return;
246 
247         // Name hasn't changed; construct old binding using name from new binding
248         Binding oldBd = new Binding(newBd.getName(), null, newBd.isRelative());
249 
250         NamingEvent e = new NamingEvent(
251             eventSrc, NamingEvent.OBJECT_CHANGED, newBd, oldBd, changeID);
252         support.queueEvent(e, namingListeners);
253     }
254 
255     /**
256      * Fires an "object renamed" to registered NamingListeners.
257      */
fireObjectRenamed(Binding newBd, String oldDN, long changeID)258     private void fireObjectRenamed(Binding newBd, String oldDN, long changeID) {
259         if (namingListeners == null || namingListeners.size() == 0)
260             return;
261 
262         Binding oldBd = null;
263         try {
264             LdapName dn = new LdapName(oldDN);
265             if (dn.startsWith(context.currentParsedDN)) {
266                 String relDN = dn.getSuffix(context.currentParsedDN.size()).toString();
267                 oldBd = new Binding(relDN, null);
268             }
269         } catch (NamingException e) {}
270 
271         if (oldBd == null) {
272             oldBd = new Binding(oldDN, null, false /* not relative name */);
273         }
274 
275         NamingEvent e = new NamingEvent(
276             eventSrc, NamingEvent.OBJECT_RENAMED, newBd, oldBd, changeID);
277         support.queueEvent(e, namingListeners);
278     }
279 
fireNamingException(NamingException e)280     private void fireNamingException(NamingException e) {
281         if (namingListeners == null || namingListeners.size() == 0)
282             return;
283 
284         NamingExceptionEvent evt = new NamingExceptionEvent(eventSrc, e);
285         support.queueEvent(evt, namingListeners);
286     }
287 }
288