KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ca > commons > jndi > JNDIOps


1 package com.ca.commons.jndi;
2
3
4
5
6 import javax.naming.*;
7 import javax.naming.directory.*;
8 import javax.security.auth.login.LoginContext JavaDoc;
9 import javax.security.auth.login.LoginException JavaDoc;
10 import javax.security.auth.Subject JavaDoc;
11 import java.util.*;
12 import java.util.logging.Logger JavaDoc;
13
14 /**
15  * <p>JNDI Ops is a bare-bones utility class that takes up some
16  * of the over-head of making jndi calls. It is used by
17  * BasicOps which adds validation, error handling and logging.<p>
18  * <p/>
19  * <p>This utility class assumes you will be using ldap v3 with
20  * the environment defaults:
21  * "java.naming.ldap.deleteRDN" = "false"
22  * Context.REFERRAL (java.naming.referral), "ignore");
23  * java.naming.ldap.attributes.binary", "photo jpegphoto jpegPhoto");
24  * java.naming.ldap.derefAliases", "finding");
25  */

26
27 public class JNDIOps
28 {
29
30     private static final String JavaDoc DEFAULT_CTX = "com.sun.jndi.ldap.LdapCtxFactory";
31
32     /**
33      * To speed up existance checks, we use a single static constraints object that
34      * never changes.
35      */

36     private SearchControls existanceConstraints;
37
38
39     /**
40      * How to handle ldap referrals if unspecified
41      */

42     public static final String JavaDoc DEFAULT_REFERRAL_HANDLING = "ignore";
43
44     /**
45      * How to handle ldap aliases if unspecified
46      */

47     public static final String JavaDoc DEFAULT_ALIAS_HANDLING = "finding";
48
49
50     // we may wish to extend this class to opening DSML connections in future.
51
//private static final String DEFAULT_DSML_CTX = "com.sun.jndi.dsmlv2.soap.DsmlSoapCtxFactory";
52

53     private NameParser nameParser;
54
55     //AJR: converted protected member variable into private, added getContext / setContext accessors.
56
//NOTE: used Context rather than Ctx to match existing getContext() in BasicOps
57
private DirContext ctx = null;
58
59     private static Logger JavaDoc log = Logger.getLogger(JNDIOps.class.getName()); // ...It's round it's heavy it's wood... It's better than bad, it's good...
60

61     // initialise a reusable static constraints object for fast existance searching in 'exists()' methods
62
{
63         existanceConstraints = new SearchControls();
64         existanceConstraints.setSearchScope(SearchControls.OBJECT_SCOPE);
65         existanceConstraints.setCountLimit(0);
66         existanceConstraints.setTimeLimit(0);
67         existanceConstraints.setReturningAttributes(new String JavaDoc[]{"1.1"}); // just the names Madam
68
}
69
70     /**
71      * Initialise a Basic Operation object with a context.
72      */

73
74     public JNDIOps(DirContext c)
75     {
76         setContext(c);
77     }
78
79     /**
80      * This creates a jndi connection with a particular set of environment properties.
81      * Often this will be obtained by using one of the set...Properties methods to
82      * create a base list of properties and then modifing it before calling this
83      * constructor.
84      */

85
86     /**
87      * This creates a jndi connection with a particular set of environment properties.
88      * Often this will be obtained by using one of the set...Properties methods to
89      * create a base list of properties and then modifing it before calling this
90      * constructor.
91      */

92
93     public JNDIOps(Hashtable env) throws NamingException
94     {
95
96         // GSSAPI/Kerberos code from Vadim Tarassov
97
// TODO: consider refactoring to make this handling
98
// similar to other setup... methods? Maybe in CBOpenConWin?
99
if ((env.get(Context.SECURITY_AUTHENTICATION)).equals("GSSAPI"))
100         {
101             setupKerberosContext(env);
102         }
103         else
104         {
105             setContext(openContext(env)); // create the connection!
106
}
107     }
108
109     /**
110      * This creates an ldap context using GSSAPI/Kerberos for
111      * security. It uses cached kerberos credentials, or
112      * calls JXCallbackHandler() to obtain credentials if none
113      * are already available.
114      * @param env
115      * @throws NamingException
116      */

117     protected void setupKerberosContext(Hashtable env)
118             throws NamingException
119     {
120         // debug
121
log.finest("dumping kerberos environment keys");
122         Enumeration keys = env.keys();
123         while (keys.hasMoreElements())
124         {
125             String JavaDoc key = (String JavaDoc)keys.nextElement();
126             log.finest(key + " : " + env.get(key));
127         }
128
129
130         // Create LoginContext
131

132         LoginContext JavaDoc lc = null;
133         try
134         {
135             lc = new LoginContext JavaDoc(JNDIOps.class.getName(),
136                     new JXCallbackHandler());
137             lc.login();
138         }
139         catch (LoginException JavaDoc ex)
140         {
141             ex.printStackTrace();
142             throw new NamingException("login problem: " + ex);
143         }
144
145         DirContext newCtx = (DirContext) Subject.doAs(lc.getSubject(), new JndiAction(env));
146
147
148         if (newCtx == null)
149         {
150             throw new NamingException("a problem with GSSAPI occurred - couldn't create a GSSAPI directory context");
151         } // Vadim: other then GSSAPI
152

153         setContext(newCtx);
154
155
156         if (ctx == null)
157         {
158             throw new NamingException("another problem with GSSAPI occurred"); // usually caught by the first error
159
} // Vadim: other then GSSAPI
160
}
161
162     /**
163      * This creates a simple, unauthenticated jndi connection to an ldap url.
164      * Note that this ftn may take some time to return...
165      *
166      * @param url a url of the form ldap://hostname:portnumber.
167      */

168
169     public JNDIOps(String JavaDoc url)
170             throws NamingException
171     {
172         Hashtable env = new Hashtable(); // an environment for jndi context parameters
173

174         setupBasicProperties(env, url); // set up the bare minimum parameters
175

176         setContext(openContext(env)); // create the connection!
177
}
178
179     /**
180      * <p>This creates a JNDIOps object using simple username + password authentication.</p>
181      * <p/>
182      * <p>This constructor opens an initial context. Note that this ftn may take some
183      * time to return...</p>
184      *
185      * @param url a url of the form ldap://hostname:portnumber.
186      * @param userDN the Manager User's distinguished name (optionally null if not used).
187      * @param pwd the Manager User's password - (is null if user is not manager).
188      */

189
190     public JNDIOps(String JavaDoc url, String JavaDoc userDN, char[] pwd)
191             throws NamingException
192     {
193
194         Hashtable env = new Hashtable(); // an environment for jndi context parameters
195

196         setupBasicProperties(env, url); // set up the bare minimum parameters
197

198         setupSimpleSecurityProperties(env, userDN, pwd); // add the username + password parameters
199

200         setContext(openContext(env)); // create the connection !
201
}
202
203     /**
204      * This creates a JNDIOps object with an SSL or SASL Connection.
205      * <p/>
206      * If only SSL is desired, the clientcerts, clientKeystorePwd and clientKeystoreType
207      * variables may be set to null.
208      *
209      * @param url a url of the form ldap://hostname:portnumber.
210      * @param cacerts the file containing the trusted server certificates (no keys).
211      * @param clientcerts the file containing client certificates.
212      * @param caKeystorePwd the password to the ca's keystore (may be null for non-client authenticated ssl).
213      * @param clientKeystorePwd the password to the client's keystore (may be null for non-client authenticated ssl).
214      * @param caKeystoreType the type of keystore file; e.g. 'JKS', or 'PKCS12'.
215      * @param clientKeystoreType the type of keystore file; e.g. 'JKS', or 'PKCS12'.
216      * @param tracing whether to set BER tracing on or not.
217      * @param sslTracing whether to set SSL tracing on or not.
218      */

219
220     public JNDIOps(String JavaDoc url,
221                    String JavaDoc cacerts, String JavaDoc clientcerts,
222                    char[] caKeystorePwd, char[] clientKeystorePwd,
223                    String JavaDoc caKeystoreType, String JavaDoc clientKeystoreType,
224                    boolean tracing, boolean sslTracing, String JavaDoc sslSocketFactory)
225             throws NamingException
226     {
227
228         Hashtable env = new Hashtable(); // an environment for jndi context parameters
229

230
231         setupBasicProperties(env, url, tracing, DEFAULT_REFERRAL_HANDLING, DEFAULT_ALIAS_HANDLING); // set up the bare minimum parameters
232

233         // add the SSL ('ca...') and possible SASL ('client...') parameters
234
setupSSLProperties(env, cacerts, clientcerts,
235                 caKeystorePwd, clientKeystorePwd,
236                 caKeystoreType, clientKeystoreType,
237                 sslTracing, sslSocketFactory);
238
239         setContext(openContext(env)); // create the connection !
240
}
241
242     /**
243      * @param env
244      * @param url
245      * @throws NamingException
246      */

247     public static void setupBasicProperties(Hashtable env, String JavaDoc url)
248             throws NamingException
249     {
250         setupBasicProperties(env, url, false, DEFAULT_REFERRAL_HANDLING, DEFAULT_ALIAS_HANDLING);
251     }
252
253
254     /**
255      * This method combines a serverURL (e.g. ldap://localhost:19389) with a base DN to
256      * start searching from (e.g. o=democorp,c=au) to create a full ldap URL
257      * (e.g. ldap://localhost:19389/o=democorp,c=au. It does some of the required
258      * escaping to the baseDN, but does not formally check either the DN or the
259      * serverURL for correctness.
260      *
261      * see RFC 2252 for more details.
262      * @param serverURL
263      * @param baseDN
264      * @return
265      */

266
267 /* from RFC 1738 2.2
268      Unsafe:
269
270    Characters can be unsafe for a number of reasons. The space
271    character is unsafe because significant spaces may disappear and
272    insignificant spaces may be introduced when URLs are transcribed or
273    typeset or subjected to the treatment of word-processing programs.
274    The characters "<" and ">" are unsafe because they are used as the
275    delimiters around URLs in free text; the quote mark (""") is used to
276    delimit URLs in some systems. The character "#" is unsafe and should
277    always be encoded because it is used in World Wide Web and in other
278    systems to delimit a URL from a fragment/anchor identifier that might
279    follow it. The character "%" is unsafe because it is used for
280    encodings of other characters. Other characters are unsafe because
281    gateways and other transport agents are known to sometimes modify
282    such characters. These characters are "{", "}", "|", "\", "^", "~",
283    "[", "]", and "`".
284 */

285     public static String JavaDoc makeServerURL(String JavaDoc serverURL, String JavaDoc baseDN)
286     {
287         if (baseDN != null && baseDN.length() > 0)
288         {
289             // trim any extra '/' on the end of the server URL (we add it back below)
290
if (serverURL.length()>7 && serverURL.endsWith("/"))
291                 serverURL = serverURL.substring(0, serverURL.length()-1);
292
293             // XXX really important that this one happens first!!
294
baseDN = baseDN.replaceAll("[%]", "%25");
295
296             baseDN = baseDN.replaceAll(" ", "%20");
297             baseDN = baseDN.replaceAll("[<]", "%3c");
298             baseDN = baseDN.replaceAll("[>]", "%3e");
299             baseDN = baseDN.replaceAll("[\"]", "%3f");
300             baseDN = baseDN.replaceAll("[#]", "%23");
301             baseDN = baseDN.replaceAll("[{]", "%7b");
302             baseDN = baseDN.replaceAll("[}]", "%7d");
303             baseDN = baseDN.replaceAll("[|]", "%7c");
304             baseDN = baseDN.replaceAll("[\\\\]", "%5c"); // double check this one :-)
305
baseDN = baseDN.replaceAll("[\\^]", "%5e");
306             baseDN = baseDN.replaceAll("[~]", "%7e");
307             baseDN = baseDN.replaceAll("[\\[]", "%5b");
308             baseDN = baseDN.replaceAll("[\\]]", "%5d");
309             baseDN = baseDN.replaceAll("[']", "%27");
310
311             baseDN = baseDN.replaceAll("[?]", "%3f");
312
313             serverURL = serverURL + "/" + baseDN;
314         }
315
316         return serverURL;
317
318
319     }
320
321     /**
322      * This sets the basic environment properties needed for a simple,
323      * unauthenticated jndi connection. It is used by openBasicContext().
324      * <p/>
325      * This method is provided as a convenience for people wishing to append
326      * or modify the jndi environment, without setting it up entirely from
327      * scratch.
328      *
329      * @param url
330      * @param env
331      * @throws NamingException
332      */

333
334
335     public static void setupBasicProperties(Hashtable env, String JavaDoc url, boolean tracing, String JavaDoc referralType, String JavaDoc aliasType)
336             throws NamingException
337     {
338         // sanity check
339
if (url == null)
340             throw new NamingException("URL not specified in openContext()!");
341
342         // set the tracing level now, since (wierdly) it can't be set once the connection is open.
343
if (tracing)
344             env.put("com.sun.jndi.ldap.trace.ber", System.err);
345
346         env.put("java.naming.ldap.version", "3"); // always use ldap v3
347

348         if (env.get(Context.INITIAL_CONTEXT_FACTORY) == null)
349             env.put(Context.INITIAL_CONTEXT_FACTORY, DEFAULT_CTX); // use jndi provider
350

351         env.put("java.naming.ldap.deleteRDN", "false"); // usually what we want
352

353         env.put(Context.REFERRAL, referralType); //could be: follow, ignore, throw
354

355         env.put("java.naming.ldap.attributes.binary", "photo jpegphoto jpegPhoto"); // special hack to handle non-standard binary atts
356

357         env.put("java.naming.ldap.derefAliases", aliasType); // could be: finding, searching, etc.
358

359         env.put(Context.SECURITY_AUTHENTICATION, "none"); // no authentication (may be modified by other code)
360

361         env.put(Context.PROVIDER_URL, url); // the ldap url to connect to; e.g. "ldap://ca.com:389"
362
}
363
364     /**
365      * This sets the environment properties needed for a simple username +
366      * password authenticated jndi connection. It is used by openSimpleSecurityContext().
367      * <p/>
368      * This method is provided as a convenience for people wishing to append
369      * or modify the jndi environment, without setting it up entirely from
370      * scratch.
371      *
372      * @param env
373      * @param userDN
374      * @param pwd
375      */

376     public static void setupSimpleSecurityProperties(Hashtable env, String JavaDoc userDN, char[] pwd)
377     {
378         env.put(Context.SECURITY_AUTHENTICATION, "simple"); // 'simple' = username + password
379

380         env.put(Context.SECURITY_PRINCIPAL, userDN); // add the full user dn
381

382         env.put(Context.SECURITY_CREDENTIALS, new String JavaDoc(pwd)); // stupid jndi requires us to cast this to a string-
383
// this opens a security weakness with swapped memory etc.
384
}
385
386     /**
387      * @param env
388      * @param cacerts
389      * @param clientcerts
390      * @param caKeystorePwd
391      * @param clientKeystorePwd
392      * @param caKeystoreType
393      * @param clientKeystoreType
394      * @param tracing
395      * @param sslTracing
396      * @param sslSocketFactory
397      * @throws NamingException
398      * @deprecated - use the version without the tracing flag (set in setupBasicProperties).
399      */

400     public static void setupSSLProperties(Hashtable env,
401                                           String JavaDoc cacerts, String JavaDoc clientcerts,
402                                           char[] caKeystorePwd, char[] clientKeystorePwd,
403                                           String JavaDoc caKeystoreType, String JavaDoc clientKeystoreType,
404                                           boolean tracing, boolean sslTracing,
405                                           String JavaDoc sslSocketFactory)
406             throws NamingException
407     {
408         setupSSLProperties(env, cacerts, clientcerts, caKeystorePwd, clientKeystorePwd, caKeystoreType, clientKeystoreType, sslTracing, sslSocketFactory);
409     }
410
411     /* This static ftn. sets the environment used to open an SSL or SASL context.
412     * It is used by openSSLContext.
413     *
414     * If only SSL is desired, the clientcerts, clientKeystorePwd and clientKeystoreType
415     * variables may be set to null.
416     *
417     * This method is provided as a convenience for people wishing to append
418     * or modify the jndi environment, without setting it up entirely from
419     * scratch.
420     *
421     * @param url a url of the form ldap://hostname:portnumber.
422     * @param tracing whether to set BER tracing on or not.
423     * @param cacerts the file containing the trusted server certificates (no keys).
424     * @param clientcerts the file containing client certificates.
425     * @param caKeystorePwd the password to the ca's keystore (may be null for non-client authenticated ssl).
426     * @param clientKeystorePwd the password to the client's keystore (may be null for non-client authenticated ssl).
427     * @param caKeystoreType the type of keystore file; e.g. 'JKS', or 'PKCS12'.
428     * @param clientKeystoreType the type of keystore file; e.g. 'JKS', or 'PKCS12'.
429     *
430     * @return The created context.
431     */

432
433     public static void setupSSLProperties(Hashtable env,
434                                           String JavaDoc cacerts, String JavaDoc clientcerts,
435                                           char[] caKeystorePwd, char[] clientKeystorePwd,
436                                           String JavaDoc caKeystoreType, String JavaDoc clientKeystoreType,
437                                           boolean sslTracing,
438                                           String JavaDoc sslSocketFactory)
439             throws NamingException
440     {
441
442         // sanity check
443
if (cacerts == null)
444             throw new NamingException("Cannot use SSL without a trusted CA certificates JKS file.");
445
446         // the exact protocol (e.g. "TLS") set in JndiSocketFactory
447
env.put(Context.SECURITY_PROTOCOL, "ssl");
448
449         // Initialise the SSL Socket Factory. Due to architectural wierdnesses, this is
450
// a separate, static method in our own separate SSL Factory class.
451

452         if (sslSocketFactory.equals("com.ca.commons.jndi.JndiSocketFactory"))
453         {
454             JndiSocketFactory.init(cacerts, clientcerts,
455                     caKeystorePwd, clientKeystorePwd,
456                     caKeystoreType, clientKeystoreType);
457         }
458
459         // Tell JNDI to use our own, separate SSL Factory class with the keystores set as previously
460
env.put("java.naming.ldap.factory.socket", sslSocketFactory);
461
462         // try to use client authentication (SASL) if a clientcert keystore and pwd supplied
463
if (clientcerts != null && (clientKeystorePwd != null && clientKeystorePwd.length > 0))
464         {
465             env.put(Context.SECURITY_AUTHENTICATION, "EXTERNAL"); // Use sasl external (i.e., certificate) auth
466
}
467
468         if (sslTracing)
469         {
470             // XXX doesn't seem to work?
471
System.setProperty("javax.net.debug", "ssl handshake verbose");
472         }
473     }
474
475
476     /**
477      * This is a raw interface to javax.naming.directory.InitialDirContext, that allows
478      * an arbitrary environment string to be passed through. Often it will be
479      * convenient to create that environment list using a set...Properties call (or just
480      * use one of the constructors to create a JNDIOps object.
481      *
482      * @param env a list of environment variables for the context
483      * @return a newly created DirContext.
484      */

485
486     public static DirContext openContext(Hashtable env)
487             throws NamingException
488     {
489         /* DEBUG code - do not remove
490         System.out.println("-- listing properties --");
491         for (Enumeration e = env.keys() ; e.hasMoreElements() ;)
492         {
493             String key = e.nextElement().toString();
494             String val = env.get(key).toString();
495             if (val.length() > 40) {
496                     val = val.substring(0, 37) + "...";
497             }
498             System.out.println(key + "=" + val);
499         }
500         System.out.println("-- end list --");
501         */

502
503
504         DirContext ctx = new InitialDirContext(env);
505
506         if (ctx == null)
507             throw new NamingException("Internal Error with jndi connection: No Context was returned, however no exception was reported by jndi.");
508
509         return ctx;
510     }
511
512
513     /**
514      * <p>A wrapper for context.rename... changes the
515      * distinguished name of an object, checks for error.
516      * !! Only changes the final RDN.</p>
517      * <p/>
518      * <p>WARNING! this will fail for single valued manditory attributes.
519      * since using 'deleteRDN = false' - use renameEntry(old, new, deleteOldRdn)
520      * method instead - 30 May 2002.</p>
521      *
522      * @param oldDN current distinguished name of an object.
523      * @param newDN the name it is to be changed to.
524      */

525
526     public void renameEntry(Name oldDN, Name newDN)
527             throws NamingException
528     {
529         Name rdn = newDN.getSuffix(newDN.size() - 1);
530         Name oldRdn = oldDN.getSuffix(oldDN.size() - 1);
531
532         if (oldRdn.toString().equals(rdn.toString()) == false) // do nothing if names the same.
533
ctx.rename(oldDN, rdn);
534     }
535
536
537     /**
538      * Copies an object to a new DN by the simple expedient of adding
539      * an object with the new DN, and the attributes of the old object.
540      *
541      * @param fromDN the original object being copied
542      * @param toDN the new object being created
543      */

544
545     public void copyEntry(Name fromDN, Name toDN)
546             throws NamingException
547     {
548         addEntry(toDN, read(fromDN));
549     }
550
551
552     /**
553      * creates a new object (subcontext) with the given
554      * dn and attributes.
555      *
556      * @param dn the distinguished name of the new object
557      * @param atts attributes for the new object
558      */

559
560     public void addEntry(Name dn, Attributes atts)
561             throws NamingException
562     {
563         ctx.createSubcontext(dn, atts);
564     }
565
566     /**
567      * deletes a leaf entry (subcontext). It is
568      * an error to attempt to delete an entry which is not a leaf
569      * entry, i.e. which has children.
570      */

571
572     public void deleteEntry(Name dn)
573             throws NamingException
574     {
575         ctx.destroySubcontext(dn);
576     }
577
578
579     /**
580      * Checks the existence of a particular DN, without (necessarily)
581      * reading any attributes.
582      *
583      * @param nodeDN the DN to check
584      * @return the existence of the nodeDN (or false if an error occurs).
585      */

586
587     public boolean exists(Name nodeDN)
588             throws NamingException
589     {
590
591         try
592         {
593             ctx.search(nodeDN, "(objectclass=*)", existanceConstraints);
594             return true;
595         }
596         catch (NoSuchAttributeException e) // well, there has to be an entry for us not to find attributes on right?
597
{ // so maybe it's an exchange server, or has weird visibility permissions...
598
return true;
599         }
600         catch (NameNotFoundException e) // ugly as sin, but there seems no other way of doing things
601
{
602             return false;
603         }
604                 /*
605                  * This is what is known in the programming trade as 'a filthy hack'. There is a bug in the Sun DSML provider
606                  * where a null pointer exception is thrown at com.sun.jndi.dsmlv2.soap.DsmlSoapCtx.c_lookup(DsmlSoapCtx.java:571)
607                  * (possibly when a referral is returned?). For our purposes though it translates to 'not found' - so we intercept
608                  * the bug and return false instead...
609                  */

610         catch (NullPointerException JavaDoc e)
611         {
612             if ((ctx != null) && (ctx.getEnvironment().get(Context.INITIAL_CONTEXT_FACTORY).toString().indexOf("dsml") > 0))
613                 return false;
614             else
615                 throw e;
616         }
617     }
618
619
620     /**
621      * Checks the existence of a particular DN, without (necessarily)
622      * reading any attributes.
623      *
624      * @param nodeDN the DN to check
625      * @return the existence of the nodeDN (or false if an error occurs).
626      */

627
628     public boolean exists(String JavaDoc nodeDN)
629             throws NamingException
630     {
631         try
632         {
633             ctx.search(nodeDN, "(objectclass=*)", existanceConstraints);
634             return true;
635         }
636         catch (NameNotFoundException e) // ugly as sin, but there seems no other way of doing things
637
{
638             return false;
639         }
640                 /*
641                  * This is what is known in the programming trade as 'a filthy hack'. There is a bug in the Sun DSML provider
642                  * where a null pointer exception is thrown at com.sun.jndi.dsmlv2.soap.DsmlSoapCtx.c_lookup(DsmlSoapCtx.java:571)
643                  * (possibly when a referral is returned?). For our purposes though it translates to 'not found' - so we intercept
644                  * the bug and return false instead...
645                  */

646         catch (NullPointerException JavaDoc e)
647         {
648             if ((ctx != null) && (ctx.getEnvironment().get(Context.INITIAL_CONTEXT_FACTORY).toString().indexOf("dsml") > 0))
649                 return false;
650             else
651                 throw e;
652         }
653     }
654
655     /**
656      * Reads all the attribute type and values for the given entry.
657      *
658      * @param dn the ldap string distinguished name of entry to be read
659      * @return an 'Attributes' object containing a list of all Attribute
660      * objects.
661      */

662
663     public synchronized Attributes read(Name dn)
664             throws NamingException
665     {
666         return read(dn, null);
667     }
668
669
670     /**
671      * Reads all the attribute type and values for the given entry.
672      *
673      * @param dn the ldap string distinguished name of entry to be read
674      * @param returnAttributes a list of specific attributes to return.
675      * @return an 'Attributes' object containing a list of all Attribute
676      * objects.
677      */

678
679     public synchronized Attributes read(Name dn, String JavaDoc[] returnAttributes)
680             throws NamingException
681     {
682         return ctx.getAttributes(dn, returnAttributes);
683     }
684
685     /**
686      * Modifies an object's attributes, either adding, replacing or
687      * deleting the passed attributes.
688      *
689      * @param dn distinguished name of object to modify
690      * @param mod_type the modification type to be performed; one of
691      * DirContext.REPLACE_ATTRIBUTE, DirContext.DELETE_ATTRIBUTE, or
692      * DirContext.ADD_ATTRIBUTE.
693      * @param attr the new attributes to update the object with.
694      */

695
696     public void modifyAttributes(Name dn, int mod_type, Attributes attr)
697             throws NamingException
698     {
699         ctx.modifyAttributes(dn, mod_type, attr);
700     }
701
702
703     /**
704      * Modifies an object's attributes, either adding, replacing or
705      * deleting the passed attributes.
706      *
707      * @param dn distinguished name of object to modify
708      * @param modList a list of ModificationItems
709      */

710
711     public void modifyAttributes(Name dn, ModificationItem[] modList)
712             throws NamingException
713     {
714         ctx.modifyAttributes(dn, modList);
715     }
716
717     /**
718      * Updates an object with a new set of attributes
719      *
720      * @param dn distinguished name of object to update
721      * @param atts the new attributes to update the object with.
722      */

723
724     public void updateEntry(Name dn, Attributes atts)
725             throws NamingException
726     {
727         modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, atts);
728     }
729
730
731     /**
732      * deletes an attribute from an object
733      *
734      * @param dn distinguished name of object
735      * @param a the attribute to delete
736      */

