/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2015-2016 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.opendj.ldap.DN; import org.forgerock.opendj.ldap.schema.AttributeType; import org.forgerock.opendj.ldap.schema.ObjectClass; import org.forgerock.util.Pair; import org.forgerock.util.Reject; import org.opends.server.api.plugin.PluginResult; import org.opends.server.core.DirectoryServer; import org.opends.server.types.AttributeBuilder; import org.opends.server.types.Entry; import org.opends.server.types.LDIFImportConfig; 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 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 null. * @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 null 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 suffixesMap) throws IOException, LDIFException { final boolean checkSchema = importConfig.validateSchema(); while (true) { LinkedList 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; } entriesRead.incrementAndGet(); final Pair includeResult = importConfig.includeEntry(entryDN); if (!includeResult.getFirst()) { logToSkipWriter(lines, includeResult.getSecond()); 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); logToSkipWriter(lines, ERR_LDIF_SKIP.get(entryDN)); continue; } 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 lines, DN entryDN, boolean checkSchema) { // Read the set of attributes from the entry. Map objectClasses = new HashMap<>(); Map> userAttrBuilders = new HashMap<>(lines.size()); Map> operationalAttrBuilders = new HashMap<>(lines.size()); 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 entryLines) { final DN entryDN = entry.getName(); try { final Pair includeResult = importConfig.includeEntry(entry); if (!includeResult.getFirst()) { logToSkipWriter(entryLines, includeResult.getSecond()); 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 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 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 map) { DN nodeDN = dn; while (nodeDN != null) { final EntryContainer entryContainer = map.get(nodeDN); if (entryContainer != null) { return entryContainer; } nodeDN = DirectoryServer.getParentDNInSuffix(nodeDN); } 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(); } } }