View Javadoc
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  
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
95      void addNamingListener(NamingListener l) {
96          namingListeners.addElement(l);
97      }
98  
99      // package private; used by EventSupport; namingListener already synchronized
100     void removeNamingListener(NamingListener l) {
101         namingListeners.removeElement(l);
102     }
103 
104     // package private; used by EventSupport; namingListener already synchronized
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      */
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 
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      */
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      */
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, new Long(changeID));
225         support.queueEvent(e, namingListeners);
226     }
227 
228     /**
229      * Fire an "object removed" event to registered NamingListeners.
230      */
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, new Long(changeID));
237         support.queueEvent(e, namingListeners);
238     }
239 
240     /**
241      * Fires an "object changed" event to registered NamingListeners.
242      */
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, new Long(changeID));
252         support.queueEvent(e, namingListeners);
253     }
254 
255     /**
256      * Fires an "object renamed" to registered NamingListeners.
257      */
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, new Long(changeID));
277         support.queueEvent(e, namingListeners);
278     }
279 
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 }