737
738     public void deleteAttribute(Name dn, Attribute a)
739             throws NamingException
740     {
741         BasicAttributes atts = new BasicAttributes();
742         atts.put(a);
743         modifyAttributes(dn, DirContext.REMOVE_ATTRIBUTE, atts);
744     }
745
746     /**
747      * deletes a set of attribute-s from an object
748      *
749      * @param dn distinguished name of object
750      * @param a the Attributes object containing the
751      * list of attribute-s to delete
752      */

753
754
755     public void deleteAttributes(Name dn, Attributes a)
756             throws NamingException
757     {
758         modifyAttributes(dn, DirContext.REMOVE_ATTRIBUTE, a);
759     }
760
761     /**
762      * updates an Attribute with a new value set
763      *
764      * @param dn distinguished name of object
765      * @param a the attribute to modify
766      */

767
768     public void updateAttribute(Name dn, Attribute a)
769             throws NamingException
770     {
771         BasicAttributes atts = new BasicAttributes();
772         atts.put(a);
773         modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, atts);
774     }
775
776     /**
777      * updates a set of Attribute-s.
778      *
779      * @param dn distinguished name of object
780      * @param a an Attributes object containing the attribute-s to modify
781      */

782
783     public void updateAttributes(Name dn, Attributes a)
784             throws NamingException
785     {
786         modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, a);
787     }
788
789     /**
790      * Adds a new attribute to a particular dn.
791      *
792      * @param dn distinguished name of object
793      * @param a the attribute to modify
794      */

