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