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