795
796     public void addAttribute(Name dn, Attribute a)
797             throws NamingException
798     {
799         BasicAttributes atts = new BasicAttributes();
800         atts.put(a);
801         modifyAttributes(dn, DirContext.ADD_ATTRIBUTE, atts);
802     }
803
804     /**
805      * Adds a set of attributes to a particular dn.
806      *
807      * @param dn distinguished name of object
808      * @param a the Attributes (set of attribute-s) to add
809      */

810
811     public void addAttributes(Name dn, Attributes a)
812             throws NamingException
813     {
814         modifyAttributes(dn, DirContext.ADD_ATTRIBUTE, a);
815     }
816
817
818     /**
819      * returns the next level of a directory tree, returning
820      * a Enumeration of the results, *relative* to the SearchBase (i.e. not as
821      * absolute DNs), along with their object classes if possible.
822      *
823      * @param Searchbase the node in the tree to expand
824      * @return list of results (NameClassPair); the next layer of the tree...
825      */

826
827     public NamingEnumeration list(Name Searchbase)
828             throws NamingException
829     {
830         // Attempt to read the names of the next level of subentries along with their object
831
// classes. Failing that, try to just read their names.
832

833 // just do a real 'list', without object classes...
834
// return rawSearchOneLevel(Searchbase, "(objectclass=*)", 0, 0, new String[]{"1.1"});
835

836 // a JXplorer 'list' returns object classes as well, so we can play silly buggers with GUI icons
837
return rawSearchOneLevel(Searchbase, "(objectclass=*)", 0, 0, new String JavaDoc[]{"objectclass"});
838     }
839
840
841     /**
842      * Performs a one-level directory search (i.e. a search of immediate children), returning
843      * object classes if possible, otherwise just the names.
844      *
845      * @param searchbase the domain name (relative to initial context in ldap) to seach from.
846      * @param filter the non-null filter to use for the search
847      * @param limit the maximum number of results to return
848      * @param timeout the maximum time to wait before abandoning the search
849      * @return list of search results ('SearchResult's); entries matching the search filter.
850      */

