/* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2006-2008 Sun Microsystems, Inc. * Portions Copyright 2012-2013 ForgeRock AS */ package org.opends.server.tools; import org.opends.messages.Message; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.opends.server.core.DirectoryServer; import org.opends.server.extensions.ConfigFileHandler; import org.opends.server.protocols.ldap.LDAPResultCode; import org.opends.server.types.Attribute; import org.opends.server.types.AttributeType; import org.opends.server.types.AttributeValue; import org.opends.server.types.DirectoryException; import org.opends.server.types.DN; import org.opends.server.types.Entry; import org.opends.server.types.ExistingFileBehavior; import org.opends.server.types.InitializationException; import org.opends.server.types.LDAPException; import org.opends.server.types.LDIFExportConfig; import org.opends.server.types.LDIFImportConfig; import org.opends.server.types.Modification; import org.opends.server.types.NullOutputStream; import org.opends.server.types.ObjectClass; import org.opends.server.types.RawModification; import org.opends.server.util.AddChangeRecordEntry; import org.opends.server.util.BuildVersion; import org.opends.server.util.ChangeRecordEntry; import org.opends.server.util.DeleteChangeRecordEntry; import org.opends.server.util.LDIFException; import org.opends.server.util.LDIFReader; import org.opends.server.util.LDIFWriter; import org.opends.server.util.ModifyChangeRecordEntry; import org.opends.server.util.args.ArgumentException; import org.opends.server.util.args.ArgumentParser; import org.opends.server.util.args.BooleanArgument; import org.opends.server.util.args.StringArgument; import static org.opends.messages.ToolMessages.*; import static org.opends.server.util.ServerConstants.MAX_LINE_WIDTH; import static org.opends.server.util.StaticUtils.*; import static org.opends.server.tools.ToolConstants.*; /** * This class provides a program that may be used to apply a set of changes (in * LDIF change format) to an LDIF file. It will first read all of the changes * into memory, and then will iterate through an LDIF file and apply them to the * entries contained in it. Note that because of the manner in which it * processes the changes, certain types of operations will not be allowed, * including: * <BR> * <UL> * <LI>Modify DN operations</LI> * <LI>Deleting an entry that has been added</LI> * <LI>Modifying an entry that has been added</LI> * </UL> */ public class LDIFModify { /** * The fully-qualified name of this class. */ private static final String CLASS_NAME = "org.opends.server.tools.LDIFModify"; /** * Applies the specified changes to the source LDIF, writing the modified * file to the specified target. Neither the readers nor the writer will be * closed. * * @param sourceReader The LDIF reader that will be used to read the LDIF * content to be modified. * @param changeReader The LDIF reader that will be used to read the changes * to be applied. * @param targetWriter The LDIF writer that will be used to write the * modified LDIF. * @param errorList A list into which any error messages generated while * processing changes may be added. * * @return <CODE>true</CODE> if all updates were successfully applied, or * <CODE>false</CODE> if any errors were encountered. * * @throws IOException If a problem occurs while attempting to read the * source or changes, or write the target. * * @throws LDIFException If a problem occurs while attempting to decode the * source or changes, or trying to determine whether * to include the entry in the output. */ public static boolean modifyLDIF(LDIFReader sourceReader, LDIFReader changeReader, LDIFWriter targetWriter, List<Message> errorList) throws IOException, LDIFException { // Read the changes into memory. TreeMap<DN,AddChangeRecordEntry> adds = new TreeMap<DN,AddChangeRecordEntry>(); TreeMap<DN,Entry> ldifEntries = new TreeMap<DN,Entry>(); HashMap<DN,DeleteChangeRecordEntry> deletes = new HashMap<DN,DeleteChangeRecordEntry>(); HashMap<DN,LinkedList<Modification>> modifications = new HashMap<DN,LinkedList<Modification>>(); while (true) { ChangeRecordEntry changeRecord; try { changeRecord = changeReader.readChangeRecord(false); } catch (LDIFException le) { if (le.canContinueReading()) { errorList.add(le.getMessageObject()); continue; } else { throw le; } } if (changeRecord == null) { break; } DN changeDN = changeRecord.getDN(); switch (changeRecord.getChangeOperationType()) { case ADD: // The entry must not exist in the add list. if (adds.containsKey(changeDN)) { errorList.add(ERR_LDIFMODIFY_CANNOT_ADD_ENTRY_TWICE.get( String.valueOf(changeDN))); continue; } else { adds.put(changeDN, (AddChangeRecordEntry) changeRecord); } break; case DELETE: // The entry must not exist in the add list. If it exists in the // modify list, then remove the changes since we won't need to apply // them. if (adds.containsKey(changeDN)) { errorList.add(ERR_LDIFMODIFY_CANNOT_DELETE_AFTER_ADD.get( String.valueOf(changeDN))); continue; } else { modifications.remove(changeDN); deletes.put(changeDN, (DeleteChangeRecordEntry) changeRecord); } break; case MODIFY: // The entry must not exist in the add or delete lists. if (adds.containsKey(changeDN) || deletes.containsKey(changeDN)) { errorList.add(ERR_LDIFMODIFY_CANNOT_MODIFY_ADDED_OR_DELETED.get( String.valueOf(changeDN))); continue; } else { LinkedList<Modification> mods = modifications.get(changeDN); if (mods == null) { mods = new LinkedList<Modification>(); modifications.put(changeDN, mods); } for (RawModification mod : ((ModifyChangeRecordEntry) changeRecord).getModifications()) { try { mods.add(mod.toModification()); } catch (LDAPException le) { errorList.add(le.getMessageObject()); continue; } } } break; case MODIFY_DN: errorList.add(ERR_LDIFMODIFY_MODDN_NOT_SUPPORTED.get( String.valueOf(changeDN))); continue; default: errorList.add(ERR_LDIFMODIFY_UNKNOWN_CHANGETYPE.get( String.valueOf(changeDN), String.valueOf(changeRecord.getChangeOperationType()))); continue; } } // Read the source an entry at a time and apply any appropriate changes // before writing to the target LDIF. while (true) { Entry entry; try { entry = sourceReader.readEntry(); } catch (LDIFException le) { if (le.canContinueReading()) { errorList.add(le.getMessageObject()); continue; } else { throw le; } } if (entry == null) { break; } // If the entry is to be deleted, then just skip over it without writing // it to the output. DN entryDN = entry.getDN(); if (deletes.remove(entryDN) != null) { continue; } // If the entry is to be added, then that's an error, since it already // exists. if (adds.remove(entryDN) != null) { errorList.add(ERR_LDIFMODIFY_ADD_ALREADY_EXISTS.get( String.valueOf(entryDN))); continue; } // If the entry is to be modified, then process the changes. LinkedList<Modification> mods = modifications.remove(entryDN); if ((mods != null) && (! mods.isEmpty())) { try { entry.applyModifications(mods); } catch (DirectoryException de) { errorList.add(de.getMessageObject()); continue; } } // If we've gotten here, then the (possibly updated) entry should be // written to the LDIF entry Map. ldifEntries.put(entry.getDN(),entry); } // Perform any adds that may be necessary. for (AddChangeRecordEntry add : adds.values()) { Map<ObjectClass,String> objectClasses = new LinkedHashMap<ObjectClass,String>(); Map<AttributeType,List<Attribute>> userAttributes = new LinkedHashMap<AttributeType,List<Attribute>>(); Map<AttributeType,List<Attribute>> operationalAttributes = new LinkedHashMap<AttributeType,List<Attribute>>(); for (Attribute a : add.getAttributes()) { AttributeType t = a.getAttributeType(); if (t.isObjectClassType()) { for (AttributeValue v : a) { String stringValue = v.getValue().toString(); String lowerValue = toLowerCase(stringValue); ObjectClass oc = DirectoryServer.getObjectClass(lowerValue, true); objectClasses.put(oc, stringValue); } } else if (t.isOperational()) { List<Attribute> attrList = operationalAttributes.get(t); if (attrList == null) { attrList = new LinkedList<Attribute>(); operationalAttributes.put(t, attrList); } attrList.add(a); } else { List<Attribute> attrList = userAttributes.get(t); if (attrList == null) { attrList = new LinkedList<Attribute>(); userAttributes.put(t, attrList); } attrList.add(a); } } Entry e = new Entry(add.getDN(), objectClasses, userAttributes, operationalAttributes); //Put the entry to be added into the LDIF entry map. ldifEntries.put(e.getDN(),e); } // If there are any entries left in the delete or modify lists, then that's // a problem because they didn't exist. if (! deletes.isEmpty()) { for (DN dn : deletes.keySet()) { errorList.add( ERR_LDIFMODIFY_DELETE_NO_SUCH_ENTRY.get(String.valueOf(dn))); } } if (! modifications.isEmpty()) { for (DN dn : modifications.keySet()) { errorList.add(ERR_LDIFMODIFY_MODIFY_NO_SUCH_ENTRY.get( String.valueOf(dn))); } } return targetWriter.writeEntries(ldifEntries.values()) && errorList.isEmpty(); } /** * Invokes <CODE>ldifModifyMain</CODE> to perform the appropriate processing. * * @param args The command-line arguments provided to the client. */ public static void main(String[] args) { int returnCode = ldifModifyMain(args, false, System.out, System.err); if (returnCode != 0) { System.exit(filterExitCode(returnCode)); } } /** * Processes the command-line arguments and makes the appropriate updates to * the LDIF file. * * @param args The command line arguments provided to this * program. * @param serverInitialized Indicates whether the Directory Server has * already been initialized (and therefore should * not be initialized a second time). * @param outStream The output stream to use for standard output, or * {@code null} if standard output is not needed. * @param errStream The output stream to use for standard error, or * {@code null} if standard error is not needed. * * @return A value of zero if everything completed properly, or nonzero if * any problem(s) occurred. */ public static int ldifModifyMain(String[] args, boolean serverInitialized, OutputStream outStream, OutputStream errStream) { PrintStream err; if (errStream == null) { err = NullOutputStream.printStream(); } else { err = new PrintStream(errStream); } // Prepare the argument parser. BooleanArgument showUsage; StringArgument changesFile; StringArgument configClass; StringArgument configFile; StringArgument sourceFile; StringArgument targetFile; Message toolDescription = INFO_LDIFMODIFY_TOOL_DESCRIPTION.get(); ArgumentParser argParser = new ArgumentParser(CLASS_NAME, toolDescription, false); try { configFile = new StringArgument("configfile", 'c', "configFile", true, false, true, INFO_CONFIGFILE_PLACEHOLDER.get(), null, null, INFO_DESCRIPTION_CONFIG_FILE.get()); configFile.setHidden(true); argParser.addArgument(configFile); configClass = new StringArgument("configclass", OPTION_SHORT_CONFIG_CLASS, OPTION_LONG_CONFIG_CLASS, false, false, true, INFO_CONFIGCLASS_PLACEHOLDER.get(), ConfigFileHandler.class.getName(), null, INFO_DESCRIPTION_CONFIG_CLASS.get()); configClass.setHidden(true); argParser.addArgument(configClass); sourceFile = new StringArgument("sourceldif", 's', "sourceLDIF", true, false, true, INFO_LDIFFILE_PLACEHOLDER.get(), null, null, INFO_LDIFMODIFY_DESCRIPTION_SOURCE.get()); argParser.addArgument(sourceFile); changesFile = new StringArgument("changesldif", 'm', "changesLDIF", true, false, true, INFO_LDIFFILE_PLACEHOLDER.get(), null, null, INFO_LDIFMODIFY_DESCRIPTION_CHANGES.get()); argParser.addArgument(changesFile); targetFile = new StringArgument("targetldif", 't', "targetLDIF", true, false, true, INFO_LDIFFILE_PLACEHOLDER.get(), null, null, INFO_LDIFMODIFY_DESCRIPTION_TARGET.get()); argParser.addArgument(targetFile); showUsage = new BooleanArgument("help", OPTION_SHORT_HELP, OPTION_LONG_HELP, INFO_LDIFMODIFY_DESCRIPTION_HELP.get()); argParser.addArgument(showUsage); argParser.setUsageArgument(showUsage); } catch (ArgumentException ae) { Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()); err.println(message); return 1; } // Parse the command-line arguments provided to the program. try { argParser.parseArguments(args); } catch (ArgumentException ae) { Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage()); err.println(message); err.println(argParser.getUsage()); return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR; } // If we should just display usage or version information, // then print it and exit. if (argParser.usageOrVersionDisplayed()) { return 0; } // Checks the version - if upgrade required, the tool is unusable try { BuildVersion.checkVersionMismatch(); } catch (InitializationException e) { err.println(wrapText(e.getMessage(), MAX_LINE_WIDTH)); return 1; } if (! serverInitialized) { // Bootstrap the Directory Server configuration for use as a client. DirectoryServer directoryServer = DirectoryServer.getInstance(); DirectoryServer.bootstrapClient(); // If we're to use the configuration then initialize it, along with the // schema. boolean checkSchema = configFile.isPresent(); if (checkSchema) { try { DirectoryServer.initializeJMX(); } catch (Exception e) { Message message = ERR_LDIFMODIFY_CANNOT_INITIALIZE_JMX.get( String.valueOf(configFile.getValue()), e.getMessage()); err.println(message); return 1; } try { directoryServer.initializeConfiguration(configClass.getValue(), configFile.getValue()); } catch (Exception e) { Message message = ERR_LDIFMODIFY_CANNOT_INITIALIZE_CONFIG.get( String.valueOf(configFile.getValue()), e.getMessage()); err.println(message); return 1; } try { directoryServer.initializeSchema(); } catch (Exception e) { Message message = ERR_LDIFMODIFY_CANNOT_INITIALIZE_SCHEMA.get( String.valueOf(configFile.getValue()), e.getMessage()); err.println(message); return 1; } } } // Create the LDIF readers and writer from the arguments. File source = new File(sourceFile.getValue()); if (! source.exists()) { Message message = ERR_LDIFMODIFY_SOURCE_DOES_NOT_EXIST.get( sourceFile.getValue()); err.println(message); return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR; } LDIFImportConfig importConfig = new LDIFImportConfig(sourceFile.getValue()); LDIFReader sourceReader; try { sourceReader = new LDIFReader(importConfig); } catch (IOException ioe) { Message message = ERR_LDIFMODIFY_CANNOT_OPEN_SOURCE.get( sourceFile.getValue(), String.valueOf(ioe)); err.println(message); return LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR; } File changes = new File(changesFile.getValue()); if (! changes.exists()) { Message message = ERR_LDIFMODIFY_CHANGES_DOES_NOT_EXIST.get( changesFile.getValue()); err.println(message); return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR; } importConfig = new LDIFImportConfig(changesFile.getValue()); LDIFReader changeReader; try { changeReader = new LDIFReader(importConfig); } catch (IOException ioe) { Message message = ERR_LDIFMODIFY_CANNOT_OPEN_CHANGES.get( sourceFile.getValue(), ioe.getMessage()); err.println(message); return LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR; } LDIFExportConfig exportConfig = new LDIFExportConfig(targetFile.getValue(), ExistingFileBehavior.OVERWRITE); LDIFWriter targetWriter; try { targetWriter = new LDIFWriter(exportConfig); } catch (IOException ioe) { Message message = ERR_LDIFMODIFY_CANNOT_OPEN_TARGET.get( sourceFile.getValue(), ioe.getMessage()); err.println(message); return LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR; } // Actually invoke the LDIF procesing. LinkedList<Message> errorList = new LinkedList<Message>(); boolean successful; try { successful = modifyLDIF(sourceReader, changeReader, targetWriter, errorList); } catch (Exception e) { Message message = ERR_LDIFMODIFY_ERROR_PROCESSING_LDIF.get( String.valueOf(e)); err.println(message); successful = false; } try { sourceReader.close(); } catch (Exception e) {} try { changeReader.close(); } catch (Exception e) {} try { targetWriter.close(); } catch (Exception e) {} for (Message s : errorList) { err.println(s); } return (successful ? 0 : 1); } }