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, 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, 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, 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, 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 }