851
852     public NamingEnumeration searchOneLevel(String JavaDoc searchbase, String JavaDoc filter, int limit, int timeout)
853             throws NamingException
854     {
855         return searchOneLevel(searchbase, filter, limit, timeout, new String JavaDoc[]{"1.1"});
856     }
857
858     /**
859      * Performs a one-level directory search (i.e. a search of immediate children)
860      *
861      * @param searchbase the domain name (relative to initial context in ldap) to seach from.
862      * @param filter the non-null filter to use for the search
863      * @param limit the maximum number of results to return
864      * @param timeout the maximum time to wait before abandoning the search
865      * @param returnAttributes an array of strings containing the names of attributes to search. (null = all, empty array = none)
866      * @return list of search results ('SearchResult's); entries matching the search filter.
867      */

868
869
870     public NamingEnumeration searchOneLevel(String JavaDoc searchbase, String JavaDoc filter, int limit,
871                                             int timeout, String JavaDoc[] returnAttributes)
872             throws NamingException
873     {
874         return rawSearchOneLevel(nameParser.parse(searchbase), filter, limit, timeout, returnAttributes);
875     }
876
877
878     /**
879      * Performs a one-level directory search (i.e. a search of immediate children), returning
880      * object classes if possible, otherwise just the names.
881      *
882      * @param searchbase the domain name (relative to initial context in ldap) to seach from.
883      * @param filter the non-null filter to use for the search
884      * @param limit the maximum number of results to return
885      * @param timeout the maximum time to wait before abandoning the search
886      * @return list of search results ('SearchResult's); entries matching the search filter.
887      */

