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 }