KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ca > jndiproviders > dsml > DsmlContext


1 package com.ca.jndiproviders.dsml;
2
3 import com.ca.commons.naming.DXNamingEnumeration;
4 import com.ca.commons.naming.DN;
5 import com.ca.commons.cbutil.CBBase64;
6 import com.ca.commons.cbutil.CBBase64EncodingException;
7
8 import javax.naming.directory.*;
9 import javax.naming.*;
10 import java.util.*;
11 import java.util.regex.Matcher JavaDoc;
12 import java.util.regex.Pattern JavaDoc;
13 import java.util.logging.Logger JavaDoc;
14 import java.util.logging.Level JavaDoc;
15 import java.io.UnsupportedEncodingException JavaDoc;
16 import java.io.IOException JavaDoc;
17
18 /**
19  * <p>This is a DSML jndiproviders context, that provides support for all the basic DSML operations.</p>
20  * <p/>
21  * <p>However, it deliberately does *not* support:</p>
22  * <ul>
23  * <li>binding java objects - the bind methods are not implemented. Users will need to do their
24  * own serialisation if they want this functionality.
25  * <li> referrals aren't implemented yet.
26  * <li> ldap v3 extensions.
27  * <li> composite names spanning multiple namespaces - all names are DSML names only. (Composite
28  * names are a bloody stupid idea that come close to making jndiproviders unusable, IMNSHO - CB)
29  * </ul>
30  * <p/>
31  * <p>If you need the above features, you may be better off using the Sun DSML provider which is
32  * far more ambitious in scope (but which has some licencing problems, and is a bit rocky in parts).</p>
33  */

34
35 /*
36  * DSML SERVER ERROR NOTES
37  *
38  * Server is incorrectly parsing escaped dns - e.g. dn="cn=Vivienne \"LEVER\", ... ".
39  * Server is returning HTTP 500, which prevents parsing of returned soap error
40  *
41  */

42
43
44 //TODO: schema support??
45

46 //TODO: we ended up using strings rather than names, so adjust all methods to chain to the string methods instead of the name methods.
47

48 //TODO: makes heavy use of static stuff - may not be thread safe.
49

