KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > scriptella > driver > ldap > ldif > LdifReader


1 /*
2  * Copyright 2006 The Apache Software Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */

17 package scriptella.driver.ldap.ldif;
18
19 import scriptella.util.StringUtils;
20
21 import javax.naming.directory.DirContext JavaDoc;
22 import javax.naming.ldap.BasicControl JavaDoc;
23 import javax.naming.ldap.Control JavaDoc;
24 import java.io.BufferedReader JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.InputStream JavaDoc;
27 import java.io.Reader JavaDoc;
28 import java.io.StringReader JavaDoc;
29 import java.io.UnsupportedEncodingException JavaDoc;
30 import java.net.URL JavaDoc;
31 import java.util.ArrayList JavaDoc;
32 import java.util.Iterator JavaDoc;
33 import java.util.List JavaDoc;
34 import java.util.NoSuchElementException JavaDoc;
35 import java.util.regex.Matcher JavaDoc;
36 import java.util.regex.Pattern JavaDoc;
37
38 /**
39  * <pre>
40  * &lt;ldif-file&gt; ::= &quot;version:&quot; &lt;fill&gt; &lt;number&gt; &lt;seps&gt; &lt;dn-spec&gt; &lt;sep&gt; &lt;ldif-content-change&gt;
41  * <p/>
42  * &lt;ldif-content-change&gt; ::=
43  * &lt;number&gt; &lt;oid&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt; &lt;ldif-attrval-record-e&gt; |
44  * &lt;alpha&gt; &lt;chars-e&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt; &lt;ldif-attrval-record-e&gt; |
45  * &quot;control:&quot; &lt;fill&gt; &lt;number&gt; &lt;oid&gt; &lt;spaces-e&gt; &lt;criticality&gt; &lt;value-spec-e&gt; &lt;sep&gt; &lt;controls-e&gt;
46  * &quot;changetype:&quot; &lt;fill&gt; &lt;changerecord-type&gt; &lt;ldif-change-record-e&gt; |
47  * &quot;changetype:&quot; &lt;fill&gt; &lt;changerecord-type&gt; &lt;ldif-change-record-e&gt;
48  * <p/>
49  * &lt;ldif-attrval-record-e&gt; ::= &lt;seps&gt; &lt;dn-spec&gt; &lt;sep&gt; &lt;attributeType&gt;
50  * &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt;
51  * &lt;ldif-attrval-record-e&gt; | e
52  * <p/>
53  * &lt;ldif-change-record-e&gt; ::= &lt;seps&gt; &lt;dn-spec&gt; &lt;sep&gt; &lt;controls-e&gt;
54  * &quot;changetype:&quot; &lt;fill&gt; &lt;changerecord-type&gt; &lt;ldif-change-record-e&gt; | e
55  * <p/>
56  * &lt;dn-spec&gt; ::= &quot;dn:&quot; &lt;fill&gt; &lt;safe-string&gt; | &quot;dn::&quot; &lt;fill&gt; &lt;base64-string&gt;
57  * <p/>
58  * &lt;controls-e&gt; ::= &quot;control:&quot; &lt;fill&gt; &lt;number&gt; &lt;oid&gt; &lt;spaces-e&gt; &lt;criticality&gt;
59  * &lt;value-spec-e&gt; &lt;sep&gt; &lt;controls-e&gt; | e
60  * <p/>
61  * &lt;criticality&gt; ::= &quot;true&quot; | &quot;false&quot; | e
62  * <p/>
63  * &lt;oid&gt; ::= '.' &lt;number&gt; &lt;oid&gt; | e
64  * <p/>
65  * &lt;attrval-specs-e&gt; ::= &lt;number&gt; &lt;oid&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt; |
66  * &lt;alpha&gt; &lt;chars-e&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt; | e
67  * <p/>
68  * &lt;value-spec-e&gt; ::= &lt;value-spec&gt; | e
69  * <p/>
70  * &lt;value-spec&gt; ::= ':' &lt;fill&gt; &lt;safe-string-e&gt; |
71  * &quot;::&quot; &lt;fill&gt; &lt;base64-chars&gt; |
72  * &quot;:&lt;&quot; &lt;fill&gt; &lt;url&gt;
73  * <p/>
74  * &lt;attributeType&gt; ::= &lt;number&gt; &lt;oid&gt; | &lt;alpha&gt; &lt;chars-e&gt;
75  * <p/>
76  * &lt;options-e&gt; ::= ';' &lt;char&gt; &lt;chars-e&gt; &lt;options-e&gt; |e
77  * <p/>
78  * &lt;chars-e&gt; ::= &lt;char&gt; &lt;chars-e&gt; | e
79  * <p/>
80  * &lt;changerecord-type&gt; ::= &quot;add&quot; &lt;sep&gt; &lt;attributeType&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt; |
81  * &quot;delete&quot; &lt;sep&gt; |
82  * &quot;modify&quot; &lt;sep&gt; &lt;mod-type&gt; &lt;fill&gt; &lt;attributeType&gt; &lt;options-e&gt; &lt;sep&gt; &lt;attrval-specs-e&gt; &lt;sep&gt; '-' &lt;sep&gt; &lt;mod-specs-e&gt; |
83  * &quot;moddn&quot; &lt;sep&gt; &lt;newrdn&gt; &lt;sep&gt; &quot;deleteoldrdn:&quot; &lt;fill&gt; &lt;0-1&gt; &lt;sep&gt; &lt;newsuperior-e&gt; &lt;sep&gt; |
84  * &quot;modrdn&quot; &lt;sep&gt; &lt;newrdn&gt; &lt;sep&gt; &quot;deleteoldrdn:&quot; &lt;fill&gt; &lt;0-1&gt; &lt;sep&gt; &lt;newsuperior-e&gt; &lt;sep&gt;
85  * <p/>
86  * &lt;newrdn&gt; ::= ':' &lt;fill&gt; &lt;safe-string&gt; | &quot;::&quot; &lt;fill&gt; &lt;base64-chars&gt;
87  * <p/>
88  * &lt;newsuperior-e&gt; ::= &quot;newsuperior&quot; &lt;newrdn&gt; | e
89  * <p/>
90  * &lt;mod-specs-e&gt; ::= &lt;mod-type&gt; &lt;fill&gt; &lt;attributeType&gt; &lt;options-e&gt;
91  * &lt;sep&gt; &lt;attrval-specs-e&gt; &lt;sep&gt; '-' &lt;sep&gt; &lt;mod-specs-e&gt; | e
92  * <p/>
93  * &lt;mod-type&gt; ::= &quot;add:&quot; | &quot;delete:&quot; | &quot;replace:&quot;
94  * <p/>
95  * &lt;url&gt; ::= &lt;a Uniform Resource Locator, as defined in [6]&gt;
96  * <p/>
97  * <p/>
98  * <p/>
99  * LEXICAL
100  * -------
101  * <p/>
102  * &lt;fill&gt; ::= ' ' &lt;fill&gt; | e
103  * &lt;char&gt; ::= &lt;alpha&gt; | &lt;digit&gt; | '-'
104  * &lt;number&gt; ::= &lt;digit&gt; &lt;digits&gt;
105  * &lt;0-1&gt; ::= '0' | '1'
106  * &lt;digits&gt; ::= &lt;digit&gt; &lt;digits&gt; | e
107  * &lt;digit&gt; ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
108  * &lt;seps&gt; ::= &lt;sep&gt; &lt;seps-e&gt;
109  * &lt;seps-e&gt; ::= &lt;sep&gt; &lt;seps-e&gt; | e
110  * &lt;sep&gt; ::= 0x0D 0x0A | 0x0A
111  * &lt;spaces&gt; ::= ' ' &lt;spaces-e&gt;
112  * &lt;spaces-e&gt; ::= ' ' &lt;spaces-e&gt; | e
113  * &lt;safe-string-e&gt; ::= &lt;safe-string&gt; | e
114  * &lt;safe-string&gt; ::= &lt;safe-init-char&gt; &lt;safe-chars&gt;
115  * &lt;safe-init-char&gt; ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F]
116  * &lt;safe-chars&gt; ::= &lt;safe-char&gt; &lt;safe-chars&gt; | e
117  * &lt;safe-char&gt; ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F]
118  * &lt;base64-string&gt; ::= &lt;base64-char&gt; &lt;base64-chars&gt;
119  * &lt;base64-chars&gt; ::= &lt;base64-char&gt; &lt;base64-chars&gt; | e
120  * &lt;base64-char&gt; ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-9x5A] | [0x61-0x7A]
121  * &lt;alpha&gt; ::= [0x41-0x5A] | [0x61-0x7A]
122  * <p/>
123  * COMMENTS
124  * --------
125  * - The ldap-oid VN is not correct in the RFC-2849. It has been changed from 1*DIGIT 0*1(&quot;.&quot; 1*DIGIT) to
126  * DIGIT+ (&quot;.&quot; DIGIT+)*
127  * - The mod-spec lacks a sep between *attrval-spec and &quot;-&quot;.
128  * - The BASE64-UTF8-STRING should be BASE64-CHAR BASE64-STRING
129  * - The ValueSpec rule must accept multilines values. In this case, we have a LF followed by a
130  * single space before the continued value.
131  * </pre>
132  */