888
889     public NamingEnumeration searchOneLevel(Name searchbase, String JavaDoc filter, int limit, int timeout)
890             throws NamingException
891     {
892         return rawSearchOneLevel(searchbase, filter, limit, timeout, new String JavaDoc[]{"1.1"});
893     }
894
895     /**
896      * Performs a one-level directory search (i.e. a search of immediate children)
897      *
898      * @param searchbase the domain name (relative to initial context in ldap) to seach from.
899      * @param filter the non-null filter to use for the search
900      * @param limit the maximum number of results to return
901      * @param timeout the maximum time to wait before abandoning the search
902      * @param returnAttributes an array of strings containing the names of attributes to search. (null = all, empty array = none)
903      * @return list of search results ('SearchResult's); entries matching the search filter.
904      */

905
906
907     public NamingEnumeration searchOneLevel(Name searchbase, String JavaDoc filter, int limit,
908                                             int timeout, String JavaDoc[] returnAttributes)
909             throws NamingException
910     {
911         return rawSearchOneLevel(searchbase, filter, limit, timeout, returnAttributes);
912     }
913
914     /**
915      * Method that calls the actual search on the jndi context.
916      *
917      * @param searchbase the domain name (relative to initial context in ldap) to seach from.
918      * @param filter the non-null filter to use for the search
919      * @param limit the maximum number of results to return
920      * @param timeout the maximum time to wait before abandoning the search
921      * @param returnAttributes an array of strings containing the names of attributes to search. (null = all, empty array = none)
922      * @return
923      * @throws NamingException
924      */

925     protected NamingEnumeration rawSearchOneLevel(Name searchbase, String JavaDoc filter, int limit,
926                                                   int timeout, String JavaDoc[] returnAttributes) throws NamingException
927     {
928         /* specify search constraints to search one level */
929         SearchControls constraints = new SearchControls();
930
931         constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
932         constraints.setCountLimit(limit);
933         constraints.setTimeLimit(timeout);
934
935         constraints.setReturningAttributes(returnAttributes);
936
937 // NamingEnumeration results = ctx.search(searchbase, filter, null);
938
NamingEnumeration results = ctx.search(searchbase, filter, constraints);
939
940         return results;
941
942     }
943
944     /**
945      * Performs a directory sub tree search (i.e. of the next level and all subsequent levels below),
946      * returning just dns);
947      *
948      * @param searchbase the domain name (relative to initial context in ldap) to seach from.
949      * @param filter the non-null filter to use for the search
950      * @param limit the maximum number of results to return
951      * @param timeout the maximum time to wait before abandoning the search
952      * @return list of search results ('SearchResult's); entries matching the search filter. WARNING - these may be RELATIVE to the seachbase.
953      */

954     public NamingEnumeration searchSubTree(Name searchbase, String JavaDoc filter, int limit, int timeout)
955             throws NamingException
956     {
957         return searchSubTree(searchbase, filter, limit, timeout, new String JavaDoc[]{"1.1"});
958     }
959
960     /**
961      * Performs a directory sub tree search (i.e. of the next level and all subsequent levels below),
962      * returning just dns);
963      *
964      * @param searchbase the domain name (relative to initial context in ldap) to seach from.
965      * @param filter the non-null filter to use for the search
966      * @param limit the maximum number of results to return
967      * @param timeout the maximum time to wait before abandoning the search
968      * @return list of search results ('SearchResult's); entries matching the search filter. WARNING - these may be RELATIVE to the seachbase.
969      */

970     public NamingEnumeration searchSubTree(String JavaDoc searchbase, String JavaDoc filter, int limit, int timeout)
971             throws NamingException
972     {
973         return searchSubTree((searchbase), filter, limit, timeout, new String JavaDoc[]{"1.1"});
974     }
975
976     /**
977      * Performs a directory sub tree search (i.e. of the next level and all subsequent levels below).
978      *
979      * @param searchbase the domain name (relative to initial context in ldap) to seach from.
980      * @param filter the non-null filter to use for the search
981      * @param limit the maximum number of results to return
982      * @param timeout the maximum time to wait before abandoning the search
983      * @param returnAttributes an array of strings containing the names of attributes to search. (null = all, empty array = none)
984      * @return list of search results ('SearchResult's); entries matching the search filter. WARNING - these may be RELATIVE to the seachbase.
985      */

986
987     public NamingEnumeration searchSubTree(String JavaDoc searchbase, String JavaDoc filter, int limit,
988                                            int timeout, String JavaDoc[] returnAttributes)
989             throws NamingException
990     {
991         return rawSearchSubTree(nameParser.parse(searchbase), filter, limit, timeout, returnAttributes);
992
993 // SearchControls constraints = setSubTreeSearchControls(returnAttributes, limit, timeout);
994

995 // return ctx.search(searchbase, filter, constraints);
996
}
997
998     /**
999      * Performs a directory sub tree search (i.e. of the next level and all subsequent levels below).
1000     *
1001     * @param searchbase the domain name (relative to initial context in ldap) to seach from.
1002     * @param filter the non-null filter to use for the search
1003     * @param limit the maximum number of results to return
1004     * @param timeout the maximum time to wait before abandoning the search
1005     * @param returnAttributes an array of strings containing the names of attributes to search. (null = all, empty array = none)
1006     * @return list of search results ('SearchResult's); entries matching the search filter. WARNING - these may be RELATIVE to the seachbase.
1007     */

1008
1009    public NamingEnumeration searchSubTree(Name searchbase, String JavaDoc filter, int limit,
1010                                           int timeout, String JavaDoc[] returnAttributes)
1011            throws NamingException
1012    {
1013        return rawSearchSubTree(searchbase, filter, limit, timeout, returnAttributes);
1014    }
1015
1016    protected NamingEnumeration rawSearchSubTree(Name searchbase, String JavaDoc filter, int limit,
1017                                                 int timeout, String JavaDoc[] returnAttributes) throws NamingException
1018    {
1019        if (returnAttributes != null && returnAttributes.length == 0)
1020            returnAttributes = new String JavaDoc[]{"objectClass"};
1021
1022        /* specify search constraints to search subtree */
1023        SearchControls constraints1 = new SearchControls();
1024
1025        constraints1.setSearchScope(SearchControls.SUBTREE_SCOPE);
1026        constraints1.setCountLimit(limit);
1027        constraints1.setTimeLimit(timeout);
1028
1029        constraints1.setReturningAttributes(returnAttributes);
1030        SearchControls constraints = constraints1;
1031
1032        return ctx.search(searchbase, filter, constraints);
1033    }
1034
1035
1036    /**
1037     * Performs a base object search (i.e. just a search of the current entry, nothing below it),
1038     * returning no attributes (i.e. just DNs);
1039     *
1040     * @param searchbase the domain name (relative to initial context in ldap) to seach from.
1041     * @param filter the non-null filter to use for the search
1042     * @param limit the maximum number of results to return
1043     * @param timeout the maximum time to wait before abandoning the search
1044     * @return list of search results ('SearchResult's); entries matching the search filter.
1045     */

