001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2009-2010 Sun Microsystems, Inc. 015 * Portions copyright 2011-2016 ForgeRock AS. 016 * Portions copyright 2016 Matthew Stevenson 017 */ 018package org.forgerock.opendj.ldif; 019 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.Reader; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.NoSuchElementException; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030 031import org.forgerock.i18n.LocalizableMessage; 032import org.forgerock.i18n.LocalizableMessageBuilder; 033import org.forgerock.i18n.LocalizedIllegalArgumentException; 034import org.forgerock.opendj.ldap.AttributeDescription; 035import org.forgerock.opendj.ldap.ByteString; 036import org.forgerock.opendj.ldap.DN; 037import org.forgerock.opendj.ldap.DecodeException; 038import org.forgerock.opendj.ldap.Entry; 039import org.forgerock.opendj.ldap.LinkedAttribute; 040import org.forgerock.opendj.ldap.LinkedHashMapEntry; 041import org.forgerock.opendj.ldap.Modification; 042import org.forgerock.opendj.ldap.ModificationType; 043import org.forgerock.opendj.ldap.RDN; 044import org.forgerock.opendj.ldap.controls.Control; 045import org.forgerock.opendj.ldap.controls.GenericControl; 046import org.forgerock.opendj.ldap.requests.ModifyDNRequest; 047import org.forgerock.opendj.ldap.requests.ModifyRequest; 048import org.forgerock.opendj.ldap.requests.Requests; 049import org.forgerock.opendj.ldap.schema.Schema; 050import org.forgerock.opendj.ldap.schema.SchemaValidationPolicy; 051import org.forgerock.opendj.ldap.schema.Syntax; 052import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException; 053import org.forgerock.util.Reject; 054 055import static com.forgerock.opendj.ldap.CoreMessages.*; 056import static com.forgerock.opendj.util.StaticUtils.*; 057 058/** 059 * An LDIF change record reader reads change records using the LDAP Data 060 * Interchange Format (LDIF) from a user defined source. 061 * <p> 062 * The following example reads changes from LDIF, and writes the changes to the 063 * directory server. 064 * 065 * <pre> 066 * InputStream ldif = ...; 067 * LDIFChangeRecordReader reader = new LDIFChangeRecordReader(ldif); 068 * 069 * Connection connection = ...; 070 * connection.bind(...); 071 * 072 * ConnectionChangeRecordWriter writer = 073 * new ConnectionChangeRecordWriter(connection); 074 * while (reader.hasNext()) { 075 * ChangeRecord changeRecord = reader.readChangeRecord(); 076 * writer.writeChangeRecord(changeRecord); 077 * } 078 * </pre> 079 * 080 * @see <a href="http://tools.ietf.org/html/rfc2849">RFC 2849 - The LDAP Data 081 * Interchange Format (LDIF) - Technical Specification </a> 082 */ 083public final class LDIFChangeRecordReader extends AbstractLDIFReader implements ChangeRecordReader { 084 private static final Pattern CONTROL_REGEX = Pattern 085 .compile("^\\s*(\\d+(.\\d+)*)(\\s+((true)|(false)))?\\s*(:(:)?\\s*?\\S+)?\\s*$"); 086 087 /** Poison used to indicate end of LDIF. */ 088 private static final ChangeRecord EOF = Requests.newAddRequest(DN.rootDN()); 089 090 /** 091 * Parses the provided array of LDIF lines as a single LDIF change record. 092 * 093 * @param ldifLines 094 * The lines of LDIF to be parsed. 095 * @return The parsed LDIF change record. 096 * @throws LocalizedIllegalArgumentException 097 * If {@code ldifLines} did not contain an LDIF change record, 098 * if it contained multiple change records, if contained 099 * malformed LDIF, or if the change record could not be decoded 100 * using the default schema. 101 * @throws NullPointerException 102 * If {@code ldifLines} was {@code null}. 103 */ 104 public static ChangeRecord valueOfLDIFChangeRecord(final String... ldifLines) { 105 // LDIF change record reader is tolerant to missing change types. 106 try (final LDIFChangeRecordReader reader = new LDIFChangeRecordReader(ldifLines)) { 107 if (!reader.hasNext()) { 108 // No change record found. 109 final LocalizableMessage message = 110 WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND.get(); 111 throw new LocalizedIllegalArgumentException(message); 112 } 113 114 final ChangeRecord record = reader.readChangeRecord(); 115 116 if (reader.hasNext()) { 117 // Multiple change records found. 118 final LocalizableMessage message = 119 WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND.get(); 120 throw new LocalizedIllegalArgumentException(message); 121 } 122 123 return record; 124 } catch (final DecodeException e) { 125 // Badly formed LDIF. 126 throw new LocalizedIllegalArgumentException(e.getMessageObject()); 127 } catch (final IOException e) { 128 // This should never happen for a String based reader. 129 final LocalizableMessage message = 130 WARN_READ_LDIF_RECORD_UNEXPECTED_IO_ERROR.get(e.getMessage()); 131 throw new LocalizedIllegalArgumentException(message); 132 } 133 } 134 135 private ChangeRecord nextChangeRecord; 136 137 /** 138 * Creates a new LDIF change record reader whose source is the provided 139 * input stream. 140 * 141 * @param in 142 * The input stream to use. 143 * @throws NullPointerException 144 * If {@code in} was {@code null}. 145 */ 146 public LDIFChangeRecordReader(final InputStream in) { 147 super(in); 148 } 149 150 /** 151 * Creates a new LDIF change record reader which will read lines of LDIF 152 * from the provided list of LDIF lines. 153 * 154 * @param ldifLines 155 * The lines of LDIF to be read. 156 * @throws NullPointerException 157 * If {@code ldifLines} was {@code null}. 158 */ 159 public LDIFChangeRecordReader(final List<String> ldifLines) { 160 super(ldifLines); 161 } 162 163 /** 164 * Creates a new LDIF change record reader whose source is the provided 165 * character stream reader. 166 * 167 * @param reader 168 * The character stream reader to use. 169 * @throws NullPointerException 170 * If {@code reader} was {@code null}. 171 */ 172 public LDIFChangeRecordReader(final Reader reader) { 173 super(reader); 174 } 175 176 /** 177 * Creates a new LDIF change record reader which will read lines of LDIF 178 * from the provided array of LDIF lines. 179 * 180 * @param ldifLines 181 * The lines of LDIF to be read. 182 * @throws NullPointerException 183 * If {@code ldifLines} was {@code null}. 184 */ 185 public LDIFChangeRecordReader(final String... ldifLines) { 186 super(Arrays.asList(ldifLines)); 187 } 188 189 @Override 190 public void close() throws IOException { 191 close0(); 192 } 193 194 /** 195 * {@inheritDoc} 196 * 197 * @throws DecodeException 198 * If the change record could not be decoded because it was 199 * malformed. 200 */ 201 @Override 202 public boolean hasNext() throws DecodeException, IOException { 203 return getNextChangeRecord() != EOF; 204 } 205 206 /** 207 * {@inheritDoc} 208 * 209 * @throws DecodeException 210 * If the entry could not be decoded because it was malformed. 211 */ 212 @Override 213 public ChangeRecord readChangeRecord() throws DecodeException, IOException { 214 if (!hasNext()) { 215 // LDIF reader has completed successfully. 216 throw new NoSuchElementException(); 217 } 218 219 final ChangeRecord changeRecord = nextChangeRecord; 220 nextChangeRecord = null; 221 return changeRecord; 222 } 223 224 /** 225 * Specifies whether all operational attributes should be excluded 226 * from any change records that are read from LDIF. The default is 227 * {@code false}. 228 * 229 * @param excludeOperationalAttributes 230 * {@code true} if all operational attributes should be excluded, 231 * or {@code false} otherwise. 232 * @return A reference to this {@code LDIFChangeRecordReader}. 233 */ 234 public LDIFChangeRecordReader setExcludeAllOperationalAttributes( 235 final boolean excludeOperationalAttributes) { 236 this.excludeOperationalAttributes = excludeOperationalAttributes; 237 return this; 238 } 239 240 /** 241 * Specifies whether all user attributes should be excluded from any 242 * change records that are read from LDIF. The default is {@code false}. 243 * 244 * @param excludeUserAttributes 245 * {@code true} if all user attributes should be excluded, or 246 * {@code false} otherwise. 247 * @return A reference to this {@code LDIFChangeRecordReader}. 248 */ 249 public LDIFChangeRecordReader setExcludeAllUserAttributes(final boolean excludeUserAttributes) { 250 this.excludeUserAttributes = excludeUserAttributes; 251 return this; 252 } 253 254 /** 255 * Excludes the named attribute from any change records that are read from 256 * LDIF. By default all attributes are included unless explicitly excluded. 257 * 258 * @param attributeDescription 259 * The name of the attribute to be excluded. 260 * @return A reference to this {@code LDIFChangeRecordReader}. 261 */ 262 public LDIFChangeRecordReader setExcludeAttribute( 263 final AttributeDescription attributeDescription) { 264 Reject.ifNull(attributeDescription); 265 excludeAttributes.add(attributeDescription); 266 return this; 267 } 268 269 /** 270 * Excludes all change records which target entries beneath the named entry 271 * (inclusive) from being read from LDIF. By default all change records are 272 * read unless explicitly excluded or included. 273 * 274 * @param excludeBranch 275 * The distinguished name of the branch to be excluded. 276 * @return A reference to this {@code LDIFChangeRecordReader}. 277 */ 278 public LDIFChangeRecordReader setExcludeBranch(final DN excludeBranch) { 279 Reject.ifNull(excludeBranch); 280 excludeBranches.add(excludeBranch); 281 return this; 282 } 283 284 /** 285 * Ensures that the named attribute is not excluded from any change records 286 * that are read from LDIF. By default all attributes are included unless 287 * explicitly excluded. 288 * 289 * @param attributeDescription 290 * The name of the attribute to be included. 291 * @return A reference to this {@code LDIFChangeRecordReader}. 292 */ 293 public LDIFChangeRecordReader setIncludeAttribute( 294 final AttributeDescription attributeDescription) { 295 Reject.ifNull(attributeDescription); 296 includeAttributes.add(attributeDescription); 297 return this; 298 } 299 300 /** 301 * Ensures that all change records which target entries beneath the named 302 * entry (inclusive) are read from LDIF. By default all change records are 303 * read unless explicitly excluded or included. 304 * 305 * @param includeBranch 306 * The distinguished name of the branch to be included. 307 * @return A reference to this {@code LDIFChangeRecordReader}. 308 */ 309 public LDIFChangeRecordReader setIncludeBranch(final DN includeBranch) { 310 Reject.ifNull(includeBranch); 311 includeBranches.add(includeBranch); 312 return this; 313 } 314 315 /** 316 * Sets the rejected record listener which should be notified whenever an 317 * LDIF record is skipped, malformed, or fails schema validation. 318 * <p> 319 * By default the {@link RejectedLDIFListener#FAIL_FAST} listener is used. 320 * 321 * @param listener 322 * The rejected record listener. 323 * @return A reference to this {@code LDIFChangeRecordReader}. 324 */ 325 public LDIFChangeRecordReader setRejectedLDIFListener(final RejectedLDIFListener listener) { 326 this.rejectedRecordListener = listener; 327 return this; 328 } 329 330 /** 331 * Sets the schema which should be used for decoding change records that are 332 * read from LDIF. The default schema is used if no other is specified. 333 * 334 * @param schema 335 * The schema which should be used for decoding change records 336 * that are read from LDIF. 337 * @return A reference to this {@code LDIFChangeRecordReader}. 338 */ 339 public LDIFChangeRecordReader setSchema(final Schema schema) { 340 Reject.ifNull(schema); 341 this.schema = schemaValidationPolicy.adaptSchemaForValidation(schema); 342 return this; 343 } 344 345 /** 346 * Specifies the schema validation which should be used when reading LDIF 347 * change records. If attribute value validation is enabled then all checks 348 * will be performed. 349 * <p> 350 * Schema validation is disabled by default. 351 * <p> 352 * <b>NOTE:</b> this method copies the provided policy so changes made to it 353 * after this method has been called will have no effect. 354 * 355 * @param policy 356 * The schema validation which should be used when reading LDIF 357 * change records. 358 * @return A reference to this {@code LDIFChangeRecordReader}. 359 */ 360 public LDIFChangeRecordReader setSchemaValidationPolicy(final SchemaValidationPolicy policy) { 361 this.schemaValidationPolicy = SchemaValidationPolicy.copyOf(policy); 362 this.schema = schemaValidationPolicy.adaptSchemaForValidation(schema); 363 return this; 364 } 365 366 private ChangeRecord getNextChangeRecord() throws DecodeException, IOException { 367 while (nextChangeRecord == null) { 368 // Read the set of lines that make up the next entry. 369 final LDIFRecord record = readLDIFRecord(); 370 if (record == null) { 371 nextChangeRecord = EOF; 372 break; 373 } 374 375 try { 376 /* Read the DN of the entry and see if it is one that should be included in the import. */ 377 final DN entryDN = readLDIFRecordDN(record); 378 if (entryDN == null) { 379 // Skip version record. 380 continue; 381 } 382 383 // Skip if branch containing the entry DN is excluded. 384 if (isBranchExcluded(entryDN)) { 385 final LocalizableMessage message = 386 ERR_LDIF_CHANGE_EXCLUDED_BY_DN.get(record.lineNumber, entryDN); 387 handleSkippedRecord(record, message); 388 continue; 389 } 390 391 KeyValuePair pair; 392 String ldifLine; 393 List<Control> controls = null; 394 while (true) { 395 if (!record.iterator.hasNext()) { 396 throw DecodeException.error( 397 ERR_LDIF_NO_CHANGE_TYPE.get(record.lineNumber, entryDN)); 398 } 399 400 pair = new KeyValuePair(); 401 ldifLine = readLDIFRecordKeyValuePair(record, pair, false); 402 if (pair.key == null) { 403 throw DecodeException.error( 404 ERR_LDIF_MALFORMED_CHANGE_TYPE.get(record.lineNumber, entryDN, ldifLine)); 405 } 406 407 if (!"control".equals(toLowerCase(pair.key))) { 408 break; 409 } 410 411 if (controls == null) { 412 controls = new LinkedList<>(); 413 } 414 415 controls.add(parseControl(entryDN, record, ldifLine, pair.value)); 416 } 417 418 if (!"changetype".equals(toLowerCase(pair.key))) { 419 // Default to add change record. 420 nextChangeRecord = parseAddChangeRecordEntry(entryDN, ldifLine, record); 421 } else { 422 final String changeType = toLowerCase(pair.value); 423 if ("add".equals(changeType)) { 424 nextChangeRecord = parseAddChangeRecordEntry(entryDN, null, record); 425 } else if ("delete".equals(changeType)) { 426 nextChangeRecord = parseDeleteChangeRecordEntry(entryDN, record); 427 } else if ("modify".equals(changeType)) { 428 nextChangeRecord = parseModifyChangeRecordEntry(entryDN, record); 429 } else if ("modrdn".equals(changeType)) { 430 nextChangeRecord = parseModifyDNChangeRecordEntry(entryDN, record); 431 } else if ("moddn".equals(changeType)) { 432 nextChangeRecord = parseModifyDNChangeRecordEntry(entryDN, record); 433 } else { 434 throw DecodeException.error( 435 ERR_LDIF_BAD_CHANGE_TYPE.get(record.lineNumber, entryDN, pair.value)); 436 } 437 438 // Add the controls to the record. 439 if (controls != null) { 440 for (final Control control : controls) { 441 nextChangeRecord.addControl(control); 442 } 443 } 444 } 445 } catch (final DecodeException e) { 446 handleMalformedRecord(record, e.getMessageObject()); 447 continue; 448 } 449 } 450 return nextChangeRecord; 451 } 452 453 private ChangeRecord parseAddChangeRecordEntry(final DN entryDN, final String lastLDIFLine, 454 final LDIFRecord record) throws DecodeException { 455 // Use an Entry for the AttributeSequence. 456 final Entry entry = new LinkedHashMapEntry(entryDN); 457 boolean schemaValidationFailure = false; 458 final List<LocalizableMessage> schemaErrors = new LinkedList<>(); 459 460 if (lastLDIFLine != null 461 // This line was read when looking for the change type. 462 && !readLDIFRecordAttributeValue(record, lastLDIFLine, entry, schemaErrors)) { 463 schemaValidationFailure = true; 464 } 465 466 while (record.iterator.hasNext()) { 467 final String ldifLine = record.iterator.next(); 468 if (!readLDIFRecordAttributeValue(record, ldifLine, entry, schemaErrors)) { 469 schemaValidationFailure = true; 470 } 471 } 472 473 if (!schema.validateEntry(entry, schemaValidationPolicy, schemaErrors)) { 474 schemaValidationFailure = true; 475 } 476 477 if (schemaValidationFailure) { 478 handleSchemaValidationFailure(record, schemaErrors); 479 return null; 480 } 481 482 if (!schemaErrors.isEmpty()) { 483 handleSchemaValidationWarning(record, schemaErrors); 484 } 485 return Requests.newAddRequest(entry); 486 } 487 488 private Control parseControl(final DN entryDN, final LDIFRecord record, final String ldifLine, 489 final String value) throws DecodeException { 490 final Matcher matcher = CONTROL_REGEX.matcher(value); 491 if (!matcher.matches()) { 492 throw DecodeException.error(ERR_LDIF_MALFORMED_CONTROL.get(record.lineNumber, entryDN, ldifLine)); 493 } 494 final String oid = matcher.group(1); 495 final boolean isCritical = matcher.group(5) != null; 496 final String controlValueString = matcher.group(7); 497 ByteString controlValue = null; 498 if (controlValueString != null) { 499 controlValue = 500 parseSingleValue(record, ldifLine, entryDN, ldifLine.indexOf(':', 8), oid); 501 } 502 return GenericControl.newControl(oid, isCritical, controlValue); 503 } 504 505 private ChangeRecord parseDeleteChangeRecordEntry(final DN entryDN, final LDIFRecord record) 506 throws DecodeException { 507 if (record.iterator.hasNext()) { 508 throw DecodeException.error(ERR_LDIF_MALFORMED_DELETE.get(record.lineNumber, entryDN)); 509 } 510 return Requests.newDeleteRequest(entryDN); 511 } 512 513 private ChangeRecord parseModifyChangeRecordEntry(final DN entryDN, final LDIFRecord record) 514 throws DecodeException { 515 final ModifyRequest modifyRequest = Requests.newModifyRequest(entryDN); 516 final KeyValuePair pair = new KeyValuePair(); 517 final List<ByteString> attributeValues = new ArrayList<>(); 518 boolean schemaValidationFailure = false; 519 final List<LocalizableMessage> schemaErrors = new LinkedList<>(); 520 521 while (record.iterator.hasNext()) { 522 String ldifLine = readLDIFRecordKeyValuePair(record, pair, false); 523 if (pair.key == null) { 524 throw DecodeException.error( 525 ERR_LDIF_MALFORMED_MODIFICATION_TYPE.get(record.lineNumber, entryDN, ldifLine)); 526 } 527 528 final String changeType = toLowerCase(pair.key); 529 530 ModificationType modType; 531 if ("add".equals(changeType)) { 532 modType = ModificationType.ADD; 533 } else if ("delete".equals(changeType)) { 534 modType = ModificationType.DELETE; 535 } else if ("replace".equals(changeType)) { 536 modType = ModificationType.REPLACE; 537 } else if ("increment".equals(changeType)) { 538 modType = ModificationType.INCREMENT; 539 } else { 540 throw DecodeException.error( 541 ERR_LDIF_BAD_MODIFICATION_TYPE.get(record.lineNumber, entryDN, pair.key)); 542 } 543 544 AttributeDescription attributeDescription; 545 try { 546 attributeDescription = AttributeDescription.valueOf(pair.value, schema); 547 } catch (final UnknownSchemaElementException e) { 548 final LocalizableMessage message = 549 ERR_LDIF_UNKNOWN_ATTRIBUTE_TYPE.get(record.lineNumber, entryDN, pair.value); 550 switch (schemaValidationPolicy.checkAttributesAndObjectClasses()) { 551 case REJECT: 552 schemaValidationFailure = true; 553 schemaErrors.add(message); 554 continue; 555 case WARN: 556 schemaErrors.add(message); 557 continue; 558 default: // Ignore 559 /* This should not happen: we should be using a non-strict schema for this policy. */ 560 throw new IllegalStateException("Schema is not consistent with policy", e); 561 } 562 } catch (final LocalizedIllegalArgumentException e) { 563 throw DecodeException.error( 564 ERR_LDIF_MALFORMED_ATTRIBUTE_NAME.get(record.lineNumber, entryDN, pair.value)); 565 } 566 567 /* 568 * Skip the attribute if requested before performing any schema 569 * checking: the attribute may have been excluded because it is 570 * known to violate the schema. 571 */ 572 if (isAttributeExcluded(attributeDescription)) { 573 continue; 574 } 575 576 final Syntax syntax = attributeDescription.getAttributeType().getSyntax(); 577 578 // Ensure that the binary option is present if required. 579 if (!syntax.isBEREncodingRequired()) { 580 if (schemaValidationPolicy.checkAttributeValues().needsChecking() 581 && attributeDescription.hasOption("binary")) { 582 final LocalizableMessage message = 583 ERR_LDIF_UNEXPECTED_BINARY_OPTION.get(record.lineNumber, entryDN, pair.value); 584 if (schemaValidationPolicy.checkAttributeValues().isReject()) { 585 schemaValidationFailure = true; 586 } 587 schemaErrors.add(message); 588 continue; 589 } 590 } else { 591 attributeDescription = attributeDescription.withOption("binary"); 592 } 593 594 /* Now go through the rest of the attributes until the "-" line is reached. */ 595 attributeValues.clear(); 596 while (record.iterator.hasNext()) { 597 ldifLine = record.iterator.next(); 598 if ("-".equals(ldifLine)) { 599 break; 600 } 601 602 // Parse the attribute description. 603 final int colonPos = parseColonPosition(record, ldifLine); 604 final String attrDescr = ldifLine.substring(0, colonPos); 605 606 AttributeDescription attributeDescription2; 607 try { 608 attributeDescription2 = AttributeDescription.valueOf(attrDescr, schema); 609 } catch (final LocalizedIllegalArgumentException e) { 610 /* 611 * No need to catch schema exception here because it implies 612 * that the attribute name is wrong and the record is 613 * malformed. 614 */ 615 throw DecodeException.error( 616 ERR_LDIF_MALFORMED_ATTRIBUTE_NAME.get(record.lineNumber, entryDN, attrDescr)); 617 } 618 619 // Ensure that the binary option is present if required. 620 if (attributeDescription.getAttributeType().getSyntax().isBEREncodingRequired()) { 621 attributeDescription2 = attributeDescription2.withOption("binary"); 622 } 623 624 if (!attributeDescription2.equals(attributeDescription)) { 625 // Malformed record. 626 throw DecodeException.error(ERR_LDIF_ATTRIBUTE_NAME_MISMATCH.get( 627 record.lineNumber, entryDN, attributeDescription2, attributeDescription)); 628 } 629 630 // Parse the attribute value and check it if needed. 631 final ByteString value = 632 parseSingleValue(record, ldifLine, entryDN, colonPos, attrDescr); 633 if (schemaValidationPolicy.checkAttributeValues().needsChecking()) { 634 final LocalizableMessageBuilder builder = new LocalizableMessageBuilder(); 635 if (!syntax.valueIsAcceptable(value, builder)) { 636 /* 637 * Just log a message, but don't skip the value since 638 * this could change the semantics of the modification 639 * (e.g. if all values in a delete are skipped then this 640 * implies that the whole attribute should be removed). 641 */ 642 if (schemaValidationPolicy.checkAttributeValues().isReject()) { 643 schemaValidationFailure = true; 644 } 645 schemaErrors.add(builder.toMessage()); 646 } 647 } 648 attributeValues.add(value); 649 } 650 651 final Modification change = 652 new Modification(modType, new LinkedAttribute(attributeDescription, 653 attributeValues)); 654 modifyRequest.addModification(change); 655 } 656 657 if (schemaValidationFailure) { 658 handleSchemaValidationFailure(record, schemaErrors); 659 return null; 660 } 661 662 if (!schemaErrors.isEmpty()) { 663 handleSchemaValidationWarning(record, schemaErrors); 664 } 665 666 return modifyRequest; 667 } 668 669 private ChangeRecord parseModifyDNChangeRecordEntry(final DN entryDN, final LDIFRecord record) 670 throws DecodeException { 671 // Parse the newrdn. 672 if (!record.iterator.hasNext()) { 673 throw DecodeException.error(ERR_LDIF_NO_NEW_RDN.get(record.lineNumber, entryDN)); 674 } 675 676 final KeyValuePair pair = new KeyValuePair(); 677 String ldifLine = readLDIFRecordKeyValuePair(record, pair, true); 678 679 if (pair.key == null || !"newrdn".equals(toLowerCase(pair.key))) { 680 throw DecodeException.error( 681 ERR_LDIF_MALFORMED_NEW_RDN.get(record.lineNumber, entryDN, ldifLine)); 682 } 683 684 final ModifyDNRequest modifyDNRequest; 685 try { 686 final RDN newRDN = RDN.valueOf(pair.value, schema); 687 modifyDNRequest = Requests.newModifyDNRequest(entryDN, newRDN); 688 } catch (final LocalizedIllegalArgumentException e) { 689 throw DecodeException.error( 690 ERR_LDIF_MALFORMED_NEW_RDN.get(record.lineNumber, entryDN, pair.value)); 691 } 692 693 // Parse the deleteoldrdn. 694 if (!record.iterator.hasNext()) { 695 final LocalizableMessage message = 696 ERR_LDIF_NO_DELETE_OLD_RDN.get(record.lineNumber, entryDN.toString()); 697 throw DecodeException.error(message); 698 } 699 700 ldifLine = readLDIFRecordKeyValuePair(record, pair, true); 701 if (pair.key == null || !"deleteoldrdn".equals(toLowerCase(pair.key))) { 702 final LocalizableMessage message = 703 ERR_LDIF_MALFORMED_DELETE_OLD_RDN.get(record.lineNumber, entryDN.toString(), 704 ldifLine); 705 throw DecodeException.error(message); 706 } 707 708 final String delStr = toLowerCase(pair.value); 709 if ("false".equals(delStr) || "no".equals(delStr) || "0".equals(delStr)) { 710 modifyDNRequest.setDeleteOldRDN(false); 711 } else if ("true".equals(delStr) || "yes".equals(delStr) || "1".equals(delStr)) { 712 modifyDNRequest.setDeleteOldRDN(true); 713 } else { 714 final LocalizableMessage message = 715 ERR_LDIF_MALFORMED_DELETE_OLD_RDN.get(record.lineNumber, entryDN.toString(), 716 pair.value); 717 throw DecodeException.error(message); 718 } 719 720 // Parse the newsuperior if present. 721 if (record.iterator.hasNext()) { 722 ldifLine = readLDIFRecordKeyValuePair(record, pair, true); 723 if (pair.key == null || !"newsuperior".equals(toLowerCase(pair.key)) || "".equals(pair.value)) { 724 throw DecodeException.error( 725 ERR_LDIF_MALFORMED_NEW_SUPERIOR.get(record.lineNumber, entryDN, ldifLine)); 726 } 727 728 try { 729 final DN newSuperiorDN = DN.valueOf(pair.value, schema); 730 modifyDNRequest.setNewSuperior(newSuperiorDN.toString()); 731 } catch (final LocalizedIllegalArgumentException e) { 732 final LocalizableMessage message = 733 ERR_LDIF_MALFORMED_NEW_SUPERIOR.get(record.lineNumber, entryDN.toString(), 734 pair.value); 735 throw DecodeException.error(message); 736 } 737 } 738 739 return modifyDNRequest; 740 } 741}