/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2015 ForgeRock AS */ package org.opends.server.backends.pluggable; import static org.opends.messages.UtilityMessages.*; import static org.opends.server.util.StaticUtils.*; import java.io.IOException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.LocalizableMessageBuilder; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.util.Reject; import org.opends.server.api.plugin.PluginResult; import org.opends.server.types.AttributeBuilder; import org.opends.server.types.AttributeType; import org.opends.server.types.DN; import org.opends.server.types.Entry; import org.opends.server.types.LDIFImportConfig; import org.opends.server.types.ObjectClass; import org.opends.server.util.LDIFException; import org.opends.server.util.LDIFReader; /** This class specializes the LDIFReader for imports. */ final class ImportLDIFReader extends LDIFReader { private final ConcurrentHashMap<DN, CountDownLatch> pendingMap = new ConcurrentHashMap<>(); /** * A class holding the entry, its entryID as assigned by the LDIF reader and its suffix as * determined by the LDIF reader. */ static final class EntryInformation { private final Entry entry; private final EntryID entryID; private final EntryContainer entryContainer; private EntryInformation(Entry entry, EntryID entryID, EntryContainer entryContainer) { this.entry = entry; this.entryID = entryID; this.entryContainer = entryContainer; } Entry getEntry() { return entry; } EntryID getEntryID() { return entryID; } EntryContainer getEntryContainer() { return entryContainer; } } private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); private final RootContainer rootContainer; /** * Creates a new LDIF reader that will read information from the specified file. * * @param importConfig * The import configuration for this LDIF reader. It must not be <CODE>null</CODE>. * @param rootContainer * The root container needed to get the next entry ID. * @throws IOException * If a problem occurs while opening the LDIF file for reading. */ public ImportLDIFReader(LDIFImportConfig importConfig, RootContainer rootContainer) throws IOException { super(importConfig); Reject.ifNull(importConfig, rootContainer); this.rootContainer = rootContainer; } /** * Reads the next entry from the LDIF source. * * @return The next entry information read from the LDIF source, or <CODE>null</CODE> if the end of the LDIF * data is reached of if the import has been cancelled. * @param suffixesMap * A map of entry containers instances. * @throws IOException * If an I/O problem occurs while reading from the file. * @throws LDIFException * If the information read cannot be parsed as an LDIF entry. */ public final EntryInformation readEntry(Map<DN, EntryContainer> suffixesMap) throws IOException, LDIFException { final boolean checkSchema = importConfig.validateSchema(); while (true) { LinkedList<StringBuilder> lines; DN entryDN; EntryID entryID; final EntryContainer entryContainer; synchronized (this) { // Read the set of lines that make up the next entry. lines = readEntryLines(); if (lines == null) { return null; } lastEntryBodyLines = lines; lastEntryHeaderLines = new LinkedList<>(); // Read the DN of the entry and see if it is one that should be included // in the import. try { entryDN = readDN(lines); } catch (LDIFException e) { logger.traceException(e); continue; } if (entryDN == null) { // This should only happen if the LDIF starts with the "version:" line // and has a blank line immediately after that. In that case, simply // read and return the next entry. continue; } if (!importConfig.includeEntry(entryDN)) { logger.trace("Skipping entry %s because the DN is not one that " + "should be included based on the include and exclude branches.", entryDN); entriesRead.incrementAndGet(); logToSkipWriter(lines, ERR_LDIF_SKIP.get(entryDN)); continue; } entryContainer = getEntryContainer(entryDN, suffixesMap); if (entryContainer == null) { logger.trace("Skipping entry %s because the DN is not one that " + "should be included based on a suffix match check.", entryDN); entriesRead.incrementAndGet(); logToSkipWriter(lines, ERR_LDIF_SKIP.get(entryDN)); continue; } entriesRead.incrementAndGet(); entryID = rootContainer.getNextEntryID(); if (!addPending(entryDN)) { logger.trace("Skipping entry %s because the DN already exists.", entryDN); logToSkipWriter(lines, ERR_LDIF_SKIP.get(entryDN)); continue; } } // Create the entry and see if it is one that should be included in the import final Entry entry = createEntry(lines, entryDN, checkSchema); if (entry == null || !isIncludedInImport(entry, lines) || !invokeImportPlugins(entry, lines) || (checkSchema && !isValidAgainstSchema(entry, lines))) { removePending(entryDN); continue; } return new EntryInformation(entry, entryID, entryContainer); } } private Entry createEntry(List<StringBuilder> lines, DN entryDN, boolean checkSchema) { // Read the set of attributes from the entry. Map<ObjectClass, String> objectClasses = new HashMap<>(); Map<AttributeType, List<AttributeBuilder>> userAttrBuilders = new HashMap<>(); Map<AttributeType, List<AttributeBuilder>> operationalAttrBuilders = new HashMap<>(); try { for (StringBuilder line : lines) { readAttribute(lines, line, entryDN, objectClasses, userAttrBuilders, operationalAttrBuilders, checkSchema); } } catch (LDIFException e) { if (logger.isTraceEnabled()) { logger.trace("Skipping entry %s because reading" + "its attributes failed.", entryDN); } logToSkipWriter(lines, ERR_LDIF_READ_ATTR_SKIP.get(entryDN, e.getMessage())); return null; } final Entry entry = new Entry(entryDN, objectClasses, toAttributesMap(userAttrBuilders), toAttributesMap(operationalAttrBuilders)); logger.trace("readEntry(), created entry: %s", entry); return entry; } private boolean isIncludedInImport(Entry entry, LinkedList<StringBuilder> entryLines) { final DN entryDN = entry.getName(); try { if (!importConfig.includeEntry(entry)) { logger.trace("Skipping entry %s because the DN is not one that " + "should be included based on the include and exclude filters.", entryDN); logToSkipWriter(entryLines, ERR_LDIF_SKIP.get(entryDN)); return false; } return true; } catch (Exception e) { logToSkipWriter(entryLines, ERR_LDIF_COULD_NOT_EVALUATE_FILTERS_FOR_IMPORT.get(entryDN, lastEntryLineNumber, e)); return false; } } private boolean invokeImportPlugins(final Entry entry, LinkedList<StringBuilder> lines) { if (importConfig.invokeImportPlugins()) { PluginResult.ImportLDIF pluginResult = pluginConfigManager.invokeLDIFImportPlugins(importConfig, entry); if (!pluginResult.continueProcessing()) { final DN entryDN = entry.getName(); LocalizableMessage m; LocalizableMessage rejectMessage = pluginResult.getErrorMessage(); if (rejectMessage != null) { m = ERR_LDIF_REJECTED_BY_PLUGIN.get(entryDN, rejectMessage); } else { m = ERR_LDIF_REJECTED_BY_PLUGIN_NOMESSAGE.get(entryDN); } logToRejectWriter(lines, m); return false; } } return true; } private boolean isValidAgainstSchema(Entry entry, LinkedList<StringBuilder> lines) { final DN entryDN = entry.getName(); addRDNAttributesIfNecessary(entryDN, entry.getUserAttributes(), entry.getOperationalAttributes()); // Add any superior objectclass(s) missing in the objectclass map. addSuperiorObjectClasses(entry.getObjectClasses()); LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder(); if (!entry.conformsToSchema(null, false, true, false, invalidReason)) { LocalizableMessage message = ERR_LDIF_SCHEMA_VIOLATION.get(entryDN, lastEntryLineNumber, invalidReason); logToRejectWriter(lines, message); return false; } return true; } /** * Return the suffix instance in the specified map that matches the specified DN. * * @param dn * The DN to search for. * @param map * The map to search. * @return The entry container instance that matches the DN, or null if no match is found. */ private EntryContainer getEntryContainer(DN dn, Map<DN, EntryContainer> map) { DN nodeDN = dn; while (nodeDN != null) { final EntryContainer entryContainer = map.get(nodeDN); if (entryContainer != null) { return entryContainer; } nodeDN = nodeDN.getParentDNInSuffix(); } return null; } /** * Make sure the specified parent DN is not in the pending map. * * @param parentDN The DN of the parent. */ void waitIfPending(DN parentDN) throws InterruptedException { final CountDownLatch l = pendingMap.get(parentDN); if (l != null) { l.await(); } } /** * Add specified DN to the pending map. * * @param dn The DN to add to the map. * @return true if the DN was added, false if the DN is already present. */ private boolean addPending(DN dn) { return pendingMap.putIfAbsent(dn, new CountDownLatch(1)) == null; } /** * Remove the specified DN from the pending map, it may not exist if the * entries are being migrated so just return. * * @param dn The DN to remove from the map. */ void removePending(DN dn) { CountDownLatch l = pendingMap.remove(dn); if(l != null) { l.countDown(); } } }