1046
1047    public NamingEnumeration searchBaseEntry(Name searchbase, String JavaDoc filter, int limit, int timeout)
1048            throws NamingException
1049    {
1050        return rawSearchBaseEntry(searchbase, filter, limit, timeout, new String JavaDoc[]{"objectClass"});
1051    }
1052
1053
1054    /**
1055     * Performs a base object search (i.e. just a search of the current entry, nothing below it).
1056     *
1057     * @param searchbase the domain name (relative to initial context in ldap) to seach from.
1058     * @param filter the non-null filter to use for the search
1059     * @param limit the maximum number of results to return
1060     * @param timeout the maximum time to wait before abandoning the search
1061     * @param returnAttributes an array of strings containing the names of attributes to search. (null = all, empty array = none)
1062     * @return list of search results ('SearchResult's); entries matching the search filter.
1063     */

1064
1065    public NamingEnumeration searchBaseEntry(Name searchbase, String JavaDoc filter, int limit,
1066                                             int timeout, String JavaDoc[] returnAttributes)
1067            throws NamingException
1068    {
1069        return rawSearchBaseEntry(searchbase, filter, limit, timeout, returnAttributes);
1070    }
1071
1072    /**
1073     * This is the core method for all base entry searches.
1074     *
1075     * @param searchbase the domain name (relative to initial context in ldap) to seach from.
1076     * @param filter the non-null filter to use for the search
1077     * @param limit the maximum number of results to return
1078     * @param timeout the maximum time to wait before abandoning the search
1079     * @param returnAttributes an array of strings containing the names of attributes to search. (null = all, empty array = none)
1080     * @return list of search results ('SearchResult's); entries matching the search filter.
1081     */

1082
1083    protected NamingEnumeration rawSearchBaseEntry(Name searchbase, String JavaDoc filter, int limit,
1084                                                   int timeout, String JavaDoc[] returnAttributes)
1085            throws NamingException
1086    {
1087        NamingEnumeration result = null;
1088
1089        if (returnAttributes != null && returnAttributes.length == 0)
1090            returnAttributes = new String JavaDoc[]{"objectClass"};
1091
1092        /* specify search constraints to search subtree */
1093        SearchControls constraints = new SearchControls();
1094
1095        constraints.setSearchScope(SearchControls.OBJECT_SCOPE);
1096        constraints.setCountLimit(limit);
1097        constraints.setTimeLimit(timeout);
1098
1099        constraints.setReturningAttributes(returnAttributes);
1100
1101        result = ctx.search(searchbase, filter, constraints);
1102
1103        return result;
1104    }
1105
1106
1107    /**
1108     * Performs a base object search (i.e. just a search of the current entry, nothing below it),
1109     * returning no attributes (i.e. just DNs);
1110     *
1111     * @param searchbase the domain name (relative to initial context in ldap) to seach from.
1112     * @param filter the non-null filter to use for the search
1113     * @param limit the maximum number of results to return
1114     * @param timeout the maximum time to wait before abandoning the search
1115     * @return list of search results ('SearchResult's); entries matching the search filter.
1116     */

1117
1118    public NamingEnumeration searchBaseEntry(String JavaDoc searchbase, String JavaDoc filter, int limit, int timeout)
1119            throws NamingException
1120    {
1121        return rawSearchBaseEntry(nameParser.parse(searchbase), filter, limit, timeout, new String JavaDoc[]{"objectClass"});
1122    }
1123
1124
1125    /**
1126     * Performs a base object search (i.e. just a search of the current entry, nothing below it).
1127     *
1128     * @param searchbase the domain name (relative to initial context in ldap) to seach from.
1129     * @param filter the non-null filter to use for the search
1130     * @param limit the maximum number of results to return
1131     * @param timeout the maximum time to wait before abandoning the search
1132     * @param returnAttributes an array of strings containing the names of attributes to search. (null = all, empty array = none)
1133     * @return list of search results ('SearchResult's); entries matching the search filter.
1134     */

1135
1136    public NamingEnumeration searchBaseEntry(String JavaDoc searchbase, String JavaDoc filter, int limit,
1137                                             int timeout, String JavaDoc[] returnAttributes)
1138            throws NamingException
1139    {
1140        return rawSearchBaseEntry(nameParser.parse(searchbase), filter, limit, timeout, returnAttributes);
1141/*
1142         NamingEnumeration result = null;
1143
1144         if (returnAttributes != null && returnAttributes.length == 0)
1145             returnAttributes = new String[] {"objectClass"};
1146
1147         // specify search constraints to search subtree
1148         SearchControls constraints = new SearchControls();
1149
1150         constraints.setSearchScope(SearchControls.OBJECT_SCOPE);
1151         constraints.setCountLimit(limit);
1152         constraints.setTimeLimit(timeout);
1153
1154         constraints.setReturningAttributes(returnAttributes);
1155
1156         result = ctx.search(searchbase, filter, constraints);
1157
1158         return result;
1159*/

1160    }
1161
1162
1163    /**
1164     * This method allows an object to be renamed, while also specifying
1165     * the exact fate of the old name.
1166     *
1167     * @param OldDN the original name to be changed
1168     * @param NewDN the new name
1169     * @param deleteOldRDN whether the rdn of the old name should be removed,
1170     * or retained as a second attribute value.
1171     */

1172
1173    public void renameEntry(Name OldDN, Name NewDN, boolean deleteOldRDN)
1174            throws NamingException
1175    {
1176        String JavaDoc value = (deleteOldRDN) ? "true" : "false";
1177        try
1178        {
1179            ctx.addToEnvironment("java.naming.ldap.deleteRDN", value);
1180
1181            renameEntry(OldDN, NewDN);
1182
1183            ctx.addToEnvironment("java.naming.ldap.deleteRDN", "false"); // reset to default of 'false' afterwards.
1184
}
1185        catch (NamingException e)
1186        {
1187            ctx.addToEnvironment("java.naming.ldap.deleteRDN", "false"); // reset to default of 'false' afterwards.
1188
throw e;
1189        }
1190    }
1191
1192
1193
1194
1195
1196// *********************************
1197

1198
1199    /**
1200     * <p>A wrapper for context.rename... changes the
1201     * distinguished name of an object.</p>
1202     * <p/>
1203     * <p>WARNING! this will fail for single valued manditory attributes.
1204     * since using 'deleteRDN = false' - use renameEntry(old, new, deleteOldRdn)
1205     * method instead - 30 May 2002.</p>
1206     *
1207     * @param oldDN current distinguished name of an object.
1208     * @param newDN the name it is to be changed to.
1209     */

1210
1211    public void renameEntry(String JavaDoc oldDN, String JavaDoc newDN)
1212            throws NamingException
1213    {
1214        ctx.rename(oldDN, newDN);
1215    }
1216
1217
1218    /**
1219     * Copies an object to a new DN by the simple expedient of adding
1220     * an object with the new DN, and the attributes of the old object.
1221     *
1222     * @param fromDN the original object being copied
1223     * @param toDN the new object being created
1224     */

