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.isEmpty()) ?
 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 }