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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.backends.jeb;
028
029import java.io.File;
030import java.util.*;
031import java.util.concurrent.ConcurrentHashMap;
032import java.util.concurrent.atomic.AtomicLong;
033
034import org.forgerock.i18n.LocalizableMessage;
035import org.forgerock.i18n.slf4j.LocalizedLogger;
036import org.forgerock.opendj.config.server.ConfigException;
037import org.opends.server.admin.server.ConfigurationChangeListener;
038import org.opends.server.admin.std.server.LocalDBBackendCfg;
039import org.opends.server.api.Backend;
040import org.opends.server.core.DirectoryServer;
041import org.forgerock.opendj.config.server.ConfigChangeResult;
042import org.opends.server.types.DN;
043import org.opends.server.types.FilePermission;
044import org.opends.server.types.InitializationException;
045
046import com.sleepycat.je.*;
047import com.sleepycat.je.config.ConfigParam;
048import com.sleepycat.je.config.EnvironmentParams;
049
050import static org.opends.messages.ConfigMessages.*;
051import static org.opends.messages.BackendMessages.*;
052import static org.opends.server.util.StaticUtils.*;
053
054/**
055 * Wrapper class for the JE environment. Root container holds all the entry
056 * containers for each base DN. It also maintains all the openings and closings
057 * of the entry containers.
058 */
059public class RootContainer
060     implements ConfigurationChangeListener<LocalDBBackendCfg>
061{
062  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
063
064  /** The JE database environment. */
065  private Environment env;
066
067  /** Used to force a checkpoint during import. */
068  private final CheckpointConfig importForceCheckPoint = new CheckpointConfig();
069
070  /** The backend configuration. */
071  private LocalDBBackendCfg config;
072
073  /** The backend to which this entry root container belongs. */
074  private final Backend<?> backend;
075
076  /** The database environment monitor for this JE environment. */
077  private DatabaseEnvironmentMonitor monitor;
078
079  /** The base DNs contained in this root container. */
080  private final ConcurrentHashMap<DN, EntryContainer> entryContainers = new ConcurrentHashMap<>();
081
082  /** The cached value of the next entry identifier to be assigned. */
083  private AtomicLong nextid = new AtomicLong(1);
084
085  /** The compressed schema manager for this backend. */
086  private JECompressedSchema compressedSchema;
087
088
089
090  /**
091   * Creates a new RootContainer object. Each root container represents a JE
092   * environment.
093   *
094   * @param config The configuration of the JE backend.
095   * @param backend A reference to the JE back end that is creating this
096   *                root container.
097   */
098  RootContainer(Backend<?> backend, LocalDBBackendCfg config)
099  {
100    this.backend = backend;
101    this.config = config;
102
103    getMonitorProvider().enableFilterUseStats(config.isIndexFilterAnalyzerEnabled());
104    getMonitorProvider().setMaxEntries(config.getIndexFilterAnalyzerMaxFilters());
105
106    config.addLocalDBChangeListener(this);
107    importForceCheckPoint.setForce(true);
108  }
109
110  /**
111   * Opens the root container using the JE configuration object provided.
112   *
113   * @param  envConfig               The JE environment configuration.
114   * @throws DatabaseException       If a database error occurs when creating
115   *                                 the environment.
116   * @throws InitializationException If an initialization error occurs while
117   *                                 creating the environment.
118   * @throws ConfigException         If an configuration error occurs while
119   *                                 creating the environment.
120   */
121  public void open(EnvironmentConfig envConfig)
122      throws DatabaseException, InitializationException, ConfigException
123  {
124    // Determine the backend database directory.
125    File parentDirectory = getFileForPath(config.getDBDirectory());
126    File backendDirectory = new File(parentDirectory, config.getBackendId());
127
128    // Create the directory if it doesn't exist.
129    if (!backendDirectory.exists())
130    {
131      if(!backendDirectory.mkdirs())
132      {
133        throw new ConfigException(ERR_CREATE_FAIL.get(backendDirectory.getPath()));
134      }
135    }
136    //Make sure the directory is valid.
137    else if (!backendDirectory.isDirectory())
138    {
139      throw new ConfigException(ERR_DIRECTORY_INVALID.get(backendDirectory.getPath()));
140    }
141
142    FilePermission backendPermission;
143    try
144    {
145      backendPermission =
146          FilePermission.decodeUNIXMode(config.getDBDirectoryPermissions());
147    }
148    catch(Exception e)
149    {
150      throw new ConfigException(ERR_CONFIG_BACKEND_MODE_INVALID.get(config.dn()));
151    }
152
153    //Make sure the mode will allow the server itself access to
154    //the database
155    if(!backendPermission.isOwnerWritable() ||
156        !backendPermission.isOwnerReadable() ||
157        !backendPermission.isOwnerExecutable())
158    {
159      LocalizableMessage message = ERR_CONFIG_BACKEND_INSANE_MODE.get(
160          config.getDBDirectoryPermissions());
161      throw new ConfigException(message);
162    }
163
164    // Get the backend database backendDirectory permissions and apply
165    try
166    {
167      if(!FilePermission.setPermissions(backendDirectory, backendPermission))
168      {
169        logger.warn(WARN_UNABLE_SET_PERMISSIONS, backendPermission, backendDirectory);
170      }
171    }
172    catch(Exception e)
173    {
174      // Log an warning that the permissions were not set.
175      logger.warn(WARN_SET_PERMISSIONS_FAILED, backendDirectory, e);
176    }
177
178    // Open the database environment
179    env = new Environment(backendDirectory,
180                          envConfig);
181
182    if (logger.isTraceEnabled())
183    {
184      logger.trace("JE (%s) environment opened with the following config: %n%s",
185          JEVersion.CURRENT_VERSION, env.getConfig());
186
187      // Get current size of heap in bytes
188      long heapSize = Runtime.getRuntime().totalMemory();
189
190      // Get maximum size of heap in bytes. The heap cannot grow beyond this size.
191      // Any attempt will result in an OutOfMemoryException.
192      long heapMaxSize = Runtime.getRuntime().maxMemory();
193
194      // Get amount of free memory within the heap in bytes. This size will increase
195      // after garbage collection and decrease as new objects are created.
196      long heapFreeSize = Runtime.getRuntime().freeMemory();
197
198      logger.trace("Current size of heap: %d bytes", heapSize);
199      logger.trace("Max size of heap: %d bytes", heapMaxSize);
200      logger.trace("Free memory in heap: %d bytes", heapFreeSize);
201    }
202
203    compressedSchema = new JECompressedSchema(env);
204    openAndRegisterEntryContainers(config.getBaseDN());
205  }
206
207  /**
208   * Opens the entry container for a base DN. If the entry container does not
209   * exist for the base DN, it will be created. The entry container will be
210   * opened with the same mode as the root container. Any entry containers
211   * opened in a read only root container will also be read only. Any entry
212   * containers opened in a non transactional root container will also be non
213   * transactional.
214   *
215   * @param baseDN The base DN of the entry container to open.
216   * @param name The name of the entry container or <CODE>NULL</CODE> to open
217   * the default entry container for the given base DN.
218   * @return The opened entry container.
219   * @throws DatabaseException If an error occurs while opening the entry
220   *                           container.
221   * @throws ConfigException If an configuration error occurs while opening
222   *                         the entry container.
223   */
224  EntryContainer openEntryContainer(DN baseDN, String name)
225      throws DatabaseException, ConfigException
226  {
227    String databasePrefix;
228    if(name == null || name.equals(""))
229    {
230      databasePrefix = baseDN.toNormalizedUrlSafeString();
231    }
232    else
233    {
234      databasePrefix = name;
235    }
236
237    EntryContainer ec = new EntryContainer(baseDN, databasePrefix, backend.getBackendID(), config, env, this);
238    ec.open();
239    return ec;
240  }
241
242  /**
243   * Registers the entry container for a base DN.
244   *
245   * @param baseDN The base DN of the entry container to close.
246   * @param entryContainer The entry container to register for the baseDN.
247   * @throws InitializationException If an error occurs while opening the
248   *                                 entry container.
249   */
250  void registerEntryContainer(DN baseDN, EntryContainer entryContainer)
251      throws InitializationException
252  {
253    EntryContainer ec1 = this.entryContainers.get(baseDN);
254
255    // If an entry container for this baseDN is already open we don't allow
256    // another to be opened.
257    if (ec1 != null)
258    {
259      throw new InitializationException(ERR_ENTRY_CONTAINER_ALREADY_REGISTERED.get(ec1.getDatabasePrefix(), baseDN));
260    }
261
262    this.entryContainers.put(baseDN, entryContainer);
263  }
264
265  /**
266   * Opens the entry containers for multiple base DNs.
267   *
268   * @param baseDNs The base DNs of the entry containers to open.
269   * @throws DatabaseException       If a database error occurs while opening
270   *                                 the entry container.
271   * @throws InitializationException If an initialization error occurs while
272   *                                 opening the entry container.
273   * @throws ConfigException         If a configuration error occurs while
274   *                                 opening the entry container.
275   */
276  private void openAndRegisterEntryContainers(Set<DN> baseDNs)
277      throws DatabaseException, InitializationException, ConfigException
278  {
279    EntryID id;
280    EntryID highestID = null;
281    for(DN baseDN : baseDNs)
282    {
283      EntryContainer ec = openEntryContainer(baseDN, null);
284      id = ec.getHighestEntryID();
285      registerEntryContainer(baseDN, ec);
286      if(highestID == null || id.compareTo(highestID) > 0)
287      {
288        highestID = id;
289      }
290    }
291
292    nextid = new AtomicLong(highestID.longValue() + 1);
293  }
294
295  /**
296   * Unregisters the entry container for a base DN.
297   *
298   * @param baseDN The base DN of the entry container to close.
299   * @return The entry container that was unregistered or NULL if a entry
300   * container for the base DN was not registered.
301   */
302  EntryContainer unregisterEntryContainer(DN baseDN)
303  {
304    return entryContainers.remove(baseDN);
305  }
306
307  /**
308   * Retrieves the compressed schema manager for this backend.
309   *
310   * @return  The compressed schema manager for this backend.
311   */
312  public JECompressedSchema getCompressedSchema()
313  {
314    return compressedSchema;
315  }
316
317  /**
318   * Get the DatabaseEnvironmentMonitor object for JE environment used by this
319   * root container.
320   *
321   * @return The DatabaseEnvironmentMonito object.
322   */
323  public DatabaseEnvironmentMonitor getMonitorProvider()
324  {
325    if(monitor == null)
326    {
327      String monitorName = backend.getBackendID() + " Database Environment";
328      monitor = new DatabaseEnvironmentMonitor(monitorName, this);
329    }
330
331    return monitor;
332  }
333
334  /**
335   * Preload the database cache. There is no preload if the configured preload
336   * time limit is zero.
337   *
338   * @param timeLimit The time limit for the preload process.
339   */
340  public void preload(long timeLimit)
341  {
342    if (timeLimit > 0)
343    {
344      // Get a list of all the databases used by the backend.
345      ArrayList<DatabaseContainer> dbList = new ArrayList<>();
346      for (EntryContainer ec : entryContainers.values())
347      {
348        ec.sharedLock.lock();
349        try
350        {
351          ec.listDatabases(dbList);
352        }
353        finally
354        {
355          ec.sharedLock.unlock();
356        }
357      }
358
359      // Sort the list in order of priority.
360      Collections.sort(dbList, new DbPreloadComparator());
361
362      // Preload each database until we reach the time limit or the cache
363      // is filled.
364      try
365      {
366        // Configure preload of Leaf Nodes (LNs) containing the data values.
367        PreloadConfig preloadConfig = new PreloadConfig();
368        preloadConfig.setLoadLNs(true);
369
370        logger.info(NOTE_CACHE_PRELOAD_STARTED, backend.getBackendID());
371
372        boolean isInterrupted = false;
373
374        long timeEnd = System.currentTimeMillis() + timeLimit;
375
376        for (DatabaseContainer db : dbList)
377        {
378          // Calculate the remaining time.
379          long timeRemaining = timeEnd - System.currentTimeMillis();
380          if (timeRemaining <= 0)
381          {
382            break;
383          }
384
385          preloadConfig.setMaxMillisecs(timeRemaining);
386          PreloadStats preloadStats = db.preload(preloadConfig);
387
388          if(logger.isTraceEnabled())
389          {
390            logger.trace("file=" + db.getName() + " LNs=" + preloadStats.getNLNsLoaded());
391          }
392
393          // Stop if the cache is full or the time limit has been exceeded.
394          PreloadStatus preloadStatus = preloadStats.getStatus();
395          if (preloadStatus != PreloadStatus.SUCCESS)
396          {
397            if (preloadStatus == PreloadStatus.EXCEEDED_TIME) {
398              logger.info(NOTE_CACHE_PRELOAD_INTERRUPTED_BY_TIME, backend.getBackendID(), db.getName());
399            } else if (preloadStatus == PreloadStatus.FILLED_CACHE) {
400              logger.info(NOTE_CACHE_PRELOAD_INTERRUPTED_BY_SIZE, backend.getBackendID(), db.getName());
401            } else {
402              logger.info(NOTE_CACHE_PRELOAD_INTERRUPTED_UNKNOWN, backend.getBackendID(), db.getName());
403            }
404
405            isInterrupted = true;
406            break;
407          }
408
409          logger.info(NOTE_CACHE_DB_PRELOADED, db.getName());
410        }
411
412        if (!isInterrupted) {
413          logger.info(NOTE_CACHE_PRELOAD_DONE, backend.getBackendID());
414        }
415
416        // Log an informational message about the size of the cache.
417        EnvironmentStats stats = env.getStats(new StatsConfig());
418        long total = stats.getCacheTotalBytes();
419
420        logger.info(NOTE_CACHE_SIZE_AFTER_PRELOAD, total / (1024 * 1024));
421      }
422      catch (DatabaseException e)
423      {
424        logger.traceException(e);
425
426        logger.error(ERR_CACHE_PRELOAD, backend.getBackendID(),
427            stackTraceToSingleLineString(e.getCause() != null ? e.getCause() : e));
428      }
429    }
430  }
431
432  /**
433   * Closes this root container.
434   *
435   * @throws DatabaseException If an error occurs while attempting to close
436   * the root container.
437   */
438  public void close() throws DatabaseException
439  {
440    for(DN baseDN : entryContainers.keySet())
441    {
442      EntryContainer ec = unregisterEntryContainer(baseDN);
443      ec.exclusiveLock.lock();
444      try
445      {
446        ec.close();
447      }
448      finally
449      {
450        ec.exclusiveLock.unlock();
451      }
452    }
453
454    compressedSchema.close();
455    config.removeLocalDBChangeListener(this);
456
457    if (env != null)
458    {
459      env.close();
460      env = null;
461    }
462  }
463
464  /**
465   * Return all the entry containers in this root container.
466   *
467   * @return The entry containers in this root container.
468   */
469  public Collection<EntryContainer> getEntryContainers()
470  {
471    return entryContainers.values();
472  }
473
474  /**
475   * Returns all the baseDNs this root container stores.
476   *
477   * @return The set of DNs this root container stores.
478   */
479  public Set<DN> getBaseDNs()
480  {
481    return entryContainers.keySet();
482  }
483
484  /**
485   * Return the entry container for a specific base DN.
486   *
487   * @param baseDN The base DN of the entry container to retrieve.
488   * @return The entry container for the base DN.
489   */
490  public EntryContainer getEntryContainer(DN baseDN)
491  {
492    EntryContainer ec = null;
493    DN nodeDN = baseDN;
494
495    while (ec == null && nodeDN != null)
496    {
497      ec = entryContainers.get(nodeDN);
498      if (ec == null)
499      {
500        nodeDN = nodeDN.getParentDNInSuffix();
501      }
502    }
503
504    return ec;
505  }
506
507  /**
508   * Get the environment stats of the JE environment used in this root
509   * container.
510   *
511   * @param statsConfig The configuration to use for the EnvironmentStats
512   *                    object.
513   * @return The environment status of the JE environment.
514   * @throws DatabaseException If an error occurs while retrieving the stats
515   *                           object.
516   */
517  public EnvironmentStats getEnvironmentStats(StatsConfig statsConfig)
518      throws DatabaseException
519  {
520    return env.getStats(statsConfig);
521  }
522
523  /**
524   * Get the environment transaction stats of the JE environment used
525   * in this root container.
526   *
527   * @param statsConfig The configuration to use for the EnvironmentStats
528   *                    object.
529   * @return The environment status of the JE environment.
530   * @throws DatabaseException If an error occurs while retrieving the stats
531   *                           object.
532   */
533  public TransactionStats getEnvironmentTransactionStats(
534      StatsConfig statsConfig) throws DatabaseException
535  {
536    return env.getTransactionStats(statsConfig);
537  }
538
539  /**
540   * Get the environment config of the JE environment used in this root
541   * container.
542   *
543   * @return The environment config of the JE environment.
544   * @throws DatabaseException If an error occurs while retrieving the
545   *                           configuration object.
546   */
547  public EnvironmentConfig getEnvironmentConfig() throws DatabaseException
548  {
549    return env.getConfig();
550  }
551
552  /**
553   * Get the backend configuration used by this root container.
554   *
555   * @return The JE backend configuration used by this root container.
556   */
557  public LocalDBBackendCfg getConfiguration()
558  {
559    return config;
560  }
561
562  /**
563   * Get the total number of entries in this root container.
564   *
565   * @return The number of entries in this root container
566   * @throws DatabaseException If an error occurs while retrieving the entry
567   *                           count.
568   */
569  public long getEntryCount() throws DatabaseException
570  {
571    long entryCount = 0;
572    for(EntryContainer ec : this.entryContainers.values())
573    {
574      ec.sharedLock.lock();
575      try
576      {
577        entryCount += ec.getEntryCount();
578      }
579      finally
580      {
581        ec.sharedLock.unlock();
582      }
583    }
584
585    return entryCount;
586  }
587
588  /**
589   * Assign the next entry ID.
590   *
591   * @return The assigned entry ID.
592   */
593  public EntryID getNextEntryID()
594  {
595    return new EntryID(nextid.getAndIncrement());
596  }
597
598  /**
599   * Return the lowest entry ID assigned.
600   *
601   * @return The lowest entry ID assigned.
602   */
603  public Long getLowestEntryID()
604  {
605    return 1L;
606  }
607
608  /**
609   * Resets the next entry ID counter to zero.  This should only be used after
610   * clearing all databases.
611   */
612  public void resetNextEntryID()
613  {
614    nextid.set(1);
615  }
616
617
618
619  /** {@inheritDoc} */
620  @Override
621  public boolean isConfigurationChangeAcceptable(
622      LocalDBBackendCfg cfg,
623      List<LocalizableMessage> unacceptableReasons)
624  {
625    boolean acceptable = true;
626
627    File parentDirectory = getFileForPath(config.getDBDirectory());
628    File backendDirectory = new File(parentDirectory, config.getBackendId());
629
630    //Make sure the directory either already exists or is able to create.
631    if (!backendDirectory.exists())
632    {
633      if(!backendDirectory.mkdirs())
634      {
635        unacceptableReasons.add(ERR_CREATE_FAIL.get(backendDirectory.getPath()));
636        acceptable = false;
637      }
638      else
639      {
640        backendDirectory.delete();
641      }
642    }
643    //Make sure the directory is valid.
644    else if (!backendDirectory.isDirectory())
645    {
646      unacceptableReasons.add(ERR_DIRECTORY_INVALID.get(backendDirectory.getPath()));
647      acceptable = false;
648    }
649
650    try
651    {
652      FilePermission newBackendPermission =
653          FilePermission.decodeUNIXMode(cfg.getDBDirectoryPermissions());
654
655      //Make sure the mode will allow the server itself access to
656      //the database
657      if(!newBackendPermission.isOwnerWritable() ||
658          !newBackendPermission.isOwnerReadable() ||
659          !newBackendPermission.isOwnerExecutable())
660      {
661        LocalizableMessage message = ERR_CONFIG_BACKEND_INSANE_MODE.get(
662            cfg.getDBDirectoryPermissions());
663        unacceptableReasons.add(message);
664        acceptable = false;
665      }
666    }
667    catch(Exception e)
668    {
669      unacceptableReasons.add(ERR_CONFIG_BACKEND_MODE_INVALID.get(cfg.dn()));
670      acceptable = false;
671    }
672
673    try
674    {
675      ConfigurableEnvironment.parseConfigEntry(cfg);
676    }
677    catch (Exception e)
678    {
679      unacceptableReasons.add(LocalizableMessage.raw(e.getLocalizedMessage()));
680      acceptable = false;
681    }
682
683    return acceptable;
684  }
685
686
687
688  /** {@inheritDoc} */
689  @Override
690  public ConfigChangeResult applyConfigurationChange(LocalDBBackendCfg cfg)
691  {
692    final ConfigChangeResult ccr = new ConfigChangeResult();
693
694    try
695    {
696      if(env != null)
697      {
698        // Check if any JE non-mutable properties were changed.
699        EnvironmentConfig oldEnvConfig = env.getConfig();
700        EnvironmentConfig newEnvConfig =
701            ConfigurableEnvironment.parseConfigEntry(cfg);
702        Map<?,?> paramsMap = EnvironmentParams.SUPPORTED_PARAMS;
703
704        // Iterate through native JE properties.
705        SortedSet<String> jeProperties = cfg.getJEProperty();
706        for (String jeEntry : jeProperties) {
707          // There is no need to validate properties yet again.
708          StringTokenizer st = new StringTokenizer(jeEntry, "=");
709          if (st.countTokens() == 2) {
710            String jePropertyName = st.nextToken();
711            String jePropertyValue = st.nextToken();
712            ConfigParam param = (ConfigParam) paramsMap.get(jePropertyName);
713            if (!param.isMutable()) {
714              String oldValue = oldEnvConfig.getConfigParam(param.getName());
715              if (!oldValue.equalsIgnoreCase(jePropertyValue)) {
716                ccr.setAdminActionRequired(true);
717                ccr.addMessage(INFO_CONFIG_JE_PROPERTY_REQUIRES_RESTART.get(jePropertyName));
718                if(logger.isTraceEnabled()) {
719                  logger.trace("The change to the following property " +
720                    "will take effect when the component is restarted: " +
721                    jePropertyName);
722                }
723              }
724            }
725          }
726        }
727
728        // Iterate through JE configuration attributes.
729        for (Object o : paramsMap.values())
730        {
731          ConfigParam param = (ConfigParam) o;
732          if (!param.isMutable())
733          {
734            String oldValue = oldEnvConfig.getConfigParam(param.getName());
735            String newValue = newEnvConfig.getConfigParam(param.getName());
736            if (!oldValue.equalsIgnoreCase(newValue))
737            {
738              ccr.setAdminActionRequired(true);
739              String configAttr = ConfigurableEnvironment.
740                  getAttributeForProperty(param.getName());
741              if (configAttr != null)
742              {
743                ccr.addMessage(NOTE_JEB_CONFIG_ATTR_REQUIRES_RESTART.get(configAttr));
744              }
745              else
746              {
747                ccr.addMessage(NOTE_JEB_CONFIG_ATTR_REQUIRES_RESTART.get(param.getName()));
748              }
749              if(logger.isTraceEnabled())
750              {
751                logger.trace("The change to the following property will " +
752                    "take effect when the backend is restarted: " +
753                    param.getName());
754              }
755            }
756          }
757        }
758
759        // This takes care of changes to the JE environment for those
760        // properties that are mutable at runtime.
761        env.setMutableConfig(newEnvConfig);
762
763        logger.trace("JE database configuration: %s", env.getConfig());
764      }
765
766      // Create the directory if it doesn't exist.
767      if(!cfg.getDBDirectory().equals(this.config.getDBDirectory()))
768      {
769        File parentDirectory = getFileForPath(cfg.getDBDirectory());
770        File backendDirectory =
771          new File(parentDirectory, cfg.getBackendId());
772
773        if (!backendDirectory.exists())
774        {
775          if(!backendDirectory.mkdirs())
776          {
777            ccr.addMessage(ERR_CREATE_FAIL.get(backendDirectory.getPath()));
778            ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
779            return ccr;
780          }
781        }
782        //Make sure the directory is valid.
783        else if (!backendDirectory.isDirectory())
784        {
785          ccr.addMessage(ERR_DIRECTORY_INVALID.get(backendDirectory.getPath()));
786          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
787          return ccr;
788        }
789
790        ccr.setAdminActionRequired(true);
791        ccr.addMessage(NOTE_CONFIG_DB_DIR_REQUIRES_RESTART.get(this.config.getDBDirectory(), cfg.getDBDirectory()));
792      }
793
794      if(!cfg.getDBDirectoryPermissions().equalsIgnoreCase(
795          config.getDBDirectoryPermissions()) ||
796          !cfg.getDBDirectory().equals(this.config.getDBDirectory()))
797      {
798        FilePermission backendPermission;
799        try
800        {
801          backendPermission =
802              FilePermission.decodeUNIXMode(cfg.getDBDirectoryPermissions());
803        }
804        catch(Exception e)
805        {
806          ccr.addMessage(ERR_CONFIG_BACKEND_MODE_INVALID.get(config.dn()));
807          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
808          return ccr;
809        }
810
811        //Make sure the mode will allow the server itself access to
812        //the database
813        if(!backendPermission.isOwnerWritable() ||
814            !backendPermission.isOwnerReadable() ||
815            !backendPermission.isOwnerExecutable())
816        {
817          ccr.addMessage(ERR_CONFIG_BACKEND_INSANE_MODE.get(
818              cfg.getDBDirectoryPermissions()));
819          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
820          return ccr;
821        }
822
823        // Get the backend database backendDirectory permissions and apply
824        File parentDirectory = getFileForPath(config.getDBDirectory());
825        File backendDirectory = new File(parentDirectory, config.getBackendId());
826        try
827        {
828          if (!FilePermission.setPermissions(backendDirectory, backendPermission))
829          {
830            logger.warn(WARN_UNABLE_SET_PERMISSIONS, backendPermission, backendDirectory);
831          }
832        }
833        catch(Exception e)
834        {
835          // Log an warning that the permissions were not set.
836          logger.warn(WARN_SET_PERMISSIONS_FAILED, backendDirectory, e);
837        }
838      }
839
840      getMonitorProvider().enableFilterUseStats(
841          cfg.isIndexFilterAnalyzerEnabled());
842      getMonitorProvider()
843          .setMaxEntries(cfg.getIndexFilterAnalyzerMaxFilters());
844
845      this.config = cfg;
846    }
847    catch (Exception e)
848    {
849      ccr.addMessage(LocalizableMessage.raw(stackTraceToSingleLineString(e)));
850      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
851      return ccr;
852    }
853
854    return ccr;
855  }
856
857  /**
858   * Returns whether this container JE database environment is
859   * open, valid and can be used.
860   *
861   * @return {@code true} if valid, or {@code false} otherwise.
862   */
863  public boolean isValid() {
864    return env.isValid();
865  }
866}