1225
1226    public void copyEntry(String JavaDoc fromDN, String JavaDoc toDN)
1227            throws NamingException
1228    {
1229        addEntry(toDN, read(fromDN));
1230    }
1231
1232
1233    /**
1234     * creates a new object (subcontext) with the given
1235     * dn and attributes.
1236     *
1237     * @param dn the distinguished name of the new object
1238     * @param atts attributes for the new object
1239     */

1240
1241    public void addEntry(String JavaDoc dn, Attributes atts)
1242            throws NamingException
1243    {
1244        ctx.createSubcontext(dn, atts);
1245    }
1246
1247    /**
1248     * deletes a leaf entry (subcontext). It is
1249     * an error to attempt to delete an entry which is not a leaf
1250     * entry, i.e. which has children.
1251     */

1252
1253    public void deleteEntry(String JavaDoc dn)
1254            throws NamingException
1255    {
1256        ctx.destroySubcontext(dn);
1257    }
1258
1259
1260    /**
1261     * Reads all the attribute type and values for the given entry.
1262     *
1263     * @param dn the ldap string distinguished name of entry to be read
1264     * @return an 'Attributes' object containing a list of all Attribute
1265     * objects.
1266     */

1267
1268    public synchronized Attributes read(String JavaDoc dn)
1269            throws NamingException
1270    {
1271        return read(dn, null);
1272    }
1273
1274
1275    /**
1276     * Reads all the attribute type and values for the given entry.
1277     *
1278     * @param dn the ldap string distinguished name of entry to be read
1279     * @param returnAttributes a list of specific attributes to return.
1280     * @return an 'Attributes' object containing a list of all Attribute
1281     * objects.
1282     */

1283
1284    public synchronized Attributes read(String JavaDoc dn, String JavaDoc[] returnAttributes)
1285            throws NamingException
1286    {
1287        return ctx.getAttributes(dn, returnAttributes);
1288    }
1289
1290    /**
1291     * Modifies an object's attributes, either adding, replacing or
1292     * deleting the passed attributes.
1293     *
1294     * @param dn distinguished name of object to modify
1295     * @param mod_type the modification type to be performed; one of
1296     * DirContext.REPLACE_ATTRIBUTE, DirContext.DELETE_ATTRIBUTE, or
1297     * DirContext.ADD_ATTRIBUTE.
1298     * @param attr the new attributes to update the object with.
1299     */

1300
1301    public void modifyAttributes(String JavaDoc dn, int mod_type, Attributes attr)
1302            throws NamingException
1303    {
1304        ctx.modifyAttributes(dn, mod_type, attr);
1305    }
1306
1307
1308    /**
1309     * Modifies an object's attributes, either adding, replacing or
1310     * deleting the passed attributes.
1311     *
1312     * @param dn distinguished name of object to modify
1313     * @param modList a list of ModificationItems
1314     */

1315
1316    public void modifyAttributes(String JavaDoc dn, ModificationItem[] modList)
1317            throws NamingException
1318    {
1319        ctx.modifyAttributes(dn, modList);
1320    }
1321
1322    /**
1323     * Updates an object with a new set of attributes
1324     *
1325     * @param dn distinguished name of object to update
1326     * @param atts the new attributes to update the object with.
1327     */

1328
1329    public void updateEntry(String JavaDoc dn, Attributes atts)
1330            throws NamingException
1331    {
1332        modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, atts);
1333    }
1334
1335
1336    /**
1337     * deletes an attribute from an object
1338     *
1339     * @param dn distinguished name of object
1340     * @param a the attribute to delete
1341     */

1342
1343    public void deleteAttribute(String JavaDoc dn, Attribute a)
1344            throws NamingException
1345    {
1346        BasicAttributes atts = new BasicAttributes();
1347        atts.put(a);
1348        modifyAttributes(dn, DirContext.REMOVE_ATTRIBUTE, atts);
1349    }
1350
1351    /**
1352     * deletes a set of attribute-s from an object
1353     *
1354     * @param dn distinguished name of object
1355     * @param a the Attributes object containing the
1356     * list of attribute-s to delete
1357     */

1358
1359
1360    public void deleteAttributes(String JavaDoc dn, Attributes a)
1361            throws NamingException
1362    {
1363        modifyAttributes(dn, DirContext.REMOVE_ATTRIBUTE, a);
1364    }
1365
1366    /**
1367     * updates an Attribute with a new value set
1368     *
1369     * @param dn distinguished name of object
1370     * @param a the attribute to modify
1371     */

1372
1373    public void updateAttribute(String JavaDoc dn, Attribute a)
1374            throws NamingException
1375    {
1376        BasicAttributes atts = new BasicAttributes();
1377        atts.put(a);
1378        modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, atts);
1379    }
1380
1381    /**
1382     * updates a set of Attribute-s.
1383     *
1384     * @param dn distinguished name of object
1385     * @param a an Attributes object containing the attribute-s to modify
1386     */

1387
1388    public void updateAttributes(String JavaDoc dn, Attributes a)
1389            throws NamingException
1390    {
1391        modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, a);
1392    }
1393
1394    /**
1395     * Adds a new attribute to a particular dn.
1396     *
1397     * @param dn distinguished name of object
1398     * @param a the attribute to modify
1399     */

1400
1401    public void addAttribute(String JavaDoc dn, Attribute a)
1402            throws NamingException
1403    {
1404        BasicAttributes atts = new BasicAttributes();
1405        atts.put(a);
1406        modifyAttributes(dn, DirContext.ADD_ATTRIBUTE, atts);
1407    }
1408
1409    /**
1410     * Adds a set of attributes to a particular dn.
1411     *
1412     * @param dn distinguished name of object
1413     * @param a the Attributes (set of attribute-s) to add
1414     */

1415
1416    public void addAttributes(String JavaDoc dn, Attributes a)
1417            throws NamingException
1418    {
1419        modifyAttributes(dn, DirContext.ADD_ATTRIBUTE, a);
1420    }
1421
1422
1423    /**
1424     * returns the next level of a directory tree, returning
1425     * a Enumeration of the results, *relative* to the SearchBase (i.e. not as
1426     * absolute DNs), along with their object classes if possible.
1427     * <p/>
1428     * <p>WARNING - due to jndi wierdness, sometimes the entries are *not* relative, but are full DNs.</p>
1429     *
1430     * @param searchbase the node in the tree to expand
1431     * @return list of results (NameClassPair); the next layer of the tree...
1432     */

1433
1434    public NamingEnumeration list(String JavaDoc searchbase)
1435            throws NamingException
1436    {
1437        // Attempt to read the names of the next level of subentries along with their object
1438
// classes. Failing that, try to just read their names.
1439

1440        return rawSearchOneLevel(nameParser.parse(searchbase), "(objectclass=*)", 0, 0, new String JavaDoc[]{"1.1"});
1441    }
1442
1443    /**
1444     * Method that calls the actual search on the jndi context.
1445     *
1446     * @param searchbase the domain name (relative to initial context in ldap) to seach from.
1447     * @param filter the non-null filter to use for the search
1448     * @param limit the maximum number of results to return
1449     * @param timeout the maximum time to wait before abandoning the search
1450     * @param returnAttributes an array of strings containing the names of attributes to search. (null = all, empty array = none)
1451     * @return
1452     * @throws NamingException
1453     */

1454/*
1455     private NamingEnumeration rawOneLevelSearch(String searchbase, String filter, int limit,
1456                 int timeout, String[] returnAttributes ) throws NamingException
1457     {
1458         // specify search constraints to search one level
1459         SearchControls constraints = new SearchControls();
1460
1461         constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
1462         constraints.setCountLimit(limit);
1463         constraints.setTimeLimit(timeout);
1464
1465         constraints.setReturningAttributes(returnAttributes);
1466
1467         NamingEnumeration results = ctx.search(searchbase, filter, constraints);
1468
1469         return results;
1470
1471     }
1472*/

