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 }