KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ca > commons > naming > LdifUtility


1 package com.ca.commons.naming;
2
3 import javax.naming.*;
4 import javax.naming.directory.*;
5
6 import java.util.*;
7 import java.util.logging.Logger JavaDoc;
8 import java.util.logging.Level JavaDoc;
9 import java.io.*;
10
11 import com.ca.commons.cbutil.*;
12
13
14
15 /**
16  * collection of static utility ftns. for
17  * writing and reading ldif files. Currently does not
18  * handle URLs properly, but will do base64 encoding
19  * quite happily given half a chance...
20  */

21
22 //TODO: add 'version 2' handling to ldif file when using xml / ldif, and setup config parameter.
23

24 public class LdifUtility
25 {
26     private static boolean debug = false;
27
28     private static boolean handleXML = false;
29
30     private Hashtable params = null; // list of expandable strings for the ldif file, used during file parsing
31
// e.g. KEY: <base_dn>, KEY VALUE: "o=eTrust, cn=Users"
32
private String JavaDoc filedir = null;
33
34     private String JavaDoc cr = System.getProperty("line.separator", "\n");
35
36     private final static Logger JavaDoc log = Logger.getLogger(LdifUtility.class.getName());
37
38     public LdifUtility()
39     {
40     }
41
42     /**
43      * Sets whether to support the draft rfc for special handling of XML data in LDIF files.
44      * @param state
45      */

46     public static void setSupportXML_LDIF_RFC(boolean state)
47     {
48         handleXML = state;
49     }
50
51     /**
52      * Constructor
53      *
54      * @param params - hashtable with the list of string that will have to be suvstituted in the ldif file
55      * @param filedir - ldif file directory, used to find the input files specified in the ldif stream
56      */

57     public LdifUtility(Hashtable params, String JavaDoc filedir)
58     {
59         this.params = params;
60         this.filedir = filedir;
61     }
62
63     /**
64      * Set the ldif filepath - used to find input files
65      *
66      * @param filedir file path
67      */

68     public void setFileDir(String JavaDoc filedir)
69     {
70         this.filedir = filedir + "\\";
71     }
72
73     /**
74      * Set the ldif file parameters
75      *
76      * @param params list of parameters
77      */

78     public void setParams(Hashtable params)
79     {
80         this.params = params;
81     }
82
83     /**
84      * This is used to write a value that is *probably* normal
85      * string encoded, but *may* need to be base64 encoded.
86      * It also takes a boolean parameter that forces base64 encoding.
87      * Otherwise, it
88      * checks the string against the requirements of draft-good-ldap-ldif-04
89      * (initial character sane, subsequent characters not null, CR or LF),
90      * and returns the appropriate string, with appropriate ': ' or ':: '
91      * prefix.
92      *
93      * @param o the object to be ldif encoded
94      * @return the ldif encoding (possibly base64) with appropriate colons.
95      */

96     public String JavaDoc ldifEncode(Object JavaDoc o, int offset, boolean forceBase64Encoding)
97     {
98         if (forceBase64Encoding == false)
99             return ldifEncode(o, offset);
100
101         String JavaDoc ret = ":: ";
102         if (o.getClass().isArray())
103         {
104             try
105             {
106                 byte b[] = (byte[]) o;
107                 ret += CBBase64.binaryToString(b, offset + 3);
108                 return ret;
109             }
110             catch (ClassCastException JavaDoc e)
111             {
112                 System.out.println("unable to cast array to byte array.");
113             }
114         }
115         
116         // it's not a byte array; force it to a string, read as bytes,
117
// and code those. This will work in most cases, but will
118
// fail badly for isNonString data that has not been encoded properly
119
// already; e.g. a gif file should (probably) be translated to a
120
// byte array before being passed to this ftn.
121

122         ret += CBBase64.binaryToString(o.toString().getBytes(), offset + 3);
123         return ret;
124     }
125
126     /**
127      * This is used to write a value that is *probably* normal
128      * string encoded, but *may* need to be base64 encoded or xml encoded. It
129      * checks the string against the requirements of draft-good-ldap-ldif-04
130      * (initial character sane, subsequent characters not null, CR or LF),
131      * and returns the appropriate string, with appropriate ': ' or ':: '
132      * prefix. It also supports the draft 'Extended LDAP Data Interchange Foramt'
133      * handling of xml text that is identified by an initial '<?xml ' string.
134      *
135      * @param o the object to be ldif encoded
136      * @param offset The first line of the string may be offset by
137      * by this many characters for nice formatting (e.g. in
138      * an ldif file, the first line may include 'att value = ...'
139      * at the beginning of the base 64 encoded block).
140      * @return the ldif encoding (possibly base64) with appropriate colons.
141      */

142
143     public String JavaDoc ldifEncode(Object JavaDoc o, int offset)
144     {
145         boolean base64Encode = false;
146
147         boolean xmlEncode = false;
148
149         if ((o instanceof String JavaDoc) == false)
150         {
151             if (debug == true) System.out.println("found a " + o.getClass().toString());
152             if (o.getClass().isArray())
153             {
154                 try
155                 {
156                     byte b[] = (byte[]) o;
157                     String JavaDoc ret = ":: " + CBBase64.binaryToString(b, offset + 3);
158                     if (debug == true) System.out.println("phenomenal - identified and wrote '" + ret + "'");
159                     return ret;
160                 }
161                 catch (ClassCastException JavaDoc e)
162                 {
163                     if (debug == true) System.out.println("unable to cast array to byte array.");
164                 }
165             }
166             return o.toString();
167         }
168         else // we have a string
169
{
170             String JavaDoc s = o.toString();
171             int len = s.length();
172
173             if (len == 0) return ": "; // this shouldn't really happen; null attributes should be culled before we get here...
174

175
176             // run the rfc tests to see if this is a good and virtuous string
177
char startChar = s.charAt(0);
178             if ("\n\r :".indexOf(startChar) != -1) // check for safe start char
179
base64Encode = true;
180             else if (startChar == '<')
181             {
182                 if (handleXML == true && s.startsWith("<?xml ")) // we have xml text, and will cope with wierd characters differently.
183
xmlEncode = true;
184                 else
185                     base64Encode = true;
186             }
187             else
188             {
189                 char test[] = new char[len];
190                 s.getChars(0, len, test, 0);
191                 for (int i = 0; i < len; i++)
192                 {
193                     //System.out.println("checking: " + i + ": " + test[i] + " = " + CBUtility.charToHex(test[i]));
194
if (test[i] > 126 || test[i] < 32) // check for sane intermediate chars
195
{
196                         base64Encode = true; // (may be unicode international string)
197
break;
198                     }
199                 }
200             }
201
202             if (s.charAt(s.length() - 1) == ' ') // end space considered harmful
203
base64Encode = true;
204
205             if (base64Encode)
206             {
207                 return translateToLdifBase64(s, offset);
208             }
209             else if (xmlEncode)
210                 return translateToLdifXML(s);
211             else
212                 return ": " + s; // return unmodified string.
213
}
214     }
215
216     private String JavaDoc translateToLdifBase64(String JavaDoc s, int offset)
217     {
218         try
219         {
220             s = CBBase64.binaryToString(s.getBytes("UTF8"), offset + 3);
221         }
222         catch (UnsupportedEncodingException e) // why would we get this when utf8 is mandatory across all java platforms?
223
{
224             log.log(Level.WARNING, "error utf8 encoding strings...", e);
225             s = CBBase64.binaryToString(s.getBytes(), offset + 3);
226         }
227         return ":: " + s;
228     }
229
230     /**
231      * This replaces new lines (of various sorts) with a 'new line' + '>' character, as per 'xml in ldif' draft rfc.
232      * @param s
233      * @return
234      */

235     private String JavaDoc translateToLdifXML(String JavaDoc s)
236     {
237         StringBuffer JavaDoc xml = new StringBuffer JavaDoc(";transfer-rxer>:").append(cr).append(s);
238
239         // carriage return madness :-(. Runs fastest on systems that use '\n' only. Seems about right.
240

241         if (s.indexOf("\r") != -1) // mac (I think)
242
{
243             CBParse.replaceAllBufferString(xml, "\r", "\r>");
244         }
245         if (s.indexOf("\n") != -1) // catches both '\r\n' and '\n'
246
{
247             CBParse.replaceAllBufferString(xml, "\n", "\n>");
248         }
249
250         return xml.toString();
251     }
252
253     /**
254      * Writes a single ldif entry...
255      *
256      */

257     /**
258      * retrieves a single entry from the directory and writes it
259      * out to an ldif file. Note that ldif header 'version 1' must
260      * be written elsewhere...
261      *
262      * @param dn the ldap escaped dn of the entry being written
263      * @param saveFile the file to write the entry to
264      * @param originalPrefix an optional portion of the dn to update
265      * @param replacementPrefix an optional replacement for a portion of the dn
266      * @param atts the attributes of teh entry
267      */

268
269     public void writeLdifEntry(String JavaDoc dn, FileWriter saveFile, String JavaDoc originalPrefix, String JavaDoc replacementPrefix, Attributes atts)
270             throws NamingException, IOException
271     {
272         if (atts == null)
273         {
274             log.info("no attributes available for " + dn);
275             return;
276         }
277         /**
278          * Prefix replacement magic. If we are moving the tree during
279          * the save, and a different prefix has been given (i.e. the
280          * originalPrefix and replacementPrefix variables aren't zero)
281          * we switch the relavent portion of the saved dn, substituting
282          * the portion of the dn that contains the original prefix with
283          * the replacement.
284          * e.g. cn=Fredo,o=FrogFarm,c=au, with original prefix o=FrogFarm,c=au
285          * and replacement o=FreeFrogs,c=au, becomes cn=Fredo,o=FreeFrogs,c=au
286          */

287         if ((originalPrefix != null) && (dn.endsWith(originalPrefix))) // which it jolly well should...
288
{
289             if (debug == true) System.out.println("original DN = '" + dn + "'");
290             dn = dn.substring(0, dn.length() - originalPrefix.length()) + replacementPrefix;
291             if (debug == true) System.out.println("after replacement DN = '" + dn + "'");
292         }
293
294         Attribute oc; // we treat the object class attribute
295
oc = atts.get("oc"); // specially to ensure it is first after the dn.
296
if (oc != null) // XXX do a name conversion...
297
{
298             if (oc instanceof DXAttribute)
299                 ((DXAttribute) oc).setName("objectClass");
300         }
301         else // (mind you its bloody hard to track down...!)
302
oc = atts.get("objectclass"); // so keep looking...
303
if (oc == null)
304             oc = atts.get("objectClass"); // this really bites.
305
if (oc == null)
306         {
307             if (dn.endsWith("cn=schema")) // XXX el dirty hack to allow schema to be sorta written out...
308
oc = new BasicAttribute("oc", "schema");
309         }
310
311         if (oc == null)
312         {
313             log.info("unable to identify object class for " + dn + " - skipping entry");
314             return;
315         }
316
317         if (debug)
318             System.out.println("dn" + ldifEncode(dn, 2));
319         else
320             saveFile.write("dn" + ldifEncode(dn, 2) + "\n");
321
322
323         NamingEnumeration ocs = oc.getAll();
324         while (ocs.hasMore())
325         {
326             if (debug)
327                 System.out.println(oc.getID() + ": " + ocs.next());
328             else
329                 saveFile.write(oc.getID() + ldifEncode(ocs.next(), oc.getID().length()) + "\n");
330         }
331
332         NamingEnumeration allAtts = atts.getAll();
333         String JavaDoc attName;
334         Attribute currentAtt;
335         while (allAtts.hasMore())
336         {
337             currentAtt = (Attribute) allAtts.next();
338             boolean binary = false;
339             if (currentAtt instanceof DXAttribute)
340                 binary = !((DXAttribute) currentAtt).isString();
341
342             attName = currentAtt.getID();
343
344             /*
345              * Make sure we don't print out 'dn' or objectclass attributes twice
346              */

347
348
349             if ((attName.equals("dn") == false) && (attName.equals(oc.getID()) == false))
350             {
351                 NamingEnumeration values = currentAtt.getAll();
352
353                 while (values.hasMore())
354                 {
355
356                     Object JavaDoc value = values.next();
357
358                     if (value != null)
359                     {
360 //BY THE TIME IT GETS HERE THE UTF-8 IS HISTORY...
361
if (debug)
362                         {
363                             System.out.println("value class = " + value.getClass().toString() + " : " + value);
364                             System.out.println(attName + ": " + value.toString());
365                         }
366                         else
367                         {
368                             if (binary)
369                                 saveFile.write(attName + ldifEncode(value, attName.length(), true) + "\n");
370                             else
371                                 saveFile.write(attName + ldifEncode(value, attName.length()) + "\n");
372                         }
373                     }
374                 }
375             }
376         }
377         if (!debug)
378         {
379             saveFile.write("\n");
380             saveFile.flush();
381         }
382     }
383
384     /**
385      * Parse an attribute: value line of an ldif file, and place
386      * the attribute value pair in an Attributes object.
387      *
388      * @param parseableLine a complete ldif text line (unwrapped) to parse
389      * @param newEntry the partially created entry, which is modified by this
390      * method.
391      */

392
393     public void ldifDecode(String JavaDoc parseableLine, DXEntry newEntry)
394     {
395         boolean isBinary = false;
396         int breakpos = parseableLine.indexOf(':');
397         if (breakpos < 0)
398         {
399             log.warning("Error - illegal line in ldif file\n" + parseableLine);
400             return;
401         }
402
403         String JavaDoc attribute = parseableLine.substring(0, breakpos);
404         Object JavaDoc value = null;
405
406         int attLen = attribute.length();
407         
408         // auto-translate 'oc' to 'objectClass'
409
if (attribute.equals("oc")) attribute = "objectClass";
410
411
412         int startpos = 2;
413
414         if (parseableLine.length() <= breakpos + 1) // empty value
415
{
416             value = "";
417         }
418         else if (parseableLine.charAt(breakpos + 1) == ':') // check for base64 encoded isNonString
419
{
420             value = getBase64Value(parseableLine, attLen, startpos, attribute); // may return string or byte array!
421
if (value instanceof String JavaDoc == false)
422                 isBinary = true;
423         }
424         else
425         {
426             if (parseableLine.charAt(attLen + 1) != ' ') // again, may be a leading space, or may not...
427
startpos = 1;
428             value = parseableLine.substring(attLen + startpos);
429             
430             // expand the value parameters, including the urls
431
value = expandValueParams(value);
432
433         }
434
435         if ("dn".equalsIgnoreCase(attribute))
436         {
437             if (value instanceof String JavaDoc)
438             {
439                 DN dn = new DN((String JavaDoc) value);
440                 if (dn.error())
441                     log.warning("Error trying to initialise ldif DN: \n" + dn.getError());
442                 else
443                     newEntry.putDN(dn);
444             }
445             else // this code should no longer be triggered, as utf8 conversion is done when data first read...
446
{
447                 try
448                 {
449                     DN dn = new DN(new String JavaDoc((byte[]) value, "UTF8"));
450                     if (dn.error())
451                         log.log(Level.WARNING, "Error trying to initialise ldif DN: \n", dn.getError());
452                     else
453                         newEntry.putDN(dn);
454                 }
455                 catch (UnsupportedEncodingException e)
456                 {
457                 } // can't happen?: UTF8 is mandatory...
458
}
459         }
460         else if (attribute != null)
461         {
462             Attribute existing = newEntry.get(attribute);
463
464             if (existing == null)
465             {
466                 DXAttribute att = new DXAttribute(attribute, value);
467                 att.setString(!isBinary);
468                 newEntry.put(att);
469             }
470             else
471             {
472                 existing.add(value);
473                 newEntry.put(existing);
474             }
475         }
476     }
477
478     /**
479      *
480      */

481     private Object JavaDoc getBase64Value(String JavaDoc parseableLine, int attLen, int startpos, String JavaDoc attribute)
482     {
483         byte[] rawBinaryData;
484
485         if (parseableLine.charAt(attLen + 2) == ' ') // may be ::XXXX or :: XXXX -> so must adjust for possible space
486
startpos = 3;
487
488         rawBinaryData = CBBase64.stringToBinary(parseableLine.substring(attribute.length() + startpos));
489
490         // a bit dodgy - we try to guess whether the isNonString data is UTF-8, or is really isNonString...
491
// we should probably do some schema checking here, but instead we'll try to make an educated
492
// guess...
493

494         // Create a short array to test for utf-8 ishness... (we don't want to test all of large text files)
495
byte[] testBytes;
496         if (rawBinaryData.length > 256)
497         {
498             testBytes = new byte[256];
499             System.arraycopy(rawBinaryData, 0, testBytes, 0, 256);
500         }
501         else
502             testBytes = rawBinaryData;
503
504         /*
505          * Make a (slightly ad-hoc) check to see if it is actually a utf-8 string *pretending* to by bytes...
506          */

507
508         if (CBParse.isUTF8(testBytes))
509         {
510             try
511             {
512                 return new String JavaDoc(rawBinaryData, "UTF-8");
513             }
514             catch (Exception JavaDoc e) // as per String constructor doco, behaviour is 'unspecified' if the above fails...
515
{
516                 // drop through to return the raw isNonString data instead...
517
}
518         }
519         return rawBinaryData;
520     }
521
522
523     /**
524      * Read an entry from LDIF text. Attribute/value pairs are read until
525      * a blank line is encountered.
526      *
527      * @param textReader a buffered Reader to read lines of ldif text from...
528      * @return the read entry, as a DXAttributes object
529      * @throws InterruptedIOException if the user hits cancel on the progress bar
530      */

531
532     public DXEntry readLdifEntry(BufferedReader textReader)
533             throws IOException
534     {
535         DXEntry entry = new DXEntry();
536
537         // this is the 'look ahead' current line, read from the ldif file.
538
String JavaDoc line = "";
539
540         // this is the first line of the attribute, read from the ldif file.
541
String JavaDoc firstLine = "";
542
543         /* This is a little tricky. Because lines may be extended by line wrapping,
544          * we need to look ahead a line until we're sure that we've finished any
545          * possible wrapping, and only then (when we've already read the 'next' line)
546          * can we process the old line.
547          */

548
549         // WARNING - this code is a little messy - trying to make it quick since ldif load is slow :-/.
550

551         StringBuffer JavaDoc multiLineText = null; //don't use this unless we need it...
552

553         while ((line = textReader.readLine()) != null)
554         {
555             if (line.length() > 0 && line.charAt(0) == ' ') // line wrap; normal
556
{
557                 if (multiLineText == null)
558                     multiLineText = new StringBuffer JavaDoc(firstLine);
559
560                 multiLineText.append(line.substring(1));
561                 //line = firstLine + line.substring(1); // extend the value...
562
}
563             else if (line.length() > 0 && line.charAt(0) == '>') // line wrap; XML (as per draft xml/ldif standard)
564
{
565                 if (multiLineText == null)
566                     multiLineText = new StringBuffer JavaDoc(firstLine);
567
568                 multiLineText.append(line.substring(1)).append(cr);
569                 //line = firstLine + line.substring(1) + "\n"; // extend the value...
570
}
571             else if (firstLine.length() > 1 && firstLine.charAt(0) == '#')
572             {
573                 // comment... do nothing.
574
}
575             else if (firstLine.length() > 2 || multiLineText != null)
576             {
577                 if (multiLineText != null)
578                     ldifDecode(multiLineText.toString(), entry);
579                 else
580                     ldifDecode(firstLine, entry);
581
582                 multiLineText = null;
583             }
584
585             if (line == null || line.equals("")) // end of entry...
586
{
587                 return entry;
588             }
589
590             firstLine = line;
591         }
592
593         if (entry.getDN().size() > 0) // dn check is for unexpectedly truncated files
594
{
595             // unusual - end of file reached, and the file *doesn't* have
596
// a blank line at the end - hence a special case while we write
597
// the last entry
598
if (firstLine != null && firstLine.trim().length() > 0)
599                 ldifDecode(firstLine, entry);
600
601             return entry; // should be last entry
602
}
603
604         return null; // finished reading everything...
605
}
606
607     /**
608      * This method expands the strings inside the ldif file
609      * that match the list of expandable strings in params list.
610      *
611      * @param value value to be expanded
612      * @return expanded object
613      */

614     public Object JavaDoc expandValueParams(Object JavaDoc value)
615     {
616         if (params != null)
617         {
618             Enumeration keys = params.keys();
619             while (keys.hasMoreElements())
620             {
621                 String JavaDoc key = (String JavaDoc) keys.nextElement();
622                 String JavaDoc keyvalue = (String JavaDoc) params.get(key);
623
624                 // check for the key
625
String JavaDoc oldValue = (String JavaDoc) value;
626                 int index = oldValue.indexOf(key);
627                 if (index > -1)
628                 {
629                     String JavaDoc newValue = oldValue.substring(0, index) + keyvalue +
630                             oldValue.substring(index + key.length(), oldValue.length());
631                     System.out.println(newValue);
632                     value = newValue;
633                 }
634             }
635         }
636
637         // load the file if the value is a url
638
if (filedir != null)
639         {
640             // check if it is a file, i.e. look for "< file:"
641
String JavaDoc oldValue = (String JavaDoc) value;
642             String JavaDoc match = "< file://";
643
644             int index = (oldValue.toLowerCase()).indexOf(match);
645
646             if (index > -1)
647             {
648                 String JavaDoc filename = filedir + oldValue.substring(index + 9, oldValue.length());
649                 File file = new File(filename);
650                 try
651                 {
652                     FileInputStream input = new FileInputStream(file);
653
654                     int length = (int) file.length();
655                     if (length > 0)
656                     {
657                         byte[] bytes = new byte[length];
658                         int read = input.read(bytes);
659                         if (read > 0) value = bytes;
660                     }
661                     input.close();
662                 }
663                 catch (IOException e)
664                 {
665                     System.out.println("Error opening the file!" + e);
666                 }
667             }
668         }
669         return value;
670     }
671 }
Popular Tags