50 public class DsmlContext implements DirContext
51 {
52
53     // Formatting
54
private static int TABLEN = 4;
55     private static String JavaDoc TAB = " ";
56     private static String JavaDoc TAB2 = TAB + TAB;
57     private static String JavaDoc TAB3 = TAB2 + TAB;
58     private static String JavaDoc TAB4 = TAB3 + TAB;
59     private static String JavaDoc TAB5 = TAB4 + TAB;
60     private static String JavaDoc TAB6 = TAB5 + TAB;
61
62     private static String JavaDoc SOAPHEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
63             TAB + "<soap-env:Envelope xmlns:soap-env=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" +
64             TAB2 + "<soap-env:Body>\n";
65
66     private static String JavaDoc SOAPFOOTER = TAB2 + "</soap-env:Body>\n" +
67             TAB + "</soap-env:Envelope>";
68
69     private static String JavaDoc DSMLHEADER = TAB3 + "<dsml:batchRequest xmlns:dsml=\"urn:oasis:names:tc:DSML:2:0:core\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n";
70
71     private static String JavaDoc DSMLFOOTER = TAB3 + "</dsml:batchRequest>\n";
72
73     private static String JavaDoc SEARCHFOOTER = TAB3 + "</dsml:searchRequest>\n";
74
75     private static String JavaDoc STANDARDHEADER = SOAPHEADER + DSMLHEADER;
76
77     private static String JavaDoc STANDARDFOOTER = DSMLFOOTER + SOAPFOOTER;
78
79     // SEARCH SCOPE CONSTANTS
80
private static String JavaDoc BASEOBJECT = "baseObject";
81     private static String JavaDoc SINGLELEVEL = "singleLevel";
82     private static String JavaDoc WHOLESUBTREE = "wholeSubtree";
83     private static String JavaDoc[] SCOPEOPTIONS = new String JavaDoc[]{BASEOBJECT, SINGLELEVEL, WHOLESUBTREE};
84
85     // SEARCH ALIAS DEREF OPTIONS
86
private static String JavaDoc NEVER = "neverDerefAliases";
87     private static String JavaDoc SEARCHING = "derefInSearching";
88     private static String JavaDoc FINDING = "derefFindingBaseObj";
89     private static String JavaDoc ALWAYS = "derefAlways";
90     private static String JavaDoc[] ALIASOPTIONS = new String JavaDoc[]{NEVER, SEARCHING, FINDING, ALWAYS};
91
92     // Default Search Controls
93
private static SearchControls DEFAULT_SEARCH_CONTROLS = new SearchControls();
94
95
96     protected Hashtable environment;
97
98     // the name of the context (as set by 'createSubcontext' methods
99
private String JavaDoc contextName = "";
100
101     private static Logger JavaDoc log = Logger.getLogger(DsmlContext.class.getName());
102
103     // Debug
104
{
105         log.setLevel(Level.FINEST);
106     }
107
108     /**
109      * <p>This constructs the XML tag and tag attributes for a DSML search operation start tag. (filter and attributes elements
110      * must be added separately, the end tag is a defined constant.)
111      * <p/>
112      * <p>e.g.
113      * <pre>
114      * &lt;dsml:searchRequest requestID="1" dn="cn=schema" scope="baseObject" derefAliases="derefInSearching"&gt;
115      * </pre>
116      * <p/>
117      * something like
118      *
119      * @param searchHeader an existing string buffer to append the search header information in.
120      * @param dn the dn to search from
121      * @param scope the search scope, must be one of 'baseLevel', 'singleLevel' or 'wholeSubtree' (see constants)
122      * @param derefAliases how to handle aliases. must be on of 'neverDerefAliases', 'derefInSearching', 'derefFindingBaseObj', 'derefAlways' (see constants)
123      * @param sizeLimit the LDAP search size limit - default is 0 (unlimited by client)
124      * @param timeLimit the LDAP search time limit - default is 0 (unlimited by client)
125      * @param typesOnly - no idea, but it's in the spec.
126      * @return a string buffer containing the raw xml for a search operation.
127      * @throws NamingException
128      */

129     private static StringBuffer JavaDoc getSearchRequestHeader(StringBuffer JavaDoc searchHeader, String JavaDoc dn, String JavaDoc scope, String JavaDoc derefAliases, long sizeLimit, int timeLimit, boolean typesOnly)
130             throws NamingException
131     {
132         // sanity check arguments
133
if (!checkValidity(SCOPEOPTIONS, scope))
134             throw new NamingException("search scope argument '" + scope + "' is invalid");
135
136         if (!checkValidity(ALIASOPTIONS, derefAliases))
137         {
138             if (derefAliases != null) log.info("bad alias option passed '" + derefAliases + "'");
139             derefAliases = SEARCHING;
140         }
141
142         if (searchHeader == null)
143             searchHeader = new StringBuffer JavaDoc();
144
145         searchHeader.append(TAB4 + "<dsml:searchRequest dn=\""); // TODO: requestID is optional - let's drop it in the final version - CB
146
searchHeader.append(escapeName(dn));
147         searchHeader.append("\" scope=\"").append(scope);
148         searchHeader.append("\" derefAliases=\"").append(derefAliases);
149
150         if (sizeLimit > 0)
151             searchHeader.append("\" sizeLimit=\"").append(sizeLimit);
152
153         if (timeLimit > 0)
154             searchHeader.append("\" timeLimit=\"").append(timeLimit);
155
156         // never used?
157
if (typesOnly == true)
158             searchHeader.append("\" typesOnly=\"").append(typesOnly);
159
160         searchHeader.append("\">\n");
161
162         log.finest("created search header: " + searchHeader);
163
164         return searchHeader;
165     }
166
167
168     /**
169      * <p>This constructs the XML tag and elements for a DSML search attribute list
170      * <p/>
171      * e.g.
172      * <pre>
173      * &gt;dsml:attributes&lt;
174      * &gt;dsml:attribute name="attributeTypes"/&lt;
175      * &gt;dsml:attribute name="objectClasses"/&lt;
176      * &gt;dsml:attribute name="matchingRules"/&lt;
177      * &gt;dsml:attribute name="ldapSyntaxes"/&lt;
178      * &gt;dsml:attribute name="*"/&lt;
179      * &gt;/dsml:attributes&lt;
180      * </pre>
181      *
182      * @param searchAttributes an existing string buffer to append the search attribute list information to
183      * @param attributes a string array of attribute names
184      * @return
185      */

186     private static StringBuffer JavaDoc getSearchRequestAttributes(StringBuffer JavaDoc searchAttributes, String JavaDoc[] attributes)
187     {
188         if (attributes == null || attributes.length == 0)
189         {
190             return searchAttributes; // do nothing if there are no specific attributes (means return all)
191
}
192
193         if (searchAttributes == null)
194             searchAttributes = new StringBuffer JavaDoc(40 + 80 * attributes.length);
195
196         searchAttributes.append(TAB5 + "<dsml:attributes>\n");
197
198         int len = attributes.length;
199         for (int i = 0; i < len; i++)
200         {
201             searchAttributes.append(TAB6 + "<dsml:attribute name=\"").append(attributes[i]).append("\"/>\n");
202         }
203
204         searchAttributes.append(TAB5 + "</dsml:attributes>\n");
205
206         log.finest("created search attribute list: " + searchAttributes);
207
208         return searchAttributes;
209     }
210
211
212     /**
213      * Small utility method to check that a string is one of a number of valid options.
214      *
215      * @param options an array of valid options.
216      * @param checkme a string to compare against the previous array.
217      * @return true if checkme is one of the options, false if it is null, or is not one of the options.
218      */

219     private static boolean checkValidity(String JavaDoc[] options, String JavaDoc checkme)
220     {
221         if (checkme != null)
222         {
223             for (int i = 0; i < options.length; i++)
224                 if (options[i].equals(checkme))
225                     return true;
226         }
227         return false;
228     }
229
230     /**
231      * A little test method to produce dummy enumerations during development
232      *
233      * @param name
234      * @param num
235      * @return
236      */

237     private NamingEnumeration getTestEnumeration(Name name, int num)
238     {
239         log.finest("generating " + num + " test names from '" + name.toString() + "'");
240
241         String JavaDoc itemBase = "c=AU";
242         if (name.size() == 1)
243             itemBase = "o=beta";
244         else if (name.size() == 2)
245             itemBase = "ou=gamma";
246         else if (name.size() == 3)
247             itemBase = "ou=delta";
248
249         DXNamingEnumeration testEnumeration = new DXNamingEnumeration();
250         for (int i = 0; i < num; i++)
251         {
252             String JavaDoc itemName = itemBase + i;
253             testEnumeration.add(new SearchResult(itemName, null, getTestAttributes(itemName)));
254         }
255
256         return testEnumeration;
257     }
258
259     /**
260      * A little test method to produce dummy attributes objects during development.
261      *
262      * @param name
263      * @return
264      */

265     private BasicAttributes getTestAttributes(String JavaDoc name)
266     {
267         log.finest("generating test data from name '" + name + "'");
268         BasicAttributes testAttributes = new BasicAttributes("cn", name);
269         testAttributes.put(new BasicAttribute("objectClass", "person"));
270         testAttributes.put(new BasicAttribute("sn", "Test"));
271         return testAttributes;
272     }
273
274     private DsmlContext()
275     {
276     } // don't do this :-).
277

278
279     private DsmlContext(String JavaDoc baseDN, Hashtable env)
280     {
281         contextName = baseDN==null?"":baseDN;
282
283         environment = env; // Don't see any reason to clone environment for every entry?
284
}
285
286     /**
287      * Called by DsmlCtxFactory / also used for testing
288      *
289      * @param env the environment to use for this connection...
290      */

291     DsmlContext(Hashtable env)
292     {
293         environment = (env == null) ? new Hashtable() : env;
294         log.fine("Created DsmlContext");
295     }
296
297     /**
298      * Retrieves all of the attributes associated with a named object.
299      * See {@link #getAttributes(javax.naming.Name)} for details.
300      *
301      * @param name the name of the object from which to retrieve attributes
302      * @return the set of attributes associated with <code>name</code>
303      * @throws javax.naming.NamingException if a naming exception is encountered
304      */

305     public Attributes getAttributes(String JavaDoc name) throws NamingException
306     {
307         return getAttributes(name, null);
308     }
309
310     /**
311      * Modifies the attributes associated with a named object.
312      * See {@link #modifyAttributes(javax.naming.Name, int, javax.naming.directory.Attributes)} for details.
313      *
314      * @param name the name of the object whose attributes will be updated
315      * @param mod_op the modification operation, one of:
316      * <code>ADD_ATTRIBUTE</code>,
317      * <code>REPLACE_ATTRIBUTE</code>,
318      * <code>REMOVE_ATTRIBUTE</code>.
319      * @param attrs the attributes to be used for the modification; map not be null
320      * @throws javax.naming.directory.AttributeModificationException if the modification cannot
321      * be completed successfully
322      * @throws javax.naming.NamingException if a naming exception is encountered
323      */

324     public void modifyAttributes(String JavaDoc name, int mod_op, Attributes attrs) throws NamingException
325     {
326         ModificationItem[] mods = new ModificationItem[attrs.size()];
327         Enumeration attObjects = attrs.getAll();
328         int i = 0;
329         while (attObjects.hasMoreElements())
330         {
331             mods[i++] = new ModificationItem(mod_op, (Attribute) attObjects.nextElement());
332         }
333
334         modifyAttributes(name, mods);
335
336     }
337
338     /**
339      * Retrieves all of the attributes associated with a named object.
340      * See the class description regarding attribute models, attribute
341      * type names, and operational attributes.
342      *
343      * @param name the name of the object from which to retrieve attributes
344      * @return the set of attributes associated with <code>name</code>.
345      * Returns an empty attribute set if name has no attributes;
346      * never null.
347      * @throws javax.naming.NamingException if a naming exception is encountered
348      * @see #getAttributes(String)
349      * @see #getAttributes(javax.naming.Name, String[])
350      */

351     public Attributes getAttributes(Name name) throws NamingException
352     {
353         return getAttributes(name, null);
354     }
355
356     /**
357      * Modifies the attributes associated with a named object.
358      * The order of the modifications is not specified. Where
359      * possible, the modifications are performed atomically.
360      *
361      * @param name the name of the object whose attributes will be updated
362      * @param mod_op the modification operation, one of:
363      * <code>ADD_ATTRIBUTE</code>,
364      * <code>REPLACE_ATTRIBUTE</code>,
365      * <code>REMOVE_ATTRIBUTE</code>.
366      * @param attrs the attributes to be used for the modification; may not be null
367      * @throws javax.naming.directory.AttributeModificationException if the modification cannot
368      * be completed successfully
369      * @throws javax.naming.NamingException if a naming exception is encountered
370      * @see #modifyAttributes(javax.naming.Name, javax.naming.directory.ModificationItem[])
371      */

372     public void modifyAttributes(Name name, int mod_op, Attributes attrs) throws NamingException
373     {
374         modifyAttributes(name.toString(), mod_op, attrs);
375     }
376
377     /**
378      * Retrieves the schema associated with the named object.
379      * See {@link #getSchema(javax.naming.Name)} for details.
380      *
381      * @param name the name of the object whose schema is to be retrieved
382      * @return the schema associated with the context; never null
383      * @throws javax.naming.OperationNotSupportedException if schema not supported
384      * @throws javax.naming.NamingException if a naming exception is encountered
385      * @deprecated not yet implemented
386      */

387     public DirContext getSchema(String JavaDoc name) throws NamingException
388     {
389         throw new OperationNotSupportedException("DsmlContext does not support reading schema (yet)");
390     }
391
392     /**
393      * Retrieves a context containing the schema objects of the
394      * named object's class definitions.
395      * See {@link #getSchemaClassDefinition(javax.naming.Name)} for details.
396      *
397      * @param name the name of the object whose object class
398      * definition is to be retrieved
399      * @return the <tt>DirContext</tt> containing the named
400      * object's class definitions; never null
401      * @throws javax.naming.OperationNotSupportedException if schema not supported
402      * @throws javax.naming.NamingException if a naming exception is encountered
403      * @deprecated not yet implemented
404      */

405     public DirContext getSchemaClassDefinition(String JavaDoc name) throws NamingException
406     {
407         throw new OperationNotSupportedException("DsmlContext does not support reading schema (yet)");
408     }
409
410     /**
411      * Retrieves the schema associated with the named object.
412      * The schema describes rules regarding the structure of the namespace
413      * and the attributes stored within it. The schema
414      * specifies what types of objects can be added to the directory and where
415      * they can be added; what mandatory and optional attributes an object
416      * can have. The range of support for schemas is directory-specific.
417      * <p/>
418      * <p> This method returns the root of the schema information tree
419      * that is applicable to the named object. Several named objects
420      * (or even an entire directory) might share the same schema.
421      * <p/>
422      * <p> Issues such as structure and contents of the schema tree,
423      * permission to modify to the contents of the schema
424      * tree, and the effect of such modifications on the directory
425      * are dependent on the underlying directory.
426      *
427      * @param name the name of the object whose schema is to be retrieved
428      * @return the schema associated with the context; never null
429      * @throws javax.naming.OperationNotSupportedException if schema not supported
430      * @throws javax.naming.NamingException if a naming exception is encountered
431      * @deprecated not yet implemented
432      */

433     public DirContext getSchema(Name name) throws NamingException
434     {
435         throw new OperationNotSupportedException("DsmlContext does not support reading schema (yet)");
436     }
437
438     /**
439      * Retrieves a context containing the schema objects of the
440      * named object's class definitions.
441      * <p/>
442      * One category of information found in directory schemas is
443      * <em>class definitions</em>. An "object class" definition
444      * specifies the object's <em>type</em> and what attributes (mandatory
445      * and optional) the object must/can have. Note that the term
446      * "object class" being referred to here is in the directory sense
447      * rather than in the Java sense.
448      * For example, if the named object is a directory object of
449      * "Person" class, <tt>getSchemaClassDefinition()</tt> would return a
450      * <tt>DirContext</tt> representing the (directory's) object class
451      * definition of "Person".
452      * <p/>
453      * The information that can be retrieved from an object class definition
454      * is directory-dependent.
455      * <p/>
456      * Prior to JNDI 1.2, this method
457      * returned a single schema object representing the class definition of
458      * the named object.
459      * Since JNDI 1.2, this method returns a <tt>DirContext</tt> containing
460      * all of the named object's class definitions.
461      *
462      * @param name the name of the object whose object class
463      * definition is to be retrieved
464      * @return the <tt>DirContext</tt> containing the named
465      * object's class definitions; never null
466      * @throws javax.naming.OperationNotSupportedException if schema not supported
467      * @throws javax.naming.NamingException if a naming exception is encountered
468      * @deprecated not yet implemented
469      */

470     public DirContext getSchemaClassDefinition(Name name) throws NamingException
471     {
472         throw new OperationNotSupportedException("DsmlContext does not support reading schema (yet)");
473     }
474
475     /**
476      * Modifies the attributes associated with a named object using
477      * an ordered list of modifications.
478      * See {@link #modifyAttributes(javax.naming.Name, javax.naming.directory.ModificationItem[])} for details.
479      *
480      * @param name the name of the object whose attributes will be updated
481      * @param mods an ordered sequence of modifications to be performed;
482      * may not be null
483      * @throws javax.naming.directory.AttributeModificationException if the modifications
484      * cannot be completed successfully
485      * @throws javax.naming.NamingException if a naming exception is encountered
486      */

487     public void modifyAttributes(String JavaDoc name, ModificationItem[] mods) throws NamingException
488     {
489         log.finest("modify Atts of (" + name + ")");
490
491         // construct XML
492

493         StringBuffer JavaDoc modRequestBuffer = constructModRequest(name, mods);
494
495         // send XML to server
496

497         String JavaDoc response = sendDSMLRequest(modRequestBuffer);
498
499         // parse response XML
500

501         parseModResponse(response);
502
503     }
504
505     private StringBuffer JavaDoc constructModRequest(String JavaDoc name, ModificationItem[] mods)
506             throws NamingException
507     {
508         StringBuffer JavaDoc message = new StringBuffer JavaDoc(200);
509         message.append(STANDARDHEADER);
510
511         getModRequestElement(message, name, mods);
512
513         message.append(STANDARDFOOTER);
514
515         return message;
516     }
517
518     /**
519      * @param message
520      * @param name
521      * @param mods
522      */

523     /*
524     " <dsml:modifyRequest dn=\"cn=Alana SHORE,ou=Infrastructure,ou=Support,o=DEMOCORP,c=AU\">" +
525     " <dsml:modification name=\"favoriteDrink\" operation=\"add\">" +
526     " <dsml:value>japanese slipper</dsml:value>" +
527     " </dsml:modification>" +
528     " <dsml:modification name=\"address\" operation=\"delete\">\n" +
529     " <dsml:value>21 Jump Street$New York$90210</dsml:value>\n" +
530     " </dsml:modification>\n" +
531     " <dsml:modification name=\"userPassword\" operation=\"replace\">" +
532     " <dsml:value xsi:type=\"xsd:base64Binary\">c2VjcmV0IHBhc3N3b3Jk</dsml:value>" +
533     " </dsml:modification>" +
534     " </dsml:modifyRequest>" +
535     */

536     static void getModRequestElement(StringBuffer JavaDoc message, String JavaDoc name, ModificationItem[] mods)
537             throws NamingException
538     {
539         message.append(TAB4).append("<dsml:modifyRequest dn=\"").append(escapeName(name)).append("\">\n");
540
541         for (int i = 0; i < mods.length; i++)
542         {
543             Attribute att = mods[i].getAttribute();
544             NamingEnumeration values = att.getAll();
545             switch (mods[i].getModificationOp())
546             {
547                 case ADD_ATTRIBUTE:
548
549                     message.append(TAB5).append("<dsml:modification name=\"").append(att.getID()).append("\" operation=\"add\">\n");
550
551                     while (values.hasMore())
552                         createDsmlValueElement(values.next(), message);
553
554                     message.append(TAB5).append("</dsml:modification>\n");
555
556                     break;
557
558                 case REPLACE_ATTRIBUTE:
559
560                     message.append(TAB5).append("<dsml:modification name=\"").append(att.getID()).append("\" operation=\"replace\">\n");
561
562                     while (values.hasMore())
563                         createDsmlValueElement(values.next(), message);
564
565                     message.append(TAB5).append("</dsml:modification>\n");
566
567                     break;
568
569                 case REMOVE_ATTRIBUTE:
570
571                     message.append(TAB5).append("<dsml:modification name=\"").append(att.getID()).append("\" operation=\"delete\">\n");
572
573                     while (values.hasMore())
574                         createDsmlValueElement(values.next(), message);
575
576                     message.append(TAB5).append("</dsml:modification>\n");
577
578                     break;
579             }
580
581         }
582         message.append(TAB4).append("</dsml:modifyRequest>\n");
583
584     }
585
586     void parseModResponse(String JavaDoc response)
587             throws NamingException
588     {
589         // check for a dsml error
590
checkForError(response);
591
592         // quick check that it really was a modify response...
593
if (response.indexOf("modifyResponse>") == -1)
594             throw new NamingException("Unexpected DSML Response to Modify Request:\n " + response);
595     }
596
597     //private static Pattern searchResultEntry = Pattern.compile("<searchResultEntry.*?dn=\"(.*?)\">(.*?)</searchResultEntry>", Pattern.DOTALL);
598

599     //TODO: tighten up...
600
private static Pattern JavaDoc errorResult = Pattern.compile("errorResponse.*?type=\"(.*?)\"", Pattern.DOTALL);
601     private static Pattern JavaDoc errorMessage = Pattern.compile("message>(.*?)</", Pattern.DOTALL);
602     private static Pattern JavaDoc errorDetail = Pattern.compile("detail>(.*?)</", Pattern.DOTALL);
603
604     private static Pattern JavaDoc ldapResultCode = Pattern.compile("code=\"(.*?)\"", Pattern.DOTALL);
605
606     private static Pattern JavaDoc ldapResultDesc = Pattern.compile("resultCode.*?descr=\"(.*?)\"", Pattern.DOTALL);
607
608     private static Pattern JavaDoc ldapResultMsg = Pattern.compile("errorMessage>(.*?)</", Pattern.DOTALL);
609
610     static void checkForError(String JavaDoc response)
611             throws NamingException
612     {
613         Matcher JavaDoc error = errorResult.matcher(response);
614         if (error.find())
615         {
616             String JavaDoc errorMsg = "Error Processing DSML Request: " + error.group(1);
617
618             Matcher JavaDoc message = errorMessage.matcher(response);
619             if (message.find())
620                 errorMsg = errorMsg + "\n" + message.group(1);
621
622             Matcher JavaDoc detail = errorDetail.matcher(response);
623             if (detail.find())
624                 errorMsg = errorMsg + "\n" + detail.group(1);
625
626             throw new NamingException(errorMsg);
627         }
628         else
629         {
630             try
631             {
632                 Matcher JavaDoc resultMatcher = ldapResultCode.matcher(response);
633                 resultMatcher.find();
634                 String JavaDoc resultCode = resultMatcher.group(1);
635
636                 int i = Integer.parseInt(resultCode);
637
638                 if (i == 0)
639                     return; // all good here.
640

641                 Matcher JavaDoc descMatcher = ldapResultDesc.matcher(response);
642                 String JavaDoc desc = "";
643                 if (descMatcher.find())
644                     desc = descMatcher.group(1);
645
646                 Matcher JavaDoc msgMatcher = ldapResultMsg.matcher(response);
647                 String JavaDoc msg = "";
648                 if (msgMatcher.find())
649                     msg = msgMatcher.group(1);
650
651                 throw new NamingException(desc + " Exception (LDAP " + resultCode + ")\n" + msg);
652
653             }
654             catch (NumberFormatException JavaDoc e)
655             {
656                 throw new NamingException("Unable to parse result code in DSML Response\n" + response);
657             }
658             catch (IllegalStateException JavaDoc e)
659             {
660                 throw new NamingException("Unable to find result code in DSML Response\n" + response);
661             }
662         }
663     }
664
665     /**
666      * Modifies the attributes associated with a named object using
667      * an ordered list of modifications.
668      * The modifications are performed
669      * in the order specified. Each modification specifies a
670      * modification operation code and an attribute on which to
671      * operate. Where possible, the modifications are
672      * performed atomically.
673      *
674      * @param name the name of the object whose attributes will be updated
675      * @param mods an ordered sequence of modifications to be performed;
676      * may not be null
677      * @throws javax.naming.directory.AttributeModificationException if the modifications
678      * cannot be completed successfully
679      * @throws javax.naming.NamingException if a naming exception is encountered
680      * @see #modifyAttributes(javax.naming.Name, int, javax.naming.directory.Attributes)
681      * @see javax.naming.directory.ModificationItem
682      */

683     public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException
684     {
685         modifyAttributes(name.toString(), mods);
686     }
687
688     /**
689      * Searches in a single context for objects that contain a
690      * specified set of attributes.
691      * See {@link #search(javax.naming.Name, javax.naming.directory.Attributes)} for details.
692      *
693      * @param name the name of the context to search
694      * @param matchingAttributes the attributes to search for
695      * @return an enumeration of <tt>SearchResult</tt> objects
696      * @throws javax.naming.NamingException if a naming exception is encountered
697      */

698     public NamingEnumeration search(String JavaDoc name, Attributes matchingAttributes) throws NamingException
699     {
700         return search(name, matchingAttributes, null);
701     }
702
703     /**
704      * Searches in a single context for objects that contain a
705      * specified set of attributes.
706      * This method returns all the attributes of such objects.
707      * It is equivalent to supplying null as
708      * the <tt>atributesToReturn</tt> parameter to the method
709      * <code>search(Name, Attributes, String[])</code>, i.e. search(name, matchingAttributes, null)
710      * <br>
711      * See {@link #search(javax.naming.Name, javax.naming.directory.Attributes, String[])} for a full description.
712      *
713      * @param name the name of the context to search
714      * @param matchingAttributes the attributes to search for
715      * @return an enumeration of <tt>SearchResult</tt> objects
716      * @throws javax.naming.NamingException if a naming exception is encountered
717      * @see #search(javax.naming.Name, javax.naming.directory.Attributes, String[])
718      */

719     public NamingEnumeration search(Name name, Attributes matchingAttributes) throws NamingException
720     {
721         return search(name, matchingAttributes, null);
722     }
723
724     /**
725      * Binds a name to an object, along with associated attributes.
726      * See {@link #bind(javax.naming.Name, Object, javax.naming.directory.Attributes)} for details.
727      *
728      * @param name the name to bind; may not be empty
729      * @param obj the object to bind; possibly null
730      * @param attrs the attributes to associate with the binding
731      * @throws javax.naming.NameAlreadyBoundException if name is already bound
732      * @throws javax.naming.directory.InvalidAttributesException if some "mandatory" attributes
733      * of the binding are not supplied
734      * @throws javax.naming.NamingException if a naming exception is encountered
735      * @deprecated this method will not be implemented
736      */

737     public void bind(String JavaDoc name, Object JavaDoc obj, Attributes attrs) throws NamingException
738     {
739         throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
740     }
741
742     /**
743      * Binds a name to an object, along with associated attributes,
744      * overwriting any existing binding.
745      * See {@link #rebind(javax.naming.Name, Object, javax.naming.directory.Attributes)} for details.
746      *
747      * @param name the name to bind; may not be empty
748      * @param obj the object to bind; possibly null
749      * @param attrs the attributes to associate with the binding
750      * @throws javax.naming.directory.InvalidAttributesException if some "mandatory" attributes
751      * of the binding are not supplied
752      * @throws javax.naming.NamingException if a naming exception is encountered
753      * @deprecated this method will not be implemented
754      */

755     public void rebind(String JavaDoc name, Object JavaDoc obj, Attributes attrs) throws NamingException
756     {
757         throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
758     }
759
760     /**
761      * Binds a name to an object, along with associated attributes.
762      * If <tt>attrs</tt> is null, the resulting binding will have
763      * the attributes associated with <tt>obj</tt> if <tt>obj</tt> is a
764      * <tt>DirContext</tt>, and no attributes otherwise.
765      * If <tt>attrs</tt> is non-null, the resulting binding will have
766      * <tt>attrs</tt> as its attributes; any attributes associated with
767      * <tt>obj</tt> are ignored.
768      *
769      * @param name the name to bind; may not be empty
770      * @param obj the object to bind; possibly null
771      * @param attrs the attributes to associate with the binding
772      * @throws javax.naming.NameAlreadyBoundException if name is already bound
773      * @throws javax.naming.directory.InvalidAttributesException if some "mandatory" attributes
774      * of the binding are not supplied
775      * @throws javax.naming.NamingException if a naming exception is encountered
776      * @see javax.naming.Context#bind(javax.naming.Name, Object)
777      * @see #rebind(javax.naming.Name, Object, javax.naming.directory.Attributes)
778      * @deprecated this method will not be implemented
779      */

780
781     public void bind(Name name, Object JavaDoc obj, Attributes attrs) throws NamingException
782     {
783         throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
784     }
785
786     /**
787      * Binds a name to an object, along with associated attributes,
788      * overwriting any existing binding.
789      * If <tt>attrs</tt> is null and <tt>obj</tt> is a <tt>DirContext</tt>,
790      * the attributes from <tt>obj</tt> are used.
791      * If <tt>attrs</tt> is null and <tt>obj</tt> is not a <tt>DirContext</tt>,
792      * any existing attributes associated with the object already bound
793      * in the directory remain unchanged.
794      * If <tt>attrs</tt> is non-null, any existing attributes associated with
795      * the object already bound in the directory are removed and <tt>attrs</tt>
796      * is associated with the named object. If <tt>obj</tt> is a
797      * <tt>DirContext</tt> and <tt>attrs</tt> is non-null, the attributes
798      * of <tt>obj</tt> are ignored.
799      *
800      * @param name the name to bind; may not be empty
801      * @param obj the object to bind; possibly null
802      * @param attrs the attributes to associate with the binding
803      * @throws javax.naming.directory.InvalidAttributesException if some "mandatory" attributes
804      * of the binding are not supplied
805      * @throws javax.naming.NamingException if a naming exception is encountered
806      * @see javax.naming.Context#bind(javax.naming.Name, Object)
807      * @see #bind(javax.naming.Name, Object, javax.naming.directory.Attributes)
808      * @deprecated this method will not be implemented
809      */

810     public void rebind(Name name, Object JavaDoc obj, Attributes attrs) throws NamingException
811     {
812         throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
813     }
814
815     /**
816      * Retrieves selected attributes associated with a named object.
817      * See {@link #getAttributes(javax.naming.Name, String[])} for details.
818      *
819      * @param name The name of the object from which to retrieve attributes
820      * @param attrIds the identifiers of the attributes to retrieve.
821      * null indicates that all attributes should be retrieved;
822      * an empty array indicates that none should be retrieved.
823      * @return the requested attributes; never null
824      * @throws javax.naming.NamingException if a naming exception is encountered
825      */

826     public Attributes getAttributes(String JavaDoc name, String JavaDoc[] attrIds) throws NamingException
827     {
828         log.finest("getAttributes (" + name.toString() + ")");
829
830         NamingEnumeration en = doDsmlSearch(name.toString(), BASEOBJECT, SEARCHING, 0, 0, false, "(objectClass=*)", attrIds);
831
832         if (en.hasMoreElements() == false)
833             return new BasicAttributes(); // return empty attributes object for 'virtual' nodes (e.g. router entries, base DN RDNs)
834

835         SearchResult result = (SearchResult) en.next();
836
837         return result.getAttributes();
838 //return getTestAttributes(name.toString()); //To change body of implemented methods use File | Settings | File Templates.
839

840     }
841
842     /**
843      * Retrieves selected attributes associated with a named object.
844      * See the class description regarding attribute models, attribute
845      * type names, and operational attributes.
846      * <p/>
847      * <p> If the object does not have an attribute
848      * specified, the directory will ignore the nonexistent attribute
849      * and return those requested attributes that the object does have.
850      * <p/>
851      * <p> A directory might return more attributes than was requested
852      * (see <strong>Attribute Type Names</strong> in the class description),
853      * but is not allowed to return arbitrary, unrelated attributes.
854      * <p/>
855      * <p> See also <strong>Operational Attributes</strong> in the class
856      * description.
857      *
858      * @param name the name of the object from which to retrieve attributes
859      * @param attrIds the identifiers of the attributes to retrieve.
860      * null indicates that all attributes should be retrieved;
861      * an empty array indicates that none should be retrieved.
862      * @return the requested attributes; never null
863      * @throws javax.naming.NamingException if a naming exception is encountered
864      */

865     public Attributes getAttributes(Name name, String JavaDoc[] attrIds) throws NamingException
866     {
867         return getAttributes(name.toString(), attrIds);
868     }
869
870     /**
871      * Creates and binds a new context, along with associated attributes.
872      * See {@link #createSubcontext(javax.naming.Name, javax.naming.directory.Attributes)} for details.
873      *
874      * @param name the name of the context to create; may not be empty
875      * @param attrs the attributes to associate with the newly created context
876      * @return the newly created context
877      * @throws javax.naming.NameAlreadyBoundException if the name is already bound
878      * @throws javax.naming.directory.InvalidAttributesException if <code>attrs</code> does not
879      * contain all the mandatory attributes required for creation
880      * @throws javax.naming.NamingException if a naming exception is encountered
881      */

882     public DirContext createSubcontext(String JavaDoc name, Attributes attrs) throws NamingException
883     {
884         log.finest("createSubcontext (" + name.toString() + ")");
885 // construct XML
886

887         StringBuffer JavaDoc addRequestBuffer = constructAddRequest(name, attrs);
888
889 // send XML to server
890

891         String JavaDoc response = sendDSMLRequest(addRequestBuffer);
892
893 // parse response XML
894

895         parseAddResponse(response);
896
897 // return new context with new name?
898

899         return new DsmlContext(name, environment);
900     }
901
902     void parseAddResponse(String JavaDoc response)
903             throws NamingException
904     {
905         checkForError(response);
906 // quick check that it really was a modify response...
907
if (response.indexOf("addResponse>") == -1)
908             throw new NamingException("Unexpected DSML Response to Add Request:\n " + response);
909
910     }
911
912     /**
913      * Creates and binds a new context, along with associated attributes.
914      * This method creates a new subcontext with the given name, binds it in
915      * the target context (that named by all but terminal atomic
916      * component of the name), and associates the supplied attributes
917      * with the newly created object.
918      * All intermediate and target contexts must already exist.
919      * If <tt>attrs</tt> is null, this method is equivalent to
920      * <tt>Context.createSubcontext()</tt>.
921      *
922      * @param name the name of the context to create; may not be empty
923      * @param attrs the attributes to associate with the newly created context
924      * @return the newly created context
925      * @throws javax.naming.NameAlreadyBoundException if the name is already bound
926      * @throws javax.naming.directory.InvalidAttributesException if <code>attrs</code> does not
927      * contain all the mandatory attributes required for creation
928      * @throws javax.naming.NamingException if a naming exception is encountered
929      * @see javax.naming.Context#createSubcontext(javax.naming.Name)
930      */

931     public DirContext createSubcontext(Name name, Attributes attrs) throws NamingException
932     {
933         return createSubcontext(name.toString(), attrs);
934     }
935
936     /**
937      * Searches in a single context for objects that contain a
938      * specified set of attributes, and retrieves selected attributes.
939      * See {@link #search(javax.naming.Name, javax.naming.directory.Attributes, String[])} for details.
940      *
941      * @param name the name of the context to search
942      * @param matchingAttributes the attributes to search for
943      * @param attributesToReturn the attributes to return
944      * @return a non-null enumeration of <tt>SearchResult</tt> objects
945      * @throws javax.naming.NamingException if a naming exception is encountered
946      */

947     public NamingEnumeration search(String JavaDoc name, Attributes matchingAttributes, String JavaDoc[] attributesToReturn) throws NamingException
948     {
949         // create a search filter based on the matchingAttributes list.
950

951         String JavaDoc filter;
952         filter = getAttributeMatchFilter(matchingAttributes);
953
954 // TODO search context crap: alias handling, time limits etc.
955

956         return doDsmlSearch(name, WHOLESUBTREE, SEARCHING, 0, 0, false, filter, attributesToReturn);
957     }
958
959     /**
960      * Searches in a single context for objects that contain a
961      * specified set of attributes, and retrieves selected attributes.
962      * The search is performed using the default
963      * <code>SearchControls</code> settings.
964      * <p/>
965      * For an object to be selected, each attribute in
966      * <code>matchingAttributes</code> must match some attribute of the
967      * object. If <code>matchingAttributes</code> is empty or
968      * null, all objects in the target context are returned.
969      * <p/>
970      * An attribute <em>A</em><sub>1</sub> in
971      * <code>matchingAttributes</code> is considered to match an
972      * attribute <em>A</em><sub>2</sub> of an object if
973      * <em>A</em><sub>1</sub> and <em>A</em><sub>2</sub> have the same
974      * identifier, and each value of <em>A</em><sub>1</sub> is equal
975      * to some value of <em>A</em><sub>2</sub>. This implies that the
976      * order of values is not significant, and that
977      * <em>A</em><sub>2</sub> may contain "extra" values not found in
978      * <em>A</em><sub>1</sub> without affecting the comparison. It
979      * also implies that if <em>A</em><sub>1</sub> has no values, then
980      * testing for a match is equivalent to testing for the presence
981      * of an attribute <em>A</em><sub>2</sub> with the same
982      * identifier.
983      * <p/>
984      * The precise definition of "equality" used in comparing attribute values
985      * is defined by the underlying directory service. It might use the
986      * <code>Object.equals</code> method, for example, or might use a schema
987      * to specify a different equality operation.
988      * For matching based on operations other than equality (such as
989      * substring comparison) use the version of the <code>search</code>
990      * method that takes a filter argument.
991      * <p/>
992      * When changes are made to this <tt>DirContext</tt>,
993      * the effect on enumerations returned by prior calls to this method
994      * is undefined.
995      * <p/>
996      * If the object does not have the attribute
997      * specified, the directory will ignore the nonexistent attribute
998      * and return the requested attributes that the object does have.
999      * <p/>
1000     * A directory might return more attributes than was requested
1001     * (see <strong>Attribute Type Names</strong> in the class description),
1002     * but is not allowed to return arbitrary, unrelated attributes.
1003     * <p/>
1004     * See also <strong>Operational Attributes</strong> in the class
1005     * description.
1006     *
1007     * @param name the name of the context to search
1008     * @param matchingAttributes the attributes to search for. If empty or null,
1009     * all objects in the target context are returned.
1010     * @param attributesToReturn the attributes to return. null indicates that
1011     * all attributes are to be returned;
1012     * an empty array indicates that none are to be returned.
1013     * @return a non-null enumeration of <tt>SearchResult</tt> objects.
1014     * Each <tt>SearchResult</tt> contains the attributes
1015     * identified by <code>attributesToReturn</code>
1016     * and the name of the corresponding object, named relative
1017     * to the context named by <code>name</code>.
1018     * @throws javax.naming.NamingException if a naming exception is encountered
1019     * @see javax.naming.directory.SearchControls
1020     * @see javax.naming.directory.SearchResult
1021     * @see #search(javax.naming.Name, String, Object[], javax.naming.directory.SearchControls)
1022     */

1023    public NamingEnumeration search(Name name, Attributes matchingAttributes, String JavaDoc[] attributesToReturn) throws NamingException
1024    {
1025        return search(name.toString(), matchingAttributes, attributesToReturn);
1026    }
1027
1028    /**
1029     * This takes a list of Attributes and produces a 'present' match filter based on them.
1030     *
1031     * @param matchingAttributes a list of attributes required to be present (e.g. 'person', 'favoriteDrink')
1032     * @return the ldap filter for matching the attributes; e.g. (&(person=*)(favoriteDrink=*))
1033     */

1034    static String JavaDoc getAttributeMatchFilter(Attributes matchingAttributes)
1035    {
1036        String JavaDoc filter;
1037        int numbAtts = (matchingAttributes == null) ? 0 : matchingAttributes.size();
1038        if (numbAtts == 0)
1039        {
1040            filter = "(objectClass=*)";
1041        }
1042        else
1043        {
1044            StringBuffer JavaDoc filterBuffer = new StringBuffer JavaDoc();
1045            if (numbAtts > 1)
1046                filterBuffer.append("(&");
1047
1048            Enumeration atts = matchingAttributes.getIDs(); // ignore NamingException, as these atts come from the client, not the server
1049
while (atts.hasMoreElements())
1050            {
1051                String JavaDoc att = (String JavaDoc) atts.nextElement();
1052                filterBuffer.append("(").append(att).append("=*)");
1053            }
1054
1055            if (numbAtts > 1)
1056                filterBuffer.append(")");
1057            filter = filterBuffer.toString();
1058        }
1059        return filter;
1060    }
1061
1062
1063    /**
1064     * Searches in the named context or object for entries that satisfy the
1065     * given search filter. Performs the search as specified by
1066     * the search controls.
1067     * <p/>
1068     * See {@link #search(javax.naming.Name, String, javax.naming.directory.SearchControls)} for details.
1069     *
1070     * @param name the name of the context or object to search
1071     * @param filter the filter expression to use for the search; may not be null
1072     * @param cons the search controls that control the search. If null,
1073     * the default search controls are used (equivalent
1074     * to <tt>(new SearchControls())</tt>).
1075     * @return an enumeration of <tt>SearchResult</tt>s for
1076     * the objects that satisfy the filter.
1077     * @throws javax.naming.directory.InvalidSearchFilterException if the search filter specified is
1078     * not supported or understood by the underlying directory
1079     * @throws javax.naming.directory.InvalidSearchControlsException if the search controls
1080     * contain invalid settings
1081     * @throws javax.naming.NamingException if a naming exception is encountered
1082     */

1083    public NamingEnumeration search(String JavaDoc name, String JavaDoc filter, SearchControls cons) throws NamingException
1084    {
1085        return search(name, filter, null, cons); //To change body of implemented methods use File | Settings | File Templates.
1086
}
1087
1088    /**
1089     * Searches in the named context or object for entries that satisfy the
1090     * given search filter. Performs the search as specified by
1091     * the search controls.
1092     * <p/>
1093     * The format and interpretation of <code>filter</code> follows RFC 2254
1094     * with the
1095     * following interpretations for <code>attr</code> and <code>value</code>
1096     * mentioned in the RFC.
1097     * <p/>
1098     * <code>attr</code> is the attribute's identifier.
1099     * <p/>
1100     * <code>value</code> is the string representation the attribute's value.
1101     * The translation of this string representation into the attribute's value
1102     * is directory-specific.
1103     * <p/>
1104     * For the assertion "someCount=127", for example, <code>attr</code>
1105     * is "someCount" and <code>value</code> is "127".
1106     * The provider determines, based on the attribute ID ("someCount")
1107     * (and possibly its schema), that the attribute's value is an integer.
1108     * It then parses the string "127" appropriately.
1109     * <p/>
1110     * Any non-ASCII characters in the filter string should be
1111     * represented by the appropriate Java (Unicode) characters, and
1112     * not encoded as UTF-8 octets. Alternately, the
1113     * "backslash-hexcode" notation described in RFC 2254 may be used.
1114     * <p/>
1115     * If the directory does not support a string representation of
1116     * some or all of its attributes, the form of <code>search</code> that
1117     * accepts filter arguments in the form of Objects can be used instead.
1118     * The service provider for such a directory would then translate
1119     * the filter arguments to its service-specific representation
1120     * for filter evaluation.
1121     * See <code>search(Name, String, Object[], SearchControls)</code>.
1122     * <p/>
1123     * RFC 2254 defines certain operators for the filter, including substring
1124     * matches, equality, approximate match, greater than, less than. These
1125     * operators are mapped to operators with corresponding semantics in the
1126     * underlying directory. For example, for the equals operator, suppose
1127     * the directory has a matching rule defining "equality" of the
1128     * attributes in the filter. This rule would be used for checking
1129     * equality of the attributes specified in the filter with the attributes
1130     * of objects in the directory. Similarly, if the directory has a
1131     * matching rule for ordering, this rule would be used for
1132     * making "greater than" and "less than" comparisons.
1133     * <p/>
1134     * Not all of the operators defined in RFC 2254 are applicable to all
1135     * attributes. When an operator is not applicable, the exception
1136     * <code>InvalidSearchFilterException</code> is thrown.
1137     * <p/>
1138     * The result is returned in an enumeration of <tt>SearchResult</tt>s.
1139     * Each <tt>SearchResult</tt> contains the name of the object
1140     * and other information about the object (see SearchResult).
1141     * The name is either relative to the target context of the search
1142     * (which is named by the <code>name</code> parameter), or
1143     * it is a URL string. If the target context is included in
1144     * the enumeration (as is possible when
1145     * <code>cons</code> specifies a search scope of
1146     * <code>SearchControls.OBJECT_SCOPE</code> or
1147     * <code>SearchControls.SUBSTREE_SCOPE</code>), its name is the empty
1148     * string. The <tt>SearchResult</tt> may also contain attributes of the
1149     * matching object if the <tt>cons</tt> argument specified that attributes
1150     * be returned.
1151     * <p/>
1152     * If the object does not have a requested attribute, that
1153     * nonexistent attribute will be ignored. Those requested
1154     * attributes that the object does have will be returned.
1155     * <p/>
1156     * A directory might return more attributes than were requested
1157     * (see <strong>Attribute Type Names</strong> in the class description)
1158     * but is not allowed to return arbitrary, unrelated attributes.
1159     * <p/>
1160     * See also <strong>Operational Attributes</strong> in the class
1161     * description.
1162     * <p/>
1163     * <p>Equivalent to search(name, filter, null, cons).
1164     *
1165     * @param name the name of the context or object to search
1166     * @param filter the filter expression to use for the search; may not be null
1167     * @param cons the search controls that control the search. If null,
1168     * the default search controls are used (equivalent
1169     * to <tt>(new SearchControls())</tt>).
1170     * @return an enumeration of <tt>SearchResult</tt>s of
1171     * the objects that satisfy the filter; never null
1172     * @throws javax.naming.directory.InvalidSearchFilterException if the search filter specified is
1173     * not supported or understood by the underlying directory
1174     * @throws javax.naming.directory.InvalidSearchControlsException if the search controls
1175     * contain invalid settings
1176     * @throws javax.naming.NamingException if a naming exception is encountered
1177     * @see #search(javax.naming.Name, String, Object[], javax.naming.directory.SearchControls)
1178     * @see javax.naming.directory.SearchControls
1179     * @see javax.naming.directory.SearchResult
1180     */

1181    public NamingEnumeration search(Name name, String JavaDoc filter, SearchControls cons) throws NamingException
1182    {
1183        return search(name, filter, null, cons);
1184    }
1185
1186    /**
1187     * <p>Searches in the named context or object for entries that satisfy the
1188     * given search filter. Performs the search as specified by
1189     * the search controls.
1190     * <p/>
1191     * See {@link #search(javax.naming.Name, String, Object[], javax.naming.directory.SearchControls)} for details.
1192     *
1193     * @param name the name of the context or object to search
1194     * @param filterExpr the filter expression to use for the search.
1195     * The expression may contain variables of the
1196     * form "<code>{i}</code>" where <code>i</code>
1197     * is a nonnegative integer. May not be null.
1198     * @param filterArgs <b>NOT IMPLEMENTED!</b>
1199     * the array of arguments to substitute for the variables
1200     * in <code>filterExpr</code>. The value of
1201     * <code>filterArgs[i]</code> will replace each
1202     * occurrence of "<code>{i}</code>".
1203     * If null, equivalent to an empty array.
1204     * @param cons the search controls that control the search. If null,
1205     * the default search controls are used (equivalent
1206     * to <tt>(new SearchControls())</tt>).
1207     * @return an enumeration of <tt>SearchResult</tt>s of the objects
1208     * that satisfy the filter; never null
1209     * @throws ArrayIndexOutOfBoundsException if <tt>filterExpr</tt> contains
1210     * <code>{i}</code> expressions where <code>i</code> is outside
1211     * the bounds of the array <code>filterArgs</code>
1212     * @throws javax.naming.directory.InvalidSearchControlsException if <tt>cons</tt> contains
1213     * invalid settings
1214     * @throws javax.naming.directory.InvalidSearchFilterException if <tt>filterExpr</tt> with
1215     * <tt>filterArgs</tt> represents an invalid search filter
1216     * @throws javax.naming.NamingException if a naming exception is encountered
1217     */

1218    public NamingEnumeration search(String JavaDoc name, String JavaDoc filterExpr, Object JavaDoc[] filterArgs, SearchControls cons) throws NamingException
1219    {
1220
1221        if (filterExpr == null)
1222            throw new NamingException("null ldap filter in DsmlContext search");
1223
1224        if (filterArgs != null && filterArgs.length > 0)
1225            throw new NamingException("filter arguments not implemented in com.ca.jndiproviders.dsml provider");
1226
1227        log.finest("search III (" + name.toString() + ") + filter: " + filterExpr);
1228
1229        if (cons == null)
1230            cons = DEFAULT_SEARCH_CONTROLS;
1231
1232// translate JNDI search scope into DSML speak
1233
String JavaDoc searchScope;
1234        switch (cons.getSearchScope())
1235        {
1236            case SearchControls.OBJECT_SCOPE:
1237                searchScope = BASEOBJECT;
1238                break;
1239            case SearchControls.ONELEVEL_SCOPE:
1240                searchScope = SINGLELEVEL;
1241                break;
1242            case SearchControls.SUBTREE_SCOPE:
1243                searchScope = WHOLESUBTREE;
1244                break;
1245            default:
1246                throw new NamingException("unexpected error; unknown search scope in SearchControl object: " + cons.getSearchScope()); // really, really, really should be impossible
1247
}
1248
1249
1250// translate JNDI alias handling into DSML speak
1251
String JavaDoc jndiAliasHandling = (String JavaDoc) environment.get("java.naming.ldap.derefAliases");
1252
1253        if (jndiAliasHandling == null)
1254            jndiAliasHandling = "always";
1255
1256        String JavaDoc aliasHandling = "derefAlways";
1257
1258        if (jndiAliasHandling.equals("always"))
1259            aliasHandling = ALWAYS;
1260        else if (jndiAliasHandling.equals("never"))
1261            aliasHandling = NEVER;
1262        else if (jndiAliasHandling.equals("finding"))
1263            aliasHandling = FINDING;
1264        else if (jndiAliasHandling.equals("searching"))
1265            aliasHandling = SEARCHING;
1266
1267// doesn't seem to be anything corresponding to the DSML 'types only' flag, unless possibly it is cons.getReturningObjFlag()??
1268

1269// do the DSML search using the jndiproviders search controls data, environment data, and passed parameters
1270
NamingEnumeration returnEnum = doDsmlSearch(name, searchScope, aliasHandling, cons.getCountLimit(), cons.getTimeLimit(), false, filterExpr, cons.getReturningAttributes());
1271
1272        return returnEnum;
1273    }
1274
1275    /**
1276     * Searches in the named context or object for entries that satisfy the
1277     * given search filter. Performs the search as specified by
1278     * the search controls.
1279     * <p/>
1280     * The interpretation of <code>filterExpr</code> is based on RFC
1281     * 2254. It may additionally contain variables of the form
1282     * <code>{i}</code> -- where <code>i</code> is an integer -- that
1283     * refer to objects in the <code>filterArgs</code> array. The
1284     * interpretation of <code>filterExpr</code> is otherwise
1285     * identical to that of the <code>filter</code> parameter of the
1286     * method <code>search(Name, String, SearchControls)</code>.
1287     * <p/>
1288     * When a variable <code>{i}</code> appears in a search filter, it
1289     * indicates that the filter argument <code>filterArgs[i]</code>
1290     * is to be used in that place. Such variables may be used
1291     * wherever an <em>attr</em>, <em>value</em>, or
1292     * <em>matchingrule</em> production appears in the filter grammar
1293     * of RFC 2254, section 4. When a string-valued filter argument
1294     * is substituted for a variable, the filter is interpreted as if
1295     * the string were given in place of the variable, with any
1296     * characters having special significance within filters (such as
1297     * <code>'*'</code>) having been escaped according to the rules of
1298     * RFC 2254.
1299     * <p/>
1300     * For directories that do not use a string representation for
1301     * some or all of their attributes, the filter argument
1302     * corresponding to an attribute value may be of a type other than
1303     * String. Directories that support unstructured binary-valued
1304     * attributes, for example, should accept byte arrays as filter
1305     * arguments. The interpretation (if any) of filter arguments of
1306     * any other type is determined by the service provider for that
1307     * directory, which maps the filter operations onto operations with
1308     * corresponding semantics in the underlying directory.
1309     * <p/>
1310     * This method returns an enumeration of the results.
1311     * Each element in the enumeration contains the name of the object
1312     * and other information about the object (see <code>SearchResult</code>).
1313     * The name is either relative to the target context of the search
1314     * (which is named by the <code>name</code> parameter), or
1315     * it is a URL string. If the target context is included in
1316     * the enumeration (as is possible when
1317     * <code>cons</code> specifies a search scope of
1318     * <code>SearchControls.OBJECT_SCOPE</code> or
1319     * <code>SearchControls.SUBSTREE_SCOPE</code>),
1320     * its name is the empty string.
1321     * <p/>
1322     * The <tt>SearchResult</tt> may also contain attributes of the matching
1323     * object if the <tt>cons</tt> argument specifies that attributes be
1324     * returned.
1325     * <p/>
1326     * If the object does not have a requested attribute, that
1327     * nonexistent attribute will be ignored. Those requested
1328     * attributes that the object does have will be returned.
1329     * <p/>
1330     * A directory might return more attributes than were requested
1331     * (see <strong>Attribute Type Names</strong> in the class description)
1332     * but is not allowed to return arbitrary, unrelated attributes.
1333     * <p/>
1334     * If a search filter with invalid variable substitutions is provided
1335     * to this method, the result is undefined.
1336     * When changes are made to this DirContext,
1337     * the effect on enumerations returned by prior calls to this method
1338     * is undefined.
1339     * <p/>
1340     * See also <strong>Operational Attributes</strong> in the class
1341     * description.
1342     *
1343     * @param name the name of the context or object to search
1344     * @param filterExpr the filter expression to use for the search.
1345     * The expression may contain variables of the
1346     * form "<code>{i}</code>" where <code>i</code>
1347     * is a nonnegative integer. May not be null.
1348     * @param filterArgs the array of arguments to substitute for the variables
1349     * in <code>filterExpr</code>. The value of
1350     * <code>filterArgs[i]</code> will replace each
1351     * occurrence of "<code>{i}</code>".
1352     * If null, equivalent to an empty array.
1353     * @param cons the search controls that control the search. If null,
1354     * the default search controls are used (equivalent
1355     * to <tt>(new SearchControls())</tt>).
1356     * @return an enumeration of <tt>SearchResult</tt>s of the objects
1357     * that satisfy the filter; never null
1358     * @throws ArrayIndexOutOfBoundsException if <tt>filterExpr</tt> contains
1359     * <code>{i}</code> expressions where <code>i</code> is outside
1360     * the bounds of the array <code>filterArgs</code>
1361     * @throws javax.naming.directory.InvalidSearchControlsException if <tt>cons</tt> contains
1362     * invalid settings
1363     * @throws javax.naming.directory.InvalidSearchFilterException if <tt>filterExpr</tt> with
1364     * <tt>filterArgs</tt> represents an invalid search filter
1365     * @throws javax.naming.NamingException if a naming exception is encountered
1366     * @see #search(javax.naming.Name, javax.naming.directory.Attributes, String[])
1367     * @see java.text.MessageFormat
1368     */

1369    public NamingEnumeration search(Name name, String JavaDoc filterExpr, Object JavaDoc[] filterArgs, SearchControls cons) throws NamingException
1370    {
1371        return search(name.toString(), filterExpr, filterArgs, cons);
1372
1373    }
1374
1375    /**
1376     * Closes this context.
1377     * This method releases this context's resources immediately, instead of
1378     * waiting for them to be released automatically by the garbage collector.
1379     * <p/>
1380     * <p> This method is idempotent: invoking it on a context that has
1381     * already been closed has no effect. Invoking any other method
1382     * on a closed context is not allowed, and results in undefined behaviour.
1383     *
1384     * @throws javax.naming.NamingException if a naming exception is encountered
1385     */

1386    public void close() throws NamingException
1387    {
1388        log.finest("close()");
1389// TODO - any cleanup required here?
1390
//To change body of implemented methods use File | Settings | File Templates.
1391
}
1392
1393    /**
1394     * Retrieves the full name of this context within its own namespace.
1395     * <p/>
1396     * <p> Many naming services have a notion of a "full name" for objects
1397     * in their respective namespaces. For example, an LDAP entry has
1398     * a distinguished name, and a DNS record has a fully qualified name.
1399     * This method allows the client application to retrieve this name.
1400     * The string returned by this method is not a JNDI composite name
1401     * and should not be passed directly to context methods.
1402     * In naming systems for which the notion of full name does not
1403     * make sense, <tt>OperationNotSupportedException</tt> is thrown.
1404     *
1405     * @return this context's name in its own namespace; never null
1406     * @throws javax.naming.OperationNotSupportedException if the naming system does
1407     * not have the notion of a full name
1408     * @throws javax.naming.NamingException if a naming exception is encountered
1409     * @since 1.3
1410     */

1411    public String JavaDoc getNameInNamespace() throws NamingException
1412    {
1413        log.finest("getNameInNamespace()");
1414        return contextName;
1415    }
1416
1417    /**
1418     * Destroys the named context and removes it from the namespace.
1419     * See {@link #destroySubcontext(javax.naming.Name)} for details.
1420     *
1421     * @param name the name of the context to be destroyed; may not be empty
1422     * @throws javax.naming.NameNotFoundException if an intermediate context does not exist
1423     * @throws javax.naming.NotContextException if the name is bound but does not name a
1424     * context, or does not name a context of the appropriate type
1425     * @throws javax.naming.ContextNotEmptyException if the named context is not empty
1426     * @throws javax.naming.NamingException if a naming exception is encountered
1427     */

1428
1429
1430    public void destroySubcontext(String JavaDoc name) throws NamingException
1431    {
1432        log.finest("destroySubcontext (" + name.toString() + ")");
1433// construct XML
1434

1435        StringBuffer JavaDoc deleteRequestBuffer = constructDeleteRequest(name);
1436
1437// send XML to server
1438

1439        String JavaDoc response = sendDSMLRequest(deleteRequestBuffer);
1440
1441// parse response XML
1442

1443        parseDeleteResponse(response);
1444    }
1445
1446    void parseDeleteResponse(String JavaDoc response)
1447            throws NamingException
1448    {
1449        checkForError(response);
1450// quick check that it really was a modify response...
1451
if (response.indexOf("delResponse>") == -1)
1452            throw new NamingException("Unexpected DSML Response to Delete Request:\n " + response);
1453
1454    }
1455
1456    /**
1457     * Unbinds the named object.
1458     * See {@link #unbind(javax.naming.Name)} for details.
1459     *
1460     * @param name the name to unbind; may not be empty
1461     * @throws javax.naming.NameNotFoundException if an intermediate context does not exist
1462     * @throws javax.naming.NamingException if a naming exception is encountered
1463     * @deprecated bind operations not supported
1464     */

1465    public void unbind(String JavaDoc name) throws NamingException
1466    {
1467        throw new OperationNotSupportedException("DsmlContext does not support storing java objects");
1468
1469//To change body of implemented methods use File | Settings | File Templates.
1470
}
1471
1472    /**
1473     * Retrieves the environment in effect for this context.
1474     * See class description for more details on environment properties.
1475     * <p/>
1476     * <p> The caller should not make any changes to the object returned:
1477     * their effect on the context is undefined.
1478     * The environment of this context may be changed using
1479     * <tt>addToEnvironment()</tt> and <tt>removeFromEnvironment()</tt>.
1480     *
1481     * @return the environment of this context; never null
1482     * @throws javax.naming.NamingException if a naming exception is encountered
1483     * @see #addToEnvironment(String, Object)
1484     * @see #removeFromEnvironment(String)
1485     */

1486    public Hashtable getEnvironment() throws NamingException
1487    {
1488        return (Hashtable) environment.clone(); //To change body of implemented methods use File | Settings | File Templates.
1489
}
1490
1491    /**
1492     * Destroys the named context and removes it from the namespace.
1493     * Any attributes associated with the name are also removed.
1494     * Intermediate contexts are not destroyed.
1495     * <p/>
1496     * <p> This method is idempotent.
1497     * It succeeds even if the terminal atomic name
1498     * is not bound in the target context, but throws
1499     * <tt>NameNotFoundException</tt>
1500     * if any of the intermediate contexts do not exist.
1501     * <p/>
1502     * <p> In a federated naming system, a context from one naming system
1503     * may be bound to a name in another. One can subsequently
1504     * look up and perform operations on the foreign context using a
1505     * composite name. However, an attempt destroy the context using
1506     * this composite name will fail with
1507     * <tt>NotContextException</tt>, because the foreign context is not
1508     * a "subcontext" of the context in which it is bound.
1509     * Instead, use <tt>unbind()</tt> to remove the
1510     * binding of the foreign context. Destroying the foreign context
1511     * requires that the <tt>destroySubcontext()</tt> be performed
1512     * on a context from the foreign context's "native" naming system.
1513     *
1514     * @param name the name of the context to be destroyed; may not be empty
1515     * @throws javax.naming.NameNotFoundException if an intermediate context does not exist
1516     * @throws javax.naming.NotContextException if the name is bound but does not name a
1517     * context, or does not name a context of the appropriate type
1518     * @throws javax.naming.ContextNotEmptyException if the named context is not empty
1519     * @throws javax.naming.NamingException if a naming exception is encountered
1520     * @see #destroySubcontext(String)
1521     */

1522    public void destroySubcontext(Name name) throws NamingException
1523    {
1524        destroySubcontext(name.toString());
1525//To change body of implemented methods use File | Settings | File Templates.
1526
}
1527
1528    /**
1529     * Unbinds the named object.
1530     * Removes the terminal atomic name in <code>name</code>
1531     * from the target context--that named by all but the terminal
1532     * atomic part of <code>name</code>.
1533     * <p/>
1534     * <p> This method is idempotent.
1535     * It succeeds even if the terminal atomic name
1536     * is not bound in the target context, but throws
1537     * <tt>NameNotFoundException</tt>
1538     * if any of the intermediate contexts do not exist.
1539     * <p/>
1540     * <p> Any attributes associated with the name are removed.
1541     * Intermediate contexts are not changed.
1542     *
1543     * @param name the name to unbind; may not be empty
1544     * @throws javax.naming.NameNotFoundException if an intermediate context does not exist
1545     * @throws javax.naming.NamingException if a naming exception is encountered
1546     * @see #unbind(String)
1547     * @deprecated bind/unbind operations not supported by DsmlContext
1548     */

1549    public void unbind(Name name) throws NamingException
1550    {
1551        throw new OperationNotSupportedException("DsmlContext does not support storing java objects");
1552    }
1553
1554    /**
1555     * Retrieves the named object.
1556     * See {@link #lookup(javax.naming.Name)} for details.
1557     *
1558     * @param name the name of the object to look up
1559     * @return the object bound to <tt>name</tt>
1560     * @throws javax.naming.NamingException if a naming exception is encountered
1561     * @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
1562     */

1563    public Object JavaDoc lookup(String JavaDoc name) throws NamingException
1564    {
1565        throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
1566    }
1567
1568    /**
1569     * Retrieves the named object, following links except
1570     * for the terminal atomic component of the name.
1571     * See {@link #lookupLink(javax.naming.Name)} for details.
1572     *
1573     * @param name the name of the object to look up
1574     * @return the object bound to <tt>name</tt>, not following the
1575     * terminal link (if any)
1576     * @throws javax.naming.NamingException if a naming exception is encountered
1577     * @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
1578     */

1579    public Object JavaDoc lookupLink(String JavaDoc name) throws NamingException
1580    {
1581        throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
1582    }
1583
1584    /**
1585     * Removes an environment property from the environment of this
1586     * context. See class description for more details on environment
1587     * properties.
1588     *
1589     * @param propName the name of the environment property to remove; may not be null
1590     * @return the previous value of the property, or null if the property was
1591     * not in the environment
1592     * @throws javax.naming.NamingException if a naming exception is encountered
1593     * @see #getEnvironment()
1594     * @see #addToEnvironment(String, Object)
1595     */

1596    public Object JavaDoc removeFromEnvironment(String JavaDoc propName) throws NamingException
1597    {
1598        return environment.remove(propName);
1599    }
1600
1601    /**
1602     * Binds a name to an object.
1603     * See {@link #bind(javax.naming.Name, Object)} for details.
1604     *
1605     * @param name the name to bind; may not be empty
1606     * @param obj the object to bind; possibly null
1607     * @throws javax.naming.NameAlreadyBoundException if name is already bound
1608     * @throws javax.naming.directory.InvalidAttributesException if object did not supply all mandatory attributes
1609     * @throws javax.naming.NamingException if a naming exception is encountered
1610     * @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
1611     */

1612    public void bind(String JavaDoc name, Object JavaDoc obj) throws NamingException
1613    {
1614        throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
1615    }
1616
1617    /**
1618     * Binds a name to an object, overwriting any existing binding.
1619     * See {@link #rebind(javax.naming.Name, Object)} for details.
1620     *
1621     * @param name the name to bind; may not be empty
1622     * @param obj the object to bind; possibly null
1623     * @throws javax.naming.directory.InvalidAttributesException if object did not supply all mandatory attributes
1624     * @throws javax.naming.NamingException if a naming exception is encountered
1625     * @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
1626     */

1627    public void rebind(String JavaDoc name, Object JavaDoc obj) throws NamingException
1628    {
1629        throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
1630    }
1631
1632    /**
1633     * Retrieves the named object.
1634     * If <tt>name</tt> is empty, returns a new instance of this context
1635     * (which represents the same naming context as this context, but its
1636     * environment may be modified independently and it may be accessed
1637     * concurrently).
1638     *
1639     * @param name the name of the object to look up
1640     * @return the object bound to <tt>name</tt>
1641     * @throws javax.naming.NamingException if a naming exception is encountered
1642     * @see #lookup(String)
1643     * @see #lookupLink(javax.naming.Name)
1644     * @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
1645     */

1646    public Object JavaDoc lookup(Name name) throws NamingException
1647    {
1648        throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
1649    }
1650
1651    /**
1652     * Retrieves the named object, following links except
1653     * for the terminal atomic component of the name.
1654     * If the object bound to <tt>name</tt> is not a link,
1655     * returns the object itself.
1656     *
1657     * @param name the name of the object to look up
1658     * @return the object bound to <tt>name</tt>, not following the
1659     * terminal link (if any).
1660     * @throws javax.naming.NamingException if a naming exception is encountered
1661     * @see #lookupLink(String)
1662     * @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
1663     */

1664    public Object JavaDoc lookupLink(Name name) throws NamingException
1665    {
1666        throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
1667    }
1668
1669    /**
1670     * Binds a name to an object.
1671     * All intermediate contexts and the target context (that named by all
1672     * but terminal atomic component of the name) must already exist.
1673     *
1674     * @param name the name to bind; may not be empty
1675     * @param obj the object to bind; possibly null
1676     * @throws javax.naming.NameAlreadyBoundException if name is already bound
1677     * @throws javax.naming.directory.InvalidAttributesException if object did not supply all mandatory attributes
1678     * @throws javax.naming.NamingException if a naming exception is encountered
1679     * @see #bind(String, Object)
1680     * @see #rebind(javax.naming.Name, Object)
1681     * @see javax.naming.directory.DirContext#bind(javax.naming.Name, Object,
1682            * javax.naming.directory.Attributes)
1683     * @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
1684     */

1685    public void bind(Name name, Object JavaDoc obj) throws NamingException
1686    {
1687        throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
1688    }
1689
1690    /**
1691     * Binds a name to an object, overwriting any existing binding.
1692     * All intermediate contexts and the target context (that named by all
1693     * but terminal atomic component of the name) must already exist.
1694     * <p/>
1695     * <p> If the object is a <tt>DirContext</tt>, any existing attributes
1696     * associated with the name are replaced with those of the object.
1697     * Otherwise, any existing attributes associated with the name remain
1698     * unchanged.
1699     *
1700     * @param name the name to bind; may not be empty
1701     * @param obj the object to bind; possibly null
1702     * @throws javax.naming.directory.InvalidAttributesException if object did not supply all mandatory attributes
1703     * @throws javax.naming.NamingException if a naming exception is encountered
1704     * @see #rebind(String, Object)
1705     * @see #bind(javax.naming.Name, Object)
1706     * @see javax.naming.directory.DirContext#rebind(javax.naming.Name, Object,
1707            * javax.naming.directory.Attributes)
1708     * @see javax.naming.directory.DirContext
1709     * @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
1710     */

1711    public void rebind(Name name, Object JavaDoc obj) throws NamingException
1712    {
1713        throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
1714    }
1715
1716    /**
1717     * Binds a new name to the object bound to an old name, and unbinds
1718     * the old name.
1719     * See {@link #rename(javax.naming.Name, javax.naming.Name)} for details.
1720     *
1721     * @param oldName the name of the existing binding; may not be empty
1722     * @param newName the name of the new binding; may not be empty
1723     * @throws javax.naming.NameAlreadyBoundException if <tt>newName</tt> is already bound
1724     * @throws javax.naming.NamingException if a naming exception is encountered
1725     */

1726    public void rename(String JavaDoc oldName, String JavaDoc newName) throws NamingException
1727    {
1728        // note - this is the only time we use the 'name' version of the call,
1729
// because we have to do some processing rather than writing the string
1730
// out to DSML immediately.
1731
rename(new DN(oldName), new DN(newName));
1732    }
1733
1734    StringBuffer JavaDoc constructModDNRequest(String JavaDoc oldName, String JavaDoc newName)
1735            throws NamingException
1736    {
1737        StringBuffer JavaDoc message = new StringBuffer JavaDoc(200);
1738        message.append(STANDARDHEADER);
1739
1740        getModDNRequestElement(message, oldName, newName);
1741
1742        message.append(STANDARDFOOTER);
1743
1744        return message;
1745    }
1746
1747    /**
1748     * Renames an entry. Note that 'new Superior' is not supported; entries must
1749     * be copied and deleted instead.
1750     *
1751     * @param message
1752     * @param oldName the full DN of the old entry name
1753     * @param newRDN the rdn of the new entry name
1754     */

1755    /*
1756    " <modDNRequest dn="CN=Alice Johnson,DC=Example,DC=COM"
1757                         newrdn="CN=Alice Weiss"
1758                         deleteoldrdn="true"
1759                         newSuperior="OU=Marketing,DC=Example,DC=COM"/>
1760
1761    */

1762    void getModDNRequestElement(StringBuffer JavaDoc message, String JavaDoc oldName, String JavaDoc newRDN)
1763    {
1764        message.append(TAB4).append("<dsml:modDNRequest dn=\"").append(escapeName(oldName)).append("\" newrdn=\"").append(escapeName(newRDN)).append("\" ");
1765
1766        if (environment.get("java.naming.ldap.deleteRDN").toString().equalsIgnoreCase("false"))
1767            message.append("deleteoldrdn=\"false\"/>");
1768        else
1769            message.append("deleteoldrdn=\"true\"/>");
1770
1771    }
1772
1773    void parseModDNResponse(String JavaDoc response)
1774            throws NamingException
1775    {
1776        // check for a dsml error
1777
checkForError(response);
1778
1779        // quick check that it really was a modify response...
1780
if (response.indexOf("modDNResponse>") == -1)
1781            throw new NamingException("Unexpected DSML Response to Modify DN Request:\n " + response);
1782    }
1783
1784    /**
1785     * Creates and binds a new context.
1786     * See {@link #createSubcontext(javax.naming.Name)} for details.
1787     *
1788     * @param name the name of the context to create; may not be empty
1789     * @return the newly created context
1790     * @throws javax.naming.NameAlreadyBoundException if name is already bound
1791     * @throws javax.naming.directory.InvalidAttributesException if creation of the subcontext requires specification of
1792     * mandatory attributes
1793     * @throws javax.naming.NamingException if a naming exception is encountered
1794     * @deprecated it is impossible to create a directory object without attributes (RFC 2251 - sec 4.7)
1795     */

1796    public Context createSubcontext(String JavaDoc name) throws NamingException
1797    {
1798        throw new InvalidAttributesException("cannot create directory object without attributes");
1799    }
1800
1801    /**
1802     * Creates and binds a new context.
1803     * Creates a new context with the given name and binds it in
1804     * the target context (that named by all but terminal atomic
1805     * component of the name). All intermediate contexts and the
1806     * target context must already exist.
1807     *
1808     * @param name the name of the context to create; may not be empty
1809     * @return the newly created context
1810     * @throws javax.naming.NameAlreadyBoundException if name is already bound
1811     * @throws javax.naming.directory.InvalidAttributesException if creation of the subcontext requires specification of
1812     * mandatory attributes
1813     * @throws javax.naming.NamingException if a naming exception is encountered
1814     * @see #createSubcontext(String)
1815     * @see javax.naming.directory.DirContext#createSubcontext
1816     * @deprecated it is impossible to create a directory object without attributes (RFC 2251 - sec 4.7)
1817     */

1818    public Context createSubcontext(Name name) throws NamingException
1819    {
1820        throw new InvalidAttributesException("cannot create directory object without attributes");
1821    }
1822
1823    /**
1824     * Binds a new name to the object bound to an old name, and unbinds
1825     * the old name. Both names are relative to this context.
1826     * Any attributes associated with the old name become associated
1827     * with the new name.
1828     * Intermediate contexts of the old name are not changed.
1829     *
1830     * @param oldName the name of the existing binding; may not be empty
1831     * @param rdn the RDN of the new binding; may not be empty
1832     * @throws javax.naming.NameAlreadyBoundException if <tt>newName</tt> is already bound
1833     * @throws javax.naming.NamingException if a naming exception is encountered
1834     * @see #rename(String, String)
1835     * @see #bind(javax.naming.Name, Object)
1836     * @see #rebind(javax.naming.Name, Object)
1837     */

1838    public void rename(Name oldName, Name rdn) throws NamingException
1839    {
1840        log.finest("rename (" + oldName + " to " + rdn);
1841
1842        String JavaDoc oldNameString = oldName.toString();
1843
1844        if (rdn.size() != 1)
1845            throw new NamingException("cannot perform rename operation '" + rdn + "' is not an RDN \n" );
1846
1847        String JavaDoc newRDN = rdn.toString();
1848
1849
1850        /*
1851        String newNameString = newName.toString();
1852
1853        if (!oldNameString.endsWith(contextName))
1854            oldNameString = oldNameString + "," + contextName;
1855
1856        if (!newNameString.endsWith(contextName))
1857        {
1858            newNameString = newNameString + "," + contextName;
1859            newName = new DN(newNameString);
1860        }
1861
1862        if (newName.size() != 1 && newName.size() != oldName.size())
1863            throw new NamingException("cannot perform rename operation - DNs are at different levels:\n" + oldNameString + "\n" + newNameString);
1864
1865        int nameSize = newName.size();
1866
1867        if (!oldName.getPrefix(nameSize - 2).equals(newName.getPrefix(nameSize - 2)))
1868            throw new NamingException("cannot perform rename operation - RDNs have different parents:\n" + oldNameString + "\n" + newNameString);
1869
1870        String newRDN = newName.get(newName.size() - 1).toString();
1871
1872        */

1873
1874        // construct XML
1875

1876        StringBuffer JavaDoc modDNRequestBuffer = constructModDNRequest(oldNameString, newRDN);
1877
1878        // send XML to server
1879

1880        String JavaDoc response = sendDSMLRequest(modDNRequestBuffer);
1881
1882        // parse response XML
1883

1884        parseModDNResponse(response);
1885
1886
1887    }
1888
1889    /**
1890     * Retrieves the parser associated with the named context. Note that the
1891     * DsmlContext does not support composite names, and so DsmlParser.parser
1892     * is returned regardless of what name is passed.
1893     * See {@link #getNameParser(javax.naming.Name)} for details.
1894     *
1895     * @param name the name of the context from which to get the parser
1896     * @return a name parser that can parse compound names into their atomic
1897     * components
1898     * @throws javax.naming.NamingException if a naming exception is encountered
1899     */

1900    public NameParser getNameParser(String JavaDoc name) throws NamingException
1901    {
1902        return DsmlParser.parser;
1903    }
1904
1905    /**
1906     * Retrieves the parser associated with the named context. Note that the
1907     * DsmlContext does not support composite names, and so DsmlParser.parser
1908     * is returned regardless of what name is passed.
1909     *
1910     * @param name the name of the context from which to get the parser (not used)
1911     * @return a name parser that can parse compound DSML/LDAP names into their atomic
1912     * components
1913     * @throws javax.naming.NamingException if a naming exception is encountered
1914     * @see #getNameParser(String)
1915     * @see javax.naming.CompoundName
1916     */

1917    public NameParser getNameParser(Name name) throws NamingException
1918    {
1919        return DsmlParser.parser;
1920    }
1921
1922    /**
1923     * Enumerates the names bound in the named context.
1924     * See {@link #list(javax.naming.Name)} for details.
1925     *
1926     * @param name the name of the context to list
1927     * @return an enumeration of the names and class names of the
1928     * bindings in this context. Each element of the
1929     * enumeration is of type <tt>NameClassPair</tt>.
1930     * @throws javax.naming.NamingException if a naming exception is encountered
1931     */

1932    public NamingEnumeration list(String JavaDoc name) throws NamingException
1933    {
1934        log.finest("list (" + name.toString() + ")");
1935
1936        return doDsmlSearch(name, SINGLELEVEL, SEARCHING, 0, 0, false, "(objectClass=*)", null);
1937    }
1938
1939    /**
1940     * Enumerates the names bound in the named context, along with the
1941     * objects bound to them.
1942     * See {@link #listBindings(javax.naming.Name)} for details.
1943     *
1944     * @param name the name of the context to list
1945     * @return an enumeration of the bindings in this context.
1946     * Each element of the enumeration is of type
1947     * <tt>Binding</tt>.
1948     * @throws javax.naming.NamingException if a naming exception is encountered
1949     * @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
1950     */

1951    public NamingEnumeration listBindings(String JavaDoc name) throws NamingException
1952    {
1953        throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
1954    }
1955
1956    /**
1957     * Enumerates the names bound in the named context, along with the
1958     * class names of objects bound to them.
1959     * The contents of any subcontexts are not included.
1960     * <p/>
1961     * <p> If a binding is added to or removed from this context,
1962     * its effect on an enumeration previously returned is undefined.
1963     *
1964     * @param name the name of the context to list
1965     * @return an enumeration of the names and class names of the
1966     * bindings in this context. Each element of the
1967     * enumeration is of type <tt>NameClassPair</tt>.
1968     * @throws javax.naming.NamingException if a naming exception is encountered
1969     * @see #list(String)
1970     * @see #listBindings(javax.naming.Name)
1971     * @see javax.naming.NameClassPair
1972     */

1973    public NamingEnumeration list(Name name) throws NamingException
1974    {
1975        return list(name.toString());
1976
1977    }
1978
1979    /**
1980     * Enumerates the names bound in the named context, along with the
1981     * objects bound to them.
1982     * The contents of any subcontexts are not included.
1983     * <p/>
1984     * <p> If a binding is added to or removed from this context,
1985     * its effect on an enumeration previously returned is undefined.
1986     *
1987     * @param name the name of the context to list
1988     * @return an enumeration of the bindings in this context.
1989     * Each element of the enumeration is of type
1990     * <tt>Binding</tt>.
1991     * @throws javax.naming.NamingException if a naming exception is encountered
1992     * @see #listBindings(String)
1993     * @see #list(javax.naming.Name)
1994     * @see javax.naming.Binding
1995     * @deprecated java Object specific methods such as bind/unbind/lookup are not supported by DsmlContext
1996     */

1997    public NamingEnumeration listBindings(Name name) throws NamingException
1998    {
1999        throw new OperationNotSupportedException("binding of java objects is not supported by the DsmlContext");
2000    }
2001
2002    /**
2003     * Adds a new environment property to the environment of this
2004     * context. If the property already exists, its value is overwritten.
2005     * See class description for more details on environment properties.
2006     *
2007     * @param propName the name of the environment property to add; may not be null
2008     * @param propVal the value of the property to add; may not be null
2009     * @return the previous value of the property, or null if the property was
2010     * not in the environment before
2011     * @throws javax.naming.NamingException if a naming exception is encountered
2012     * @see #getEnvironment()
2013     * @see #removeFromEnvironment(String)
2014     */

2015    public Object JavaDoc addToEnvironment(String JavaDoc propName, Object JavaDoc propVal) throws NamingException
2016    {
2017        log.finest("addToEnvironment (" + propName + ": " + propVal.toString());
2018
2019        if (propName == null)
2020            return null;
2021
2022        if (propVal == null)
2023            return environment.remove(propName);
2024        else
2025            return environment.put(propName, propVal);
2026    }
2027
2028    /**
2029     * Not implemented - use DN methods instead if you want to create new Names.
2030     * DsmlContext does not support composite names (stupid idea anyway).
2031     *
2032     * @param name a name relative to this context
2033     * @param prefix the name of this context relative to one of its ancestors
2034     * @return the composition of <code>prefix</code> and <code>name</code>
2035     * @throws javax.naming.NamingException if a naming exception is encountered
2036     * @deprecated
2037     */

2038    public String JavaDoc composeName(String JavaDoc name, String JavaDoc prefix) throws NamingException
2039    {
2040        throw new OperationNotSupportedException("DsmlContext does not support composing names - use DN methods directly");
2041    }
2042
2043    /**
2044     * Not implemented - use DN methods instead if you want to create new Names.
2045     * DsmlContext does not support composite names (stupid idea anyway).
2046     *
2047     * @param name a name relative to this context
2048     * @param prefix the name of this context relative to one of its ancestors
2049     * @return the composition of <code>prefix</code> and <code>name</code>
2050     * @throws javax.naming.NamingException if a naming exception is encountered
2051     * @see #composeName(String, String)
2052     * @deprecated
2053     */

2054    public Name composeName(Name name, Name prefix) throws NamingException
2055    {
2056        throw new OperationNotSupportedException("DsmlContext does not support composing names - use DN methods directly");
2057    }
2058
2059
2060/*
2061 * ---------- DSML Specific Code ------------
2062 */

2063
2064    /**
2065     * This is the central coordinating search method. It takes all the raw paramaters, and hands them off
2066     * to various sub methods do construct the search request, send it to the DSML server and get the result.
2067     *
2068     * @param name
2069     * @param scope
2070     * @param derefAliases
2071     * @param sizeLimit
2072     * @param timeLimit
2073     * @param typesOnly
2074     * @param filter
2075     * @param attributesToReturn
2076     * @return
2077     * @throws NamingException
2078     */

2079    public NamingEnumeration doDsmlSearch(String JavaDoc name, String JavaDoc scope, String JavaDoc derefAliases, long sizeLimit, int timeLimit, boolean typesOnly, String JavaDoc filter, String JavaDoc[] attributesToReturn)
2080            throws NamingException
2081    {
2082        // construct XML
2083

2084        StringBuffer JavaDoc searchRequestBuffer = constructSearchRequest(name, scope, derefAliases, sizeLimit, timeLimit, typesOnly, filter, attributesToReturn);
2085
2086// send XML to server
2087

2088        String JavaDoc response = sendDSMLRequest(searchRequestBuffer);
2089
2090// parse response XML
2091

2092        return parseSearchResponse(response, name);
2093
2094    }
2095
2096// static patterns for parsing search results.
2097
// private static Pattern searchResultEntry = Pattern.compile("<searchResultEntry (.*)</searchResultEntry>");
2098

2099// nb. Extreme caution required here to match DNs that may contain '"' and '>' characters. Note that no legal dn
2100
// can contain the sequence '">', since the " and > both need escaping. There *might* be a requestID in there
2101
// as well, but I've never seen this in the wild (what would the point be?)
2102

2103// need to cope with option namespace prefixes...
2104
// private static Pattern searchResultEntry = Pattern.compile("<searchResultEntry.*?dn=\"(.*?)\">(.*?)</searchResultEntry>", Pattern.DOTALL);
2105
// private static Pattern searchResultAttribute = Pattern.compile("<attr name=\"(.*?)\">(.*?)</attr>", Pattern.DOTALL);
2106
// private static Pattern searchResultAttributeValues = Pattern.compile("<value(.*?)>(.*?)</value>", Pattern.DOTALL);
2107

2108    private static Pattern JavaDoc searchResultEntry = Pattern.compile("searchResultEntry.*?dn=\"(.*?)\">(.*?)searchResultEntry>", Pattern.DOTALL);
2109
2110    private static Pattern JavaDoc searchResultAttribute = Pattern.compile("attr name=\"(.*?)\">(.*?)attr>", Pattern.DOTALL);
2111
2112    private static Pattern JavaDoc searchResultAttributeValues = Pattern.compile("value(.*?)>(.*?)<.*?value>", Pattern.DOTALL);
2113
2114    private static Pattern JavaDoc searchTestType = Pattern.compile("type=\"(.*?)\"", Pattern.DOTALL);
2115
2116    /**
2117     * This is one of the central methods of the provider. It takes the raw XML returned and extracts the
2118     * DNs and attributes of the returned entries into a standard namingenumeration of SearchResult objects.
2119     *
2120     * @param response the DSML XML response
2121     * @param searchBase supply the search base so that this method can check if the DNs returned
2122     * in the search result are relative or not. By default a search result's DN is set to relative.
2123     * If the DN returned in the search result includes the search base then it is not relative and
2124     * we need to set that so users of this provider don't re-add the search base after performing a
2125     * isRelative call on the SearchResult.
2126     * @return a naming enumeration of 'SearchResult' objects.
2127     * @throws NamingException if any error is found (including a returned error from the DSML server).
2128     */

2129    static NamingEnumeration parseSearchResponse(String JavaDoc response, String JavaDoc searchBase)
2130            throws NamingException
2131    {
2132        checkForError(response);
2133
2134        DXNamingEnumeration en = new DXNamingEnumeration();
2135
2136        Matcher JavaDoc responseMatcher = searchResultEntry.matcher(response);
2137
2138// cycle through the available entries
2139
while (responseMatcher.find())
2140        {
2141            String JavaDoc dn = responseMatcher.group(1);
2142
2143            BasicAttributes atts = new BasicAttributes();
2144
2145            if (dn.indexOf("requestID") > -1) // what numb nut would put a requestID in a searchResultEntry??
2146
{
2147                int reqPos = dn.indexOf("requestID");
2148                int endPos = dn.lastIndexOf('"', reqPos);
2149                dn = dn.substring(0, endPos);
2150            }
2151
2152            if (dn.indexOf("&") > -1)
2153                dn = unescape(dn);
2154
2155            log.finest("Parsing DSML: read DN: " + dn);
2156
2157            // turn pure pristine LDAP DN into un unbelievably screwed up JNDI Composite name, and then
2158
// store it as a string SO NO ONE CAN EVER REALLY TELL WHAT THE HELL IT IS. God JNDI is
2159
// broken - what a (*&*^ joke. Object Oriented? Yeah, we've heard of that...
2160

2161            dn = new CompositeName(dn).toString();
2162
2163            log.finest("converted DN to: " + dn);
2164
2165            String JavaDoc attribute = responseMatcher.group(2);
2166
2167            Matcher JavaDoc attributeMatcher = searchResultAttribute.matcher(attribute);
2168
2169            // cycle through individual attributes
2170
while (attributeMatcher.find())
2171            {
2172                String JavaDoc attributeName = attributeMatcher.group(1);
2173
2174                BasicAttribute att = new BasicAttribute(attributeName);
2175
2176
2177                String JavaDoc attributeValues = attributeMatcher.group(2);
2178
2179                log.finest("Parsing DSML: Attribute Name " + attributeName + " length: " + attributeValues.length());
2180
2181                Matcher JavaDoc valueMatcher = searchResultAttributeValues.matcher(attributeValues);
2182
2183                while (valueMatcher.find())
2184                {
2185                    String JavaDoc typeInfo = valueMatcher.group(1);
2186
2187                    String JavaDoc type = "string"; // the default value is string
2188

2189                    if (typeInfo != null && typeInfo.length() > 6)
2190                    {
2191// don't bother using regexp - faster to use string handling (if insanely clever might modify attributeValues regexp??)
2192
int typeInfoStart = typeInfo.indexOf("type=\"");
2193                        int typeInfoEnd = typeInfo.indexOf('\"', typeInfoStart + 6);
2194                        if (typeInfoStart != -1 && typeInfoEnd > typeInfoStart)
2195                        {
2196                            type = typeInfo.substring(typeInfoStart + 6, typeInfoEnd); // extract the type; e.g. xsd:base64Binary
2197
if ((typeInfoStart = type.indexOf(':')) > -1) // trim any namespace from the type (e.g. 'xsd:')
2198
type = type.substring(typeInfoStart + 1);
2199                        }
2200                    }
2201                    String JavaDoc value = valueMatcher.group(2);
2202
2203                    if (type.equals("string")) // this is the usual case, and the default
2204
{
2205                        if (value != null && value.indexOf('&') > -1)
2206                            value = unescape(value);
2207                        att.add(value);
2208                    }
2209                    else if (type.equals("anyURI"))
2210                        throw new NamingException("CA JNDI DSML Provider does not support 'anyURI' values");
2211                    else if (type.equals("base64Binary"))
2212                    {
2213                        try
2214                        {
2215
2216                            System.out.println("PROCESSING BINARY VALUE " + attributeName);
2217                            byte[] data = CBBase64.decode(value);
2218                            System.out.println("RAW DATA: " + value.length() + " byte data " + data.length);
2219                            att.add(CBBase64.decode(value));
2220                        }
2221                        catch (CBBase64EncodingException e)
2222                        {
2223                            NamingException ne = new NamingException("unable to parse base64 value in entry: " + dn);
2224                            ne.setRootCause(e);
2225                            throw ne;
2226                        }
2227                    }
2228                }
2229                atts.put(att);
2230            }
2231
2232            SearchResult currentResult = new SearchResult(dn, null, atts);
2233
2234            // The search result sets 'is relative' to true by default. Check if the
2235
// DN in the search result contains the search base, if so set 'is relative'
2236
// to false so that the search base is NOT added to the DN again. In otherwords
2237
// check if the full DN has been returned in the search result...
2238
if(dn.endsWith(searchBase))
2239                currentResult.setRelative(false);
2240
2241            en.add(currentResult);
2242        }
2243
2244        log.finest("Parsing DSML: final enumeration - \n" + en.toString());
2245
2246        return en;
2247    }
2248
2249    /**
2250     * This converts the string buffer to raw bytes, and passes them to the SoapClient 'sendSoapMsg' method,
2251     * returning the response.
2252     *
2253     * @param searchRequestBuffer
2254     * @return
2255     * @throws NamingException
2256     */

2257    private String JavaDoc sendDSMLRequest(StringBuffer JavaDoc searchRequestBuffer)
2258            throws NamingException
2259    {
2260        String JavaDoc response;
2261
2262        try
2263        {
2264// note for performance junkies - assigning an unshared string buffer to a string is a cheap operation that shares the internal char array.
2265
String JavaDoc searchRequest = new String JavaDoc(searchRequestBuffer);
2266
2267            byte[] rawBytes = searchRequest.getBytes("UTF-8");
2268
2269            String JavaDoc URL = environment.get(PROVIDER_URL).toString();
2270
2271            log.finest("----SENDING XML OVER WIRE-----\nURL: " + URL + "\nlength: " + rawBytes.length);
2272
2273            response = SoapClient.sendSoapMsg(URL, rawBytes, "#batchRequest");
2274
2275        }
2276        catch (UnsupportedEncodingException JavaDoc e) // should never happen. really.
2277
{
2278            NamingException ne = new NamingException("unexpected exception encoding UTF-8 string for DSML message");
2279            ne.setRootCause(e);
2280            throw ne;
2281        }
2282        catch (IOException JavaDoc e)
2283        {
2284            NamingException ne = new NamingException("error contacting DSML Server");
2285            ne.setRootCause(e);
2286            throw ne;
2287        }
2288        return response;
2289    }
2290
2291    /**
2292     * @param name
2293     * @param atts
2294     * @return
2295     */

2296    static StringBuffer JavaDoc constructAddRequest(String JavaDoc name, Attributes atts)
2297            throws NamingException
2298    {
2299        StringBuffer JavaDoc message = new StringBuffer JavaDoc(200);
2300        message.append(STANDARDHEADER);
2301
2302        getAddRequestElement(message, name, atts);
2303
2304        message.append(STANDARDFOOTER);
2305
2306        return message;
2307    }
2308
2309    /**
2310     * returns the actual DSML 'delRequest' element.<br>
2311     * e.g.:&lt;br&gt:
2312     * &lt;dsml:addRequest requestID="1" dn="cn=Alana SHORE,ou=Infrastructure,ou=Support,o=DEMOCORP,c=AU"&gt:
2313     * &lt;dsml:attr name="cn"&gt:
2314     * &lt;dsml:value&gt:Alana SHORE&lt;/dsml:value&gt:
2315     * &lt;/dsml:attr&gt:
2316     * &lt;dsml:attr name="objectClass"&gt:
2317     * &lt;dsml:value&gt:inetOrgPerson&lt;/dsml:value&gt:
2318     * &lt;dsml:value&gt:organizationalPerson&lt;/dsml:value&gt:
2319     * &lt;dsml:value&gt:person&lt;/dsml:value&gt:
2320     * &lt;dsml:value&gt:top&lt;/dsml:value&gt:
2321     * &lt;/dsml:attr&gt:
2322     * &lt;dsml:attr name="sn"&gt:
2323     * &lt;dsml:value&gt:SHORE&lt;/dsml:value&gt:
2324     * &lt;/dsml:attr&gt:
2325     * &lt;/dsml:addRequest&gt:
2326     *
2327     * @param message
2328     * @param name
2329     */

2330
2331/* "<dsml:batchRequest xmlns:dsml=\"urn:oasis:names:tc:DSML:2:0:core\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
2332    " <dsml:addRequest dn=\"cn=Alana SHORE,ou=Infrastructure,ou=Support,o=DEMOCORP,c=AU\">" +
2333    " <dsml:attr name=\"cn\">" +
2334    " <dsml:value>Alana SHORE</dsml:value>" +
2335    " </dsml:attr>" +
2336    " <dsml:attr name=\"objectClass\">" +
2337    " <dsml:value>inetOrgPerson</dsml:value>" +
2338    " <dsml:value>organizationalPerson</dsml:value>" +
2339    " <dsml:value>person</dsml:value>" +
2340    " <dsml:value>top</dsml:value>" +
2341    " </dsml:attr>" +
2342    " <dsml:attr name=\"sn\">" +
2343    " <dsml:value>SHORE</dsml:value>" +
2344    " </dsml:attr>" +
2345    " </dsml:addRequest>" +
2346    "</dsml:batchRequest>"
2347*/

2348    static void getAddRequestElement(StringBuffer JavaDoc message, String JavaDoc name, Attributes atts)
2349            throws NamingException // doesn't really...
2350
{
2351        message.append(TAB4).append("<dsml:addRequest dn=\"").append(escapeName(name)).append("\">\n");
2352
2353        NamingEnumeration attEnum = atts.getAll();
2354        while (attEnum.hasMore())
2355        {
2356            Attribute att = (Attribute) attEnum.next();
2357            String JavaDoc attName = att.getID();
2358            message.append(TAB5).append("<dsml:attr name=\"").append(attName).append("\">\n");
2359            NamingEnumeration values = att.getAll();
2360            while (values.hasMore())
2361            {
2362                Object JavaDoc value = values.next();
2363                createDsmlValueElement(value, message);
2364            }
2365            message.append(TAB5).append("</dsml:attr>\n");
2366        }
2367        message.append(TAB4).append("</dsml:addRequest>\n");
2368
2369    }
2370
2371    /**
2372     * This returns a &lt;dsml:attr ... &gt; , coding the value either as a string
2373     * or as a base64Binary type value (including the type marker). Note that urls references
2374     * are not supported.
2375     *
2376     * @param value
2377     * @param message
2378     * @throws NamingException
2379     */

2380    static void createDsmlValueElement(Object JavaDoc value, StringBuffer JavaDoc message)
2381            throws NamingException
2382    {
2383        if (value instanceof String JavaDoc) // expensive check, but can't think of any way around it...
2384
{
2385            String JavaDoc stringVal = escape((String JavaDoc) value);
2386            message.append(TAB6).append("<dsml:value>").append(stringVal).append("</dsml:value>\n");
2387        }
2388        else
2389        {
2390            try
2391            {
2392                byte[] data = (byte[]) value;
2393                String JavaDoc base64Data = new String JavaDoc(CBBase64.encode(data), "US-ASCII");
2394System.out.println("SENDING BINARY DATA; byte length: " + data.length + " encoded: " + base64Data.length());
2395                message.append(TAB6).append("<dsml:value xsi:type=\"xsd:base64Binary\">").append(base64Data).append("</dsml:value>\n");
2396            }
2397            catch (UnsupportedEncodingException JavaDoc e)
2398            {
2399                NamingException ne = new NamingException("unexpected encoding exception adding attribute value of type " + value.getClass());
2400                ne.setRootCause(e);
2401                throw ne;
2402            }
2403            catch (ClassCastException JavaDoc e)
2404            {
2405                NamingException ne = new NamingException("unexpected error adding attribute value of type " + value.getClass());
2406                ne.setRootCause(e);
2407                throw ne;
2408            }
2409        }
2410    }
2411
2412// "<dsml:batchRequest xmlns:dsml=\"urn:oasis:names:tc:DSML:2:0:core\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
2413
// " <dsml:delRequest dn=\"cn=Alana SHORE,ou=Infrastructure,ou=Support,o=DEMOCORP,c=AU\"/>" +
2414
// "</dsml:batchRequest>";
2415

2416    /**
2417     * This returns a DSML batchrequest containing a single 'delRequest' element.
2418     *
2419     * @param name
2420     * @return
2421     */

2422    static StringBuffer JavaDoc constructDeleteRequest(String JavaDoc name)
2423    {
2424        StringBuffer JavaDoc message = new StringBuffer JavaDoc(200);
2425        message.append(STANDARDHEADER);
2426
2427        getDeleteRequestElement(message, name);
2428
2429        message.append(STANDARDFOOTER);
2430
2431        return message;
2432    }
2433
2434
2435    /**
2436     * returns the actual DSML 'delRequest' element.<br>
2437     * e.g.:<br>
2438     * &lt;dsml:delRequest requestID="1" dn="cn=Alana SHORE,ou=Infrastructure,ou=Support,o=DEMOCORP,c=AU"/&gt;
2439     *
2440     * @param message
2441     * @param name
2442     */

2443    static void getDeleteRequestElement(StringBuffer JavaDoc message, String JavaDoc name)
2444    {
2445        message.append(TAB4 + "<dsml:delRequest dn=\"").append(escapeName(name)).append("\"/>\n");
2446    }
2447
2448    /**
2449     * This is a co-ordinating method that builds up the Search Request XML by calling various
2450     * component building methods.
2451     *
2452     * @param name
2453     * @param scope
2454     * @param derefAliases
2455     * @param sizeLimit
2456     * @param timeLimit
2457     * @param typesOnly
2458     * @param filter
2459     * @param attributesToReturn
2460     * @return
2461     * @throws NamingException
2462     */

2463    static StringBuffer JavaDoc constructSearchRequest(String JavaDoc name, String JavaDoc scope, String JavaDoc derefAliases, long sizeLimit, int timeLimit, boolean typesOnly, String JavaDoc filter, String JavaDoc[] attributesToReturn)
2464            throws NamingException
2465    {
2466        StringBuffer JavaDoc message = new StringBuffer JavaDoc(500);
2467        message.append(STANDARDHEADER);
2468
2469        getSearchRequestHeader(message, name, scope, derefAliases, sizeLimit, timeLimit, typesOnly);
2470
2471        getSearchRequestFilter(message, filter, TAB4);
2472
2473        getSearchRequestAttributes(message, attributesToReturn);
2474
2475        message.append(SEARCHFOOTER).append(STANDARDFOOTER);
2476
2477        return message;
2478    }
2479
2480    private static Pattern JavaDoc escapeGTPattern = Pattern.compile("(&gt;)", Pattern.DOTALL);
2481    private static Pattern JavaDoc escapeLTPattern = Pattern.compile("(&lt;)", Pattern.DOTALL);
2482    private static Pattern JavaDoc escapeAMPPattern = Pattern.compile("(&amp;)", Pattern.DOTALL);
2483    private static Pattern JavaDoc escapeQUOTPattern = Pattern.compile("(&quot;)", Pattern.DOTALL);
2484    private static Pattern JavaDoc escapeAPOSPattern = Pattern.compile("(&apos;)", Pattern.DOTALL);
2485    private static Pattern JavaDoc escapeOCTOPattern = Pattern.compile("(&#.*?;)", Pattern.DOTALL);
2486
2487    /**
2488     * Translates escaped XML characters as per Extensible Markup Language (XML) 1.0 (Second Edition)
2489     * section 4.6.
2490     *
2491     * @param value a string containing escaped character sequences such as '&amp;' or '&
2492     * @return
2493     */

2494
2495// I wonder what the efficiency hit of this is? Hopefully it won't happen very often,
2496
// but it could be quite ugly for large strings. Redo via StringBuffer?
2497

2498    static String JavaDoc unescape(String JavaDoc value)
2499    {
2500        if (value.indexOf('&') == -1) // if we don't need to do anything...
2501
return value; // ... then don't.
2502

2503        value = escapeGTPattern.matcher(value).replaceAll(">");
2504        value = escapeLTPattern.matcher(value).replaceAll("<");
2505        value = escapeQUOTPattern.matcher(value).replaceAll("\"");
2506        value = escapeAPOSPattern.matcher(value).replaceAll("'");
2507        value = escapeAMPPattern.matcher(value).replaceAll("&");
2508
2509        Matcher JavaDoc OCTOMatcher = escapeOCTOPattern.matcher(value);
2510
2511// cycle through the available entries
2512
while (OCTOMatcher.find())
2513        {
2514            String JavaDoc escapeChar = OCTOMatcher.group(1);
2515
2516// TODO: this does not correctly cope with multiple escape sequence pairs -
2517
// note that wierd sequences like: &#38;#60; can occur - we only want the last pair
2518
// (&#38; == '&'). Not sure how to cope with this though, since the pattern match
2519
// only grabs the first?
2520

2521            int len = escapeChar.length();
2522            char char1 = 0;
2523            char char2 = 0;
2524
2525            if (len >= 5)
2526            {
2527                char1 = escapeChar.charAt(len - 3);
2528                char2 = escapeChar.charAt(len - 2);
2529            }
2530            if (Character.isDigit(char1) && Character.isDigit(char2))
2531            {
2532                int val = Character.digit(char1, 16) * 16 + Character.digit(char2, 16);
2533                try
2534                {
2535                    String JavaDoc replacement = new String JavaDoc(new byte[]{new Integer JavaDoc(val).byteValue()}, "US-ASCII");
2536                    value = OCTOMatcher.replaceFirst(replacement);
2537                }
2538                catch (UnsupportedEncodingException JavaDoc e) // should never happen. It's flippin ASCII after all!
2539
{
2540                    log.log(Level.SEVERE, "Unexpected exception trying to decode escaped value " + OCTOMatcher.group(1), e);
2541                }
2542            }
2543            else
2544            {
2545                log.info("unable to decode escaped value: " + escapeChar);
2546                break; // give up (including on any subsequent escape sequences)
2547
}
2548
2549            OCTOMatcher = escapeOCTOPattern.matcher(value);
2550        }
2551
2552        return value;
2553    }
2554
2555    /**
2556     * Handling to DSML escape names. Currently this is done in
2557     * exactly the same way as values, but this method will allow
2558     * us to perform any future trickiness that may be required.
2559     * @param value
2560     * @return
2561     */

2562    static String JavaDoc escapeName(String JavaDoc value)
2563    {
2564        return escape(value);
2565    }
2566
2567
2568    /**
2569     * As per the standard XML rules, we have to escape 'html' like
2570     * characters &gt;, &lt;, &apos;, &quot;, and &amp;.
2571     * @param value
2572     * @return
2573     */

2574    static String JavaDoc escape(String JavaDoc value)
2575    {
2576        int len = value.length();
2577
2578// quickly check if value needs escaping.
2579

2580        boolean needsEscaping = false;
2581        for (int i = 0; i < len; i++)
2582        {
2583            switch (value.charAt(i))
2584            {
2585                case '>':
2586                    needsEscaping = true;
2587                    break;
2588                case '<':
2589                    needsEscaping = true;
2590                    break;
2591                case '&':
2592                    needsEscaping = true;
2593                    break;
2594                case '\"':
2595                    needsEscaping = true;
2596                    break;
2597                case '\'':
2598                    needsEscaping = true;
2599                    break;
2600            }
2601            if (needsEscaping)
2602                i = len;
2603        }
2604
2605        if (!needsEscaping)
2606            return value;
2607
2608// it does need escaping; so start modifying the string...
2609
StringBuffer JavaDoc buffy = new StringBuffer JavaDoc(value);
2610
2611        for (int i = len - 1; i >= 0; i--)
2612        {
2613            switch (value.charAt(i))
2614            {
2615                case '>':
2616                    buffy.replace(i, i + 1, "&gt;");
2617                    break;
2618                case '<':
2619                    buffy.replace(i, i + 1, "&lt;");
2620                    break;
2621                case '&':
2622                    buffy.replace(i, i + 1, "&amp;");
2623                    break;
2624                case '\"':
2625                    buffy.replace(i, i + 1, "&quot;");
2626                    break;
2627                case '\'':
2628                    buffy.replace(i, i + 1, "&apos;");
2629                    break;
2630            }
2631        }
2632
2633        return buffy.toString();
2634    }
2635
2636    /**
2637     * <p>This constructs the XML tag and elements for a DSML search filter
2638     * <p/>
2639     * <p>e.g.
2640     * <pre>
2641     * &gt;dsml:filter&lt;
2642     * &gt;dsml:and&lt;
2643     * &gt;dsml:substrings name="sn"&lt;
2644     * &gt;dsml:initial&lt;Li&gt;/dsml:initial&lt;
2645     * &gt;/dsml:substrings&lt;
2646     * &gt;dsml:substrings name="cn"&lt;
2647     * &gt;dsml:initial&lt;Cr&gt;/dsml:initial&lt;
2648     * &gt;/dsml:substrings&lt;
2649     * &gt;/dsml:and&lt;
2650     * &gt;/dsml:filter&lt;
2651     * </pre>
2652     *
2653     * @param message an existing string buffer to append the search filter information to
2654     * @param filter an LDAP RFC 2254 filter to translate into verbose DSML woffle
2655     * @param indent whitespace padding to indent the filter for a pleasent and harmonious viewing experience
2656     * @return
2657     */

2658
2659    static String JavaDoc getSearchRequestFilter(StringBuffer JavaDoc message, String JavaDoc filter, String JavaDoc indent)
2660            throws NamingException
2661    {
2662        if (filter == null)
2663            throw new NamingException("null filter expression in get SearchRequestFilter");
2664
2665        filter = filter.trim();
2666        if (filter.length() == 0)
2667            throw new NamingException("empty filter expression in get SearchRequestFilter");
2668
2669        log.finest("translating ldap filter '" + filter + "'");
2670/*
2671 * This is where we 'grow' the dsml version of the ldap filter
2672 */

2673//StringBuffer message = new StringBuffer();
2674

2675/*
2676 * This is formatting fluff - we keep a margin of whitespace to indent the text nicely
2677 */

2678        StringBuffer JavaDoc padding = new StringBuffer JavaDoc(indent);
2679
2680/*
2681 * We keep operator DSML elements on a stack, so that we can output the closing tags easily
2682 */

2683        Stack stack = new Stack();
2684
2685/*
2686 * This tokenizer breaks up a RFC 2254 ldap filter into its components
2687 */

2688        StringTokenizer filterElements = new StringTokenizer(filter, "()", true);
2689
2690        message.append(padding).append("<dsml:filter>\n");
2691
2692        while (filterElements.hasMoreTokens())
2693        {
2694            String JavaDoc filterToken = filterElements.nextToken();
2695            try
2696            {
2697                if (filterToken.charAt(0) == '(')
2698                {
2699                    filterToken = filterElements.nextToken(); // skip the bracket, and look at what's next. In ldap, this *cannot* be another '(' or ')'.
2700

2701                    padding.append(TAB);
2702                    message.append(padding);
2703
2704                    if (filterToken.length() == 1)
2705                    {
2706                        switch (filterToken.charAt(0))
2707                        {
2708                            case '&':
2709                                message.append("<dsml:and>\n");
2710                                stack.push("</dsml:and>\n");
2711                                break;
2712                            case '|':
2713                                message.append("<dsml:or>\n");
2714                                stack.push("</dsml:or>\n");
2715                                break;
2716                            case '!':
2717                                message.append("<dsml:not>\n");
2718                                stack.push("</dsml:not>\n");
2719                                break;
2720                            default:
2721                                throw new NamingException("unexpected token '" + filterToken + "' in ldap filter: " + filter);
2722                        }
2723                    }
2724                    else
2725                    {
2726                        stack.push(""); // add an empty stack item.
2727
translateFilterItem(filterToken, padding, message); // add the search filter item (e.g. 'sn>=fr', 'cn=*' or 'ou=*research*'
2728
padding.setLength(padding.length() - TABLEN);
2729                    }
2730                }
2731                else if (filterToken.charAt(0) == ')')
2732                {
2733                    String JavaDoc closingElement = (String JavaDoc) stack.pop();
2734                    if (closingElement.length() > 0) // ignore empty stack items.
2735
{
2736                        message.append(padding).append(closingElement); // add closing tab
2737
padding.setLength(padding.length() - TABLEN);
2738                    }
2739                }
2740                else
2741                {
2742                    throw new NamingException("unexpected token '" + filterToken + "' in search filter: " + filter);
2743                }
2744            }
2745            catch (EmptyStackException e)
2746            {
2747                throw new NamingException("unexpected end of ldap search filter (empty or non matching brackets?): " + filter);
2748            }
2749        }
2750
2751        message.append(padding).append("</dsml:filter>\n");
2752
2753        return message.toString();
2754    }
2755
2756    static void translateFilterItem(String JavaDoc filterToken, StringBuffer JavaDoc padding, StringBuffer JavaDoc dsmlFilter)
2757            throws NamingException
2758    {
2759        int equalpos = filterToken.indexOf('=');
2760        if (equalpos < 1 || equalpos > filterToken.length() - 2)
2761            throw new NamingException("Unable to parse ldap filter element '" + filterToken + "'");
2762
2763        String JavaDoc attribute = filterToken.substring(0, equalpos); // e.g. 'cn'
2764
String JavaDoc expression = filterToken.substring(equalpos + 1); // e.g. '*fred*'
2765

2766        char endOfAttribute = attribute.charAt(equalpos - 1); // check to see if it was ~=, >= or <=
2767

2768// see if it is a 'simple' filter item
2769
if (endOfAttribute == '<' || endOfAttribute == '>' || endOfAttribute == '~')
2770        {
2771            attribute = attribute.substring(0, equalpos - 1); // trim attribute name of the ending </>/~ character
2772
String JavaDoc value = new StringBuffer JavaDoc(100).append(" name=\"").append(attribute).append("\">").append(padding).append(TAB).append("<dsml:value>").append(escape(expression)).append("</dsml:value>").append(padding).toString();
2773
2774            switch (endOfAttribute)
2775            {
2776                case '<':
2777                    dsmlFilter.append("<dsml:lessOrEqual").append(value).append("</dsml:lessOrEqual>\n");
2778                    break;
2779                case '>':
2780                    dsmlFilter.append("<dsml:greaterOrEqual").append(value).append("</dsml:greaterOrEqual>\n");
2781                    break;
2782                case '~':
2783                    dsmlFilter.append("<dsml:approxMatch").append(value).append("</dsml:approxMatch>\n");
2784                    break;
2785            }
2786        }
2787// see if it is a 'simple' equality match filter item
2788
else if (expression.indexOf('*') == -1)
2789        {
2790            dsmlFilter.append("<dsml:equalityMatch name=\"").append(attribute).append("\">").append(padding).append(" ").append("<dsml:value>").append(escape(expression)).append("</dsml:value>").append(padding).append("</dsml:equalityMatch>\n");
2791        }
2792// see if it is a 'present' filter item
2793
else if (expression.equals("*"))
2794        {
2795            dsmlFilter.append("<dsml:present name=\"").append(attribute).append("\"/>\n");
2796        }
2797// yucky hard cases.
2798
else // the expression contains at least one '*', and is longer than 1 character.
2799
{
2800            dsmlFilter.append("<dsml:substrings name=\"").append(attribute).append("\">\n");
2801            padding.append(TAB);
2802
2803// prepare to parse expressions like 'Fr*n*k*stein"
2804
StringTokenizer substringFilter = new StringTokenizer(expression, "*", true);
2805            String JavaDoc initial = substringFilter.nextToken();
2806            if (initial.equals("*") == false)
2807                dsmlFilter.append(padding).append("<dsml:initial>").append(escape(initial)).append("</dsml:initial>\n"); // were they drunk when they designed this verbose crud?
2808

2809            while (substringFilter.hasMoreTokens())
2810            {
2811                String JavaDoc filterElement = substringFilter.nextToken();
2812                if (filterElement.equals("*") == false) // skip over the stars
2813
{
2814                    if (substringFilter.hasMoreTokens() == false) // then this is the last one!
2815
dsmlFilter.append(padding).append("<dsml:final>").append(escape(filterElement)).append("</dsml:final>\n");
2816                    else
2817                        dsmlFilter.append(padding).append("<dsml:any>").append(escape(filterElement)).append("</dsml:any>\n");
2818                }
2819            }
2820            padding.setLength(padding.length() - TABLEN);
2821            dsmlFilter.append(padding).append("</dsml:substrings>\n");
2822
2823        }
2824// LDAP v3 Extensions not supported - your code here...
2825
}
2826}
2827
Popular Tags