0001: /*
0002: * Copyright 2006 The Apache Software Foundation
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: *
0016: */
0017: package scriptella.driver.ldap.ldif;
0018:
0019: import scriptella.expression.LineIterator;
0020: import scriptella.util.StringUtils;
0021:
0022: import javax.naming.directory.DirContext;
0023: import javax.naming.ldap.BasicControl;
0024: import javax.naming.ldap.Control;
0025: import java.io.IOException;
0026: import java.io.InputStream;
0027: import java.io.StringReader;
0028: import java.io.UnsupportedEncodingException;
0029: import java.net.URL;
0030: import java.util.ArrayList;
0031: import java.util.Iterator;
0032: import java.util.List;
0033: import java.util.NoSuchElementException;
0034: import java.util.regex.Matcher;
0035: import java.util.regex.Pattern;
0036:
0037: /**
0038: * <pre>
0039: * <ldif-file> ::= "version:" <fill> <number> <seps> <dn-spec> <sep> <ldif-content-change>
0040: * <p/>
0041: * <ldif-content-change> ::=
0042: * <number> <oid> <options-e> <value-spec> <sep> <attrval-specs-e> <ldif-attrval-record-e> |
0043: * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> <ldif-attrval-record-e> |
0044: * "control:" <fill> <number> <oid> <spaces-e> <criticality> <value-spec-e> <sep> <controls-e>
0045: * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> |
0046: * "changetype:" <fill> <changerecord-type> <ldif-change-record-e>
0047: * <p/>
0048: * <ldif-attrval-record-e> ::= <seps> <dn-spec> <sep> <attributeType>
0049: * <options-e> <value-spec> <sep> <attrval-specs-e>
0050: * <ldif-attrval-record-e> | e
0051: * <p/>
0052: * <ldif-change-record-e> ::= <seps> <dn-spec> <sep> <controls-e>
0053: * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | e
0054: * <p/>
0055: * <dn-spec> ::= "dn:" <fill> <safe-string> | "dn::" <fill> <base64-string>
0056: * <p/>
0057: * <controls-e> ::= "control:" <fill> <number> <oid> <spaces-e> <criticality>
0058: * <value-spec-e> <sep> <controls-e> | e
0059: * <p/>
0060: * <criticality> ::= "true" | "false" | e
0061: * <p/>
0062: * <oid> ::= '.' <number> <oid> | e
0063: * <p/>
0064: * <attrval-specs-e> ::= <number> <oid> <options-e> <value-spec> <sep> <attrval-specs-e> |
0065: * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> | e
0066: * <p/>
0067: * <value-spec-e> ::= <value-spec> | e
0068: * <p/>
0069: * <value-spec> ::= ':' <fill> <safe-string-e> |
0070: * "::" <fill> <base64-chars> |
0071: * ":<" <fill> <url>
0072: * <p/>
0073: * <attributeType> ::= <number> <oid> | <alpha> <chars-e>
0074: * <p/>
0075: * <options-e> ::= ';' <char> <chars-e> <options-e> |e
0076: * <p/>
0077: * <chars-e> ::= <char> <chars-e> | e
0078: * <p/>
0079: * <changerecord-type> ::= "add" <sep> <attributeType> <options-e> <value-spec> <sep> <attrval-specs-e> |
0080: * "delete" <sep> |
0081: * "modify" <sep> <mod-type> <fill> <attributeType> <options-e> <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> |
0082: * "moddn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep> <newsuperior-e> <sep> |
0083: * "modrdn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep> <newsuperior-e> <sep>
0084: * <p/>
0085: * <newrdn> ::= ':' <fill> <safe-string> | "::" <fill> <base64-chars>
0086: * <p/>
0087: * <newsuperior-e> ::= "newsuperior" <newrdn> | e
0088: * <p/>
0089: * <mod-specs-e> ::= <mod-type> <fill> <attributeType> <options-e>
0090: * <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | e
0091: * <p/>
0092: * <mod-type> ::= "add:" | "delete:" | "replace:"
0093: * <p/>
0094: * <url> ::= <a Uniform Resource Locator, as defined in [6]>
0095: * <p/>
0096: * <p/>
0097: * <p/>
0098: * LEXICAL
0099: * -------
0100: * <p/>
0101: * <fill> ::= ' ' <fill> | e
0102: * <char> ::= <alpha> | <digit> | '-'
0103: * <number> ::= <digit> <digits>
0104: * <0-1> ::= '0' | '1'
0105: * <digits> ::= <digit> <digits> | e
0106: * <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
0107: * <seps> ::= <sep> <seps-e>
0108: * <seps-e> ::= <sep> <seps-e> | e
0109: * <sep> ::= 0x0D 0x0A | 0x0A
0110: * <spaces> ::= ' ' <spaces-e>
0111: * <spaces-e> ::= ' ' <spaces-e> | e
0112: * <safe-string-e> ::= <safe-string> | e
0113: * <safe-string> ::= <safe-init-char> <safe-chars>
0114: * <safe-init-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F]
0115: * <safe-chars> ::= <safe-char> <safe-chars> | e
0116: * <safe-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F]
0117: * <base64-string> ::= <base64-char> <base64-chars>
0118: * <base64-chars> ::= <base64-char> <base64-chars> | e
0119: * <base64-char> ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-9x5A] | [0x61-0x7A]
0120: * <alpha> ::= [0x41-0x5A] | [0x61-0x7A]
0121: * <p/>
0122: * COMMENTS
0123: * --------
0124: * - The ldap-oid VN is not correct in the RFC-2849. It has been changed from 1*DIGIT 0*1("." 1*DIGIT) to
0125: * DIGIT+ ("." DIGIT+)*
0126: * - The mod-spec lacks a sep between *attrval-spec and "-".
0127: * - The BASE64-UTF8-STRING should be BASE64-CHAR BASE64-STRING
0128: * - The ValueSpec rule must accept multilines values. In this case, we have a LF followed by a
0129: * single space before the continued value.
0130: * </pre>
0131: */
0132: public class LdifReader implements Iterator<Entry>, Iterable<Entry> {
0133:
0134: /**
0135: * A list of read lines
0136: */
0137: private final List<String> lines = new ArrayList<String>();
0138:
0139: /**
0140: * The ldif file version default value
0141: */
0142: private static final int DEFAULT_VERSION = 1;
0143:
0144: /**
0145: * The ldif version
0146: */
0147: private int version = DEFAULT_VERSION;
0148:
0149: /**
0150: * Type of element read
0151: */
0152: private static final int ENTRY = 0;
0153:
0154: private static final int CHANGE = 1;
0155:
0156: private static final int UNKNOWN = 2;
0157:
0158: /**
0159: * Size limit for file contained values
0160: */
0161: private final long sizeLimit;
0162:
0163: /**
0164: * The default size limit : 1Mo
0165: */
0166: private static final long SIZE_LIMIT_DEFAULT = 1024000;
0167:
0168: /**
0169: * State values for the modify operation
0170: */
0171: private static final int MOD_SPEC = 0;
0172:
0173: private static final int ATTRVAL_SPEC = 1;
0174:
0175: private static final int ATTRVAL_SPEC_OR_SEP = 2;
0176:
0177: /**
0178: * Iterator prefetched entry
0179: */
0180: private Entry prefetched;
0181:
0182: /**
0183: * The ldif LineIterator
0184: */
0185: private LineIterator in;
0186:
0187: /**
0188: * A flag set if the ldif contains entries
0189: */
0190: private boolean containsEntries;
0191:
0192: /**
0193: * A flag set if the ldif contains changes
0194: */
0195: private boolean containsChanges;
0196:
0197: /**
0198: * A constructor which takes a line iterator.
0199: *
0200: * @param in A Reader containing ldif formated input
0201: * @throws LdifParseException If the file cannot be processed or if the format is incorrect
0202: */
0203: public LdifReader(LineIterator in) {
0204: this (in, SIZE_LIMIT_DEFAULT);
0205: }
0206:
0207: /**
0208: * * A constructor which takes a line iterator and a size limit.
0209: *
0210: * @param in A Reader containing ldif formated input
0211: * @param sizeLimit maximum file size that can be accepted for an attribute value
0212: */
0213: public LdifReader(LineIterator in, long sizeLimit) {
0214: this .in = in;
0215: this .sizeLimit = sizeLimit;
0216: // First get the version - if any -
0217: version = parseVersion();
0218: prefetched = parseEntry();
0219: }
0220:
0221: /**
0222: * For testing purposes.
0223: */
0224: public LdifReader(String string) {
0225: this (new LineIterator(new StringReader(string)));
0226: }
0227:
0228: /**
0229: * @return The ldif file version
0230: */
0231: public int getVersion() {
0232: return version;
0233: }
0234:
0235: /**
0236: * @return The maximum size of a file which is used into an attribute value.
0237: */
0238: public long getSizeLimit() {
0239: return sizeLimit;
0240: }
0241:
0242: /**
0243: * Parse the changeType
0244: *
0245: * @param line The line which contains the changeType
0246: * @return The operation.
0247: */
0248: private int parseChangeType(String line) {
0249: int operation = Entry.ADD;
0250:
0251: String modOp = line.substring("changetype:".length() + 1)
0252: .trim();
0253:
0254: if ("add".equalsIgnoreCase(modOp)) {
0255: operation = Entry.ADD;
0256: } else if ("delete".equalsIgnoreCase(modOp)) {
0257: operation = Entry.DELETE;
0258: } else if ("modify".equalsIgnoreCase(modOp)) {
0259: operation = Entry.MODIFY;
0260: } else if ("moddn".equalsIgnoreCase(modOp)) {
0261: operation = Entry.MODDN;
0262: } else if ("modrdn".equalsIgnoreCase(modOp)) {
0263: operation = Entry.MODRDN;
0264: }
0265:
0266: return operation;
0267: }
0268:
0269: /**
0270: * Parse the DN of an entry
0271: *
0272: * @param line The line to parse
0273: * @return A DN
0274: */
0275: private String parseDn(String line) {
0276: String dn = null;
0277:
0278: String lowerLine = line.toLowerCase();
0279:
0280: if (lowerLine.startsWith("dn:") || lowerLine.startsWith("DN:")) {
0281: // Ok, we have a DN. Is it base 64 encoded ?
0282: int length = line.length();
0283:
0284: if (length == 3) {
0285: // The DN is empty : error
0286: throw new LdifParseException("No DN for entry", line);
0287: } else if (line.charAt(3) == ':') {
0288: if (length > 4) {
0289: // This is a base 64 encoded DN.
0290: String trimmedLine = line.substring(4).trim();
0291:
0292: try {
0293: dn = new String(Utils.base64Decode(trimmedLine
0294: .toCharArray()), "UTF-8");
0295: } catch (UnsupportedEncodingException uee) {
0296: // The DN is not base 64 encoded
0297: throw new LdifParseException(
0298: "Invalid base 64 encoded DN", line);
0299: }
0300: } else {
0301: // The DN is empty : error
0302: throw new LdifParseException("No DN for entry",
0303: line);
0304: }
0305: } else {
0306: dn = line.substring(3).trim();
0307: }
0308: } else {
0309: throw new LdifParseException("No DN for entry", line);
0310: }
0311:
0312: return dn;
0313: }
0314:
0315: /**
0316: * Parse the value part.
0317: *
0318: * @param line The line which contains the value
0319: * @param pos The starting position in the line
0320: * @return A String or a byte[], depending of the kind of value we get
0321: * @throws LdifParseException If something went wrong
0322: */
0323: private Object parseValue(String line, int pos) {
0324: if (line.length() > pos + 1) {
0325: char c = line.charAt(pos + 1);
0326:
0327: if (c == ':') {
0328: String value = line.substring(pos + 2).trim();
0329:
0330: return Utils.base64Decode(value.toCharArray());
0331: } else if (c == '<') {
0332: String urlName = line.substring(pos + 2).trim();
0333: try {
0334: return Utils.toByteArray(getUriStream(urlName),
0335: sizeLimit);
0336: } catch (IOException e) {
0337: throw new LdifParseException("Failed to read \""
0338: + urlName + "\" file content", line, e);
0339: }
0340: } else {
0341: return line.substring(pos + 1).trim();
0342: }
0343: } else {
0344: return null;
0345: }
0346: }
0347:
0348: /**
0349: * Resolves URI to URL and returns a content stream.
0350: * This method just creates a new URL, subclasses may chnange this behaviour.
0351: * @param uri URI to resolve.
0352: * @return resolved URL content stream.
0353: * @throws IOException if an I/O error occurs or URI is malformed.
0354: */
0355: protected InputStream getUriStream(String uri) throws IOException {
0356: return new URL(uri).openStream();
0357: }
0358:
0359: /**
0360: * Parse a control. The grammar is : <control> ::= "control:" <fill>
0361: * <ldap-oid> <critical-e> <value-spec-e> <sep> <critical-e> ::= <spaces>
0362: * <boolean> | e <boolean> ::= "true" | "false" <value-spec-e> ::=
0363: * <value-spec> | e <value-spec> ::= ":" <fill> <SAFE-STRING-e> | "::"
0364: * <fill> <BASE64-STRING> | ":<" <fill> <url>
0365: * <p/>
0366: * It can be read as : "control:" <fill> <ldap-oid> [ " "+ ( "true" |
0367: * "false") ] [ ":" <fill> <SAFE-STRING-e> | "::" <fill> <BASE64-STRING> | ":<"
0368: * <fill> <url> ]
0369: *
0370: * @param line The line containing the control
0371: * @return A control
0372: */
0373: private Control parseControl(String line) {
0374: String lowerLine = line.toLowerCase().trim();
0375: char[] controlValue = line.trim().toCharArray();
0376: int pos = 0;
0377: int length = controlValue.length;
0378:
0379: // Get the <ldap-oid>
0380: if (pos > length) {
0381: // No OID : error !
0382: throw new LdifParseException("Bad control, no oid", line);
0383: }
0384:
0385: int initPos = pos;
0386:
0387: while (Utils.isCharASCII(controlValue, pos, '.')
0388: || Utils.isDigit(controlValue, pos)) {
0389: pos++;
0390: }
0391:
0392: if (pos == initPos) {
0393: // Not a valid OID !
0394: throw new LdifParseException("Bad control, no oid", line);
0395: }
0396:
0397: String oid = lowerLine.substring(0, pos);
0398: boolean criticality = false;
0399: byte[] controlBytes = null;
0400:
0401: // Get the criticality, if any
0402: // Skip the <fill>
0403: while (Utils.isCharASCII(controlValue, pos, ' ')) {
0404: pos++;
0405: }
0406:
0407: // Check if we have a "true" or a "false"
0408: int criticalPos = lowerLine.indexOf(':');
0409:
0410: int criticalLength = 0;
0411:
0412: if (criticalPos == -1) {
0413: criticalLength = length - pos;
0414: } else {
0415: criticalLength = criticalPos - pos;
0416: }
0417:
0418: if ((criticalLength == 4)
0419: && ("true".equalsIgnoreCase(lowerLine.substring(pos,
0420: pos + 4)))) {
0421: criticality = true;
0422: } else if ((criticalLength == 5)
0423: && ("false".equalsIgnoreCase(lowerLine.substring(pos,
0424: pos + 5)))) {
0425: criticality = false;
0426: } else if (criticalLength != 0) {
0427: // If we have a criticality, it should be either "true" or "false",
0428: // nothing else
0429: throw new LdifParseException("Bad control criticality",
0430: line);
0431: }
0432:
0433: if (criticalPos > 0) {
0434: // We have a value. It can be a normal value, a base64 encoded value
0435: // or a file contained value
0436: if (Utils.isCharASCII(controlValue, criticalPos + 1, ':')) {
0437: // Base 64 encoded value
0438: controlBytes = Utils.base64Decode(line.substring(
0439: criticalPos + 2).toCharArray());
0440: } else if (Utils.isCharASCII(controlValue, criticalPos + 1,
0441: '<')) {
0442: // File contained value
0443: } else {
0444: // Standard value
0445: byte[] value = new byte[length - criticalPos - 1];
0446:
0447: for (int i = 0; i < length - criticalPos - 1; i++) {
0448: value[i] = (byte) controlValue[i + criticalPos + 1];
0449: }
0450:
0451: controlBytes = value;
0452: }
0453: }
0454:
0455: return new BasicControl(oid, criticality, controlBytes);
0456: }
0457:
0458: /**
0459: * Parse an AttributeType/AttributeValue
0460: *
0461: * @param entry The entry where to store the value
0462: * @param line The line to parse
0463: */
0464: public void parseAttributeValue(Entry entry, String line) {
0465: int colonIndex = line.indexOf(':');
0466:
0467: String attributeType = line.substring(0, colonIndex);
0468:
0469: // We should *not* have a DN twice
0470: if (attributeType.equalsIgnoreCase("dn")) {
0471: throw new LdifParseException(
0472: "A ldif entry should not have two DN", line);
0473: }
0474:
0475: Object attributeValue = parseValue(line, colonIndex);
0476:
0477: // Update the entry
0478: entry.addAttribute(attributeType, attributeValue);
0479: }
0480:
0481: /**
0482: * Parse a ModRDN operation
0483: *
0484: * @param entry The entry to update
0485: * @param iter The lines iterator
0486: */
0487: private void parseModRdn(Entry entry, Iterator iter) {
0488: // We must have two lines : one starting with "newrdn:" or "newrdn::",
0489: // and the second starting with "deleteoldrdn:"
0490: if (iter.hasNext()) {
0491: String line = (String) iter.next();
0492: String lowerLine = line.toLowerCase();
0493:
0494: if (lowerLine.startsWith("newrdn::")
0495: || lowerLine.startsWith("newrdn:")) {
0496: int colonIndex = line.indexOf(':');
0497: Object attributeValue = parseValue(line, colonIndex);
0498: entry
0499: .setNewRdn(attributeValue instanceof String ? (String) attributeValue
0500: : Utils
0501: .utf8ToString((byte[]) attributeValue));
0502: } else {
0503: throw new LdifParseException("Bad modrdn operation",
0504: line);
0505: }
0506:
0507: } else {
0508: throw new LdifParseException(
0509: "Bad modrdn operation, no newrdn");
0510: }
0511:
0512: if (iter.hasNext()) {
0513: String line = (String) iter.next();
0514: String lowerLine = line.toLowerCase();
0515:
0516: if (lowerLine.startsWith("deleteoldrdn:")) {
0517: int colonIndex = line.indexOf(':');
0518: Object attributeValue = parseValue(line, colonIndex);
0519: entry.setDeleteOldRdn("1".equals(attributeValue));
0520: } else {
0521: throw new LdifParseException(
0522: "Bad modrdn operation, no deleteoldrdn", line);
0523: }
0524: } else {
0525: throw new LdifParseException(
0526: "Bad modrdn operation, no deleteoldrdn");
0527: }
0528:
0529: }
0530:
0531: /**
0532: * Parse a modify change type.
0533: * <p/>
0534: * The grammar is : <changerecord> ::= "changetype:" FILL "modify" SEP
0535: * <mod-spec> <mod-specs-e> <mod-spec> ::= "add:" <mod-val> | "delete:"
0536: * <mod-val-del> | "replace:" <mod-val> <mod-specs-e> ::= <mod-spec>
0537: * <mod-specs-e> | e <mod-val> ::= FILL ATTRIBUTE-DESCRIPTION SEP
0538: * ATTRVAL-SPEC <attrval-specs-e> "-" SEP <mod-val-del> ::= FILL
0539: * ATTRIBUTE-DESCRIPTION SEP <attrval-specs-e> "-" SEP <attrval-specs-e> ::=
0540: * ATTRVAL-SPEC <attrval-specs> | e *
0541: *
0542: * @param entry The entry to feed
0543: * @param iter The lines
0544: */
0545: private void parseModify(Entry entry, Iterator iter) {
0546: int state = MOD_SPEC;
0547: String modified = null;
0548: int modification = 0;
0549:
0550: // The following flag is used to deal with empty modifications
0551: boolean isEmptyValue = true;
0552:
0553: while (iter.hasNext()) {
0554: String line = (String) iter.next();
0555: String lowerLine = line.toLowerCase();
0556:
0557: if (lowerLine.startsWith("-")) {
0558: if (state != ATTRVAL_SPEC_OR_SEP) {
0559: throw new LdifParseException(
0560: "Bad modify separator", line);
0561: } else {
0562: if (isEmptyValue) {
0563: // Update the entry
0564: entry.addModificationItem(modification,
0565: modified, null);
0566: }
0567:
0568: state = MOD_SPEC;
0569: isEmptyValue = true;
0570: continue;
0571: }
0572: } else if (lowerLine.startsWith("add:")) {
0573: if ((state != MOD_SPEC) && (state != ATTRVAL_SPEC)) {
0574: throw new LdifParseException("Bad modify state",
0575: line);
0576: }
0577:
0578: modified = line.substring("add:".length()).trim();
0579: modification = DirContext.ADD_ATTRIBUTE;
0580:
0581: state = ATTRVAL_SPEC;
0582: } else if (lowerLine.startsWith("delete:")) {
0583: if ((state != MOD_SPEC) && (state != ATTRVAL_SPEC)) {
0584: throw new LdifParseException("Bad modify state",
0585: line);
0586: }
0587:
0588: modified = line.substring("delete:".length()).trim();
0589: modification = DirContext.REMOVE_ATTRIBUTE;
0590:
0591: state = ATTRVAL_SPEC_OR_SEP;
0592: } else if (lowerLine.startsWith("replace:")) {
0593: if ((state != MOD_SPEC) && (state != ATTRVAL_SPEC)) {
0594: throw new LdifParseException("Bad modify state",
0595: line);
0596: }
0597:
0598: modified = line.substring("replace:".length()).trim();
0599: modification = DirContext.REPLACE_ATTRIBUTE;
0600:
0601: state = ATTRVAL_SPEC_OR_SEP;
0602: } else {
0603: if ((state != ATTRVAL_SPEC)
0604: && (state != ATTRVAL_SPEC_OR_SEP)) {
0605: throw new LdifParseException("Bad modify state",
0606: line);
0607: }
0608:
0609: // A standard AttributeType/AttributeValue pair
0610: int colonIndex = line.indexOf(':');
0611:
0612: String attributeType = line.substring(0, colonIndex);
0613:
0614: if (!attributeType.equals(modified)) {
0615: throw new LdifParseException(
0616: "Bad modify attribute", line);
0617: }
0618:
0619: // We should *not* have a DN twice
0620: if (attributeType.equals("dn")) {
0621: throw new LdifParseException(
0622: "A ldif entry should not have two DN", line);
0623: }
0624:
0625: Object attributeValue = parseValue(line, colonIndex);
0626:
0627: // Update the entry
0628: entry.addModificationItem(modification, attributeType,
0629: attributeValue);
0630: isEmptyValue = false;
0631:
0632: state = ATTRVAL_SPEC_OR_SEP;
0633: }
0634: }
0635: }
0636:
0637: /**
0638: * Parse a change operation. We have to handle different cases depending on
0639: * the operation. 1) Delete : there should *not* be any line after the
0640: * "changetype: delete" 2) Add : we must have a list of AttributeType :
0641: * AttributeValue elements 3) ModDN : we must have two following lines: a
0642: * "newrdn:" and a "deleteoldrdn:" 4) ModRDN : the very same, but a
0643: * "newsuperior:" line is expected 5) Modify :
0644: * <p/>
0645: * The grammar is : <changerecord> ::= "changetype:" FILL "add" SEP
0646: * <attrval-spec> <attrval-specs-e> | "changetype:" FILL "delete" |
0647: * "changetype:" FILL "modrdn" SEP <newrdn> SEP <deleteoldrdn> SEP | // To
0648: * be checked "changetype:" FILL "moddn" SEP <newrdn> SEP <deleteoldrdn> SEP
0649: * <newsuperior> SEP | "changetype:" FILL "modify" SEP <mod-spec>
0650: * <mod-specs-e> <newrdn> ::= "newrdn:" FILL RDN | "newrdn::" FILL
0651: * BASE64-RDN <deleteoldrdn> ::= "deleteoldrdn:" FILL "0" | "deleteoldrdn:"
0652: * FILL "1" <newsuperior> ::= "newsuperior:" FILL DN | "newsuperior::" FILL
0653: * BASE64-DN <mod-specs-e> ::= <mod-spec> <mod-specs-e> | e <mod-spec> ::=
0654: * "add:" <mod-val> | "delete:" <mod-val> | "replace:" <mod-val> <mod-val>
0655: * ::= FILL ATTRIBUTE-DESCRIPTION SEP ATTRVAL-SPEC <attrval-specs-e> "-" SEP
0656: * <attrval-specs-e> ::= ATTRVAL-SPEC <attrval-specs> | e
0657: *
0658: * @param entry The entry to feed
0659: * @param iter The lines iterator
0660: * @param operation The change operation (add, modify, delete, moddn or modrdn)
0661: * @param control The associated control, if any
0662: */
0663: private void parseChange(Entry entry, Iterator iter, int operation,
0664: Control control) {
0665: // The changetype and operation has already been parsed.
0666: entry.setChangeType(operation);
0667:
0668: switch (operation) {
0669: case Entry.DELETE:
0670: // The change type will tell that it's a delete operation,
0671: // the dn is used as a key.
0672: return;
0673:
0674: case Entry.ADD:
0675: // We will iterate through all attribute/value pairs
0676: while (iter.hasNext()) {
0677: String line = (String) iter.next();
0678: parseAttributeValue(entry, line);
0679: }
0680:
0681: return;
0682:
0683: case Entry.MODIFY:
0684: parseModify(entry, iter);
0685: return;
0686:
0687: case Entry.MODRDN:// They are supposed to have the same syntax ???
0688: case Entry.MODDN:
0689: // First, parse the modrdn part
0690: parseModRdn(entry, iter);
0691:
0692: // The next line should be the new superior
0693: if (iter.hasNext()) {
0694: String line = (String) iter.next();
0695: String lowerLine = line.toLowerCase();
0696:
0697: if (lowerLine.startsWith("newsuperior:")) {
0698: int colonIndex = line.indexOf(':');
0699: Object attributeValue = parseValue(line, colonIndex);
0700: entry
0701: .setNewSuperior(attributeValue instanceof String ? (String) attributeValue
0702: : Utils
0703: .utf8ToString((byte[]) attributeValue));
0704: } else {
0705: if (operation == Entry.MODDN) {
0706: throw new LdifParseException(
0707: "Bad moddn operation, no newsuperior",
0708: line);
0709: }
0710: }
0711: } else {
0712: if (operation == Entry.MODDN) {
0713: throw new LdifParseException(
0714: "Bad moddn operation, no newsuperior");
0715: }
0716: }
0717:
0718: return;
0719:
0720: default:
0721: // This is an error
0722: throw new LdifParseException("Bad operation");
0723: }
0724: }
0725:
0726: /**
0727: * Parse a ldif file. The following rules are processed :
0728: * <p/>
0729: * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> |
0730: * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::=
0731: * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::=
0732: * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill>
0733: * <distinguishedName> | "dn::" <fill> <base64-distinguishedName>
0734: * <changerecord> ::= "changetype:" <fill> <change-op>
0735: */
0736: private Entry parseEntry() {
0737: if ((lines == null) || (lines.size() == 0)) {
0738: return null;
0739: }
0740:
0741: // The entry must start with a dn: or a dn::
0742: String line = lines.get(0);
0743:
0744: String dn = parseDn(line);
0745:
0746: // Ok, we have found a DN
0747: Entry entry = new Entry();
0748: entry.setDn(dn);
0749:
0750: // We remove this dn from the lines
0751: lines.remove(0);
0752:
0753: // Now, let's iterate through the other lines
0754: Iterator iter = lines.iterator();
0755:
0756: // This flag is used to distinguish between an entry and a change
0757: int type = UNKNOWN;
0758:
0759: // The following boolean is used to check that a control is *not*
0760: // found elswhere than just after the dn
0761: boolean controlSeen = false;
0762:
0763: // We use this boolean to check that we do not have AttributeValues
0764: // after a change operation
0765: boolean changeTypeSeen = false;
0766:
0767: int operation = Entry.ADD;
0768: String lowerLine = null;
0769: Control control = null;
0770:
0771: while (iter.hasNext()) {
0772: // Each line could start either with an OID, an attribute type, with
0773: // "control:" or with "changetype:"
0774: line = (String) iter.next();
0775: lowerLine = line.toLowerCase();
0776:
0777: // We have three cases :
0778: // 1) The first line after the DN is a "control:"
0779: // 2) The first line after the DN is a "changeType:"
0780: // 3) The first line after the DN is anything else
0781: if (lowerLine.startsWith("control:")) {
0782: if (containsEntries) {
0783: throw new LdifParseException(
0784: "No changes withing entries", line);
0785: }
0786:
0787: containsChanges = true;
0788:
0789: if (controlSeen) {
0790: throw new LdifParseException("Control misplaced",
0791: line);
0792: }
0793:
0794: // Parse the control
0795: control = parseControl(line.substring("control:"
0796: .length()));
0797: entry.setControl(control);
0798:
0799: } else if (lowerLine.startsWith("changetype:")) {
0800: if (containsEntries) {
0801: throw new LdifParseException(
0802: "No changes withing entries", line);
0803: }
0804:
0805: containsChanges = true;
0806:
0807: if (changeTypeSeen) {
0808: throw new LdifParseException(
0809: "ChangeType misplaced", line);
0810: }
0811:
0812: // A change request
0813: type = CHANGE;
0814: controlSeen = true;
0815:
0816: operation = parseChangeType(line);
0817:
0818: // Parse the change operation in a separate function
0819: parseChange(entry, iter, operation, control);
0820: changeTypeSeen = true;
0821: } else if (line.indexOf(':') > 0) {
0822: if (containsChanges) {
0823: throw new LdifParseException(
0824: "No entries within changes", line);
0825: }
0826:
0827: containsEntries = true;
0828:
0829: if (controlSeen || changeTypeSeen) {
0830: throw new LdifParseException(
0831: "AttributeType misplaced", line);
0832: }
0833:
0834: parseAttributeValue(entry, line);
0835: type = ENTRY;
0836: } else {
0837: // Invalid attribute Value
0838: throw new LdifParseException("Bad attribute", line);
0839: }
0840: }
0841:
0842: if (type == CHANGE) {
0843: entry.setChangeType(operation);
0844: }
0845:
0846: return entry;
0847: }
0848:
0849: /**
0850: * "version:" <fill> <number>
0851: */
0852: static final Pattern VERSION_PATTERN = Pattern
0853: .compile("[ ]*version\\:[ ]*(\\d+)[ ]*");
0854:
0855: /**
0856: * "version:" <fill>
0857: * <number>
0858: */
0859:
0860: static final Pattern VERSION_PATTERN_LINE1 = Pattern
0861: .compile("[ ]*version\\:[ ]*");
0862: static final Pattern VERSION_PATTERN_LINE2 = Pattern
0863: .compile("[ ]\\d+");
0864:
0865: /**
0866: * Parse the version from the ldif input.
0867: *
0868: * @return A number representing the version (default to 1)
0869: */
0870: private int parseVersion() {
0871:
0872: // First, read a list of lines
0873: readLines();
0874:
0875: if (lines.size() == 0) {
0876: return DEFAULT_VERSION;
0877: }
0878:
0879: // get the first line
0880: String line = lines.get(0);
0881:
0882: Matcher versionMatcher = VERSION_PATTERN.matcher(line);
0883: String versionStr = null;
0884:
0885: if (versionMatcher.matches()) {
0886: versionStr = versionMatcher.group(1);
0887: // We have found the version, just discard the line from the list
0888: lines.remove(0);
0889: } else {
0890: versionMatcher = VERSION_PATTERN_LINE1.matcher(line);
0891: if (versionMatcher.matches()) {
0892: lines.remove(0);
0893: if (!lines.isEmpty()) {
0894: versionMatcher = VERSION_PATTERN_LINE2
0895: .matcher(lines.get(1));
0896: if (versionMatcher.matches()) {
0897: versionStr = versionMatcher.group(1);
0898: }
0899: lines.remove(0);
0900: }
0901:
0902: }
0903:
0904: }
0905:
0906: if (versionStr != null) {
0907: try {
0908: return Integer.parseInt(versionStr.trim());
0909: } catch (NumberFormatException e) {
0910: throw new LdifParseException(
0911: "Invalid LDIF version number " + versionStr,
0912: line);
0913: }
0914: } else {
0915: return DEFAULT_VERSION;
0916: }
0917: }
0918:
0919: /**
0920: * Reads an entry in a ldif buffer, and returns the resulting lines, without
0921: * comments, and unfolded.
0922: * <p/>
0923: * The lines represent *one* entry.
0924: *
0925: */
0926: private void readLines() {
0927: String line;
0928: boolean insideComment = true;
0929: boolean isFirstLine = true;
0930:
0931: lines.clear();
0932: StringBuilder sb = new StringBuilder(128);
0933:
0934: while (in.hasNext()) { //while not EOF
0935: line = in.next();
0936: if (StringUtils.isAsciiWhitespacesOnly(line)) { //if line is empty
0937: if (isFirstLine) {
0938: continue;
0939: } else {
0940: // The line is empty, we have read an entry
0941: insideComment = false;
0942: if (lines.isEmpty()) { //if block is empty, i.e. comments section - read the next entry
0943: continue;
0944: } else { //otherwise stop
0945: break;
0946: }
0947: }
0948: }
0949:
0950: isFirstLine = false;
0951:
0952: // We will read the first line which is not a comment
0953: switch (line.charAt(0)) {
0954: case '#':
0955: insideComment = true;
0956: break;
0957:
0958: case ' ':
0959: if (insideComment) {
0960: continue;
0961: } else if (sb.length() == 0) {
0962: throw new LdifParseException(
0963: "Ldif Parsing error: Cannot have an empty continuation line");
0964: } else {
0965: sb.append(line.substring(1));
0966: }
0967:
0968: insideComment = false;
0969: break;
0970:
0971: default:
0972: // We have found a new entry
0973: // First, stores the previous one if any.
0974: if (sb.length() != 0) {
0975: lines.add(sb.toString());
0976: }
0977:
0978: sb = new StringBuilder(line);
0979: insideComment = false;
0980: break;
0981: }
0982: }
0983:
0984: // Stores the current line if necessary.
0985: if (sb.length() != 0) {
0986: lines.add(sb.toString());
0987: }
0988:
0989: }
0990:
0991: // ------------------------------------------------------------------------
0992: // Iterator Methods
0993: // ------------------------------------------------------------------------
0994:
0995: /**
0996: * Gets the next LDIF on the channel.
0997: *
0998: * @return the next LDIF as a String.
0999: */
1000: public Entry next() {
1001: if (!hasNext()) {
1002: throw new NoSuchElementException(
1003: "No LDIF entries to read. Use hasNext().");
1004: }
1005: Entry res = prefetched;
1006: prefetched = null;
1007: return res;
1008: }
1009:
1010: /**
1011: * Tests to see if another LDIF is on the input channel.
1012: *
1013: * @return true if another LDIF is available false otherwise.
1014: */
1015: public boolean hasNext() {
1016: if (prefetched == null) {
1017: readLines();
1018: prefetched = parseEntry();
1019: }
1020: return null != prefetched;
1021: }
1022:
1023: /**
1024: * Always throws UnsupportedOperationException!
1025: *
1026: * @see java.util.Iterator#remove()
1027: */
1028: public void remove() {
1029: throw new UnsupportedOperationException();
1030: }
1031:
1032: /**
1033: * @return An iterator on the file
1034: */
1035: public Iterator<Entry> iterator() {
1036: return this ;
1037: }
1038:
1039: /**
1040: * Just a helper method
1041: */
1042: List<Entry> asList() {
1043: // Create a list that will contain the read entries
1044: List<Entry> entries = new ArrayList<Entry>();
1045:
1046: // When done, get the entries one by one.
1047: while (hasNext()) {
1048: Entry entry = next();
1049: entries.add(entry);
1050: }
1051: return entries;
1052: }
1053:
1054: /**
1055: * @return True if the ldif file contains entries, fals if it contains
1056: * changes
1057: */
1058: public boolean containsEntries() {
1059: return containsEntries;
1060: }
1061:
1062: }
|