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
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.tools;
028 import org.opends.messages.Message;
029
030
031
032 import java.io.File;
033 import java.io.IOException;
034 import java.io.OutputStream;
035 import java.io.PrintStream;
036 import java.util.HashMap;
037 import java.util.LinkedHashMap;
038 import java.util.LinkedList;
039 import java.util.List;
040 import java.util.Map;
041 import java.util.TreeMap;
042
043 import org.opends.server.core.DirectoryServer;
044 import org.opends.server.extensions.ConfigFileHandler;
045 import org.opends.server.protocols.ldap.LDAPResultCode;
046 import org.opends.server.types.Attribute;
047 import org.opends.server.types.AttributeType;
048 import org.opends.server.types.AttributeValue;
049 import org.opends.server.types.DirectoryException;
050 import org.opends.server.types.DN;
051 import org.opends.server.types.Entry;
052 import org.opends.server.types.ExistingFileBehavior;
053 import org.opends.server.types.LDAPException;
054 import org.opends.server.types.LDIFExportConfig;
055 import org.opends.server.types.LDIFImportConfig;
056 import org.opends.server.types.Modification;
057 import org.opends.server.types.NullOutputStream;
058 import org.opends.server.types.ObjectClass;
059 import org.opends.server.types.RawModification;
060 import org.opends.server.util.AddChangeRecordEntry;
061 import org.opends.server.util.ChangeRecordEntry;
062 import org.opends.server.util.DeleteChangeRecordEntry;
063 import org.opends.server.util.LDIFException;
064 import org.opends.server.util.LDIFReader;
065 import org.opends.server.util.LDIFWriter;
066 import org.opends.server.util.ModifyChangeRecordEntry;
067 import org.opends.server.util.args.ArgumentException;
068 import org.opends.server.util.args.ArgumentParser;
069 import org.opends.server.util.args.BooleanArgument;
070 import org.opends.server.util.args.StringArgument;
071
072 import static org.opends.messages.ToolMessages.*;
073 import static org.opends.server.util.StaticUtils.*;
074 import static org.opends.server.tools.ToolConstants.*;
075
076
077
078 /**
079 * This class provides a program that may be used to apply a set of changes (in
080 * LDIF change format) to an LDIF file. It will first read all of the changes
081 * into memory, and then will iterate through an LDIF file and apply them to the
082 * entries contained in it. Note that because of the manner in which it
083 * processes the changes, certain types of operations will not be allowed,
084 * including:
085 * <BR>
086 * <UL>
087 * <LI>Modify DN operations</LI>
088 * <LI>Deleting an entry that has been added</LI>
089 * <LI>Modifying an entry that has been added</LI>
090 * </UL>
091 */
092 public class LDIFModify
093 {
094 /**
095 * The fully-qualified name of this class.
096 */
097 private static final String CLASS_NAME = "org.opends.server.tools.LDIFModify";
098
099
100
101 /**
102 * Applies the specified changes to the source LDIF, writing the modified
103 * file to the specified target. Neither the readers nor the writer will be
104 * closed.
105 *
106 * @param sourceReader The LDIF reader that will be used to read the LDIF
107 * content to be modified.
108 * @param changeReader The LDIF reader that will be used to read the changes
109 * to be applied.
110 * @param targetWriter The LDIF writer that will be used to write the
111 * modified LDIF.
112 * @param errorList A list into which any error messages generated while
113 * processing changes may be added.
114 *
115 * @return <CODE>true</CODE> if all updates were successfully applied, or
116 * <CODE>false</CODE> if any errors were encountered.
117 *
118 * @throws IOException If a problem occurs while attempting to read the
119 * source or changes, or write the target.
120 *
121 * @throws LDIFException If a problem occurs while attempting to decode the
122 * source or changes, or trying to determine whether
123 * to include the entry in the output.
124 */
125 public static boolean modifyLDIF(LDIFReader sourceReader,
126 LDIFReader changeReader,
127 LDIFWriter targetWriter,
128 List<Message> errorList)
129 throws IOException, LDIFException
130 {
131 // Read the changes into memory.
132 TreeMap<DN,AddChangeRecordEntry> adds =
133 new TreeMap<DN,AddChangeRecordEntry>();
134 TreeMap<DN,Entry> ldifEntries =
135 new TreeMap<DN,Entry>();
136 HashMap<DN,DeleteChangeRecordEntry> deletes =
137 new HashMap<DN,DeleteChangeRecordEntry>();
138 HashMap<DN,LinkedList<Modification>> modifications =
139 new HashMap<DN,LinkedList<Modification>>();
140
141 while (true)
142 {
143 ChangeRecordEntry changeRecord;
144 try
145 {
146 changeRecord = changeReader.readChangeRecord(false);
147 }
148 catch (LDIFException le)
149 {
150 if (le.canContinueReading())
151 {
152 errorList.add(le.getMessageObject());
153 continue;
154 }
155 else
156 {
157 throw le;
158 }
159 }
160
161 if (changeRecord == null)
162 {
163 break;
164 }
165
166 DN changeDN = changeRecord.getDN();
167 switch (changeRecord.getChangeOperationType())
168 {
169 case ADD:
170 // The entry must not exist in the add list.
171 if (adds.containsKey(changeDN))
172 {
173 errorList.add(ERR_LDIFMODIFY_CANNOT_ADD_ENTRY_TWICE.get(
174 String.valueOf(changeDN)));
175 continue;
176 }
177 else
178 {
179 adds.put(changeDN, (AddChangeRecordEntry) changeRecord);
180 }
181 break;
182
183 case DELETE:
184 // The entry must not exist in the add list. If it exists in the
185 // modify list, then remove the changes since we won't need to apply
186 // them.
187 if (adds.containsKey(changeDN))
188 {
189 errorList.add(ERR_LDIFMODIFY_CANNOT_DELETE_AFTER_ADD.get(
190 String.valueOf(changeDN)));
191 continue;
192 }
193 else
194 {
195 modifications.remove(changeDN);
196 deletes.put(changeDN, (DeleteChangeRecordEntry) changeRecord);
197 }
198 break;
199
200 case MODIFY:
201 // The entry must not exist in the add or delete lists.
202 if (adds.containsKey(changeDN) || deletes.containsKey(changeDN))
203 {
204 errorList.add(ERR_LDIFMODIFY_CANNOT_MODIFY_ADDED_OR_DELETED.get(
205 String.valueOf(changeDN)));
206 continue;
207 }
208 else
209 {
210 LinkedList<Modification> mods =
211 modifications.get(changeDN);
212 if (mods == null)
213 {
214 mods = new LinkedList<Modification>();
215 modifications.put(changeDN, mods);
216 }
217
218 for (RawModification mod :
219 ((ModifyChangeRecordEntry) changeRecord).getModifications())
220 {
221 try
222 {
223 mods.add(mod.toModification());
224 }
225 catch (LDAPException le)
226 {
227 errorList.add(le.getMessageObject());
228 continue;
229 }
230 }
231 }
232 break;
233
234 case MODIFY_DN:
235 errorList.add(ERR_LDIFMODIFY_MODDN_NOT_SUPPORTED.get(
236 String.valueOf(changeDN)));
237 continue;
238
239 default:
240 errorList.add(ERR_LDIFMODIFY_UNKNOWN_CHANGETYPE.get(
241 String.valueOf(changeDN),
242 String.valueOf(changeRecord.getChangeOperationType())));
243 continue;
244 }
245 }
246
247
248 // Read the source an entry at a time and apply any appropriate changes
249 // before writing to the target LDIF.
250 while (true)
251 {
252 Entry entry;
253 try
254 {
255 entry = sourceReader.readEntry();
256 }
257 catch (LDIFException le)
258 {
259 if (le.canContinueReading())
260 {
261 errorList.add(le.getMessageObject());
262 continue;
263 }
264 else
265 {
266 throw le;
267 }
268 }
269
270 if (entry == null)
271 {
272 break;
273 }
274
275
276 // If the entry is to be deleted, then just skip over it without writing
277 // it to the output.
278 DN entryDN = entry.getDN();
279 if (deletes.remove(entryDN) != null)
280 {
281 continue;
282 }
283
284
285 // If the entry is to be added, then that's an error, since it already
286 // exists.
287 if (adds.remove(entryDN) != null)
288 {
289
290 errorList.add(ERR_LDIFMODIFY_ADD_ALREADY_EXISTS.get(
291 String.valueOf(entryDN)));
292 continue;
293 }
294
295
296 // If the entry is to be modified, then process the changes.
297 LinkedList<Modification> mods = modifications.remove(entryDN);
298 if ((mods != null) && (! mods.isEmpty()))
299 {
300 try
301 {
302 entry.applyModifications(mods);
303 }
304 catch (DirectoryException de)
305 {
306 errorList.add(de.getMessageObject());
307 continue;
308 }
309 }
310
311
312 // If we've gotten here, then the (possibly updated) entry should be
313 // written to the LDIF entry Map.
314 ldifEntries.put(entry.getDN(),entry);
315 }
316
317
318 // Perform any adds that may be necessary.
319 for (AddChangeRecordEntry add : adds.values())
320 {
321 Map<ObjectClass,String> objectClasses =
322 new LinkedHashMap<ObjectClass,String>();
323 Map<AttributeType,List<Attribute>> userAttributes =
324 new LinkedHashMap<AttributeType,List<Attribute>>();
325 Map<AttributeType,List<Attribute>> operationalAttributes =
326 new LinkedHashMap<AttributeType,List<Attribute>>();
327
328 for (Attribute a : add.getAttributes())
329 {
330 AttributeType t = a.getAttributeType();
331 if (t.isObjectClassType())
332 {
333 for (AttributeValue v : a.getValues())
334 {
335 String stringValue = v.getStringValue();
336 String lowerValue = toLowerCase(stringValue);
337 ObjectClass oc = DirectoryServer.getObjectClass(lowerValue, true);
338 objectClasses.put(oc, stringValue);
339 }
340 }
341 else if (t.isOperational())
342 {
343 List<Attribute> attrList = operationalAttributes.get(t);
344 if (attrList == null)
345 {
346 attrList = new LinkedList<Attribute>();
347 operationalAttributes.put(t, attrList);
348 }
349 attrList.add(a);
350 }
351 else
352 {
353 List<Attribute> attrList = userAttributes.get(t);
354 if (attrList == null)
355 {
356 attrList = new LinkedList<Attribute>();
357 userAttributes.put(t, attrList);
358 }
359 attrList.add(a);
360 }
361 }
362
363 Entry e = new Entry(add.getDN(), objectClasses, userAttributes,
364 operationalAttributes);
365 //Put the entry to be added into the LDIF entry map.
366 ldifEntries.put(e.getDN(),e);
367 }
368
369
370 // If there are any entries left in the delete or modify lists, then that's
371 // a problem because they didn't exist.
372 if (! deletes.isEmpty())
373 {
374 for (DN dn : deletes.keySet())
375 {
376 errorList.add(
377 ERR_LDIFMODIFY_DELETE_NO_SUCH_ENTRY.get(String.valueOf(dn)));
378 }
379 }
380
381 if (! modifications.isEmpty())
382 {
383 for (DN dn : modifications.keySet())
384 {
385 errorList.add(ERR_LDIFMODIFY_MODIFY_NO_SUCH_ENTRY.get(
386 String.valueOf(dn)));
387 }
388 }
389 return targetWriter.writeEntries(ldifEntries.values()) &&
390 errorList.isEmpty();
391 }
392
393
394
395 /**
396 * Invokes <CODE>ldifModifyMain</CODE> to perform the appropriate processing.
397 *
398 * @param args The command-line arguments provided to the client.
399 */
400 public static void main(String[] args)
401 {
402 int returnCode = ldifModifyMain(args, false, System.out, System.err);
403 if (returnCode != 0)
404 {
405 System.exit(filterExitCode(returnCode));
406 }
407 }
408
409
410
411 /**
412 * Processes the command-line arguments and makes the appropriate updates to
413 * the LDIF file.
414 *
415 * @param args The command line arguments provided to this
416 * program.
417 * @param serverInitialized Indicates whether the Directory Server has
418 * already been initialized (and therefore should
419 * not be initialized a second time).
420 * @param outStream The output stream to use for standard output, or
421 * {@code null} if standard output is not needed.
422 * @param errStream The output stream to use for standard error, or
423 * {@code null} if standard error is not needed.
424 *
425 * @return A value of zero if everything completed properly, or nonzero if
426 * any problem(s) occurred.
427 */
428 public static int ldifModifyMain(String[] args, boolean serverInitialized,
429 OutputStream outStream,
430 OutputStream errStream)
431 {
432 PrintStream out;
433 if (outStream == null)
434 {
435 out = NullOutputStream.printStream();
436 }
437 else
438 {
439 out = new PrintStream(outStream);
440 }
441
442 PrintStream err;
443 if (errStream == null)
444 {
445 err = NullOutputStream.printStream();
446 }
447 else
448 {
449 err = new PrintStream(errStream);
450 }
451
452 // Prepare the argument parser.
453 BooleanArgument showUsage;
454 StringArgument changesFile;
455 StringArgument configClass;
456 StringArgument configFile;
457 StringArgument sourceFile;
458 StringArgument targetFile;
459
460 Message toolDescription = INFO_LDIFMODIFY_TOOL_DESCRIPTION.get();
461 ArgumentParser argParser = new ArgumentParser(CLASS_NAME, toolDescription,
462 false);
463
464 try
465 {
466 configFile = new StringArgument("configfile", 'c', "configFile", true,
467 false, true,
468 INFO_CONFIGFILE_PLACEHOLDER.get(), null,
469 null,
470 INFO_DESCRIPTION_CONFIG_FILE.get());
471 configFile.setHidden(true);
472 argParser.addArgument(configFile);
473
474
475 configClass = new StringArgument("configclass", OPTION_SHORT_CONFIG_CLASS,
476 OPTION_LONG_CONFIG_CLASS, false,
477 false, true, INFO_CONFIGCLASS_PLACEHOLDER.get(),
478 ConfigFileHandler.class.getName(), null,
479 INFO_DESCRIPTION_CONFIG_CLASS.get());
480 configClass.setHidden(true);
481 argParser.addArgument(configClass);
482
483
484 sourceFile = new StringArgument("sourceldif", 's', "sourceLDIF", true,
485 false, true,
486 INFO_LDIFFILE_PLACEHOLDER.get(), null,
487 null,
488 INFO_LDIFMODIFY_DESCRIPTION_SOURCE.get());
489 argParser.addArgument(sourceFile);
490
491
492 changesFile =
493 new StringArgument("changesldif", 'm', "changesLDIF", true,
494 false, true, INFO_LDIFFILE_PLACEHOLDER.get(),
495 null, null,
496 INFO_LDIFMODIFY_DESCRIPTION_CHANGES.get());
497 argParser.addArgument(changesFile);
498
499
500 targetFile = new StringArgument("targetldif", 't', "targetLDIF", true,
501 false, true,
502 INFO_LDIFFILE_PLACEHOLDER.get(), null,
503 null,
504 INFO_LDIFMODIFY_DESCRIPTION_TARGET.get());
505 argParser.addArgument(targetFile);
506
507
508 showUsage = new BooleanArgument("help", OPTION_SHORT_HELP,
509 OPTION_LONG_HELP,
510 INFO_LDIFMODIFY_DESCRIPTION_HELP.get());
511 argParser.addArgument(showUsage);
512 argParser.setUsageArgument(showUsage);
513 }
514 catch (ArgumentException ae)
515 {
516 Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
517 err.println(message);
518 return 1;
519 }
520
521
522 // Parse the command-line arguments provided to the program.
523 try
524 {
525 argParser.parseArguments(args);
526 }
527 catch (ArgumentException ae)
528 {
529 Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
530
531 err.println(message);
532 err.println(argParser.getUsage());
533 return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
534 }
535
536
537 // If we should just display usage or version information,
538 // then print it and exit.
539 if (argParser.usageOrVersionDisplayed())
540 {
541 return 0;
542 }
543
544
545 if (! serverInitialized)
546 {
547 // Bootstrap the Directory Server configuration for use as a client.
548 DirectoryServer directoryServer = DirectoryServer.getInstance();
549 directoryServer.bootstrapClient();
550
551
552 // If we're to use the configuration then initialize it, along with the
553 // schema.
554 boolean checkSchema = configFile.isPresent();
555 if (checkSchema)
556 {
557 try
558 {
559 directoryServer.initializeJMX();
560 }
561 catch (Exception e)
562 {
563 Message message = ERR_LDIFMODIFY_CANNOT_INITIALIZE_JMX.get(
564 String.valueOf(configFile.getValue()),
565 e.getMessage());
566 err.println(message);
567 return 1;
568 }
569
570 try
571 {
572 directoryServer.initializeConfiguration(configClass.getValue(),
573 configFile.getValue());
574 }
575 catch (Exception e)
576 {
577 Message message = ERR_LDIFMODIFY_CANNOT_INITIALIZE_CONFIG.get(
578 String.valueOf(configFile.getValue()),
579 e.getMessage());
580 err.println(message);
581 return 1;
582 }
583
584 try
585 {
586 directoryServer.initializeSchema();
587 }
588 catch (Exception e)
589 {
590 Message message = ERR_LDIFMODIFY_CANNOT_INITIALIZE_SCHEMA.get(
591 String.valueOf(configFile.getValue()),
592 e.getMessage());
593 err.println(message);
594 return 1;
595 }
596 }
597 }
598
599
600 // Create the LDIF readers and writer from the arguments.
601 File source = new File(sourceFile.getValue());
602 if (! source.exists())
603 {
604 Message message = ERR_LDIFMODIFY_SOURCE_DOES_NOT_EXIST.get(
605 sourceFile.getValue());
606 err.println(message);
607 return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
608 }
609
610 LDIFImportConfig importConfig = new LDIFImportConfig(sourceFile.getValue());
611 LDIFReader sourceReader;
612 try
613 {
614 sourceReader = new LDIFReader(importConfig);
615 }
616 catch (IOException ioe)
617 {
618 Message message = ERR_LDIFMODIFY_CANNOT_OPEN_SOURCE.get(
619 sourceFile.getValue(),
620 String.valueOf(ioe));
621 err.println(message);
622 return LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR;
623 }
624
625
626 File changes = new File(changesFile.getValue());
627 if (! changes.exists())
628 {
629 Message message = ERR_LDIFMODIFY_CHANGES_DOES_NOT_EXIST.get(
630 changesFile.getValue());
631 err.println(message);
632 return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
633 }
634
635 importConfig = new LDIFImportConfig(changesFile.getValue());
636 LDIFReader changeReader;
637 try
638 {
639 changeReader = new LDIFReader(importConfig);
640 }
641 catch (IOException ioe)
642 {
643 Message message = ERR_LDIFMODIFY_CANNOT_OPEN_CHANGES.get(
644 sourceFile.getValue(), ioe.getMessage());
645 err.println(message);
646 return LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR;
647 }
648
649
650 LDIFExportConfig exportConfig =
651 new LDIFExportConfig(targetFile.getValue(),
652 ExistingFileBehavior.OVERWRITE);
653 LDIFWriter targetWriter;
654 try
655 {
656 targetWriter = new LDIFWriter(exportConfig);
657 }
658 catch (IOException ioe)
659 {
660 Message message = ERR_LDIFMODIFY_CANNOT_OPEN_TARGET.get(
661 sourceFile.getValue(), ioe.getMessage());
662 err.println(message);
663 return LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR;
664 }
665
666
667 // Actually invoke the LDIF procesing.
668 LinkedList<Message> errorList = new LinkedList<Message>();
669 boolean successful;
670 try
671 {
672 successful = modifyLDIF(sourceReader, changeReader, targetWriter,
673 errorList);
674 }
675 catch (Exception e)
676 {
677 Message message = ERR_LDIFMODIFY_ERROR_PROCESSING_LDIF.get(
678 String.valueOf(e));
679 err.println(message);
680
681 successful = false;
682 }
683
684 try
685 {
686 sourceReader.close();
687 } catch (Exception e) {}
688
689 try
690 {
691 changeReader.close();
692 } catch (Exception e) {}
693
694 try
695 {
696 targetWriter.close();
697 } catch (Exception e) {}
698
699 for (Message s : errorList)
700 {
701 err.println(s);
702 }
703 return (successful ? 0 : 1);
704 }
705 }
706