1473
1474
1475
1476    /**
1477     * Shuts down the current context.<p>
1478     * nb. It is not an error to call this method multiple times.
1479     */

1480
1481    public void close()
1482            throws NamingException
1483    {
1484        if (ctx == null) return; // it is not an error to multiply disconnect.
1485
nameParser = null;
1486        ctx.close();
1487        //TODO: decide if we should set ctx to null at this point
1488
}
1489
1490    /**
1491     * This method allows an object to be renamed, while also specifying
1492     * the exact fate of the old name.
1493     *
1494     * @param OldDN the original name to be changed
1495     * @param NewDN the new name
1496     * @param deleteOldRDN whether the rdn of the old name should be removed,
1497     * or retained as a second attribute value.
1498     */

1499
1500    public void renameEntry(String JavaDoc OldDN, String JavaDoc NewDN, boolean deleteOldRDN)
1501            throws NamingException
1502    {
1503        String JavaDoc value = (deleteOldRDN) ? "true" : "false";
1504        try
1505        {
1506            ctx.addToEnvironment("java.naming.ldap.deleteRDN", value);
1507
1508            renameEntry(OldDN, NewDN);
1509
1510            ctx.addToEnvironment("java.naming.ldap.deleteRDN", "false"); // reset to default of 'false' afterwards.
1511
}
1512        catch (NamingException e)
1513        {
1514            ctx.addToEnvironment("java.naming.ldap.deleteRDN", "false"); // reset to default of 'false' afterwards.
1515
throw e; // rethrow exception...
1516
}
1517    }
1518
1519    // -----------------------------------------------------------------------
1520
// Name related magic - put here for now, but probably should go elsewhere
1521

1522    private static Properties nameParserSyntax = null;
1523
1524    /**
1525     * setupLDAPSyntax
1526     * <p/>
1527     * Set up the syntax rules for parsing LDAP DNs when creating Name objects.
1528     */

1529    private static void setupLDAPSyntax()
1530    {
1531        nameParserSyntax = new Properties();
1532
1533        nameParserSyntax.put("jndi.syntax.direction", "right_to_left");
1534        nameParserSyntax.put("jndi.syntax.separator", ",");
1535        nameParserSyntax.put("jndi.syntax.escape", "\\");
1536
1537        // Not currently used, as the parser seems to preferentially quote rather than use escape chars. May be an issue with LDAPv2
1538
// quoted RDNs.
1539
// nameParserSyntax.put("jndi.syntax.beginquote", "\"")
1540
// nameParserSyntax.put("jndi.syntax.endquote", "\"");
1541

1542        nameParserSyntax.put("jndi.syntax.trimblanks", "true");
1543        nameParserSyntax.put("jndi.syntax.separator.typeval", "=");
1544    }
1545
1546    /**
1547     * getNameFromString
1548     * <p/>
1549     * Convert DN String into JNDI Name,
1550     *
1551     * @param iDN DN in String.
1552     * @return the resulting name
1553     */

1554    //TODO: decide if this method should be static or not - should use the syntax of the current connection, after all
1555
public static Name getNameFromString(String JavaDoc iDN)
1556            throws NamingException
1557    {
1558        // iDN is assumed to either:
1559
// a) contain an LDAP DN, without either server/port information or
1560
// namespace identifier ('ldap://').
1561
// or:
1562
// b) Contain a full URL ('ldap://server:port/o=...').
1563

1564        // Parse it and return it.
1565

1566        String JavaDoc DN = iDN;
1567        Name CompositeFormDN = null;
1568        CompoundName CompoundFormDN = null;
1569
1570        if (iDN.indexOf("ldap://") != -1)
1571        {
1572            // iDN contains the string 'ldap://', and therefore has
1573
// at least 2 name spaces. Instantiate a Composite name
1574
// object and strip off the name we want.
1575
CompositeFormDN = new CompositeName(iDN);
1576            if (CompositeFormDN.size() != 0)
1577                DN = CompositeFormDN.get(CompositeFormDN.size() - 1);
1578        }
1579
1580        if (nameParserSyntax == null)
1581            setupLDAPSyntax();
1582
1583        CompoundFormDN = new CompoundName(DN, nameParserSyntax);
1584
1585        return CompoundFormDN;
1586    }
1587
1588    /**
1589     * getNameFromSearchResult
1590     * <p/>
1591     * Given a SearchResult object and Base DN, work out the complete DN of the entry, parse it into a Name object and return it.
1592     *
1593     * @param iDirectoryEntry JNDI SearchResult object containing a Directory entry.
1594     * @param iBaseDN Name object with the Base DN used for the search (may be empty).
1595     * @return Name object containing the complete DN of the entry.
1596     */

1597    //TODO: decide if this method should be static or not - should use the syntax of the current connection, after all
1598
public static Name getNameFromSearchResult(SearchResult iDirectoryEntry, Name iBaseDN)
1599            throws InvalidNameException, NamingException
1600    {
1601        // Get RDN from a string. Parse it and if required add the base DN to it, and
1602
// then return it as a JNDI Name object.
1603
// Tim Bentley
1604
// 20010404
1605

1606        // Take care of the JNDI trailing whitespace problem:
1607
String JavaDoc RDN = applyJNDIRDNBugWorkAround(iDirectoryEntry.getName());
1608
1609        Name JNDIRDN = getNameFromString(RDN);
1610
1611        if (JNDIRDN != null)
1612        { // if the name is relative, insert the base DN
1613
if (iDirectoryEntry.isRelative())
1614                JNDIRDN.addAll(0, iBaseDN);
1615        }
1616        else
1617            JNDIRDN = (Name) iBaseDN.clone(); // if the RDN is null, use the base DN
1618

1619        return JNDIRDN;
1620    }
1621
1622    /**
1623     * applyJNDIRDNBugWorkAround
1624     * <p/>
1625     * Cope with escaping bug in JNDI RDN handling.
1626     *
1627     * @param iRDN String containing RDN to check escaping on.
1628     * @return String containing correctly escaped RDN.
1629     */

1630    private static String JavaDoc applyJNDIRDNBugWorkAround(String JavaDoc iRDN)
1631    {
1632
1633        // Tim Bentley
1634
// 20010328
1635
// JNDI's SearchResult.getName() removes any trailing space character from the
1636
// RDN without also removing the LDAP escaping character ('\') - in fact it
1637
// then escapes the '\' character, resulting in '\\' at the end of the RDN.
1638
// Parse the passed in RDN and if the last two chars are '\'s, remove
1639
// them.
1640

1641// int SlashPos = iRDN.indexOf("\\");
1642
int SlashPos = iRDN.lastIndexOf("\\\\"); // AJR: need LAST occurrence, and need to escape backslashes
1643
String JavaDoc ReturnString;
1644
1645        if (SlashPos == iRDN.length() - 2)
1646            ReturnString = iRDN.substring(0, SlashPos);
1647        else
1648            ReturnString = iRDN;
1649
1650        return ReturnString;
1651    }
1652
1653
1654    public DirContext getContext()
1655    {
1656        return ctx;
1657    }
1658
1659    public void setContext(DirContext ctx)
1660    {
1661        this.ctx = ctx;
1662
1663        try
1664        {
1665            nameParser = ctx.getNameParser("");
1666        }
1667        catch (NamingException e)
1668        {
1669            // TODO: add logging to this class :-)
1670
System.out.println("Error initialising name parser " + e);
1671        }
1672    }
1673
1674
1675}
1676
Popular Tags