133 public class LdifReader implements Iterator JavaDoc<Entry>, Iterable JavaDoc<Entry> {
134
135     /**
136      * A list of read lines
137      */

138     private final List JavaDoc<String JavaDoc> lines=new ArrayList JavaDoc<String JavaDoc>();
139
140     /**
141      * The ldif file version default value
142      */

143     private static final int DEFAULT_VERSION = 1;
144
145     /**
146      * The ldif version
147      */

148     private int version=DEFAULT_VERSION;
149
150     /**
151      * Type of element read
152      */

153     private static final int ENTRY = 0;
154
155     private static final int CHANGE = 1;
156
157     private static final int UNKNOWN = 2;
158
159     /**
160      * Size limit for file contained values
161      */

162     private long sizeLimit = SIZE_LIMIT_DEFAULT;
163
164     /**
165      * The default size limit : 1Mo
166      */

167     private static final long SIZE_LIMIT_DEFAULT = 1024000;
168
169     /**
170      * State values for the modify operation
171      */

172     private static final int MOD_SPEC = 0;
173
174     private static final int ATTRVAL_SPEC = 1;
175
176     private static final int ATTRVAL_SPEC_OR_SEP = 2;
177
178     /**
179      * Iterator prefetched entry
180      */

181     private Entry prefetched;
182
183     /**
184      * The ldif Reader
185      */

186     private Reader JavaDoc in;
187
188     /**
189      * A flag set if the ldif contains entries
190      */

191     private boolean containsEntries;
192
193     /**
194      * A flag set if the ldif contains changes
195      */

196     private boolean containsChanges;
197
198
199     /**
200      * Constructors
201      */

202     public LdifReader() {
203     }
204
205     private void init(BufferedReader JavaDoc in) throws LdifParseException {
206         this.in = in;
207
208         // First get the version - if any -
209
version = parseVersion();
210         prefetched = parseEntry();
211     }
212
213     /**
214      * A constructor which takes a Reader
215      *
216      * @param in A Reader containing ldif formated input
217      * @throws LdifParseException If the file cannot be processed or if the format is incorrect
218      */

219     public LdifReader(Reader JavaDoc in) {
220         if (in instanceof BufferedReader JavaDoc) {//check to avoid double buffering
221
init((BufferedReader JavaDoc) in);
222         } else {
223             init(new BufferedReader JavaDoc(in));
224         }
225     }
226
227     /**
228      * @return The ldif file version
229      */

230     public int getVersion() {
231         return version;
232     }
233
234     /**
235      * @return The maximum size of a file which is used into an attribute value.
236      */

237     public long getSizeLimit() {
238         return sizeLimit;
239     }
240
241     /**
242      * Set the maximum file size that can be accepted for an attribute value
243      *
244      * @param sizeLimit The size in bytes
245      */

246     public void setSizeLimit(long sizeLimit) {
247         this.sizeLimit = sizeLimit;
248     }
249
250
251     /**
252      * Parse the changeType
253      *
254      * @param line The line which contains the changeType
255      * @return The operation.
256      */

257     private int parseChangeType(String JavaDoc line) {
258         int operation = Entry.ADD;
259
260         String JavaDoc modOp = line.substring("changetype:".length() + 1).trim();
261
262         if ("add".equalsIgnoreCase(modOp)) {
263             operation = Entry.ADD;
264         } else if ("delete".equalsIgnoreCase(modOp)) {
265             operation = Entry.DELETE;
266         } else if ("modify".equalsIgnoreCase(modOp)) {
267             operation = Entry.MODIFY;
268         } else if ("moddn".equalsIgnoreCase(modOp)) {
269             operation = Entry.MODDN;
270         } else if ("modrdn".equalsIgnoreCase(modOp)) {
271             operation = Entry.MODRDN;
272         }
273
274         return operation;
275     }
276
277     /**
278      * Parse the DN of an entry
279      *
280      * @param line The line to parse
281      * @return A DN
282      */

283     private String JavaDoc parseDn(String JavaDoc line) {
284         String JavaDoc dn = null;
285
286         String JavaDoc lowerLine = line.toLowerCase();
287
288         if (lowerLine.startsWith("dn:") || lowerLine.startsWith("DN:")) {
289             // Ok, we have a DN. Is it base 64 encoded ?
290
int length = line.length();
291
292             if (length == 3) {
293                 // The DN is empty : error
294
throw new LdifParseException("No DN for entry", line);
295             } else if (line.charAt(3) == ':') {
296                 if (length > 4) {
297                     // This is a base 64 encoded DN.
298
String JavaDoc trimmedLine = line.substring(4).trim();
299
300                     try {
301                         dn = new String JavaDoc(Utils.base64Decode(trimmedLine.toCharArray()), "UTF-8");
302                     }
303                     catch (UnsupportedEncodingException JavaDoc uee) {
304                         // The DN is not base 64 encoded
305
throw new LdifParseException("Invalid base 64 encoded DN",line);
306                     }
307                 } else {
308                     // The DN is empty : error
309
throw new LdifParseException("No DN for entry", line);
310                 }
311             } else {
312                 dn = line.substring(3).trim();
313             }
314         } else {
315             throw new LdifParseException("No DN for entry", line);
316         }
317
318         return dn;
319     }
320
321     /**
322      * Parse the value part.
323      *
324      * @param line The line which contains the value
325      * @param pos The starting position in the line
326      * @return A String or a byte[], depending of the kind of value we get
327      * @throws LdifParseException If something went wrong
328      */

329     private Object JavaDoc parseValue(String JavaDoc line, int pos) {
330         if (line.length() > pos + 1) {
331             char c = line.charAt(pos + 1);
332
333             if (c == ':') {
334                 String JavaDoc value = line.substring(pos + 2).trim();
335
336                 return Utils.base64Decode(value.toCharArray());
337             } else if (c == '<') {
338                 String JavaDoc urlName = line.substring(pos + 2).trim();
339                 try {
340                     return Utils.toByteArray(getUriStream(urlName), sizeLimit);
341                 } catch (IOException JavaDoc e) {
342                     throw new LdifParseException("Failed to read \""+urlName+"\" file content",line,e);
343                 }
344             } else {
345                 return line.substring(pos + 1).trim();
346             }
347         } else {
348             return null;
349         }
350     }
351
352     /**
353      * Resolves URI to URL and returns a content stream.
354      * This method just creates a new URL, subclasses may chnange this behaviour.
355      * @param uri URI to resolve.
356      * @return resolved URL content stream.
357      * @throws IOException if an I/O error occurs or URI is malformed.
358      */

359     protected InputStream JavaDoc getUriStream(String JavaDoc uri) throws IOException JavaDoc {
360         return new URL JavaDoc(uri).openStream();
361     }
362
363     /**
364      * Parse a control. The grammar is : <control> ::= "control:" <fill>
365      * <ldap-oid> <critical-e> <value-spec-e> <sep> <critical-e> ::= <spaces>
366      * <boolean> | e <boolean> ::= "true" | "false" <value-spec-e> ::=
367      * <value-spec> | e <value-spec> ::= ":" <fill> <SAFE-STRING-e> | "::"
368      * <fill> <BASE64-STRING> | ":<" <fill> <url>
369      * <p/>
370      * It can be read as : "control:" <fill> <ldap-oid> [ " "+ ( "true" |
371      * "false") ] [ ":" <fill> <SAFE-STRING-e> | "::" <fill> <BASE64-STRING> | ":<"
372      * <fill> <url> ]
373      *
374      * @param line The line containing the control
375      * @return A control
376      */

377     private Control JavaDoc parseControl(String JavaDoc line) {
378         String JavaDoc lowerLine = line.toLowerCase().trim();
379         char[] controlValue = line.trim().toCharArray();
380         int pos = 0;
381         int length = controlValue.length;
382
383         // Get the <ldap-oid>
384
if (pos > length) {
385             // No OID : error !
386
throw new LdifParseException("Bad control, no oid", line);
387         }
388
389         int initPos = pos;
390
391         while (Utils.isCharASCII(controlValue, pos, '.') || Utils.isDigit(controlValue, pos)) {
392             pos++;
393         }
394
395         if (pos == initPos) {
396             // Not a valid OID !
397
throw new LdifParseException("Bad control, no oid", line);
398         }
399
400
401         String JavaDoc oid = lowerLine.substring(0, pos);
402         boolean criticality=false;
403         byte[] controlBytes = null;
404
405
406
407         // Get the criticality, if any
408
// Skip the <fill>
409
while (Utils.isCharASCII(controlValue, pos, ' ')) {
410             pos++;
411         }
412
413         // Check if we have a "true" or a "false"
414
int criticalPos = lowerLine.indexOf(':');
415
416         int criticalLength = 0;
417
418         if (criticalPos == -1) {
419             criticalLength = length - pos;
420         } else {
421             criticalLength = criticalPos - pos;
422         }
423
424         if ((criticalLength == 4) && ("true".equalsIgnoreCase(lowerLine.substring(pos, pos + 4)))) {
425             criticality=true;
426         } else if ((criticalLength == 5) && ("false".equalsIgnoreCase(lowerLine.substring(pos, pos + 5)))) {
427             criticality=false;
428         } else if (criticalLength != 0) {
429             // If we have a criticality, it should be either "true" or "false",
430
// nothing else
431
throw new LdifParseException("Bad control criticality", line);
432         }
433
434         if (criticalPos > 0) {
435             // We have a value. It can be a normal value, a base64 encoded value
436
// or a file contained value
437
if (Utils.isCharASCII(controlValue, criticalPos + 1, ':')) {
438                 // Base 64 encoded value
439
controlBytes = Utils.base64Decode(line.substring(criticalPos + 2).toCharArray());
440             } else if (Utils.isCharASCII(controlValue, criticalPos + 1, '<')) {
441                 // File contained value
442
} else {
443                 // Standard value
444
byte[] value = new byte[length - criticalPos - 1];
445
446                 for (int i = 0; i < length - criticalPos - 1; i++) {
447                     value[i] = (byte) controlValue[i + criticalPos + 1];
448                 }
449
450                 controlBytes=value;
451             }
452         }
453
454         return new BasicControl JavaDoc(oid, criticality, controlBytes);
455     }
456
457
458     /**
459      * Parse an AttributeType/AttributeValue
460      *
461      * @param entry The entry where to store the value
462      * @param line The line to parse
463      */

464     public void parseAttributeValue(Entry entry, String JavaDoc line) {
465         int colonIndex = line.indexOf(':');
466
467         String JavaDoc attributeType = line.substring(0, colonIndex);
468
469         // We should *not* have a DN twice
470
if (attributeType.equalsIgnoreCase("dn")) {
471             throw new LdifParseException("A ldif entry should not have two DN", line);
472         }
473
474         Object JavaDoc attributeValue = parseValue(line, colonIndex);
475
476         // Update the entry
477
entry.addAttribute(attributeType, attributeValue);
478     }
479
480     /**
481      * Parse a ModRDN operation
482      *
483      * @param entry The entry to update
484      * @param iter The lines iterator
485      */

486     private void parseModRdn(Entry entry, Iterator JavaDoc iter) {
487         // We must have two lines : one starting with "newrdn:" or "newrdn::",
488
// and the second starting with "deleteoldrdn:"
489
if (iter.hasNext()) {
490             String JavaDoc line = (String JavaDoc) iter.next();
491             String JavaDoc lowerLine = line.toLowerCase();
492
493             if (lowerLine.startsWith("newrdn::") || lowerLine.startsWith("newrdn:")) {
494                 int colonIndex = line.indexOf(':');
495                 Object JavaDoc attributeValue = parseValue(line, colonIndex);
496                 entry.setNewRdn(attributeValue instanceof String JavaDoc ? (String JavaDoc) attributeValue : Utils
497                         .utf8ToString((byte[]) attributeValue));
498             } else {
499                 throw new LdifParseException("Bad modrdn operation", line);
500             }
501
502         } else {
503             throw new LdifParseException("Bad modrdn operation, no newrdn");
504         }
505
506         if (iter.hasNext()) {
507             String JavaDoc line = (String JavaDoc) iter.next();
508             String JavaDoc lowerLine = line.toLowerCase();
509
510             if (lowerLine.startsWith("deleteoldrdn:")) {
511                 int colonIndex = line.indexOf(':');
512                 Object JavaDoc attributeValue = parseValue(line, colonIndex);
513                 entry.setDeleteOldRdn("1".equals(attributeValue));
514             } else {
515                 throw new LdifParseException("Bad modrdn operation, no deleteoldrdn", line);
516             }
517         } else {
518             throw new LdifParseException("Bad modrdn operation, no deleteoldrdn");
519         }
520
521     }
522
523     /**
524      * Parse a modify change type.
525      * <p/>
526      * The grammar is : <changerecord> ::= "changetype:" FILL "modify" SEP
527      * <mod-spec> <mod-specs-e> <mod-spec> ::= "add:" <mod-val> | "delete:"
528      * <mod-val-del> | "replace:" <mod-val> <mod-specs-e> ::= <mod-spec>
529      * <mod-specs-e> | e <mod-val> ::= FILL ATTRIBUTE-DESCRIPTION SEP
530      * ATTRVAL-SPEC <attrval-specs-e> "-" SEP <mod-val-del> ::= FILL
531      * ATTRIBUTE-DESCRIPTION SEP <attrval-specs-e> "-" SEP <attrval-specs-e> ::=
532      * ATTRVAL-SPEC <attrval-specs> | e *
533      *
534      * @param entry The entry to feed
535      * @param iter The lines
536      */

537     private void parseModify(Entry entry, Iterator JavaDoc iter) {
538         int state = MOD_SPEC;
539         String JavaDoc modified = null;
540         int modification = 0;
541
542         // The following flag is used to deal with empty modifications
543
boolean isEmptyValue = true;
544
545         while (iter.hasNext()) {
546             String JavaDoc line = (String JavaDoc) iter.next();
547             String JavaDoc lowerLine = line.toLowerCase();
548
549             if (lowerLine.startsWith("-")) {
550                 if (state != ATTRVAL_SPEC_OR_SEP) {
551                     throw new LdifParseException("Bad modify separator", line);
552                 } else {
553                     if (isEmptyValue) {
554                         // Update the entry
555
entry.addModificationItem(modification, modified, null);
556                     }
557
558                     state = MOD_SPEC;
559                     isEmptyValue = true;
560                     continue;
561                 }
562             } else if (lowerLine.startsWith("add:")) {
563                 if ((state != MOD_SPEC) && (state != ATTRVAL_SPEC)) {
564                     throw new LdifParseException("Bad modify state", line);
565                 }
566
567                 modified = line.substring("add:".length()).trim();
568                 modification = DirContext.ADD_ATTRIBUTE;
569
570                 state = ATTRVAL_SPEC;
571             } else if (lowerLine.startsWith("delete:")) {
572                 if ((state != MOD_SPEC) && (state != ATTRVAL_SPEC)) {
573                     throw new LdifParseException("Bad modify state", line);
574                 }
575
576                 modified = line.substring("delete:".length()).trim();
577                 modification = DirContext.REMOVE_ATTRIBUTE;
578
579                 state = ATTRVAL_SPEC_OR_SEP;
580             } else if (lowerLine.startsWith("replace:")) {
581                 if ((state != MOD_SPEC) && (state != ATTRVAL_SPEC)) {
582                     throw new LdifParseException("Bad modify state", line);
583                 }
584
585                 modified = line.substring("replace:".length()).trim();
586                 modification = DirContext.REPLACE_ATTRIBUTE;
587
588                 state = ATTRVAL_SPEC_OR_SEP;
589             } else {
590                 if ((state != ATTRVAL_SPEC) && (state != ATTRVAL_SPEC_OR_SEP)) {
591                     throw new LdifParseException("Bad modify state", line);
592                 }
593
594                 // A standard AttributeType/AttributeValue pair
595
int colonIndex = line.indexOf(':');
596
597                 String JavaDoc attributeType = line.substring(0, colonIndex);
598
599                 if (!attributeType.equals(modified)) {
600                     throw new LdifParseException("Bad modify attribute", line);
601                 }
602
603                 // We should *not* have a DN twice
604
if (attributeType.equals("dn")) {
605                     throw new LdifParseException("A ldif entry should not have two DN", line);
606                 }
607
608                 Object JavaDoc attributeValue = parseValue(line, colonIndex);
609
610                 // Update the entry
611
entry.addModificationItem(modification, attributeType, attributeValue);
612                 isEmptyValue = false;
613
614                 state = ATTRVAL_SPEC_OR_SEP;
615             }
616         }
617     }
618
619     /**
620      * Parse a change operation. We have to handle different cases depending on
621      * the operation. 1) Delete : there should *not* be any line after the
622      * "changetype: delete" 2) Add : we must have a list of AttributeType :
623      * AttributeValue elements 3) ModDN : we must have two following lines: a
624      * "newrdn:" and a "deleteoldrdn:" 4) ModRDN : the very same, but a
625      * "newsuperior:" line is expected 5) Modify :
626      * <p/>
627      * The grammar is : <changerecord> ::= "changetype:" FILL "add" SEP
628      * <attrval-spec> <attrval-specs-e> | "changetype:" FILL "delete" |
629      * "changetype:" FILL "modrdn" SEP <newrdn> SEP <deleteoldrdn> SEP | // To
630      * be checked "changetype:" FILL "moddn" SEP <newrdn> SEP <deleteoldrdn> SEP
631      * <newsuperior> SEP | "changetype:" FILL "modify" SEP <mod-spec>
632      * <mod-specs-e> <newrdn> ::= "newrdn:" FILL RDN | "newrdn::" FILL
633      * BASE64-RDN <deleteoldrdn> ::= "deleteoldrdn:" FILL "0" | "deleteoldrdn:"
634      * FILL "1" <newsuperior> ::= "newsuperior:" FILL DN | "newsuperior::" FILL
635      * BASE64-DN <mod-specs-e> ::= <mod-spec> <mod-specs-e> | e <mod-spec> ::=
636      * "add:" <mod-val> | "delete:" <mod-val> | "replace:" <mod-val> <mod-val>
637      * ::= FILL ATTRIBUTE-DESCRIPTION SEP ATTRVAL-SPEC <attrval-specs-e> "-" SEP
638      * <attrval-specs-e> ::= ATTRVAL-SPEC <attrval-specs> | e
639      *
640      * @param entry The entry to feed
641      * @param iter The lines iterator
642      * @param operation The change operation (add, modify, delete, moddn or modrdn)
643      * @param control The associated control, if any
644      */

645     private void parseChange(Entry entry, Iterator JavaDoc iter, int operation, Control JavaDoc control) {
646         // The changetype and operation has already been parsed.
647
entry.setChangeType(operation);
648
649         switch (operation) {
650             case Entry.DELETE:
651                 // The change type will tell that it's a delete operation,
652
// the dn is used as a key.
653
return;
654
655             case Entry.ADD:
656                 // We will iterate through all attribute/value pairs
657
while (iter.hasNext()) {
658                     String JavaDoc line = (String JavaDoc) iter.next();
659                     parseAttributeValue(entry, line);
660                 }
661
662                 return;
663
664             case Entry.MODIFY:
665                 parseModify(entry, iter);
666                 return;
667
668             case Entry.MODRDN:// They are supposed to have the same syntax ???
669
case Entry.MODDN:
670                 // First, parse the modrdn part
671
parseModRdn(entry, iter);
672
673                 // The next line should be the new superior
674
if (iter.hasNext()) {
675                     String JavaDoc line = (String JavaDoc) iter.next();
676                     String JavaDoc lowerLine = line.toLowerCase();
677
678                     if (lowerLine.startsWith("newsuperior:")) {
679                         int colonIndex = line.indexOf(':');
680                         Object JavaDoc attributeValue = parseValue(line, colonIndex);
681                         entry.setNewSuperior(attributeValue instanceof String JavaDoc ? (String JavaDoc) attributeValue : Utils
682                                 .utf8ToString((byte[]) attributeValue));
683                     } else {
684                         if (operation == Entry.MODDN) {
685                             throw new LdifParseException("Bad moddn operation, no newsuperior", line);
686                         }
687                     }
688                 } else {
689                     if (operation == Entry.MODDN) {
690                         throw new LdifParseException("Bad moddn operation, no newsuperior");
691                     }
692                 }
693
694                 return;
695
696             default:
697                 // This is an error
698
throw new LdifParseException("Bad operation");
699         }
700     }
701
702     /**
703      * Parse a ldif file. The following rules are processed :
704      * <p/>
705      * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> |
706      * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::=
707      * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::=
708      * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill>
709      * <distinguishedName> | "dn::" <fill> <base64-distinguishedName>
710      * <changerecord> ::= "changetype:" <fill> <change-op>
711      */

712     private Entry parseEntry() {
713         if ((lines == null) || (lines.size() == 0)) {
714             return null;
715         }
716
717         // The entry must start with a dn: or a dn::
718
String JavaDoc line = lines.get(0);
719
720         String JavaDoc dn = parseDn(line);
721
722         // Ok, we have found a DN
723
Entry entry = new Entry();
724         entry.setDn(dn);
725
726         // We remove this dn from the lines
727
lines.remove(0);
728
729         // Now, let's iterate through the other lines
730
Iterator JavaDoc iter = lines.iterator();
731
732         // This flag is used to distinguish between an entry and a change
733
int type = UNKNOWN;
734
735         // The following boolean is used to check that a control is *not*
736
// found elswhere than just after the dn
737
boolean controlSeen = false;
738
739         // We use this boolean to check that we do not have AttributeValues
740
// after a change operation
741
boolean changeTypeSeen = false;
742
743         int operation = Entry.ADD;
744         String JavaDoc lowerLine = null;
745         Control JavaDoc control = null;
746
747         while (iter.hasNext()) {
748             // Each line could start either with an OID, an attribute type, with
749
// "control:" or with "changetype:"
750
line = (String JavaDoc) iter.next();
751             lowerLine = line.toLowerCase();
752
753             // We have three cases :
754
// 1) The first line after the DN is a "control:"
755
// 2) The first line after the DN is a "changeType:"
756
// 3) The first line after the DN is anything else
757
if (lowerLine.startsWith("control:")) {
758                 if (containsEntries) {
759                     throw new LdifParseException("No changes withing entries", line);
760                 }
761
762                 containsChanges = true;
763
764                 if (controlSeen) {
765                     throw new LdifParseException("Control misplaced", line);
766                 }
767
768                 // Parse the control
769
control = parseControl(line.substring("control:".length()));
770                 entry.setControl(control);
771
772             } else if (lowerLine.startsWith("changetype:")) {
773                 if (containsEntries) {
774                     throw new LdifParseException("No changes withing entries", line);
775                 }
776
777                 containsChanges = true;
778
779                 if (changeTypeSeen) {
780                     throw new LdifParseException("ChangeType misplaced", line);
781                 }
782
783                 // A change request
784
type = CHANGE;
785                 controlSeen = true;
786
787                 operation = parseChangeType(line);
788
789                 // Parse the change operation in a separate function
790
parseChange(entry, iter, operation, control);
791                 changeTypeSeen = true;
792             } else if (line.indexOf(':') > 0) {
793                 if (containsChanges) {
794                     throw new LdifParseException("No entries within changes", line);
795                 }
796
797                 containsEntries = true;
798
799                 if (controlSeen || changeTypeSeen) {
800                     throw new LdifParseException("AttributeType misplaced", line);
801                 }
802
803                 parseAttributeValue(entry, line);
804                 type = ENTRY;
805             } else {
806                 // Invalid attribute Value
807
throw new LdifParseException("Bad attribute", line);
808             }
809         }
810
811         if (type == CHANGE) {
812             entry.setChangeType(operation);
813         }
814
815         return entry;
816     }
817
818     /**
819      * "version:" <fill> <number>
820      */

821     static final Pattern JavaDoc VERSION_PATTERN = Pattern.compile("[ ]*version\\:[ ]*(\\d+)[ ]*");
822
823     /**
824      * "version:" <fill>
825      * <number>
826      */

827
828     static final Pattern JavaDoc VERSION_PATTERN_LINE1 = Pattern.compile("[ ]*version\\:[ ]*");
829     static final Pattern JavaDoc VERSION_PATTERN_LINE2 = Pattern.compile("[ ]\\d+");
830
831     /**
832      * Parse the version from the ldif input.
833      *
834      * @return A number representing the version (default to 1)
835      */

836     private int parseVersion() {
837
838         // First, read a list of lines
839
readLines();
840
841         if (lines.size() == 0) {
842             return DEFAULT_VERSION;
843         }
844
845         // get the first line
846
String JavaDoc line = lines.get(0);
847
848
849         Matcher JavaDoc versionMatcher = VERSION_PATTERN.matcher(line);
850         String JavaDoc versionStr = null;
851
852         if (versionMatcher.matches()) {
853             versionStr=versionMatcher.group(1);
854             // We have found the version, just discard the line from the list
855
lines.remove(0);
856         } else {
857             versionMatcher = VERSION_PATTERN_LINE1.matcher(line);
858             if (versionMatcher.matches()) {
859                 lines.remove(0);
860                 if (!lines.isEmpty()) {
861                     versionMatcher = VERSION_PATTERN_LINE2.matcher(lines.get(1));
862                     if (versionMatcher.matches()) {
863                         versionStr=versionMatcher.group(1);
864                     }
865                     lines.remove(0);
866                 }
867
868             }
869
870         }
871
872         if (versionStr!=null) {
873             try {
874                 return Integer.parseInt(versionStr.trim());
875             } catch (NumberFormatException JavaDoc e) {
876                 throw new LdifParseException("Invalid LDIF version number "+versionStr, line);
877             }
878         } else {
879             return DEFAULT_VERSION;
880         }
881     }
882
883     /**
884      * Reads an entry in a ldif buffer, and returns the resulting lines, without
885      * comments, and unfolded.
886      * <p/>
887      * The lines represent *one* entry.
888      *
889      */

890     private void readLines() {
891         String JavaDoc line;
892         boolean insideComment = true;
893         boolean isFirstLine = true;
894
895         lines.clear();
896         StringBuilder JavaDoc sb = new StringBuilder JavaDoc(128);
897
898         try {
899             while ((line = ((BufferedReader JavaDoc) in).readLine()) != null) { //while not EOF
900
if (StringUtils.isAsciiWhitespacesOnly(line)) { //if line is empty
901
if (isFirstLine) {
902                         continue;
903                     } else {
904                         // The line is empty, we have read an entry
905
insideComment = false;
906                         if (lines.isEmpty()) { //if block is empty, i.e. comments section - read the next entry
907
continue;
908                         } else { //otherwise stop
909
break;
910                         }
911                     }
912                 }
913
914                 isFirstLine = false;
915
916                 // We will read the first line which is not a comment
917
switch (line.charAt(0)) {
918                     case '#':
919                         insideComment = true;
920                         break;
921
922                     case ' ':
923                         if (insideComment) {
924                             continue;
925                         } else if (sb.length() == 0) {
926                             throw new LdifParseException("Ldif Parsing error: Cannot have an empty continuation line");
927                         } else {
928                             sb.append(line.substring(1));
929                         }
930
931                         insideComment = false;
932                         break;
933
934                     default:
935                         // We have found a new entry
936
// First, stores the previous one if any.
937
if (sb.length() != 0) {
938                             lines.add(sb.toString());
939                         }
940
941                         sb = new StringBuilder JavaDoc(line);
942                         insideComment = false;
943                         break;
944                 }
945             }
946         }
947         catch (IOException JavaDoc ioe) {
948             throw new LdifParseException("Error while reading ldif lines");
949         }
950
951         // Stores the current line if necessary.
952
if (sb.length() != 0) {
953             lines.add(sb.toString());
954         }
955
956     }
957
958
959     // ------------------------------------------------------------------------
960
// Iterator Methods
961
// ------------------------------------------------------------------------
962

963     /**
964      * Gets the next LDIF on the channel.
965      *
966      * @return the next LDIF as a String.
967      */

968     public Entry next() {
969         if (!hasNext()) {
970             throw new NoSuchElementException JavaDoc("No LDIF entries to read. Use hasNext().");
971         }
972         Entry res = prefetched;
973         prefetched=null;
974         return res;
975     }
976
977     /**
978      * Tests to see if another LDIF is on the input channel.
979      *
980      * @return true if another LDIF is available false otherwise.
981      */

982     public boolean hasNext() {
983         if (prefetched==null) {
984             readLines();
985             prefetched = parseEntry();
986         }
987         return null != prefetched;
988     }
989
990     /**
991      * Always throws UnsupportedOperationException!
992      *
993      * @see java.util.Iterator#remove()
994      */

995     public void remove() {
996         throw new UnsupportedOperationException JavaDoc();
997     }
998
999     /**
1000     * @return An iterator on the file
1001     */

1002    public Iterator JavaDoc<Entry> iterator() {
1003        return this;
1004    }
1005
1006
1007    List JavaDoc parseLdif(String JavaDoc s) {
1008        return parseLdif(new BufferedReader JavaDoc(new StringReader JavaDoc(s)));
1009    }
1010
1011    /**
1012     * The main entry point of the LdifParser. It reads a buffer and returns a
1013     * List of entries.
1014     *
1015     * @param in The buffer being processed
1016     * @return A list of entries
1017     * @throws LdifParseException If something went wrong
1018     */

1019    public List JavaDoc<Entry> parseLdif(BufferedReader JavaDoc in) {
1020        init(in);
1021        // Create a list that will contain the read entries
1022
List JavaDoc<Entry> entries = new ArrayList JavaDoc<Entry>();
1023
1024        // When done, get the entries one by one.
1025
while (hasNext()) {
1026            Entry entry = next();
1027            entries.add(entry);
1028        }
1029
1030        return entries;
1031    }
1032
1033    /**
1034     * @return True if the ldif file contains entries, fals if it contains
1035     * changes
1036     */

1037    public boolean containsEntries() {
1038        return containsEntries;
1039    }
1040
1041
1042
1043
1044
1045}
Popular Tags