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 2007-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.backends;
028
029
030
031 import java.io.File;
032 import java.util.HashMap;
033 import java.util.HashSet;
034 import java.util.LinkedHashMap;
035 import java.util.LinkedList;
036 import java.util.List;
037 import java.util.Set;
038 import java.util.concurrent.locks.ReentrantReadWriteLock;
039
040 import org.opends.messages.Message;
041 import org.opends.server.admin.Configuration;
042 import org.opends.server.admin.server.ConfigurationChangeListener;
043 import org.opends.server.admin.std.server.LDIFBackendCfg;
044 import org.opends.server.api.AlertGenerator;
045 import org.opends.server.api.Backend;
046 import org.opends.server.config.ConfigException;
047 import org.opends.server.core.AddOperation;
048 import org.opends.server.core.DeleteOperation;
049 import org.opends.server.core.DirectoryServer;
050 import org.opends.server.core.ModifyOperation;
051 import org.opends.server.core.ModifyDNOperation;
052 import org.opends.server.core.SearchOperation;
053 import org.opends.server.loggers.debug.DebugTracer;
054 import org.opends.server.types.AttributeType;
055 import org.opends.server.types.BackupConfig;
056 import org.opends.server.types.BackupDirectory;
057 import org.opends.server.types.ConditionResult;
058 import org.opends.server.types.ConfigChangeResult;
059 import org.opends.server.types.Control;
060 import org.opends.server.types.DebugLogLevel;
061 import org.opends.server.types.DirectoryException;
062 import org.opends.server.types.DN;
063 import org.opends.server.types.Entry;
064 import org.opends.server.types.ExistingFileBehavior;
065 import org.opends.server.types.IndexType;
066 import org.opends.server.types.InitializationException;
067 import org.opends.server.types.LDIFExportConfig;
068 import org.opends.server.types.LDIFImportConfig;
069 import org.opends.server.types.LDIFImportResult;
070 import org.opends.server.types.RestoreConfig;
071 import org.opends.server.types.ResultCode;
072 import org.opends.server.types.SearchFilter;
073 import org.opends.server.types.SearchScope;
074 import org.opends.server.util.LDIFException;
075 import org.opends.server.util.LDIFReader;
076 import org.opends.server.util.LDIFWriter;
077 import org.opends.server.util.Validator;
078
079 import static org.opends.messages.BackendMessages.*;
080 import static org.opends.server.loggers.ErrorLogger.*;
081 import static org.opends.server.loggers.debug.DebugLogger.*;
082 import static org.opends.server.util.ServerConstants.*;
083 import static org.opends.server.util.StaticUtils.*;
084
085
086
087 /**
088 * This class provides a backend implementation that stores the underlying data
089 * in an LDIF file. When the backend is initialized, the contents of the
090 * backend are read into memory and all read operations are performed purely
091 * from memory. Write operations cause the underlying LDIF file to be
092 * re-written on disk.
093 */
094 public class LDIFBackend
095 extends Backend
096 implements ConfigurationChangeListener<LDIFBackendCfg>, AlertGenerator
097 {
098 /**
099 * The tracer object for the debug logger.
100 */
101 private static final DebugTracer TRACER = getTracer();
102
103
104
105 // The base DNs for this backend.
106 private DN[] baseDNs;
107
108 // The mapping between parent DNs and their immediate children.
109 private HashMap<DN,HashSet<DN>> childDNs;
110
111 // The base DNs for this backend, in a hash set.
112 private HashSet<DN> baseDNSet;
113
114 // The set of supported controls for this backend.
115 private HashSet<String> supportedControls;
116
117 // The set of supported features for this backend.
118 private HashSet<String> supportedFeatures;
119
120 // The current configuration for this backend.
121 private LDIFBackendCfg currentConfig;
122
123 // The mapping between entry DNs and the corresponding entries.
124 private LinkedHashMap<DN,Entry> entryMap;
125
126 // A read-write lock used to protect access to this backend.
127 private ReentrantReadWriteLock backendLock;
128
129 // The path to the LDIF file containing the data for this backend.
130 private String ldifFilePath;
131
132
133
134 /**
135 * Creates a new backend with the provided information. All backend
136 * implementations must implement a default constructor that use
137 * <CODE>super()</CODE> to invoke this constructor.
138 */
139 public LDIFBackend()
140 {
141 super();
142
143 entryMap = new LinkedHashMap<DN,Entry>();
144 childDNs = new HashMap<DN,HashSet<DN>>();
145
146 boolean useFairLocking =
147 DirectoryServer.getEnvironmentConfig().getLockManagerFairOrdering();
148 backendLock = new ReentrantReadWriteLock(useFairLocking);
149 }
150
151
152
153 /**
154 * {@inheritDoc}
155 */
156 @Override()
157 public void initializeBackend()
158 throws ConfigException, InitializationException
159 {
160 // We won't support anything other than exactly one base DN in this
161 // implementation. If we were to add such support in the future, we would
162 // likely want to separate the data for each base DN into a separate entry
163 // map.
164 if ((baseDNs == null) || (baseDNs.length != 1))
165 {
166 Message message = ERR_LDIF_BACKEND_MULTIPLE_BASE_DNS.get(
167 currentConfig.dn().toString());
168 throw new ConfigException(message);
169 }
170
171 for (DN dn : baseDNs)
172 {
173 try
174 {
175 DirectoryServer.registerBaseDN(dn, this,
176 currentConfig.isIsPrivateBackend());
177 }
178 catch (Exception e)
179 {
180 if (debugEnabled())
181 {
182 TRACER.debugCaught(DebugLogLevel.ERROR, e);
183 }
184
185 Message message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(
186 dn.toString(), getExceptionMessage(e));
187 throw new InitializationException(message, e);
188 }
189 }
190
191 DirectoryServer.registerAlertGenerator(this);
192
193 readLDIF();
194 }
195
196
197
198 /**
199 * Reads the contents of the LDIF backing file into memory.
200 *
201 * @throws InitializationException If a problem occurs while reading the
202 * LDIF file.
203 */
204 private void readLDIF()
205 throws InitializationException
206 {
207 File ldifFile = getFileForPath(ldifFilePath);
208 if (! ldifFile.exists())
209 {
210 // This is fine. We will just start with an empty backend.
211 if (debugEnabled())
212 {
213 TRACER.debugInfo("LDIF backend starting empty because LDIF file " +
214 ldifFilePath + " does not exist");
215 }
216
217 entryMap.clear();
218 childDNs.clear();
219 return;
220 }
221
222
223 try
224 {
225 importLDIF(new LDIFImportConfig(ldifFile.getAbsolutePath()), false);
226 }
227 catch (DirectoryException de)
228 {
229 throw new InitializationException(de.getMessageObject(), de);
230 }
231 }
232
233
234
235 /**
236 * Writes the current set of entries to the target LDIF file. The new LDIF
237 * will first be created as a temporary file and then renamed into place. The
238 * caller must either hold the write lock for this backend, or must ensure
239 * that it's in some other state that guarantees exclusive access to the data.
240 *
241 * @throws DirectoryException If a problem occurs that prevents the updated
242 * LDIF from being written.
243 */
244 private void writeLDIF()
245 throws DirectoryException
246 {
247 File ldifFile = getFileForPath(ldifFilePath);
248 File tempFile = new File(ldifFile.getAbsolutePath() + ".new");
249 File oldFile = new File(ldifFile.getAbsolutePath() + ".old");
250
251
252 // Write the new data to a temporary file.
253 LDIFWriter writer;
254 try
255 {
256 LDIFExportConfig exportConfig =
257 new LDIFExportConfig(tempFile.getAbsolutePath(),
258 ExistingFileBehavior.OVERWRITE);
259 writer = new LDIFWriter(exportConfig);
260 }
261 catch (Exception e)
262 {
263 if (debugEnabled())
264 {
265 TRACER.debugCaught(DebugLogLevel.ERROR, e);
266 }
267
268 Message m = ERR_LDIF_BACKEND_ERROR_CREATING_FILE.get(
269 tempFile.getAbsolutePath(),
270 currentConfig.dn().toString(),
271 stackTraceToSingleLineString(e));
272 DirectoryServer.sendAlertNotification(this,
273 ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m);
274 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
275 m, e);
276 }
277
278
279 for (Entry entry : entryMap.values())
280 {
281 try
282 {
283 writer.writeEntry(entry);
284 }
285 catch (Exception e)
286 {
287 if (debugEnabled())
288 {
289 TRACER.debugCaught(DebugLogLevel.ERROR, e);
290 }
291
292 try
293 {
294 writer.close();
295 } catch (Exception e2) {}
296
297 Message m = ERR_LDIF_BACKEND_ERROR_WRITING_FILE.get(
298 tempFile.getAbsolutePath(),
299 currentConfig.dn().toString(),
300 stackTraceToSingleLineString(e));
301 DirectoryServer.sendAlertNotification(this,
302 ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m);
303 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
304 m, e);
305 }
306 }
307
308 try
309 {
310 writer.close();
311 } catch (Exception e) {}
312
313
314 // Rename the existing "live" file out of the way and move the new file
315 // into place.
316 try
317 {
318 if (oldFile.exists())
319 {
320 oldFile.delete();
321 }
322 } catch (Exception e) {}
323
324 try
325 {
326 if (ldifFile.exists())
327 {
328 ldifFile.renameTo(oldFile);
329 }
330 }
331 catch (Exception e)
332 {
333 if (debugEnabled())
334 {
335 TRACER.debugCaught(DebugLogLevel.ERROR, e);
336 }
337 }
338
339 try
340 {
341 tempFile.renameTo(ldifFile);
342 }
343 catch (Exception e)
344 {
345 if (debugEnabled())
346 {
347 TRACER.debugCaught(DebugLogLevel.ERROR, e);
348 }
349
350 Message m = ERR_LDIF_BACKEND_ERROR_RENAMING_FILE.get(
351 tempFile.getAbsolutePath(),
352 ldifFile.getAbsolutePath(),
353 currentConfig.dn().toString(),
354 stackTraceToSingleLineString(e));
355 DirectoryServer.sendAlertNotification(this,
356 ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE, m);
357 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
358 m, e);
359 }
360 }
361
362
363
364 /**
365 * {@inheritDoc}
366 */
367 @Override()
368 public void finalizeBackend()
369 {
370 backendLock.writeLock().lock();
371
372 try
373 {
374 currentConfig.removeLDIFChangeListener(this);
375 DirectoryServer.deregisterAlertGenerator(this);
376
377 for (DN dn : baseDNs)
378 {
379 try
380 {
381 DirectoryServer.deregisterBaseDN(dn);
382 }
383 catch (Exception e)
384 {
385 if (debugEnabled())
386 {
387 TRACER.debugCaught(DebugLogLevel.ERROR, e);
388 }
389 }
390 }
391 }
392 finally
393 {
394 backendLock.writeLock().unlock();
395 }
396 }
397
398
399
400 /**
401 * {@inheritDoc}
402 */
403 @Override()
404 public DN[] getBaseDNs()
405 {
406 return baseDNs;
407 }
408
409
410
411 /**
412 * {@inheritDoc}
413 */
414 @Override()
415 public long getEntryCount()
416 {
417 backendLock.readLock().lock();
418
419 try
420 {
421 if (entryMap != null)
422 {
423 return entryMap.size();
424 }
425
426 return -1;
427 }
428 finally
429 {
430 backendLock.readLock().unlock();
431 }
432 }
433
434
435
436 /**
437 * {@inheritDoc}
438 */
439 @Override()
440 public boolean isLocal()
441 {
442 return true;
443 }
444
445
446
447 /**
448 * {@inheritDoc}
449 */
450 @Override()
451 public boolean isIndexed(AttributeType attributeType, IndexType indexType)
452 {
453 // All searches in this backend will always be considered indexed.
454 return true;
455 }
456
457
458
459 /**
460 * {@inheritDoc}
461 */
462 @Override()
463 public ConditionResult hasSubordinates(DN entryDN)
464 throws DirectoryException
465 {
466 backendLock.readLock().lock();
467
468 try
469 {
470 HashSet<DN> childDNSet = childDNs.get(entryDN);
471 if ((childDNSet == null) || childDNSet.isEmpty())
472 {
473 // It could be that the entry doesn't exist, in which case we should
474 // throw an exception.
475 if (entryMap.containsKey(entryDN))
476 {
477 return ConditionResult.FALSE;
478 }
479 else
480 {
481 Message m = ERR_LDIF_BACKEND_HAS_SUBORDINATES_NO_SUCH_ENTRY.get(
482 String.valueOf(entryDN));
483 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m);
484 }
485 }
486 else
487 {
488 return ConditionResult.TRUE;
489 }
490 }
491 finally
492 {
493 backendLock.readLock().unlock();
494 }
495 }
496
497
498
499 /**
500 * {@inheritDoc}
501 */
502 @Override()
503 public long numSubordinates(DN entryDN, boolean subtree)
504 throws DirectoryException
505 {
506 backendLock.readLock().lock();
507
508 try
509 {
510 HashSet<DN> childDNSet = childDNs.get(entryDN);
511 if ((childDNSet == null) || childDNSet.isEmpty())
512 {
513 // It could be that the entry doesn't exist, in which case we should
514 // throw an exception.
515 if (entryMap.containsKey(entryDN))
516 {
517 return 0L;
518 }
519 else
520 {
521 Message m = ERR_LDIF_BACKEND_NUM_SUBORDINATES_NO_SUCH_ENTRY.get(
522 String.valueOf(entryDN));
523 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m);
524 }
525 }
526 else
527 {
528 if(!subtree)
529 {
530 return childDNSet.size();
531 }
532 else
533 {
534 long count = 0;
535 for(DN childDN : childDNSet)
536 {
537 count += numSubordinates(childDN, true);
538 count ++;
539 }
540 return count;
541 }
542
543 }
544 }
545 finally
546 {
547 backendLock.readLock().unlock();
548 }
549 }
550
551
552
553 /**
554 * {@inheritDoc}
555 */
556 @Override()
557 public Entry getEntry(DN entryDN)
558 {
559 backendLock.readLock().lock();
560
561 try
562 {
563 return entryMap.get(entryDN);
564 }
565 finally
566 {
567 backendLock.readLock().unlock();
568 }
569 }
570
571
572
573 /**
574 * {@inheritDoc}
575 */
576 @Override()
577 public boolean entryExists(DN entryDN)
578 {
579 backendLock.readLock().lock();
580
581 try
582 {
583 return entryMap.containsKey(entryDN);
584 }
585 finally
586 {
587 backendLock.readLock().unlock();
588 }
589 }
590
591
592
593 /**
594 * {@inheritDoc}
595 */
596 @Override()
597 public void addEntry(Entry entry, AddOperation addOperation)
598 throws DirectoryException
599 {
600 backendLock.writeLock().lock();
601
602 try
603 {
604 // Make sure that the target entry does not already exist, but that its
605 // parent does exist (or that the entry being added is the base DN).
606 DN entryDN = entry.getDN();
607 if (entryMap.containsKey(entryDN))
608 {
609 Message m = ERR_LDIF_BACKEND_ADD_ALREADY_EXISTS.get(entryDN.toString());
610 throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, m);
611 }
612
613 if (baseDNSet.contains(entryDN))
614 {
615 entryMap.put(entryDN, entry.duplicate(false));
616 writeLDIF();
617 return;
618 }
619 else
620 {
621 DN parentDN = entryDN.getParentDNInSuffix();
622 if ((parentDN != null) && entryMap.containsKey(parentDN))
623 {
624 entryMap.put(entryDN, entry.duplicate(false));
625
626 HashSet<DN> childDNSet = childDNs.get(parentDN);
627 if (childDNSet == null)
628 {
629 childDNSet = new HashSet<DN>();
630 childDNs.put(parentDN, childDNSet);
631 }
632 childDNSet.add(entryDN);
633 writeLDIF();
634 return;
635 }
636 else
637 {
638 DN matchedDN = null;
639 while (true)
640 {
641 parentDN = parentDN.getParentDNInSuffix();
642 if (parentDN == null)
643 {
644 break;
645 }
646
647 if (entryMap.containsKey(parentDN))
648 {
649 matchedDN = parentDN;
650 break;
651 }
652 }
653
654 Message m =
655 ERR_LDIF_BACKEND_ADD_MISSING_PARENT.get(entryDN.toString());
656 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m, matchedDN,
657 null);
658 }
659 }
660 }
661 finally
662 {
663 backendLock.writeLock().unlock();
664 }
665 }
666
667
668
669 /**
670 * {@inheritDoc}
671 */
672 @Override()
673 public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
674 throws DirectoryException
675 {
676 backendLock.writeLock().lock();
677
678 try
679 {
680 // Get the DN of the target entry's parent, if it exists. We'll need to
681 // also remove the reference to the target entry from the parent's set of
682 // children.
683 DN parentDN = entryDN.getParentDNInSuffix();
684
685 // Make sure that the target entry exists. If not, then fail.
686 if (! entryMap.containsKey(entryDN))
687 {
688 DN matchedDN = null;
689 while (parentDN != null)
690 {
691 if (entryMap.containsKey(parentDN))
692 {
693 matchedDN = parentDN;
694 break;
695 }
696
697 parentDN = parentDN.getParentDNInSuffix();
698 }
699
700 Message m =
701 ERR_LDIF_BACKEND_DELETE_NO_SUCH_ENTRY.get(entryDN.toString());
702 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m, matchedDN,
703 null);
704 }
705
706
707 // See if the target entry has any children. If so, then we'll only
708 // delete it if the request contains the subtree delete control (in
709 // which case we'll delete the entire subtree).
710 HashSet<DN> childDNSet = childDNs.get(entryDN);
711 if ((childDNSet == null) || childDNSet.isEmpty())
712 {
713 entryMap.remove(entryDN);
714 childDNs.remove(entryDN);
715
716 if (parentDN != null)
717 {
718 HashSet<DN> parentChildren = childDNs.get(parentDN);
719 if (parentChildren != null)
720 {
721 parentChildren.remove(entryDN);
722 if (parentChildren.isEmpty())
723 {
724 childDNs.remove(parentDN);
725 }
726 }
727 }
728
729 writeLDIF();
730 return;
731 }
732 else
733 {
734 boolean subtreeDelete = false;
735 for (Control c : deleteOperation.getRequestControls())
736 {
737 if (c.getOID().equals(OID_SUBTREE_DELETE_CONTROL))
738 {
739 subtreeDelete = true;
740 break;
741 }
742 }
743
744 if (! subtreeDelete)
745 {
746 Message m = ERR_LDIF_BACKEND_DELETE_NONLEAF.get(entryDN.toString());
747 throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, m);
748 }
749
750 entryMap.remove(entryDN);
751 childDNs.remove(entryDN);
752
753 if (parentDN != null)
754 {
755 HashSet<DN> parentChildren = childDNs.get(parentDN);
756 if (parentChildren != null)
757 {
758 parentChildren.remove(entryDN);
759 if (parentChildren.isEmpty())
760 {
761 childDNs.remove(parentDN);
762 }
763 }
764 }
765
766 for (DN childDN : childDNSet)
767 {
768 subtreeDelete(childDN);
769 }
770
771 writeLDIF();
772 return;
773 }
774 }
775 finally
776 {
777 backendLock.writeLock().unlock();
778 }
779 }
780
781
782
783 /**
784 * Removes the specified entry and any subordinates that it may have from
785 * the backend. This method assumes that the caller holds the backend write
786 * lock.
787 *
788 * @param entryDN The DN of the entry to remove, along with all of its
789 * subordinate entries.
790 */
791 private void subtreeDelete(DN entryDN)
792 {
793 entryMap.remove(entryDN);
794 HashSet<DN> childDNSet = childDNs.remove(entryDN);
795 if (childDNSet != null)
796 {
797 for (DN childDN : childDNSet)
798 {
799 subtreeDelete(childDN);
800 }
801 }
802 }
803
804
805
806 /**
807 * {@inheritDoc}
808 */
809 @Override()
810 public void replaceEntry(Entry entry, ModifyOperation modifyOperation)
811 throws DirectoryException
812 {
813 backendLock.writeLock().lock();
814
815 try
816 {
817 // Make sure that the target entry exists. If not, then fail.
818 DN entryDN = entry.getDN();
819 if (! entryMap.containsKey(entryDN))
820 {
821 DN matchedDN = null;
822 DN parentDN = entryDN.getParentDNInSuffix();
823 while (parentDN != null)
824 {
825 if (entryMap.containsKey(parentDN))
826 {
827 matchedDN = parentDN;
828 break;
829 }
830
831 parentDN = parentDN.getParentDNInSuffix();
832 }
833
834 Message m =
835 ERR_LDIF_BACKEND_MODIFY_NO_SUCH_ENTRY.get(entryDN.toString());
836 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m, matchedDN,
837 null);
838 }
839
840 entryMap.put(entryDN, entry.duplicate(false));
841 writeLDIF();
842 return;
843 }
844 finally
845 {
846 backendLock.writeLock().unlock();
847 }
848 }
849
850
851
852 /**
853 * {@inheritDoc}
854 */
855 @Override()
856 public void renameEntry(DN currentDN, Entry entry,
857 ModifyDNOperation modifyDNOperation)
858 throws DirectoryException
859 {
860 backendLock.writeLock().lock();
861
862 try
863 {
864 // Make sure that the original entry exists and that the new entry doesn't
865 // exist but its parent does.
866 DN newDN = entry.getDN();
867 if (! entryMap.containsKey(currentDN))
868 {
869 DN matchedDN = null;
870 DN parentDN = currentDN.getParentDNInSuffix();
871 while (parentDN != null)
872 {
873 if (entryMap.containsKey(parentDN))
874 {
875 matchedDN = parentDN;
876 break;
877 }
878
879 parentDN = parentDN.getParentDNInSuffix();
880 }
881
882 Message m = ERR_LDIF_BACKEND_MODDN_NO_SUCH_SOURCE_ENTRY.get(
883 currentDN.toString());
884 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m, matchedDN,
885 null);
886 }
887
888 if (entryMap.containsKey(newDN))
889 {
890 Message m = ERR_LDIF_BACKEND_MODDN_TARGET_ENTRY_ALREADY_EXISTS.get(
891 newDN.toString());
892 throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, m);
893 }
894
895 DN newParentDN = newDN.getParentDNInSuffix();
896 if (! entryMap.containsKey(newParentDN))
897 {
898 Message m = ERR_LDIF_BACKEND_MODDN_NEW_PARENT_DOESNT_EXIST.get(
899 String.valueOf(newParentDN));
900 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, m);
901 }
902
903 // Remove the entry from the list of children for the old parent and
904 // add the new entry DN to the set of children for the new parent.
905 DN oldParentDN = currentDN.getParentDNInSuffix();
906 HashSet<DN> parentChildDNs = childDNs.get(oldParentDN);
907 if (parentChildDNs != null)
908 {
909 parentChildDNs.remove(currentDN);
910 if (parentChildDNs.isEmpty() &&
911 (modifyDNOperation.getNewSuperior() != null))
912 {
913 childDNs.remove(oldParentDN);
914 }
915 }
916
917 parentChildDNs = childDNs.get(newParentDN);
918 if (parentChildDNs == null)
919 {
920 parentChildDNs = new HashSet<DN>();
921 childDNs.put(newParentDN, parentChildDNs);
922 }
923 parentChildDNs.add(newDN);
924
925
926 // If the entry has children, then we'll need to work on the whole
927 // subtree. Otherwise, just work on the target entry.
928 Set<DN> childDNSet = childDNs.remove(currentDN);
929 if ((childDNSet == null) || childDNSet.isEmpty())
930 {
931 entryMap.remove(currentDN);
932 entryMap.put(newDN, entry.duplicate(false));
933 writeLDIF();
934 return;
935 }
936 else
937 {
938 entryMap.remove(currentDN);
939 entryMap.put(newDN, entry.duplicate(false));
940 for (DN childDN : childDNSet)
941 {
942 subtreeRename(childDN, newDN);
943 }
944 writeLDIF();
945 return;
946 }
947 }
948 finally
949 {
950 backendLock.writeLock().unlock();
951 }
952 }
953
954
955
956 /**
957 * Moves the specified entry and all of its children so that they are
958 * appropriately placed below the given new parent DN. This method assumes
959 * that the caller holds the backend write lock.
960 *
961 * @param entryDN The DN of the entry to move/rename.
962 * @param newParentDN The DN of the new parent under which the entry should
963 * be placed.
964 */
965 private void subtreeRename(DN entryDN, DN newParentDN)
966 {
967 Set<DN> childDNSet = childDNs.remove(entryDN);
968 DN newEntryDN = new DN(entryDN.getRDN(), newParentDN);
969
970 Entry oldEntry = entryMap.remove(entryDN);
971 if (oldEntry == null)
972 {
973 // This should never happen.
974 if (debugEnabled())
975 {
976 TRACER.debugWarning("Subtree rename encountered entry DN " +
977 entryDN.toString() + " for nonexistent entry.");
978 }
979 return;
980 }
981
982 Entry newEntry = oldEntry.duplicate(false);
983 newEntry.setDN(newEntryDN);
984 entryMap.put(newEntryDN, newEntry);
985
986 HashSet<DN> parentChildren = childDNs.get(newParentDN);
987 if (parentChildren == null)
988 {
989 parentChildren = new HashSet<DN>();
990 childDNs.put(newParentDN, parentChildren);
991 }
992 parentChildren.add(newEntryDN);
993
994 if (childDNSet != null)
995 {
996 for (DN childDN : childDNSet)
997 {
998 subtreeRename(childDN, newEntryDN);
999 }
1000 }
1001 }
1002
1003
1004
1005 /**
1006 * {@inheritDoc}
1007 */
1008 @Override()
1009 public void search(SearchOperation searchOperation)
1010 throws DirectoryException
1011 {
1012 backendLock.readLock().lock();
1013
1014 try
1015 {
1016 // Get the base DN, scope, and filter for the search.
1017 DN baseDN = searchOperation.getBaseDN();
1018 SearchScope scope = searchOperation.getScope();
1019 SearchFilter filter = searchOperation.getFilter();
1020
1021
1022 // Make sure the base entry exists if it's supposed to be in this backend.
1023 Entry baseEntry = entryMap.get(baseDN);
1024 if ((baseEntry == null) && handlesEntry(baseDN))
1025 {
1026 DN matchedDN = baseDN.getParentDNInSuffix();
1027 while (matchedDN != null)
1028 {
1029 if (entryMap.containsKey(matchedDN))
1030 {
1031 break;
1032 }
1033
1034 matchedDN = matchedDN.getParentDNInSuffix();
1035 }
1036
1037 Message m = ERR_LDIF_BACKEND_SEARCH_NO_SUCH_BASE.get(
1038 String.valueOf(baseDN));
1039 throw new DirectoryException(
1040 ResultCode.NO_SUCH_OBJECT, m, matchedDN, null);
1041 }
1042
1043 if (baseEntry != null)
1044 {
1045 baseEntry = baseEntry.duplicate(true);
1046 }
1047
1048 // If it's a base-level search, then just get that entry and return it if
1049 // it matches the filter.
1050 if (scope == SearchScope.BASE_OBJECT)
1051 {
1052 if (filter.matchesEntry(baseEntry))
1053 {
1054 searchOperation.returnEntry(baseEntry, new LinkedList<Control>());
1055 }
1056 }
1057 else
1058 {
1059 // Walk through all entries and send the ones that match.
1060 for (Entry e : entryMap.values())
1061 {
1062 e = e.duplicate(true);
1063 if (e.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(e))
1064 {
1065 searchOperation.returnEntry(e, new LinkedList<Control>());
1066 }
1067 }
1068 }
1069 }
1070 finally
1071 {
1072 backendLock.readLock().unlock();
1073 }
1074 }
1075
1076
1077
1078 /**
1079 * {@inheritDoc}
1080 */
1081 @Override()
1082 public HashSet<String> getSupportedControls()
1083 {
1084 return supportedControls;
1085 }
1086
1087
1088
1089 /**
1090 * {@inheritDoc}
1091 */
1092 @Override()
1093 public HashSet<String> getSupportedFeatures()
1094 {
1095 return supportedFeatures;
1096 }
1097
1098
1099
1100 /**
1101 * {@inheritDoc}
1102 */
1103 @Override()
1104 public boolean supportsLDIFExport()
1105 {
1106 return true;
1107 }
1108
1109
1110
1111 /**
1112 * {@inheritDoc}
1113 */
1114 @Override()
1115 public void exportLDIF(LDIFExportConfig exportConfig)
1116 throws DirectoryException
1117 {
1118 backendLock.readLock().lock();
1119
1120 try
1121 {
1122 // Create the LDIF writer.
1123 LDIFWriter ldifWriter;
1124 try
1125 {
1126 ldifWriter = new LDIFWriter(exportConfig);
1127 }
1128 catch (Exception e)
1129 {
1130 if (debugEnabled())
1131 {
1132 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1133 }
1134
1135 Message m = ERR_LDIF_BACKEND_CANNOT_CREATE_LDIF_WRITER.get(
1136 stackTraceToSingleLineString(e));
1137 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1138 m, e);
1139 }
1140
1141
1142 // Walk through all the entries and write them to LDIF.
1143 DN entryDN = null;
1144 try
1145 {
1146 for (Entry entry : entryMap.values())
1147 {
1148 entryDN = entry.getDN();
1149 ldifWriter.writeEntry(entry);
1150 }
1151 }
1152 catch (Exception e)
1153 {
1154 Message m = ERR_LDIF_BACKEND_CANNOT_WRITE_ENTRY_TO_LDIF.get(
1155 String.valueOf(entryDN),
1156 stackTraceToSingleLineString(e));
1157 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1158 m, e);
1159 }
1160 finally
1161 {
1162 try
1163 {
1164 ldifWriter.close();
1165 }
1166 catch (Exception e)
1167 {
1168 if (debugEnabled())
1169 {
1170 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1171 }
1172 }
1173 }
1174 }
1175 finally
1176 {
1177 backendLock.readLock().unlock();
1178 }
1179 }
1180
1181
1182
1183 /**
1184 * {@inheritDoc}
1185 */
1186 @Override()
1187 public boolean supportsLDIFImport()
1188 {
1189 return true;
1190 }
1191
1192
1193
1194 /**
1195 * {@inheritDoc}
1196 */
1197 @Override()
1198 public LDIFImportResult importLDIF(LDIFImportConfig importConfig)
1199 throws DirectoryException
1200 {
1201 return importLDIF(importConfig, true);
1202 }
1203
1204
1205
1206 /**
1207 * Processes an LDIF import operation, optionally writing the resulting LDIF
1208 * to disk.
1209 *
1210 * @param importConfig The LDIF import configuration.
1211 * @param writeLDIF Indicates whether the LDIF backing file for this
1212 * backend should be updated when the import is
1213 * complete. This should only be {@code false} when
1214 * reading the LDIF as the backend is coming online.
1215 */
1216 private LDIFImportResult importLDIF(LDIFImportConfig importConfig,
1217 boolean writeLDIF)
1218 throws DirectoryException
1219 {
1220 backendLock.writeLock().lock();
1221
1222 try
1223 {
1224 LDIFReader reader;
1225 try
1226 {
1227 reader = new LDIFReader(importConfig);
1228 }
1229 catch (Exception e)
1230 {
1231 Message m = ERR_LDIF_BACKEND_CANNOT_CREATE_LDIF_READER.get(
1232 stackTraceToSingleLineString(e));
1233 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1234 m, e);
1235 }
1236
1237 entryMap.clear();
1238 childDNs.clear();
1239
1240
1241 try
1242 {
1243 while (true)
1244 {
1245 Entry e = null;
1246 try
1247 {
1248 e = reader.readEntry();
1249 if (e == null)
1250 {
1251 break;
1252 }
1253 }
1254 catch (LDIFException le)
1255 {
1256 if (! le.canContinueReading())
1257 {
1258 Message m = ERR_LDIF_BACKEND_ERROR_READING_LDIF.get(
1259 stackTraceToSingleLineString(le));
1260 throw new DirectoryException(
1261 DirectoryServer.getServerErrorResultCode(), m, le);
1262 }
1263 else
1264 {
1265 continue;
1266 }
1267 }
1268
1269 // Make sure that we don't already have an entry with the same DN. If
1270 // a duplicate is encountered, then log a message and continue.
1271 DN entryDN = e.getDN();
1272 if (entryMap.containsKey(entryDN))
1273 {
1274 Message m = ERR_LDIF_BACKEND_DUPLICATE_ENTRY.get(ldifFilePath,
1275 currentConfig.dn().toString(), entryDN.toString());
1276 logError(m);
1277 reader.rejectLastEntry(m);
1278 continue;
1279 }
1280
1281
1282 // If the entry DN is a base DN, then add it with no more processing.
1283 if (baseDNSet.contains(entryDN))
1284 {
1285 entryMap.put(entryDN, e);
1286 continue;
1287 }
1288
1289
1290 // Make sure that the parent exists. If not, then reject the entry.
1291 boolean isBelowBaseDN = false;
1292 for (DN baseDN : baseDNs)
1293 {
1294 if (baseDN.isAncestorOf(entryDN))
1295 {
1296 isBelowBaseDN = true;
1297 break;
1298 }
1299 }
1300
1301 if (! isBelowBaseDN)
1302 {
1303 Message m = ERR_LDIF_BACKEND_ENTRY_OUT_OF_SCOPE.get(ldifFilePath,
1304 currentConfig.dn().toString(), entryDN.toString());
1305 logError(m);
1306 reader.rejectLastEntry(m);
1307 continue;
1308 }
1309
1310 DN parentDN = entryDN.getParentDNInSuffix();
1311 if ((parentDN == null) || (! entryMap.containsKey(parentDN)))
1312 {
1313 Message m = ERR_LDIF_BACKEND_MISSING_PARENT.get(ldifFilePath,
1314 currentConfig.dn().toString(), entryDN.toString());
1315 logError(m);
1316 reader.rejectLastEntry(m);
1317 continue;
1318 }
1319
1320
1321 // The entry does not exist but its parent does, so add it and update
1322 // the set of children for the parent.
1323 entryMap.put(entryDN, e);
1324
1325 HashSet<DN> childDNSet = childDNs.get(parentDN);
1326 if (childDNSet == null)
1327 {
1328 childDNSet = new HashSet<DN>();
1329 childDNs.put(parentDN, childDNSet);
1330 }
1331
1332 childDNSet.add(entryDN);
1333 }
1334
1335
1336 if (writeLDIF)
1337 {
1338 writeLDIF();
1339 }
1340
1341 return new LDIFImportResult(reader.getEntriesRead(),
1342 reader.getEntriesRejected(),
1343 reader.getEntriesIgnored());
1344 }
1345 catch (DirectoryException de)
1346 {
1347 throw de;
1348 }
1349 catch (Exception e)
1350 {
1351 Message m = ERR_LDIF_BACKEND_ERROR_READING_LDIF.get(
1352 stackTraceToSingleLineString(e));
1353 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
1354 m, e);
1355 }
1356 finally
1357 {
1358 reader.close();
1359 }
1360 }
1361 finally
1362 {
1363 backendLock.writeLock().unlock();
1364 }
1365 }
1366
1367
1368
1369 /**
1370 * {@inheritDoc}
1371 */
1372 @Override()
1373 public boolean supportsBackup()
1374 {
1375 // This backend does not provide a backup/restore mechanism.
1376 return false;
1377 }
1378
1379
1380
1381 /**
1382 * {@inheritDoc}
1383 */
1384 @Override()
1385 public boolean supportsBackup(BackupConfig backupConfig,
1386 StringBuilder unsupportedReason)
1387 {
1388 // This backend does not provide a backup/restore mechanism.
1389 return false;
1390 }
1391
1392
1393
1394 /**
1395 * {@inheritDoc}
1396 */
1397 @Override()
1398 public void createBackup(BackupConfig backupConfig)
1399 throws DirectoryException
1400 {
1401 Message message = ERR_LDIF_BACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get();
1402 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1403 }
1404
1405
1406
1407 /**
1408 * {@inheritDoc}
1409 */
1410 @Override()
1411 public void removeBackup(BackupDirectory backupDirectory, String backupID)
1412 throws DirectoryException
1413 {
1414 Message message = ERR_LDIF_BACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get();
1415 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1416 }
1417
1418
1419
1420 /**
1421 * {@inheritDoc}
1422 */
1423 @Override()
1424 public boolean supportsRestore()
1425 {
1426 // This backend does not provide a backup/restore mechanism.
1427 return false;
1428 }
1429
1430
1431
1432 /**
1433 * {@inheritDoc}
1434 */
1435 @Override()
1436 public void restoreBackup(RestoreConfig restoreConfig)
1437 throws DirectoryException
1438 {
1439 Message message = ERR_LDIF_BACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get();
1440 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1441 }
1442
1443
1444
1445 /**
1446 * {@inheritDoc}
1447 */
1448 @Override()
1449 public void configureBackend(Configuration config)
1450 throws ConfigException
1451 {
1452 if (config != null)
1453 {
1454 Validator.ensureTrue(config instanceof LDIFBackendCfg);
1455 currentConfig = (LDIFBackendCfg) config;
1456 currentConfig.addLDIFChangeListener(this);
1457
1458 baseDNs = new DN[currentConfig.getBaseDN().size()];
1459 currentConfig.getBaseDN().toArray(baseDNs);
1460 if (baseDNs.length != 1)
1461 {
1462 throw new ConfigException(ERR_LDIF_BACKEND_MULTIPLE_BASE_DNS.get(
1463 currentConfig.dn().toString()));
1464 }
1465
1466 baseDNSet = new HashSet<DN>();
1467 for (DN dn : baseDNs)
1468 {
1469 baseDNSet.add(dn);
1470 }
1471
1472 supportedControls = new HashSet<String>(1);
1473 supportedControls.add(OID_SUBTREE_DELETE_CONTROL);
1474
1475 supportedFeatures = new HashSet<String>(0);
1476
1477 ldifFilePath = currentConfig.getLDIFFile();
1478 }
1479 }
1480
1481
1482
1483 /**
1484 * {@inheritDoc}
1485 */
1486 public boolean isConfigurationChangeAcceptable(LDIFBackendCfg configuration,
1487 List<Message> unacceptableReasons)
1488 {
1489 boolean configAcceptable = true;
1490
1491 // Make sure that there is only a single base DN.
1492 if (configuration.getBaseDN().size() != 1)
1493 {
1494 unacceptableReasons.add(ERR_LDIF_BACKEND_MULTIPLE_BASE_DNS.get(
1495 configuration.dn().toString()));
1496 configAcceptable = false;
1497 }
1498
1499 return configAcceptable;
1500 }
1501
1502
1503
1504 /**
1505 * {@inheritDoc}
1506 */
1507 public ConfigChangeResult applyConfigurationChange(
1508 LDIFBackendCfg configuration)
1509 {
1510 // We don't actually need to do anything in response to this. However, if
1511 // the base DNs or LDIF file are different from what we're currently using
1512 // then indicate that admin action is required.
1513 boolean adminActionRequired = false;
1514 LinkedList<Message> messages = new LinkedList<Message>();
1515
1516 if (ldifFilePath != null)
1517 {
1518 File currentLDIF = getFileForPath(ldifFilePath);
1519 File newLDIF = getFileForPath(configuration.getLDIFFile());
1520 if (! currentLDIF.equals(newLDIF))
1521 {
1522 messages.add(INFO_LDIF_BACKEND_LDIF_FILE_CHANGED.get());
1523 adminActionRequired = true;
1524 }
1525 }
1526
1527 if (baseDNSet != null)
1528 {
1529 if (! baseDNSet.equals(configuration.getBaseDN()))
1530 {
1531 messages.add(INFO_LDIF_BACKEND_BASE_DN_CHANGED.get());
1532 adminActionRequired = true;
1533 }
1534 }
1535
1536 currentConfig = configuration;
1537 return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
1538 messages);
1539 }
1540
1541
1542
1543 /**
1544 * {@inheritDoc}
1545 */
1546 public DN getComponentEntryDN()
1547 {
1548 return currentConfig.dn();
1549 }
1550
1551
1552
1553 /**
1554 * {@inheritDoc}
1555 */
1556 public String getClassName()
1557 {
1558 return LDIFBackend.class.getName();
1559 }
1560
1561
1562
1563 /**
1564 * {@inheritDoc}
1565 */
1566 public LinkedHashMap<String,String> getAlerts()
1567 {
1568 LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>();
1569
1570 alerts.put(ALERT_TYPE_LDIF_BACKEND_CANNOT_WRITE_UPDATE,
1571 ALERT_DESCRIPTION_LDIF_BACKEND_CANNOT_WRITE_UPDATE);
1572
1573 return alerts;
1574 }
1575
1576
1577
1578 /**
1579 * {@inheritDoc}
1580 */
1581 public void preloadEntryCache() throws UnsupportedOperationException {
1582 throw new UnsupportedOperationException("Operation not supported.");
1583 }
1584 }
1585