KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ca > directory > jxplorer > broker > JNDIBroker


1 package com.ca.directory.jxplorer.broker;
2
3 import javax.naming.*;
4 import javax.naming.directory.*;
5
6
7 import java.util.*;
8 import java.util.logging.Logger JavaDoc;
9 import java.util.logging.Level JavaDoc;
10 import java.io.*;
11
12 import com.ca.directory.jxplorer.*;
13 import com.ca.commons.naming.*;
14 import com.ca.commons.cbutil.*;
15 import com.ca.commons.jndi.*;
16
17
18
19
20 /**
21 * This utility class handles all the JNDIBroker LDAP calls, returning objects
22 * to calling classes and managing the connection.<p>
23 *
24 * Before examining this class make sure to examing the base Broker class thoroughly.
25 * The base Broker class takes user requests, and creates DataQuery objects. A
26 * separate thread takes these DataQuery objects and uses the methods of derived
27 * classes (such as this one) to do the actual grunt work.
28 */

29
30 public class JNDIBroker extends Broker
31 {
32 // private static final String DEFAULT_CTX = "com.sun.jndi.ldap.LdapCtxFactory";
33
private static final int SEARCHLIMIT = 0;
34     private static final int SEARCHTIMEOUT = 0;
35
36     /**
37      * Used as a parameter to unthreadedSearch, this specifies to only search
38      * the base object.
39      */

40
41     public static final int SEARCH_BASE_OBJECT = 0;
42
43     /**
44      * Used as a parameter to unthreadedSearch, this specifies to only search
45      * the next level from the current DN.
46      */

47
48     public static final int SEARCH_ONE_LEVEL = 1;
49
50     /**
51      * Used as a parameter to unthreadedSearch, this specifies to search
52      * the entire subtree from the current DN.
53      */

54
55     public static final int SEARCH_SUB_TREE = 2;
56
57     private DirContext ctx;
58 // private DirContext schemactx;
59
// private Attributes schemaOps;
60
private boolean tracing = false;
61     private boolean connectionError = true;
62
63
64     private Hashtable attributeNames;
65
66     private boolean quietMode = false; // suppress gui ops (esp. error msgs.)
67
private boolean errorWhileQuietFlag = false; // used when broker is in 'quiet gui mode'.
68

69     int limit = SEARCHLIMIT; // default number of results returned.
70
int timeout = SEARCHTIMEOUT; // default timeout.
71

72     static int threadID = 1; // debug identifier for thread tracking
73
static final boolean DEBUGTHREADS = false; // debug flag for threadiness
74

75     private CBGraphicsOps dirOps = null; // the low level directory operations class.
76
private SchemaOps schemaOps; // the low level schemaOps class
77

78     private HashSet specialObjectClasses; // OS390 hack
79

80     private final static Logger JavaDoc log = Logger.getLogger(JNDIBroker.class.getName());
81
82    /**
83     * Helper class for Broker, this encapsulates an ldap-like connection
84     * request that is placed on the Broker queue for eventual resolution.
85     * The Connection Request object is intended to be used once, and then
86     * discarded.
87     */

88
89     public class DataConnectionQuery extends DataQuery
90     {
91         public final ConnectionData conData;
92
93
94        /**
95         * Defines a request to open a connection to an LDAP (only) server.
96         * @param cData a data object containing connection information.
97         */

98
99         public DataConnectionQuery(ConnectionData cData)
100         {
101             super(DataQuery.EXTENDED);
102
103             conData = cData;
104
105             setExtendedData("version", String.valueOf(conData.version));
106             setExtendedData("url", conData.getURL());
107         }
108
109
110
111        /**
112         * Utility name translation method
113         */

114
115         public String JavaDoc getTypeString()
116         {
117             return super.getTypeString() + " Connection Request";
118         }
119     }
120
121
122
123    /**
124     * Constructor does nothing except create an env object ( 'connect()'
125     * is used to open a connection)
126     */

127
128     public JNDIBroker()
129     {
130         initSpecialObjectClasses();
131     }
132
133
134
135    /**
136     * Clones a JNDIBroker, using the same underlying directory connection,
137     * but clearing the data listener list, and having its own debug flags.
138     * Any StopMonitors however will need to be re-registered with the new
139     * Broker.
140     * @param cloneMe
141     */

142
143     public JNDIBroker(JNDIBroker cloneMe)
144     {
145         registerDirectoryConnection(cloneMe);
146     }
147
148
149
150    /**
151     * Resets a JNDIBroker, using the same underlying directory connection
152     * as the passed broker, but clearing the data listener list, and resetting debug flags.
153     * Any StopMonitors however will need to be re-registered.
154     * @param cloneMe
155     */

156
157     public void registerDirectoryConnection(JNDIBroker cloneMe)
158     {
159         ctx = cloneMe.ctx;
160 // schemaOps = cloneMe.schemaOps;
161
tracing = cloneMe.tracing;
162         connectionError = cloneMe.connectionError;
163         attributeNames = cloneMe.attributeNames;
164
165         limit = cloneMe.limit;
166         timeout = cloneMe.timeout;
167
168         dirOps = cloneMe.dirOps;
169         schemaOps = cloneMe.schemaOps;
170
171         specialObjectClasses = cloneMe.specialObjectClasses; // OS390 hack
172
}
173
174
175
176    /**
177     * Mitch/OS390 hack
178     */

179
180     protected void initSpecialObjectClasses()
181     {
182         String JavaDoc fileName = System.getProperty("user.dir") + File.separator + "specialocs.txt";
183         if (new File(fileName).exists())
184         {
185             try
186             {
187                 String JavaDoc text = CBUtility.readTextFile(new File(fileName));
188                 StringTokenizer st = new StringTokenizer(text);
189                 specialObjectClasses = new HashSet(10);
190                 while (st.hasMoreTokens())
191                 {
192                     String JavaDoc oc = st.nextToken();
193                     specialObjectClasses.add(oc);
194                 }
195             }
196             catch (Exception JavaDoc e)
197             {
198                 log.info("unable to obtain special object classes list:\n " + e.toString());
199                 specialObjectClasses = null;
200             }
201         }
202     }
203
204
205
206    /**
207     * Suppresses user notification of errors via GUI dialogs,
208     * and logs them instead. Necessary for large batch ops. like
209     * importing an ldif file.
210     * @param status
211     */

212
213     public void setGUIQuiet(boolean status)
214     {
215         quietMode = status;
216         dirOps.setQuietMode(status);
217
218         if (quietMode == false)
219             setQuietError(false); // clear quiet error flag.
220
}
221
222
223
224    /**
225     * Sets the quiet error flag status.
226     * @param status
227     */

228
229     public void setQuietError(boolean status)
230     {
231         errorWhileQuietFlag = status;
232     }
233
234
235
236    /**
237     * This returns whether one or more errors occured while the
238     * program was in 'quiet gui' (i.e. no error dialogs) mode.
239     * It does not return the actual error, since frequently there
240     * were many: the user should consult the log file.
241     * @return
242     */

243
244     public boolean getQuietError()
245     {
246         return (errorWhileQuietFlag || dirOps.errorWhileQuietFlag);
247     }
248
249
250    /**
251     * Sets ber tracing status. Set to true this generates a huge
252     * amount of comms. tracing info, <i>when the next connection is opened</i>.
253     * It doesn't seem possible to set it for an already open connection, so
254     * we no longer even try.
255     * @param traceStatus
256     */

257
258     public void setTracing(boolean traceStatus)
259     {
260         tracing = traceStatus;
261     }
262
263     /**
264      * Returns ber tracing status. When true this generates a huge
265      * amount of comms. tracing info, <i>when the next connection is opened</i>.
266      * It doesn't seem possible to set it for an already open connection, so
267      * we no longer even try.
268      * @return the traceStatus
269      */

270
271     public boolean getTracing() { return tracing; }
272
273    /**
274     * <p>Queues a request to open a connection to an LDAP (only) server.</p>
275     *
276     * <p>Note that some rarely modified connection status variables are set externally - e.g.
277     * BER tracing status (derived from the log level, set by setTracing() ),
278     * and the security keystore type and external security provider (if any)
279     * which are set in the config file).</p>
280     *
281     * @param baseDN the base DN from which to browse.
282     * @param version the LDAP Version (2 or 3) being used.
283     * @param host the LDAP server url.
284     * @param port the LDAP server port (default 389) being used.
285     * @param userDN the Manager User's DN - (is null if user is not manager)
286     * @param pwd the Manager User's password - (is null if user is not manager)
287     * @param referralType the jndi ldap referral type: [follow:ignore:throw]
288     * @param aliasType how aliases are handled: 'always'|'never'|'finding'|'searching'
289     * @param useSSL whether to use SSL for encryption and/or authentication (dependant on other parameters)
290     * @param cacerts path to a store of trusted server certificates or CA certificates - required for Server-auth ssl
291     * @param clientcerts path to client certificates - if available, will use for client authentication
292     * @param caKeystorePwd the password to the client's keystore (may be null for non-client authenticated ssl).
293     * @param clientKeystorePwd the password to the client certificates - required to use client certs for authentication
294     * @deprecated use connect(ConnectionData) instead.
295     * @return returns the thread that is used to make the connection
296     */

297
298     // nb capitalisation of 'cacerts' and 'clientcerts' wierd to match actual default file names.
299

300     public DataQuery connect(String JavaDoc baseDN, int version, String JavaDoc host,
301           int port, String JavaDoc userDN, char[] pwd,
302           String JavaDoc referralType, String JavaDoc aliasType, boolean useSSL,
303           String JavaDoc cacerts, String JavaDoc clientcerts,
304           char[] caKeystorePwd, char[] clientKeystorePwd)
305     {
306         ConnectionData cData = new ConnectionData();
307         cData.setURL(host,port);
308
309         cData.baseDN = baseDN;
310         cData.version = version;
311         cData.setURL(host, port);
312         cData.userDN = userDN;
313         cData.pwd = pwd;
314         cData.referralType = referralType;
315         cData.aliasType = aliasType;
316         cData.useSSL = useSSL;
317         cData.cacerts = cacerts;
318         cData.clientcerts = clientcerts;
319         cData.caKeystorePwd = caKeystorePwd;
320         cData.clientKeystorePwd = clientKeystorePwd;
321         cData.tracing = getTracing();
322         return connect(cData);
323     }
324
325
326
327    /**
328     * <p>Queues a request to open a connection to an LDAP (only) server.</p>
329     *
330     * <p>Note that some rarely modified connection status variables are set externally - e.g.
331     * BER tracing status (derived from the log level, set by setTracing() ),
332     * and the security keystore type and external security provider (if any)
333     * which are set in the config file).</p>
334     * @param cData data object containing all the connection information.
335     * @return returns the thread that is used to make the connection
336     */

337
338     public DataQuery connect(ConnectionData cData)
339     {
340         cData.caKeystoreType = JXplorer.getProperty("keystoreType.cacerts", "JKS");
341         cData.clientKeystoreType = JXplorer.getProperty("keystoreType.clientcerts", "JKS");
342
343         DataQuery openCon = new DataConnectionQuery(cData);
344
345         return push(openCon);
346     }
347
348
349
350    /**
351     * Extends the base class processRequest method to handle DataConnectionRequest objects.
352     * @param request the connection data query.
353     */

354
355     protected void processRequest(DataQuery request)
356     {
357         try
358         {
359             if (request instanceof DataConnectionQuery)
360                 openConnection((DataConnectionQuery) request);
361             else
362                 super.processRequest(request);
363         }
364         catch (Exception JavaDoc e)
365         {
366             request.setException(e);
367             e.printStackTrace();
368         }
369     }
370
371
372
373    /**
374     * Does the actual grunt work of opening a new connection.
375     * @param request a DataQuery object that contains the connection details.
376     * @return the data query object.
377     */

378
379     protected DataQuery openConnection(DataConnectionQuery request)
380     {
381         disconnect(); // clear out any existing cobwebs...
382

383         ConnectionData cData = request.conData;
384
385         String JavaDoc url = cData.url;
386
387         connectionError = false;
388
389         ctx = null; // null the current directory context (can't be used again).
390

391         // Try to get a directory context using above info.
392

393         try
394         {
395             dirOps = new CBGraphicsOps(cData); // this wraps up ctx for basic operations
396
ctx = dirOps.getContext();
397
398             if (ctx == null)
399                 throw new NamingException("unable to open connection: unknown condition, no error returned.");
400
401             // make a bogus, fast, directory request to trigger some activity on the context. Without this the
402
// context may *appear* to be open since jndi sometimes won't actually try to use it until a request is made
403
// (e.g. with DSML, SSL connections etc.)
404

405             String JavaDoc base = (request.conData.baseDN==null)?"":request.conData.baseDN;
406
407             //XXX bogus request failing - why??? (ans SASL error in jdk 1.4.0 - 1.4.1)
408
//ctx.search(base, "objectClass=*", new SearchControls(SearchControls.OBJECT_SCOPE, 0, 0, new String[]{}, false, false));
409
if (dirOps.exists(base) == false)
410                 cData.baseDN = getActualDN(cData.baseDN); //TE: (for bug 2363) - try to solve case sensitive DN problem.
411

412             // At this stage we should have a valid ldap context
413
}
414         // can throw NamingException, GeneralSecurityException, IOException
415
catch (Exception JavaDoc ne) // An error has occurred. Exit connection routine
416
{ // taking no further action.
417
log.warning("initial receipt of exception by jndi broker " + ne.getMessage());
418             ne.printStackTrace();
419             request.setException(ne);
420             request.setStatus(false);
421             request.finish();
422             return request;
423         }
424
425         try
426         {
427             schemaOps = new SchemaOps(ctx);
428         } // this wraps up ctx for basic operations
429
catch (NamingException e)
430         {
431             log.log(Level.WARNING, "unable to init schemaOps Ops ", e);
432             schemaOps = null;
433         }
434
435         if (schemaOps != null && (cData.protocol == ConnectionData.DSML || cData.version > 2)) // if ldap 3 or better try to open a schemaOps context
436
{
437             try
438             {
439                 String JavaDoc binaries = schemaOps.getNewBinaryAttributes();
440                 if (binaries.trim() != "")
441                     ctx.addToEnvironment("java.naming.ldap.attributes.binary", binaries);
442                 initAttributeNamesHash();
443             }
444             catch (NamingException e) // annoying, but not fatal, error.
445
{
446                 log.log(Level.WARNING, "Failed to connect to schemaOps: " + url, e);
447             }
448         }
449         else
450         {
451             log.info("skipped schemaOps stuff : " + cData.protocol);
452         }
453
454         if (cData.protocol.equalsIgnoreCase("dsml")) //TODO: some day we may support a different version of DSML. If so the version variable in ConnectionData should be called ldapVersion and a new variable called dsmlVersion added.
455
log.info("Successfully connected to " + url + " using " + cData.protocol + " version 2");
456         else
457             log.info("Successfully connected to " + url + " using " + cData.protocol + " version " + cData.version);
458
459         request.setStatus(true); // success!
460
request.finish();
461         return request;
462     }
463     
464     
465
466    /**
467     * Warning: ETrust Directory Specific Code! Currently unused.
468     *
469     * This is a bit of a hack to verify the base DN. It basically takes a DN and does a base object
470     * search and returns the DN of the results. This method is needed as a bit of a hack for when the
471     * the user puts in a base DN that is of a different case to what is stored in the dsa. If this happens
472     * the tree remembers the wrong DN and throws some errors when the user tries to modify the base DN
473     * entry (bug 2363). However, currently (20 June 02) the dsa gives the DN back the way it was put in. I've
474     * assigned the bug to them to fix...
475     * @param dn the DN to verify (or do the base object search on).
476     * @return the DN that the search returns (one day this will be what is actually stored in the dsa). If
477     * there is an exception thrown, the DN will be returned unchanged.
478     * .
479     */

480     //TE: NOTE: WAITING IN DSA CHANGE BEFORE THIS CAN BE USED...BUG 2363
481
public String JavaDoc getActualDN(String JavaDoc dn)
482     {
483         if(true)
484             return dn;
485             
486         try
487         {
488             NamingEnumeration en = dirOps.searchBaseEntry(new DN(dn), "objectClass=*",0,0, null);
489             String JavaDoc temp = "";
490             while (en.hasMoreElements())
491             {
492                 temp = (en.nextElement()).toString();
493                 temp = temp.substring(0, temp.indexOf(":"));
494             }
495             return temp;
496         }
497         catch(Exception JavaDoc e)
498         {
499             return dn;
500         }
501     }
502
503
504
505    /**
506     * The attributeNames hash provides a quick look up mapping
507     * between numericoids and attribute names. This initialises
508     * that hashtable.
509     */

510
511     // XXX probably neater ways of doing this now jndi 1.2 is out...
512
// TODO: ... and why is it being done here, and not in schema ops?
513
protected void initAttributeNamesHash()
514     {
515         attributeNames = new Hashtable(100);
516
517         /*
518          * XXX This tries to parse efficiently the syntax line ... not very happy with it though, it seems a bit ad-hoc.
519          * we may want to rewrite this more generically some time...
520          */

521         try
522         {
523             Attribute attDefs = schemaOps.getAttributeTypes();
524             if (attDefs==null)
525             {
526                 log.warning("unable to read schema attributes in JNDIBroker:initAttributeNamesHash");
527                 return;
528             }
529             StringTokenizer tokenizer;
530             for (int i=0; i<attDefs.size(); i++)
531             {
532                 String JavaDoc parseMe = attDefs.get(i).toString(); // response something like: ( 2.5.4.4 NAME ( 'sn' 'surname' ) SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
533
int namePos = parseMe.indexOf("NAME");
534                 if (namePos == -1)
535                     throw new NamingException("unable to parse ldap syntax '" + parseMe + "'");
536                 String JavaDoc oid = parseMe.substring(1, namePos).trim();
537                 String JavaDoc names = "";
538                 if (parseMe.indexOf("SYNTAX")>-1)
539                     names = parseMe.substring(parseMe.indexOf("'", namePos), parseMe.indexOf("SYNTAX")-2); // trim off final bracket, if it exists...
540

541                 tokenizer = new StringTokenizer(names, "'");
542                 while (tokenizer.hasMoreElements())
543                 {
544                     String JavaDoc name = tokenizer.nextToken().trim();
545                     if (name.length() > 0)
546                     {
547                         attributeNames.put(oid, name);
548                     }
549                 }
550
551             }
552         }
553         catch (NamingException e)
554         {
555             log.warning("unable to parse schemaOps at JndiBroker:initAttributeNamesHash " + e + "\n");
556         }
557         catch (Exception JavaDoc e2)
558         {
559             log.warning("Unexpected exception parsing schemaOps at JndiBroker:initAttributeNamesHash " + e2 + "\n");
560             e2.printStackTrace();
561         }
562
563     }
564
565
566
567    /**
568     * @param attributeoid the attributes OID.
569     * @return the attribute description. If attributeNames is null "(schemaOps not correctly read)"
570     * is returned, if attributeNames are still null after trying to get the description
571     * "(attribute not listed in schemaOps)" is returned.
572     */

573
574     public String JavaDoc getAttributeDescription(String JavaDoc attributeoid)
575     {
576         if (attributeNames == null) return "(schemaOps not correctly read)";
577         String JavaDoc attributeName = (String JavaDoc)attributeNames.get(attributeoid);
578         if (attributeName == null) attributeName = "(attribute not listed in schemaOps)";
579         return attributeName;
580     }
581
582
583    /**
584     * This returns the version of ldap currently in use.
585     * @return current ldap version ('2' or '3')
586     */

587
588     public int getVersion()
589     {
590         return dirOps.getLdapVersion();
591     }
592
593
594
595    /**
596     * Used for debugging; prints out the first level of a dn from
597     * a particular context, headlined with
598     * with the supplied message.
599     * @param C the context to print out info for.
600     * @param dn the DN to print the children of.
601     * @param message text to print out along with the context data.
602     */

603
604     public void printContextList(Context C, DN dn, String JavaDoc message)
605     {
606         System.out.println("******* " + message + " ******\nPrinting context '" + dn + "'");
607         try
608         {
609             NamingEnumeration debug = C.list(dn);
610             while (debug.hasMore())
611             {
612                 System.out.println(((NameClassPair)(debug.next())).getName());
613             }
614         }
615         catch (NamingException e)
616         {
617             System.out.println("error printing context " + dn + "( " + message + " )" +e);
618         }
619     }
620
621
622
623    /**
624     * Disconnects from the current context, freeing both context and
625     * context environment parameters list.
626     */

627
628     public void disconnect()
629     {
630         attributeNames = null;
631         ctx = null;
632         schemaOps = null;
633
634         if (dirOps == null)
635             return; // no context open (not even a BasicOps object in existance!).
636
try
637          {
638              dirOps.close(); // closes ctx...
639
}
640          catch (NamingException e)
641          {
642              e.printStackTrace(); //To change body of catch statement use Options | File Templates.
643
}
644          dirOps = null;
645
646         Runtime.getRuntime().gc();
647         Runtime.getRuntime().runFinalization();
648     }
649
650
651
652    /**
653     * Sets the maximum number of objects returned by a search
654     * @param maxResponses the maximum returned objects
655     */

656
657     public void setLimit(int maxResponses) { limit = maxResponses; }
658
659
660
661    /**
662     * Sets the timeout period before the connection gives up trying
663     * to fetch a given data request.
664     * @param maxTime the maximum time allowed for a query.
665     */

666
667     public void setTimeout(int maxTime) { timeout = maxTime; }
668
669
670
671    /**
672     * returns the next level of a directory tree, returning
673     * a Enumeration of the results
674     * @param searchbase the node in the tree to expand
675     * @return list of results (NameClassPair); the next layer of the tree...
676     */

677
678     public DXNamingEnumeration unthreadedList(DN searchbase)
679     {
680         SetContextToBrowsingAliases();
681
682         try
683         {
684             return new DXNamingEnumeration(dirOps.list(searchbase));
685         }
686         catch (NamingException e)
687         {
688             error("unable to list " + searchbase, e);
689             return new DXNamingEnumeration(); // return empty list.
690
}
691     }
692
693
694
695    /**
696     * Performs a directory search.
697     * @param dn the DN (relative to initial context in ldap) to seach from.
698     * @param filter the non-null filter to use for the search
699     * @param search_level whether to search the base object, the next level or the whole subtree.
700     * @param returnAttributes - a list of attributes to return. If set to null,
701     * only the objectClass is returned.
702     * @return list of results ('SearchResult's); the next layer of the tree...
703     */

704
705     public DXNamingEnumeration unthreadedSearch(DN dn, String JavaDoc filter, int search_level, String JavaDoc[] returnAttributes)
706     {
707         SetContextToSearchingAliases();
708
709         DXNamingEnumeration ret;
710
711         // CB - use constants for search levels.
712

713         try
714         {
715             if (search_level == SEARCH_BASE_OBJECT)
716                 ret = new DXNamingEnumeration(dirOps.searchBaseEntry(dn, filter, limit, timeout, returnAttributes));
717             else if (search_level == SEARCH_ONE_LEVEL)
718                 ret = new DXNamingEnumeration(dirOps.searchOneLevel(dn, filter, limit, timeout, returnAttributes));
719             else if (search_level == SEARCH_SUB_TREE)
720                 ret = new DXNamingEnumeration(dirOps.searchSubTree(dn, filter, limit, timeout, returnAttributes));
721             else
722                 return null;
723         }
724         catch (NamingException e)
725         {
726             error("unable to search " + dn, e);
727             return new DXNamingEnumeration();
728         }
729
730         SetContextToBrowsingAliases();
731
732         return ret;
733     }
734
735
736
737    /**
738     * Reads all the attribute type and values for the given entry.
739     * Converts utf-8 to unicode if necessary.
740     * @param dn the ldap string distinguished name of entry to be read
741     * @return an 'Attributes' object containing a list of all Attribute
742     * objects.
743     */

744
745     public synchronized Attributes read(DN dn)
746     {
747         Attributes atts = null;
748         try
749         {
750             atts = dirOps.read(dn);
751         }
752         catch (NamingException e)
753         {
754             error("unable to read " + dn, e);
755
756         }
757         return new DXAttributes(atts);
758     }
759
760
761
762    /**
763     * Deletes a subtree by recursively deleting sub-sub trees from the given DN.
764     * @param nodeDN the DN of the node where to do the recursive delete.
765     */

766
767     public void deleteTree(DN nodeDN)
768         throws NamingException
769     {
770          dirOps.deleteTree(nodeDN);
771     }
772
773
774
775    /**
776     * Moves a DN to a new DN, including all subordinate entries.
777     * (nb it is up to the implementer how this is done; e.g. if it is an
778     * ldap broker, it may choose rename, or copy-and-delete, as appropriate)
779     * @param oldNodeDN the original DN of the sub tree root (may be a single
780     * entry).
781     * @param newNodeDN the target DN for the tree to be moved to.
782     */

783
784     public void moveTree(DN oldNodeDN, DN newNodeDN) // may be a single node.
785
throws NamingException
786     {
787         dirOps.moveTree(oldNodeDN, newNodeDN);
788     }
789
790
791
792    /**
793     * Copies a DN representing a subtree to a new subtree, including
794     * copying all subordinate entries.
795     * @param oldNodeDN the original DN of the sub tree root
796     * to be copied (may be a single entry).
797     * @param newNodeDN the target DN for the tree to be moved to.
798     */

799
800     public void unthreadedCopy(DN oldNodeDN, DN newNodeDN) // may be a single node.
801
throws NamingException
802     {
803         dirOps.copyTree(oldNodeDN, newNodeDN);
804     }
805
806
807
808    /**
809     * Checks the existance of a given entry.
810     * @param checkMe the DN of the entry to check for existance.
811     * @return whether the entry could be found in the directory.
812     */

813
814     public boolean unthreadedExists(DN checkMe)
815            throws NamingException
816     {
817         return dirOps.exists(checkMe);
818     }
819
820
821
822    /**
823     * @return
824     */

825
826     public boolean processQueue()
827     {
828         if (dirOps != null) dirOps.setQuietMode(true);
829         boolean ret = super.processQueue();
830
831
832         // WARNING - THREAD PROBLEMS POSSIBLE
833
// If this thread is cancelled, we run the risk of interfering
834
// with dirOps quiet mode status. However, since only jndi brokers
835
// should be accessing it, it's probably o.k. to leave it in
836
// quiet mode = true. Setting quiet mode = false while something
837
// else was using it could be very bad howerver, hence the
838
// immediate bail out.
839
if (ret == false) return false;
840
841         if (dirOps != null) dirOps.setQuietMode(false);
842         return true;
843     }
844
845
846    /**
847     * Utility method for extended queries - returns whether
848     * a 'masked' exception has occured.
849     */

850
851     public Exception JavaDoc getException()
852     {
853         return dirOps.quietException;
854     }
855
856
857
858    /**
859     * Utility method for extended queries - allows
860     * a 'masked' exception to be cleared.
861     */

862
863     public void clearException()
864     {
865         dirOps.quietException = null;
866     }
867
868
869
870    /**
871     * @param request
872     * @return the query.
873     */

874
875     protected DataQuery finish(DataQuery request)
876     {
877         request.setException(dirOps.quietException); // probably null
878
request.finish();
879         return request;
880     }
881
882
883    /**
884     * whether the data source is currently on-line.
885     * @return on-line status
886     */

887
888     public boolean isActive() { return (ctx != null); }
889
890
891
892    /**
893     * whether the data source is currently on-line.
894     * @return on-line status
895     */

896
897     public boolean hasConnectionError() { return ((ctx==null)||(connectionError)); }
898
899
900
901    /**
902     * Reads an entry with all its attributes from the directory.
903     * @param entryDN the DN of the entry.
904     * @param returnAttributes a list of string names of attributes to return in the search.
905     * (null means 'return all entries', a zero length array means 'return no attributes'.)
906     * @return the entry.
907     */

908
909     public DXEntry unthreadedReadEntry(DN entryDN, String JavaDoc[] returnAttributes)
910            throws NamingException
911     {
912         DXAttributes atts = new DXAttributes(dirOps.read(entryDN, returnAttributes));
913         return new DXEntry(atts, entryDN);
914     }
915
916
917
918    /**
919     * Add the new entry to the directory.
920     * @param newEntry the new entry containing the replacement set of attributes.
921     */

922
923     public void addEntry(DXEntry newEntry)
924            throws NamingException
925     {
926         // sanity check
927
if (newEntry.getDN() == null)
928             throw new NamingException("Internal Error: Entry with null DN passed to JNDIBroker addEntry(). Modify Request Cancelled.");
929
930         dirOps.addEntry(newEntry.getDN(), newEntry);
931     }
932
933
934
935    /**
936     * Utility ftn for updateNode - takes a list of attributes to modify, and
937     * the type of modification, and adds them to an array of modifications (starting
938     * at a particular index).
939     *
940     * @param mods the array of modification items
941     * @param atts an enumeration of attributes to add to the mod array
942     * @param TYPE the type of modification (DELETE,REPLACE,ADD)
943     * @param index the position in the modification array to start filling entries in
944     * @return the final index position reached.
945     */

946
947     protected int loadMods(ModificationItem[] mods, NamingEnumeration atts, int TYPE, int index)
948         throws NamingException
949     {
950         while (atts.hasMore())
951         {
952           Attribute temp = (Attribute)atts.next();
953           mods[index++] = new ModificationItem(TYPE,temp);
954         }
955         return index;
956     }
957
958
959
960    /**
961     * Update an entry with the designated DN.
962     * @param oldEntry the old entry containing teh old set of attributes.
963     * @param newEntry the new entry containing the replacement set of attributes.
964     */

965
966     public void unthreadedModify(DXEntry oldEntry, DXEntry newEntry)
967         throws NamingException
968     {
969         if (useSpecialWriteAllAttsMode()) // do magic for Mitch
970
doSpecialWriteAllAttsHandling(oldEntry, newEntry);
971         else
972             dirOps.modifyEntry(oldEntry, newEntry);
973     }
974
975     private void doSpecialWriteAllAttsHandling(DXEntry oldEntry, DXEntry newEntry)
976         throws NamingException
977     {
978         // check for cases where handling is the same as normal
979
if ( (newEntry == null) || (oldEntry == null && newEntry.getStatus() == DXEntry.NEW) )
980         {
981             dirOps.modifyEntry(oldEntry, newEntry);
982         }
983
984         // do any rename required
985
if (oldEntry.getDN().toString().equals(newEntry.getDN().toString()) == false)
986         {
987             moveTree(oldEntry.getDN(), newEntry.getDN());
988         }
989
990         if (DXAttributes.attributesEqual(oldEntry,newEntry))
991         {
992             return; // nothing to do.
993
}
994
995         DN nodeDN = newEntry.getDN();
996         RDN newRDN = nodeDN.getLowestRDN();
997
998         DXAttributes adds = null; // use modify-add for new attribute values (inc entirely new attribute)
999
DXAttributes reps = null; // use modify-replace for changed attribute values
1000
DXAttributes dels = null; // use modify-delete for deleted attribute values (inc deleting entire attribute)
1001

1002        try
1003        {
1004            if (hasVerboseObjectClass(newEntry)) // ?? XXX is this right? Shouldn't oldSet be null or something?
1005
reps = Special.getReplacementSet(newRDN, oldEntry, newEntry);
1006            else
1007                reps = new DXAttributes();
1008
1009            dels = Special.getDeletionSet(newRDN, oldEntry, newEntry);
1010            adds = Special.getAdditionSet(newRDN, oldEntry, newEntry);
1011
1012            log.fine("updateNode: " + nodeDN);
1013
1014            ModificationItem[] mods;
1015
1016            mods = new ModificationItem[dels.size() + reps.size() + adds.size()];
1017
1018            int modIndex = 0;
1019            modIndex = loadMods(mods, dels.getAll(), DirContext.REMOVE_ATTRIBUTE, modIndex);
1020            modIndex = loadMods(mods, adds.getAll(), DirContext.ADD_ATTRIBUTE, modIndex);
1021            modIndex = loadMods(mods, reps.getAll(), DirContext.REPLACE_ATTRIBUTE, modIndex);
1022
1023            dirOps.modifyAttributes(nodeDN, mods);
1024        }
1025        catch (Exception JavaDoc e)
1026        {
1027            NamingException os390 = new NamingException("SPECIAL OS390 MODE ERROR: Unexpected Error updating node " + oldEntry.getDN() + "! " + e.toString());
1028            os390.initCause(e);
1029            throw os390;
1030        }
1031    }
1032
1033
1034
1035
1036   /*
1037    * Optional debug code. Very useful. NEVER REMOVE!!!
1038    * @param oldSet old entry.
1039    * @param newSet new entry.
1040    * @param adds list of attributes to add.
1041    * @param reps list of attributes to replace.
1042    * @param dels list of attributes to delete.
1043    */

1044/*
1045    private void printDebug(DXEntry oldSet, DXEntry newSet, DXAttributes adds, DXAttributes reps, DXAttributes dels)
1046    {
1047
1048
1049        System.out.println("\n*** entries are ***\n\nold:\n" + oldSet.toString() + "\n\nnew:\n" + newSet.toString());
1050        System.out.println("\n-----------------\nreps:\n" + reps.toString());
1051        System.out.println("\n-----------------\ndels:\n" + dels.toString());
1052        System.out.println("\n-----------------\nadds:\n" + adds.toString());
1053        //Thread.currentThread().dumpStack();
1054    }
1055 */

1056
1057    protected boolean useSpecialWriteAllAttsMode()
1058    {
1059        // XXX Warning - the 'special code for Mitch' works very differently
1060
// XXX from the normal DXAttributes.get... code in the normal operation
1061
// XXX section - be careful about changing it.
1062

1063        return (specialObjectClasses != null); // test for special hack for Mitch
1064
}
1065
1066   /**
1067    * Checks if any of the objectClasses of this object are on the list
1068    * of special objects that require all attributes to be sent in a
1069    * 'replace' list, whether or not they have been modified by the user.
1070    * (by request of Mitch Rozonkiewiecz and the OS390 security people...)
1071    * @param atts the list of attributes.
1072    * @return true if on the list, false other wise.
1073    */

1074
1075    protected boolean hasVerboseObjectClass(Attributes atts)
1076    {
1077        if (specialObjectClasses == null) return false; // there are none.
1078

1079        try
1080        {
1081            Attribute oc;
1082            if (atts instanceof DXAttributes == false)
1083                oc = atts.get("objectClass"); // even odds: this may screw up due to capitalization.
1084
else // (but this *should* always be a DXAttributes object...)
1085
oc = ((DXAttributes)atts).getAllObjectClasses();
1086
1087            NamingEnumeration ocs = oc.getAll();
1088            while (ocs.hasMore())
1089            {
1090                Object JavaDoc test = ocs.next();
1091                if (specialObjectClasses.contains(test))
1092                {
1093                    return true;
1094                }
1095            }
1096            return false;
1097        }
1098        catch (NamingException e)
1099        {
1100            log.warning("error getting object classes in jndibroker " + e);
1101            return false;
1102        }
1103    }
1104
1105
1106
1107   /**
1108    * Checks whether the current data source is modifiable. (Nb.,
1109    * a directory may have different access controls defined on
1110    * different parts of the directory: if this is the case, the
1111    * directory may return true to isModifiable(), however a
1112    * particular modify attempt may still fail.
1113    *
1114    * @return whether the directory is modifiable
1115    */

1116
1117    public boolean isModifiable()
1118    {
1119        return true;
1120    }
1121
1122
1123
1124    /*** DataSource-Like methods querying schemaOps. These are used
1125          by 'SchemaBroker' to support querying of schemaOps info. ***/

1126
1127
1128
1129   /**
1130    * returns the root DN of the schemaOps subentry as a string.
1131    * @return the schemaOps subentry (i.e. something like 'cn=schemaOps')
1132    */

1133
1134    public String JavaDoc getSchemaRoot()
1135    {
1136        if (ctx==null)
1137            return "";
1138
1139        String JavaDoc fnord = "";
1140        //System.out.println("long shot...");
1141
try
1142        {
1143            fnord = ctx.getSchema("").toString();
1144            return fnord;
1145        }
1146        catch (NamingException e)
1147            { log.warning("error reading schemaOps\n" + e);}
1148
1149
1150        return "cn=schemaOps"; // default: often what is anyway... :-)
1151
}
1152
1153
1154
1155   /**
1156    * Gets a list of the object classes most likely
1157    * to be used for the next Level of the DN...
1158    * @param dn the dn of the parent to determine likely
1159    * child object classes for
1160    * @return list of recommended object classes...
1161    */

1162    public ArrayList unthreadedGetRecOCs(DN dn)
1163    {
1164        return schemaOps.getRecommendedObjectClasses(dn.toString());
1165    }
1166
1167   /**
1168    * Modifies the attributes associated with a named object using an an ordered list of modifications.
1169    * @param dn distinguished name of object to modify
1170    * @param mods an ordered sequence of modifications to be performed; may not be null
1171    */

1172
1173    public void modifyAttributes(DN dn, ModificationItem[] mods)
1174        throws AttributeModificationException, NamingException
1175    {
1176            if (ctx == null)
1177                throw new NamingException("no directory context to work with");
1178
1179            ctx.modifyAttributes(dn, mods);
1180    }
1181
1182   /**
1183    * Usually shells to CBUtility.error, but will log instead if quiet mode
1184    * is set.
1185    * @param msg the error message to display.
1186    * @param e the exception caused.
1187    */

1188
1189    public void error(String JavaDoc msg, Exception JavaDoc e)
1190    {
1191        if (quietMode == false)
1192        {
1193            CBUtility.error(msg, e);
1194        }
1195        else
1196        {
1197            errorWhileQuietFlag = true;
1198            log.warning(msg + "\n (details) " + e.toString());
1199        }
1200    }
1201
1202
1203
1204   /**
1205    * @return the directory operations object.
1206    */

1207
1208    public CBGraphicsOps getDirOp() { return dirOps; }
1209
1210
1211
1212   /**
1213    * @return the directory operations context.
1214    */

1215
1216    public DirContext getDirContext() { return (dirOps==null)?null:dirOps.getContext(); }
1217
1218
1219
1220   /**
1221    * If the rootDN doesn't exist, we try to read the
1222    * the root DN from the server. Not all servers
1223    * support this functionality, but some include a
1224    * <i>namingcontexts</i> attribute for their empty
1225    * entry...
1226    * @return the rootDN, or null if none was found.
1227    */

1228
1229    public DN[] readFallbackRoot()
1230    {
1231        log.finer("reading fallback root DN...");
1232        try
1233        {
1234            Attributes a = unthreadedReadEntry(new DN(""), new String JavaDoc[] {"namingContexts"});
1235
1236            if (a==null) return null; // can't do anything, bail.
1237

1238            log.finer("...Got root DSE data...");
1239
1240
1241//XXX namingContexts may need to be explicitly asked for, since it is an op att.
1242

1243            Attribute rootDNC;
1244            rootDNC = a.get("namingcontexts");
1245            if (rootDNC == null)
1246                rootDNC = a.get("namingContexts"); // some servers do 'ave em...
1247

1248            if (rootDNC == null || rootDNC.size()==0) return new DN[] {new DN()}; // can't do anything; bail with an empty DN.
1249

1250            if (rootDNC.size() == 1)
1251            {
1252                String JavaDoc rootDNString = rootDNC.get().toString();
1253
1254                log.info("read fallback root DN as: " + rootDNString);
1255                return new DN[] {new DN(rootDNString)};
1256            }
1257
1258            // Multiple Naming Contexts!!
1259

1260            DN contexts[] = new DN[rootDNC.size()];
1261
1262            int index=0;
1263            Enumeration roots = rootDNC.getAll();
1264            while (roots.hasMoreElements())
1265            {
1266                String JavaDoc dn = roots.nextElement().toString();
1267                contexts[index++] = new DN(dn);
1268            }
1269            return contexts;
1270        }
1271        catch (NamingException e)
1272        {
1273            log.log(Level.WARNING, "Error reading fallback root: ",e);
1274            return null;
1275        }
1276    }
1277
1278
1279
1280   /**
1281    * This sets the context to use either jndi 'searching' or 'never' alias
1282    * resolution, depending on the value of the jxplorer property
1283    * option.ldap.searchAliasBehaviour.
1284    */

1285
1286    public void SetContextToSearchingAliases()
1287    {
1288
1289        try
1290        {
1291            if (ctx != null) ctx.addToEnvironment("java.naming.ldap.derefAliases", JXplorer.getProperty("option.ldap.searchAliasBehaviour"));
1292        }
1293        catch( Exception JavaDoc e)
1294        {
1295            log.warning("Unexpected exception setting search alias handling behaviour in context to " + JXplorer.getProperty("option.ldap.searchAliasBehaviour") + "\n " + e);
1296        }
1297    }
1298
1299
1300
1301   /**
1302    * This sets the context to use either jndi 'browsing' or 'never' alias
1303    * resolution, depending on the value of the jxplorer property
1304    * option.ldap.browseAliasBehaviour.
1305    */

1306
1307    public void SetContextToBrowsingAliases()
1308    {
1309        try
1310        {
1311            if (ctx != null) ctx.addToEnvironment("java.naming.ldap.derefAliases", JXplorer.getProperty("option.ldap.browseAliasBehaviour"));
1312        }
1313        catch( Exception JavaDoc e)
1314        {
1315            log.warning("Unexpected exception setting browse alias handling behaviour in context to " + JXplorer.getProperty("option.ldap.browseAliasBehaviour") + "\n " + e);
1316        }
1317    }
1318
1319    /**
1320     * Returns a schemaOps ops object capable of answering questions about schemaOps and syntax.
1321     * @return a schemaOps aware schemaOps object linked to the same directory context as the broker.
1322     */

1323    public SchemaOps getSchemaOps()
1324    {
1325        return schemaOps;
1326    }
1327
1328}
Popular Tags