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.backends.jeb;
028    import org.opends.messages.Message;
029    import com.sleepycat.je.config.EnvironmentParams;
030    import com.sleepycat.je.config.ConfigParam;
031    import com.sleepycat.je.*;
032    import java.util.concurrent.ConcurrentHashMap;
033    import java.util.concurrent.atomic.AtomicLong;
034    import java.util.*;
035    import java.io.File;
036    import java.io.FilenameFilter;
037    import org.opends.server.monitors.DatabaseEnvironmentMonitor;
038    import org.opends.server.types.DebugLogLevel;
039    import org.opends.server.types.DN;
040    import org.opends.server.types.FilePermission;
041    import org.opends.server.types.ConfigChangeResult;
042    import org.opends.server.types.ResultCode;
043    import org.opends.server.api.Backend;
044    import org.opends.server.admin.std.server.LocalDBBackendCfg;
045    import org.opends.server.admin.server.ConfigurationChangeListener;
046    import org.opends.server.core.DirectoryServer;
047    import org.opends.server.config.ConfigException;
048    import static org.opends.server.loggers.ErrorLogger.logError;
049    import static org.opends.server.loggers.debug.DebugLogger.*;
050    import org.opends.server.loggers.debug.DebugTracer;
051    import static org.opends.messages.JebMessages.*;
052    import static org.opends.messages.ConfigMessages.
053        ERR_CONFIG_BACKEND_MODE_INVALID;
054    import static org.opends.messages.ConfigMessages.
055        ERR_CONFIG_BACKEND_INSANE_MODE;
056    import static org.opends.server.util.StaticUtils.*;
057    import static org.opends.messages.ConfigMessages.*;
058    
059    /**
060     * Wrapper class for the JE environment. Root container holds all the entry
061     * containers for each base DN. It also maintains all the openings and closings
062     * of the entry containers.
063     */
064    public class RootContainer
065         implements ConfigurationChangeListener<LocalDBBackendCfg>
066    {
067      /**
068       * The tracer object for the debug logger.
069       */
070      private static final DebugTracer TRACER = getTracer();
071    
072    
073      /**
074       * The JE database environment.
075       */
076      private Environment env;
077    
078      //Used to force a checkpoint during import.
079      private CheckpointConfig importForceCheckPoint = new CheckpointConfig();
080    
081      /**
082       * The backend configuration.
083       */
084      private LocalDBBackendCfg config;
085    
086      /**
087       * The backend to which this entry root container belongs.
088       */
089      private Backend backend;
090    
091      /**
092       * The database environment monitor for this JE environment.
093       */
094      private DatabaseEnvironmentMonitor monitor;
095    
096      /**
097       * The base DNs contained in this entryContainer.
098       */
099      private ConcurrentHashMap<DN, EntryContainer> entryContainers;
100    
101      /**
102       * The cached value of the next entry identifier to be assigned.
103       */
104      private AtomicLong nextid = new AtomicLong(1);
105    
106      /**
107       * The compressed schema manager for this backend.
108       */
109      private JECompressedSchema compressedSchema;
110    
111    
112    
113      /**
114       * Creates a new RootContainer object. Each root container represents a JE
115       * environment.
116       *
117       * @param config The configuration of the JE backend.
118       * @param backend A reference to the JE back end that is creating this
119       *                root container.
120       */
121      public RootContainer(Backend backend, LocalDBBackendCfg config)
122      {
123        this.env = null;
124        this.monitor = null;
125        this.entryContainers = new ConcurrentHashMap<DN, EntryContainer>();
126        this.backend = backend;
127        this.config = config;
128        this.compressedSchema = null;
129    
130        config.addLocalDBChangeListener(this);
131        importForceCheckPoint.setForce(true);
132      }
133    
134      /**
135       * Opens the root container using the JE configuration object provided.
136       *
137       * @param envConfig The JE environment configuration.
138       * @throws DatabaseException If an error occurs when creating the environment.
139       * @throws ConfigException If an configuration error occurs while creating
140       * the enviornment.
141       */
142      public void open(EnvironmentConfig envConfig)
143          throws DatabaseException, ConfigException
144      {
145        // Determine the backend database directory.
146        File parentDirectory = getFileForPath(config.getDBDirectory());
147        File backendDirectory = new File(parentDirectory, config.getBackendId());
148    
149        // Create the directory if it doesn't exist.
150        if (!backendDirectory.exists())
151        {
152          if(!backendDirectory.mkdirs())
153          {
154            Message message =
155              ERR_JEB_CREATE_FAIL.get(backendDirectory.getPath());
156            throw new ConfigException(message);
157          }
158        }
159        //Make sure the directory is valid.
160        else if (!backendDirectory.isDirectory())
161        {
162          Message message =
163              ERR_JEB_DIRECTORY_INVALID.get(backendDirectory.getPath());
164          throw new ConfigException(message);
165        }
166    
167        FilePermission backendPermission;
168        try
169        {
170          backendPermission =
171              FilePermission.decodeUNIXMode(config.getDBDirectoryPermissions());
172        }
173        catch(Exception e)
174        {
175          Message message =
176              ERR_CONFIG_BACKEND_MODE_INVALID.get(config.dn().toString());
177          throw new ConfigException(message);
178        }
179    
180        //Make sure the mode will allow the server itself access to
181        //the database
182        if(!backendPermission.isOwnerWritable() ||
183            !backendPermission.isOwnerReadable() ||
184            !backendPermission.isOwnerExecutable())
185        {
186          Message message = ERR_CONFIG_BACKEND_INSANE_MODE.get(
187              config.getDBDirectoryPermissions());
188          throw new ConfigException(message);
189        }
190    
191        // Get the backend database backendDirectory permissions and apply
192        if(FilePermission.canSetPermissions())
193        {
194          try
195          {
196            if(!FilePermission.setPermissions(backendDirectory, backendPermission))
197            {
198              Message message = WARN_JEB_UNABLE_SET_PERMISSIONS.get(
199                  backendPermission.toString(), backendDirectory.toString());
200              logError(message);
201            }
202          }
203          catch(Exception e)
204          {
205            // Log an warning that the permissions were not set.
206            Message message = WARN_JEB_SET_PERMISSIONS_FAILED.get(
207                backendDirectory.toString(), e.toString());
208            logError(message);
209          }
210        }
211    
212        // Open the database environment
213        env = new Environment(backendDirectory,
214                              envConfig);
215    
216        if (debugEnabled())
217        {
218          TRACER.debugInfo("JE (%s) environment opened with the following " +
219              "config: %n%s", JEVersion.CURRENT_VERSION.toString(),
220                              env.getConfig().toString());
221    
222          // Get current size of heap in bytes
223          long heapSize = Runtime.getRuntime().totalMemory();
224    
225          // Get maximum size of heap in bytes. The heap cannot grow beyond this
226          // size.
227          // Any attempt will result in an OutOfMemoryException.
228          long heapMaxSize = Runtime.getRuntime().maxMemory();
229    
230          // Get amount of free memory within the heap in bytes. This size will
231          // increase
232          // after garbage collection and decrease as new objects are created.
233          long heapFreeSize = Runtime.getRuntime().freeMemory();
234    
235          TRACER.debugInfo("Current size of heap: %d bytes", heapSize);
236          TRACER.debugInfo("Max size of heap: %d bytes", heapMaxSize);
237          TRACER.debugInfo("Free memory in heap: %d bytes", heapFreeSize);
238        }
239    
240        compressedSchema = new JECompressedSchema(env);
241        openAndRegisterEntryContainers(config.getBaseDN());
242      }
243    
244      /**
245       * Opens the entry container for a base DN. If the entry container does not
246       * exist for the base DN, it will be created. The entry container will be
247       * opened with the same mode as the root container. Any entry containers
248       * opened in a read only root container will also be read only. Any entry
249       * containers opened in a non transactional root container will also be non
250       * transactional.
251       *
252       * @param baseDN The base DN of the entry container to open.
253       * @param name The name of the entry container or <CODE>NULL</CODE> to open
254       * the default entry container for the given base DN.
255       * @return The opened entry container.
256       * @throws DatabaseException If an error occurs while opening the entry
257       *                           container.
258       * @throws ConfigException If an configuration error occurs while opening
259       *                         the entry container.
260       */
261      public EntryContainer openEntryContainer(DN baseDN, String name)
262          throws DatabaseException, ConfigException
263      {
264        String databasePrefix;
265        if(name == null || name.equals(""))
266        {
267          databasePrefix = baseDN.toNormalizedString();
268        }
269        else
270        {
271          databasePrefix = name;
272        }
273    
274        EntryContainer ec = new EntryContainer(baseDN, databasePrefix,
275                                               backend, config, env, this);
276        ec.open();
277        return ec;
278      }
279    
280      /**
281       * Registeres the entry container for a base DN.
282       *
283       * @param baseDN The base DN of the entry container to close.
284       * @param entryContainer The entry container to register for the baseDN.
285       * @throws DatabaseException If an error occurs while opening the entry
286       *                           container.
287       */
288      public void registerEntryContainer(DN baseDN,
289                                         EntryContainer entryContainer)
290          throws DatabaseException
291      {
292        EntryContainer ec1=this.entryContainers.get(baseDN);
293    
294        //If an entry container for this baseDN is already open we don't allow
295        //another to be opened.
296        if (ec1 != null)
297          throw new DatabaseException("An entry container named " +
298              ec1.getDatabasePrefix() + " is alreadly registered for base DN " +
299              baseDN.toString());
300    
301        this.entryContainers.put(baseDN, entryContainer);
302      }
303    
304      /**
305       * Opens the entry containers for multiple base DNs.
306       *
307       * @param baseDNs The base DNs of the entry containers to open.
308       * @throws DatabaseException If an error occurs while opening the entry
309       *                           container.
310       * @throws ConfigException if a configuration error occurs while opening the
311       *                         container.
312       */
313      private void openAndRegisterEntryContainers(Set<DN> baseDNs)
314          throws DatabaseException, ConfigException
315      {
316        EntryID id;
317        EntryID highestID = null;
318        for(DN baseDN : baseDNs)
319        {
320          EntryContainer ec = openEntryContainer(baseDN, null);
321          id = ec.getHighestEntryID();
322          registerEntryContainer(baseDN, ec);
323          if(highestID == null || id.compareTo(highestID) > 0)
324          {
325            highestID = id;
326          }
327        }
328    
329        nextid = new AtomicLong(highestID.longValue() + 1);
330      }
331    
332      /**
333       * Unregisteres the entry container for a base DN.
334       *
335       * @param baseDN The base DN of the entry container to close.
336       * @return The entry container that was unregistered or NULL if a entry
337       * container for the base DN was not registered.
338       */
339      public EntryContainer unregisterEntryContainer(DN baseDN)
340      {
341        return entryContainers.remove(baseDN);
342    
343      }
344    
345      /**
346       * Retrieves the compressed schema manager for this backend.
347       *
348       * @return  The compressed schema manager for this backend.
349       */
350      public JECompressedSchema getCompressedSchema()
351      {
352        return compressedSchema;
353      }
354    
355      /**
356       * Get the DatabaseEnvironmentMonitor object for JE environment used by this
357       * root container.
358       *
359       * @return The DatabaseEnvironmentMonito object.
360       */
361      public DatabaseEnvironmentMonitor getMonitorProvider()
362      {
363        if(monitor == null)
364        {
365          String monitorName = backend.getBackendID() + " Database Environment";
366          monitor = new DatabaseEnvironmentMonitor(monitorName, this);
367        }
368    
369        return monitor;
370      }
371    
372      /**
373       * Preload the database cache. There is no preload if the configured preload
374       * time limit is zero.
375       *
376       * @param timeLimit The time limit for the preload process.
377       */
378      public void preload(long timeLimit)
379      {
380        if (timeLimit > 0)
381        {
382          // Get a list of all the databases used by the backend.
383          ArrayList<DatabaseContainer> dbList =
384              new ArrayList<DatabaseContainer>();
385          for (EntryContainer ec : entryContainers.values())
386          {
387            ec.sharedLock.lock();
388            try
389            {
390              ec.listDatabases(dbList);
391            }
392            finally
393            {
394              ec.sharedLock.unlock();
395            }
396          }
397    
398          // Sort the list in order of priority.
399          Collections.sort(dbList, new DbPreloadComparator());
400    
401          // Preload each database until we reach the time limit or the cache
402          // is filled.
403          try
404          {
405            // Configure preload of Leaf Nodes (LNs) containing the data values.
406            PreloadConfig preloadConfig = new PreloadConfig();
407            preloadConfig.setLoadLNs(true);
408    
409            Message message =
410                NOTE_JEB_CACHE_PRELOAD_STARTED.get(backend.getBackendID());
411            logError(message);
412    
413            boolean isInterrupted = false;
414    
415            long timeEnd = System.currentTimeMillis() + timeLimit;
416    
417            for (DatabaseContainer db : dbList)
418            {
419              // Calculate the remaining time.
420              long timeRemaining = timeEnd - System.currentTimeMillis();
421              if (timeRemaining <= 0)
422              {
423                break;
424              }
425    
426              preloadConfig.setMaxMillisecs(timeRemaining);
427              PreloadStats preloadStats = db.preload(preloadConfig);
428    
429              if(debugEnabled())
430              {
431                TRACER.debugInfo("file=" + db.getName() +
432                          " LNs=" + preloadStats.getNLNsLoaded());
433              }
434    
435              // Stop if the cache is full or the time limit has been exceeded.
436              PreloadStatus preloadStatus = preloadStats.getStatus();
437              if (preloadStatus != PreloadStatus.SUCCESS)
438              {
439                if (preloadStatus == PreloadStatus.EXCEEDED_TIME) {
440                  message =
441                    NOTE_JEB_CACHE_PRELOAD_INTERRUPTED_BY_TIME.get(
442                    backend.getBackendID(), db.getName());
443                  logError(message);
444                } else if (preloadStatus == PreloadStatus.FILLED_CACHE) {
445                  message =
446                    NOTE_JEB_CACHE_PRELOAD_INTERRUPTED_BY_SIZE.get(
447                    backend.getBackendID(), db.getName());
448                  logError(message);
449                } else {
450                  message =
451                    NOTE_JEB_CACHE_PRELOAD_INTERRUPTED_UNKNOWN.get(
452                    backend.getBackendID(), db.getName());
453                  logError(message);
454                }
455    
456                isInterrupted = true;
457                break;
458              }
459    
460              message = NOTE_JEB_CACHE_DB_PRELOADED.get(db.getName());
461              logError(message);
462            }
463    
464            if (!isInterrupted) {
465              message = NOTE_JEB_CACHE_PRELOAD_DONE.get(backend.getBackendID());
466              logError(message);
467            }
468    
469            // Log an informational message about the size of the cache.
470            EnvironmentStats stats = env.getStats(new StatsConfig());
471            long total = stats.getCacheTotalBytes();
472    
473            message =
474                NOTE_JEB_CACHE_SIZE_AFTER_PRELOAD.get(total / (1024 * 1024));
475            logError(message);
476          }
477          catch (DatabaseException e)
478          {
479            if (debugEnabled())
480            {
481              TRACER.debugCaught(DebugLogLevel.ERROR, e);
482            }
483    
484            Message message =
485              ERR_JEB_CACHE_PRELOAD.get(backend.getBackendID(),
486              (e.getCause() != null ? e.getCause().getMessage() :
487                stackTraceToSingleLineString(e)));
488            logError(message);
489          }
490        }
491      }
492    
493      /**
494       * Synchronously invokes the cleaner on the database environment then forces a
495       * checkpoint to delete the log files that are no longer in use.
496       *
497       * @throws DatabaseException If an error occurs while cleaning the database
498       * environment.
499       */
500      private void cleanDatabase()
501           throws DatabaseException
502      {
503        Message message;
504    
505        FilenameFilter filenameFilter = new FilenameFilter()
506        {
507          public boolean accept(File d, String name)
508          {
509            return name.endsWith(".jdb");
510          }
511        };
512    
513        File backendDirectory = env.getHome();
514        int beforeLogfileCount = backendDirectory.list(filenameFilter).length;
515    
516        message = NOTE_JEB_CLEAN_DATABASE_START.get(
517            beforeLogfileCount, backendDirectory.getPath());
518        logError(message);
519    
520        int currentCleaned = 0;
521        int totalCleaned = 0;
522        while ((currentCleaned = env.cleanLog()) > 0)
523        {
524          totalCleaned += currentCleaned;
525        }
526    
527        message = NOTE_JEB_CLEAN_DATABASE_MARKED.get(totalCleaned);
528        logError(message);
529    
530        if (totalCleaned > 0)
531        {
532          CheckpointConfig force = new CheckpointConfig();
533          force.setForce(true);
534          env.checkpoint(force);
535        }
536    
537        int afterLogfileCount = backendDirectory.list(filenameFilter).length;
538    
539        message = NOTE_JEB_CLEAN_DATABASE_FINISH.get(afterLogfileCount);
540        logError(message);
541    
542      }
543    
544      /**
545       * Close the root entryContainer.
546       *
547       * @throws DatabaseException If an error occurs while attempting to close
548       * the entryContainer.
549       */
550      public void close() throws DatabaseException
551      {
552        for(DN baseDN : entryContainers.keySet())
553        {
554          EntryContainer ec = unregisterEntryContainer(baseDN);
555          ec.exclusiveLock.lock();
556          try
557          {
558            ec.close();
559          }
560          finally
561          {
562            ec.exclusiveLock.unlock();
563          }
564        }
565    
566        compressedSchema.close();
567    
568        if (env != null)
569        {
570          env.close();
571          env = null;
572        }
573    
574        config.removeLocalDBChangeListener(this);
575      }
576    
577      /**
578       * Return all the entry containers in this root container.
579       *
580       * @return The entry containers in this root container.
581       */
582      public Collection<EntryContainer> getEntryContainers()
583      {
584        return entryContainers.values();
585      }
586    
587      /**
588       * Returns all the baseDNs this root container stores.
589       *
590       * @return The set of DNs this root container stores.
591       */
592      public Set<DN> getBaseDNs()
593      {
594        return entryContainers.keySet();
595      }
596    
597      /**
598       * Return the entry container for a specific base DN.
599       *
600       * @param baseDN The base DN of the entry container to retrive.
601       * @return The entry container for the base DN.
602       */
603      public EntryContainer getEntryContainer(DN baseDN)
604      {
605        EntryContainer ec = null;
606        DN nodeDN = baseDN;
607    
608        while (ec == null && nodeDN != null)
609        {
610          ec = entryContainers.get(nodeDN);
611          if (ec == null)
612          {
613            nodeDN = nodeDN.getParentDNInSuffix();
614          }
615        }
616    
617        return ec;
618      }
619    
620      /**
621       * Get the environment stats of the JE environment used in this root
622       * container.
623       *
624       * @param statsConfig The configuration to use for the EnvironmentStats
625       *                    object.
626       * @return The environment status of the JE environment.
627       * @throws DatabaseException If an error occurs while retriving the stats
628       *                           object.
629       */
630      public EnvironmentStats getEnvironmentStats(StatsConfig statsConfig)
631          throws DatabaseException
632      {
633        return env.getStats(statsConfig);
634      }
635    
636      /**
637       * Get the environment lock stats of the JE environment used in this
638       * root container.
639       *
640       * @param statsConfig The configuration to use for the EnvironmentStats
641       *                    object.
642       * @return The environment status of the JE environment.
643       * @throws DatabaseException If an error occurs while retriving the stats
644       *                           object.
645       */
646      public LockStats getEnvironmentLockStats(StatsConfig statsConfig)
647          throws DatabaseException
648      {
649        return env.getLockStats(statsConfig);
650      }
651    
652      /**
653       * Get the environment transaction stats of the JE environment used
654       * in this root container.
655       *
656       * @param statsConfig The configuration to use for the EnvironmentStats
657       *                    object.
658       * @return The environment status of the JE environment.
659       * @throws DatabaseException If an error occurs while retriving the stats
660       *                           object.
661       */
662      public TransactionStats getEnvironmentTransactionStats(
663          StatsConfig statsConfig) throws DatabaseException
664      {
665        return env.getTransactionStats(statsConfig);
666      }
667    
668      /**
669       * Get the environment config of the JE environment used in this root
670       * container.
671       *
672       * @return The environment config of the JE environment.
673       * @throws DatabaseException If an error occurs while retriving the
674       *                           configuration object.
675       */
676      public EnvironmentConfig getEnvironmentConfig() throws DatabaseException
677      {
678        return env.getConfig();
679      }
680    
681      /**
682       * Get the backend configuration used by this root container.
683       *
684       * @return The JE backend configuration used by this root container.
685       */
686      public LocalDBBackendCfg getConfiguration()
687      {
688        return config;
689      }
690    
691      /**
692       * Get the total number of entries in this root container.
693       *
694       * @return The number of entries in this root container
695       * @throws DatabaseException If an error occurs while retriving the entry
696       *                           count.
697       */
698      public long getEntryCount() throws DatabaseException
699      {
700        long entryCount = 0;
701        for(EntryContainer ec : this.entryContainers.values())
702        {
703          ec.sharedLock.lock();
704          try
705          {
706            entryCount += ec.getEntryCount();
707          }
708          finally
709          {
710            ec.sharedLock.unlock();
711          }
712        }
713    
714        return entryCount;
715      }
716    
717      /**
718       * Assign the next entry ID.
719       *
720       * @return The assigned entry ID.
721       */
722      public EntryID getNextEntryID()
723      {
724        return new EntryID(nextid.getAndIncrement());
725      }
726    
727      /**
728       * Return the lowest entry ID assigned.
729       *
730       * @return The lowest entry ID assigned.
731       */
732      public Long getLowestEntryID()
733      {
734        return 1L;
735      }
736    
737      /**
738       * Return the highest entry ID assigned.
739       *
740       * @return The highest entry ID assigned.
741       */
742      public Long getHighestEntryID()
743      {
744        return (nextid.get() - 1);
745      }
746    
747      /**
748       * Resets the next entry ID counter to zero.  This should only be used after
749       * clearing all databases.
750       */
751      public void resetNextEntryID()
752      {
753        nextid.set(1);
754      }
755    
756    
757    
758      /**
759       * {@inheritDoc}
760       */
761      public boolean isConfigurationChangeAcceptable(
762          LocalDBBackendCfg cfg,
763          List<Message> unacceptableReasons)
764      {
765        boolean acceptable = true;
766    
767        File parentDirectory = getFileForPath(config.getDBDirectory());
768        File backendDirectory = new File(parentDirectory, config.getBackendId());
769    
770        //Make sure the directory either already exists or is able to create.
771        if (!backendDirectory.exists())
772        {
773          if(!backendDirectory.mkdirs())
774          {
775            Message message =
776              ERR_JEB_CREATE_FAIL.get(backendDirectory.getPath());
777            unacceptableReasons.add(message);
778            acceptable = false;
779          }
780          else
781          {
782            backendDirectory.delete();
783          }
784        }
785        //Make sure the directory is valid.
786        else if (!backendDirectory.isDirectory())
787        {
788          Message message =
789              ERR_JEB_DIRECTORY_INVALID.get(backendDirectory.getPath());
790          unacceptableReasons.add(message);
791          acceptable = false;
792        }
793    
794        try
795        {
796          FilePermission newBackendPermission =
797              FilePermission.decodeUNIXMode(cfg.getDBDirectoryPermissions());
798    
799          //Make sure the mode will allow the server itself access to
800          //the database
801          if(!newBackendPermission.isOwnerWritable() ||
802              !newBackendPermission.isOwnerReadable() ||
803              !newBackendPermission.isOwnerExecutable())
804          {
805            Message message = ERR_CONFIG_BACKEND_INSANE_MODE.get(
806                cfg.getDBDirectoryPermissions());
807            unacceptableReasons.add(message);
808            acceptable = false;
809          }
810        }
811        catch(Exception e)
812        {
813          Message message =
814                  ERR_CONFIG_BACKEND_MODE_INVALID.get(cfg.dn().toString());
815          unacceptableReasons.add(message);
816          acceptable = false;
817        }
818    
819        try
820        {
821          ConfigurableEnvironment.parseConfigEntry(cfg);
822        }
823        catch (Exception e)
824        {
825          unacceptableReasons.add(Message.raw(e.getLocalizedMessage()));
826          acceptable = false;
827        }
828    
829        return acceptable;
830      }
831    
832    
833    
834      /**
835       * {@inheritDoc}
836       */
837      public ConfigChangeResult applyConfigurationChange(LocalDBBackendCfg cfg)
838      {
839        ConfigChangeResult ccr;
840        boolean adminActionRequired = false;
841        ArrayList<Message> messages = new ArrayList<Message>();
842    
843        try
844        {
845          if(env != null)
846          {
847            // Check if any JE non-mutable properties were changed.
848            EnvironmentConfig oldEnvConfig = env.getConfig();
849            EnvironmentConfig newEnvConfig =
850                ConfigurableEnvironment.parseConfigEntry(cfg);
851            Map<?,?> paramsMap = EnvironmentParams.SUPPORTED_PARAMS;
852    
853            // Iterate through native JE properties.
854            SortedSet<String> jeProperties = cfg.getJEProperty();
855            for (String jeEntry : jeProperties) {
856              // There is no need to validate properties yet again.
857              StringTokenizer st = new StringTokenizer(jeEntry, "=");
858              if (st.countTokens() == 2) {
859                String jePropertyName = st.nextToken();
860                String jePropertyValue = st.nextToken();
861                ConfigParam param = (ConfigParam) paramsMap.get(jePropertyName);
862                if (!param.isMutable()) {
863                  String oldValue = oldEnvConfig.getConfigParam(param.getName());
864                  String newValue = jePropertyValue;
865                  if (!oldValue.equalsIgnoreCase(newValue)) {
866                    adminActionRequired = true;
867                    messages.add(INFO_CONFIG_JE_PROPERTY_REQUIRES_RESTART.get(
868                            jePropertyName));
869                    if(debugEnabled()) {
870                      TRACER.debugInfo("The change to the following property " +
871                        "will take effect when the component is restarted: " +
872                        jePropertyName);
873                    }
874                  }
875                }
876              }
877            }
878    
879            // Iterate through JE configuration attributes.
880            for (Object o : paramsMap.values())
881            {
882              ConfigParam param = (ConfigParam) o;
883              if (!param.isMutable())
884              {
885                String oldValue = oldEnvConfig.getConfigParam(param.getName());
886                String newValue = newEnvConfig.getConfigParam(param.getName());
887                if (!oldValue.equalsIgnoreCase(newValue))
888                {
889                  adminActionRequired = true;
890                  String configAttr = ConfigurableEnvironment.
891                      getAttributeForProperty(param.getName());
892                  if (configAttr != null)
893                  {
894                    messages.add(NOTE_JEB_CONFIG_ATTR_REQUIRES_RESTART.get(
895                            configAttr));
896                  }
897                  if(debugEnabled())
898                  {
899                    TRACER.debugInfo("The change to the following property will " +
900                        "take effect when the backend is restarted: " +
901                        param.getName());
902                  }
903                }
904              }
905            }
906    
907            // This takes care of changes to the JE environment for those
908            // properties that are mutable at runtime.
909            env.setMutableConfig(newEnvConfig);
910    
911            if (debugEnabled())
912            {
913              TRACER.debugInfo(env.getConfig().toString());
914            }
915          }
916    
917          // Create the directory if it doesn't exist.
918          if(!cfg.getDBDirectory().equals(this.config.getDBDirectory()))
919          {
920            File parentDirectory = getFileForPath(config.getDBDirectory());
921            File backendDirectory =
922              new File(parentDirectory, config.getBackendId());
923    
924            if (!backendDirectory.exists())
925            {
926              if(!backendDirectory.mkdirs())
927              {
928                messages.add(ERR_JEB_CREATE_FAIL.get(
929                    backendDirectory.getPath()));
930                ccr = new ConfigChangeResult(
931                    DirectoryServer.getServerErrorResultCode(),
932                    adminActionRequired,
933                    messages);
934                return ccr;
935              }
936            }
937            //Make sure the directory is valid.
938            else if (!backendDirectory.isDirectory())
939            {
940              messages.add(ERR_JEB_DIRECTORY_INVALID.get(
941                  backendDirectory.getPath()));
942              ccr = new ConfigChangeResult(
943                  DirectoryServer.getServerErrorResultCode(),
944                  adminActionRequired,
945                  messages);
946              return ccr;
947            }
948    
949            adminActionRequired = true;
950            messages.add(NOTE_JEB_CONFIG_DB_DIR_REQUIRES_RESTART.get(
951                            this.config.getDBDirectory(), cfg.getDBDirectory()));
952          }
953    
954          if(!cfg.getDBDirectoryPermissions().equalsIgnoreCase(
955              config.getDBDirectoryPermissions()) ||
956              !cfg.getDBDirectory().equals(this.config.getDBDirectory()))
957          {
958            FilePermission backendPermission;
959            try
960            {
961              backendPermission =
962                  FilePermission.decodeUNIXMode(cfg.getDBDirectoryPermissions());
963            }
964            catch(Exception e)
965            {
966              messages.add(ERR_CONFIG_BACKEND_MODE_INVALID.get(
967                  config.dn().toString()));
968              ccr = new ConfigChangeResult(
969                  DirectoryServer.getServerErrorResultCode(),
970                  adminActionRequired,
971                  messages);
972              return ccr;
973            }
974    
975            //Make sure the mode will allow the server itself access to
976            //the database
977            if(!backendPermission.isOwnerWritable() ||
978                !backendPermission.isOwnerReadable() ||
979                !backendPermission.isOwnerExecutable())
980            {
981              messages.add(ERR_CONFIG_BACKEND_INSANE_MODE.get(
982                  cfg.getDBDirectoryPermissions()));
983              ccr = new ConfigChangeResult(
984                  DirectoryServer.getServerErrorResultCode(),
985                  adminActionRequired,
986                  messages);
987              return ccr;
988            }
989    
990            // Get the backend database backendDirectory permissions and apply
991            if(FilePermission.canSetPermissions())
992            {
993              File parentDirectory = getFileForPath(config.getDBDirectory());
994              File backendDirectory = new File(parentDirectory,
995                  config.getBackendId());
996              try
997              {
998                if(!FilePermission.setPermissions(backendDirectory,
999                    backendPermission))
1000                {
1001                  Message message = WARN_JEB_UNABLE_SET_PERMISSIONS.get(
1002                      backendPermission.toString(), backendDirectory.toString());
1003                  logError(message);
1004                }
1005              }
1006              catch(Exception e)
1007              {
1008                // Log an warning that the permissions were not set.
1009                Message message = WARN_JEB_SET_PERMISSIONS_FAILED.get(
1010                    backendDirectory.toString(), e.toString());
1011                logError(message);
1012              }
1013            }
1014          }
1015    
1016          this.config = cfg;
1017        }
1018        catch (Exception e)
1019        {
1020          messages.add(Message.raw(stackTraceToSingleLineString(e)));
1021          ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
1022                                       adminActionRequired,
1023                                       messages);
1024          return ccr;
1025        }
1026    
1027    
1028        ccr = new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
1029                                     messages);
1030        return ccr;
1031      }
1032    
1033      /**
1034       * Force a checkpoint.
1035       *
1036       * @throws DatabaseException If a database error occurs.
1037       */
1038      public void importForceCheckPoint() throws DatabaseException {
1039        env.checkpoint(importForceCheckPoint);
1040      }
1041    
1042      /**
1043       * Run the cleaner and return the number of files cleaned.
1044       *
1045       * @return The number of logs cleaned.
1046       * @throws DatabaseException If a database error occurs.
1047       */
1048      public int cleanedLogFiles() throws DatabaseException {
1049        int cleaned, totalCleaned = 0;
1050        while((cleaned = env.cleanLog()) > 0) {
1051          totalCleaned += cleaned;
1052        }
1053        return totalCleaned;
1054      }
1055    }