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 */ 017package org.forgerock.opendj.ldif; 018 019import static com.forgerock.opendj.ldap.CoreMessages.*; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.Reader; 024import java.util.Arrays; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.NoSuchElementException; 028 029import org.forgerock.i18n.LocalizableMessage; 030import org.forgerock.i18n.LocalizedIllegalArgumentException; 031import org.forgerock.opendj.ldap.AttributeDescription; 032import org.forgerock.opendj.ldap.DN; 033import org.forgerock.opendj.ldap.DecodeException; 034import org.forgerock.opendj.ldap.Entry; 035import org.forgerock.opendj.ldap.LinkedHashMapEntry; 036import org.forgerock.opendj.ldap.Matcher; 037import org.forgerock.opendj.ldap.schema.Schema; 038import org.forgerock.opendj.ldap.schema.SchemaValidationPolicy; 039import org.forgerock.util.Reject; 040 041/** 042 * An LDIF entry reader reads attribute value records (entries) using the LDAP 043 * Data Interchange Format (LDIF) from a user defined source. 044 * 045 * @see <a href="http://tools.ietf.org/html/rfc2849">RFC 2849 - The LDAP Data 046 * Interchange Format (LDIF) - Technical Specification </a> 047 */ 048public final class LDIFEntryReader extends AbstractLDIFReader implements EntryReader { 049 /** Poison used to indicate end of LDIF. */ 050 private static final Entry EOF = new LinkedHashMapEntry(); 051 052 /** 053 * Parses the provided array of LDIF lines as a single LDIF entry. 054 * 055 * @param ldifLines 056 * The lines of LDIF to be parsed. 057 * @return The parsed LDIF entry. 058 * @throws LocalizedIllegalArgumentException 059 * If {@code ldifLines} did not contain an LDIF entry, if it 060 * contained multiple entries, if contained malformed LDIF, or 061 * if the entry could not be decoded using the default schema. 062 * @throws NullPointerException 063 * If {@code ldifLines} was {@code null}. 064 */ 065 public static Entry valueOfLDIFEntry(final String... ldifLines) { 066 try (final LDIFEntryReader reader = new LDIFEntryReader(ldifLines)) { 067 if (!reader.hasNext()) { 068 // No change record found. 069 final LocalizableMessage message = 070 WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND.get(); 071 throw new LocalizedIllegalArgumentException(message); 072 } 073 074 final Entry entry = reader.readEntry(); 075 076 if (reader.hasNext()) { 077 // Multiple change records found. 078 final LocalizableMessage message = 079 WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND.get(); 080 throw new LocalizedIllegalArgumentException(message); 081 } 082 083 return entry; 084 } catch (final DecodeException e) { 085 // Badly formed LDIF. 086 throw new LocalizedIllegalArgumentException(e.getMessageObject()); 087 } catch (final IOException e) { 088 // This should never happen for a String based reader. 089 final LocalizableMessage message = 090 WARN_READ_LDIF_RECORD_UNEXPECTED_IO_ERROR.get(e.getMessage()); 091 throw new LocalizedIllegalArgumentException(message); 092 } 093 } 094 095 private Entry nextEntry; 096 097 /** 098 * Creates a new LDIF entry reader whose source is the provided input 099 * stream. 100 * 101 * @param in 102 * The input stream to use. 103 * @throws NullPointerException 104 * If {@code in} was {@code null}. 105 */ 106 public LDIFEntryReader(final InputStream in) { 107 super(in); 108 } 109 110 /** 111 * Creates a new LDIF entry reader which will read lines of LDIF from the 112 * provided list of LDIF lines. 113 * 114 * @param ldifLines 115 * The lines of LDIF to be read. 116 * @throws NullPointerException 117 * If {@code ldifLines} was {@code null}. 118 */ 119 public LDIFEntryReader(final List<String> ldifLines) { 120 super(ldifLines); 121 } 122 123 /** 124 * Creates a new LDIF entry reader whose source is the provided character 125 * stream reader. 126 * 127 * @param reader 128 * The character stream reader to use. 129 * @throws NullPointerException 130 * If {@code reader} was {@code null}. 131 */ 132 public LDIFEntryReader(final Reader reader) { 133 super(reader); 134 } 135 136 /** 137 * Creates a new LDIF entry reader which will read lines of LDIF from the 138 * provided array of LDIF lines. 139 * 140 * @param ldifLines 141 * The lines of LDIF to be read. 142 * @throws NullPointerException 143 * If {@code ldifLines} was {@code null}. 144 */ 145 public LDIFEntryReader(final String... ldifLines) { 146 super(Arrays.asList(ldifLines)); 147 } 148 149 @Override 150 public void close() throws IOException { 151 close0(); 152 } 153 154 /** 155 * {@inheritDoc} 156 * 157 * @throws DecodeException 158 * If the entry could not be decoded because it was malformed. 159 */ 160 @Override 161 public boolean hasNext() throws DecodeException, IOException { 162 return getNextEntry() != EOF; 163 } 164 165 /** 166 * {@inheritDoc} 167 * 168 * @throws DecodeException 169 * If the entry could not be decoded because it was malformed. 170 */ 171 @Override 172 public Entry readEntry() throws DecodeException, IOException { 173 if (!hasNext()) { 174 // LDIF reader has completed successfully. 175 throw new NoSuchElementException(); 176 } 177 178 final Entry entry = nextEntry; 179 nextEntry = null; 180 return entry; 181 } 182 183 /** 184 * Specifies whether all operational attributes should be excluded 185 * from any entries that are read from LDIF. The default is {@code false}. 186 * 187 * @param excludeOperationalAttributes 188 * {@code true} if all operational attributes should be excluded, 189 * or {@code false} otherwise. 190 * @return A reference to this {@code LDIFEntryReader}. 191 */ 192 public LDIFEntryReader setExcludeAllOperationalAttributes( 193 final boolean excludeOperationalAttributes) { 194 this.excludeOperationalAttributes = excludeOperationalAttributes; 195 return this; 196 } 197 198 /** 199 * Specifies whether all user attributes should be excluded from any 200 * entries that are read from LDIF. The default is {@code false}. 201 * 202 * @param excludeUserAttributes 203 * {@code true} if all user attributes should be excluded, or 204 * {@code false} otherwise. 205 * @return A reference to this {@code LDIFEntryReader}. 206 */ 207 public LDIFEntryReader setExcludeAllUserAttributes(final boolean excludeUserAttributes) { 208 this.excludeUserAttributes = excludeUserAttributes; 209 return this; 210 } 211 212 /** 213 * Excludes the named attribute from any entries that are read from LDIF. By 214 * default all attributes are included unless explicitly excluded. 215 * 216 * @param attributeDescription 217 * The name of the attribute to be excluded. 218 * @return A reference to this {@code LDIFEntryReader}. 219 */ 220 public LDIFEntryReader setExcludeAttribute(final AttributeDescription attributeDescription) { 221 Reject.ifNull(attributeDescription); 222 excludeAttributes.add(attributeDescription); 223 return this; 224 } 225 226 /** 227 * Excludes all entries beneath the named entry (inclusive) from being read 228 * from LDIF. By default all entries are written unless explicitly excluded 229 * or included by branches or filters. 230 * 231 * @param excludeBranch 232 * The distinguished name of the branch to be excluded. 233 * @return A reference to this {@code LDIFEntryReader}. 234 */ 235 public LDIFEntryReader setExcludeBranch(final DN excludeBranch) { 236 Reject.ifNull(excludeBranch); 237 excludeBranches.add(excludeBranch); 238 return this; 239 } 240 241 /** 242 * Excludes all entries which match the provided filter matcher from being 243 * read from LDIF. By default all entries are read unless explicitly 244 * excluded or included by branches or filters. 245 * 246 * @param excludeFilter 247 * The filter matcher. 248 * @return A reference to this {@code LDIFEntryReader}. 249 */ 250 public LDIFEntryReader setExcludeFilter(final Matcher excludeFilter) { 251 Reject.ifNull(excludeFilter); 252 excludeFilters.add(excludeFilter); 253 return this; 254 } 255 256 /** 257 * Ensures that the named attribute is not excluded from any entries that 258 * are read from LDIF. By default all attributes are included unless 259 * explicitly excluded. 260 * 261 * @param attributeDescription 262 * The name of the attribute to be included. 263 * @return A reference to this {@code LDIFEntryReader}. 264 */ 265 public LDIFEntryReader setIncludeAttribute(final AttributeDescription attributeDescription) { 266 Reject.ifNull(attributeDescription); 267 includeAttributes.add(attributeDescription); 268 return this; 269 } 270 271 /** 272 * Ensures that all entries beneath the named entry (inclusive) are read 273 * from LDIF. By default all entries are written unless explicitly excluded 274 * or included by branches or filters. 275 * 276 * @param includeBranch 277 * The distinguished name of the branch to be included. 278 * @return A reference to this {@code LDIFEntryReader}. 279 */ 280 public LDIFEntryReader setIncludeBranch(final DN includeBranch) { 281 Reject.ifNull(includeBranch); 282 includeBranches.add(includeBranch); 283 return this; 284 } 285 286 /** 287 * Ensures that all entries which match the provided filter matcher are read 288 * from LDIF. By default all entries are read unless explicitly excluded or 289 * included by branches or filters. 290 * 291 * @param includeFilter 292 * The filter matcher. 293 * @return A reference to this {@code LDIFEntryReader}. 294 */ 295 public LDIFEntryReader setIncludeFilter(final Matcher includeFilter) { 296 Reject.ifNull(includeFilter); 297 includeFilters.add(includeFilter); 298 return this; 299 } 300 301 /** 302 * Sets the rejected record listener which should be notified whenever an 303 * LDIF record is skipped, malformed, or fails schema validation. 304 * <p> 305 * By default the {@link RejectedLDIFListener#FAIL_FAST} listener is used. 306 * 307 * @param listener 308 * The rejected record listener. 309 * @return A reference to this {@code LDIFEntryReader}. 310 */ 311 public LDIFEntryReader setRejectedLDIFListener(final RejectedLDIFListener listener) { 312 this.rejectedRecordListener = listener; 313 return this; 314 } 315 316 /** 317 * Sets the schema which should be used for decoding entries that are read 318 * from LDIF. The default schema is used if no other is specified. 319 * 320 * @param schema 321 * The schema which should be used for decoding entries that are 322 * read from LDIF. 323 * @return A reference to this {@code LDIFEntryReader}. 324 */ 325 public LDIFEntryReader setSchema(final Schema schema) { 326 Reject.ifNull(schema); 327 this.schema = schemaValidationPolicy.adaptSchemaForValidation(schema); 328 return this; 329 } 330 331 /** 332 * Specifies the schema validation which should be used when reading LDIF 333 * entry records. If attribute value validation is enabled then all checks 334 * will be performed. 335 * <p> 336 * Schema validation is disabled by default. 337 * <p> 338 * <b>NOTE:</b> this method copies the provided policy so changes made to it 339 * after this method has been called will have no effect. 340 * 341 * @param policy 342 * The schema validation which should be used when reading LDIF 343 * entry records. 344 * @return A reference to this {@code LDIFEntryReader}. 345 */ 346 public LDIFEntryReader setSchemaValidationPolicy(final SchemaValidationPolicy policy) { 347 this.schemaValidationPolicy = SchemaValidationPolicy.copyOf(policy); 348 this.schema = schemaValidationPolicy.adaptSchemaForValidation(schema); 349 return this; 350 } 351 352 private Entry getNextEntry() throws DecodeException, IOException { 353 while (nextEntry == null) { 354 // Read the set of lines that make up the next entry. 355 final LDIFRecord record = readLDIFRecord(); 356 if (record == null) { 357 nextEntry = EOF; 358 break; 359 } 360 361 try { 362 /* Read the DN of the entry and see if it is one that should be included in the import. */ 363 final DN entryDN = readLDIFRecordDN(record); 364 if (entryDN == null) { 365 // Skip version record. 366 continue; 367 } 368 369 // Skip if branch containing the entry DN is excluded. 370 if (isBranchExcluded(entryDN)) { 371 final LocalizableMessage message = 372 ERR_LDIF_ENTRY_EXCLUDED_BY_DN 373 .get(record.lineNumber, entryDN.toString()); 374 handleSkippedRecord(record, message); 375 continue; 376 } 377 378 // Use an Entry for the AttributeSequence. 379 final Entry entry = new LinkedHashMapEntry(entryDN); 380 boolean schemaValidationFailure = false; 381 final List<LocalizableMessage> schemaErrors = new LinkedList<>(); 382 while (record.iterator.hasNext()) { 383 final String ldifLine = record.iterator.next(); 384 if (!readLDIFRecordAttributeValue(record, ldifLine, entry, schemaErrors)) { 385 schemaValidationFailure = true; 386 } 387 } 388 389 // Skip if the entry is excluded by any filters. 390 if (isEntryExcluded(entry)) { 391 final LocalizableMessage message = 392 ERR_LDIF_ENTRY_EXCLUDED_BY_FILTER.get(record.lineNumber, entryDN 393 .toString()); 394 handleSkippedRecord(record, message); 395 continue; 396 } 397 398 if (!schema.validateEntry(entry, schemaValidationPolicy, schemaErrors)) { 399 schemaValidationFailure = true; 400 } 401 402 if (schemaValidationFailure) { 403 handleSchemaValidationFailure(record, schemaErrors); 404 continue; 405 } 406 407 if (!schemaErrors.isEmpty()) { 408 handleSchemaValidationWarning(record, schemaErrors); 409 } 410 411 nextEntry = entry; 412 } catch (final DecodeException e) { 413 handleMalformedRecord(record, e.getMessageObject()); 414 continue; 415 } 416 } 417 418 return nextEntry; 419 } 420}