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.pluggable; 028 029import static org.opends.messages.BackendMessages.*; 030import static org.opends.server.util.StaticUtils.*; 031 032import java.util.ArrayList; 033import java.util.Collection; 034import java.util.Collections; 035import java.util.List; 036import java.util.Set; 037import java.util.concurrent.ConcurrentHashMap; 038import java.util.concurrent.ConcurrentMap; 039import java.util.concurrent.atomic.AtomicLong; 040 041import org.forgerock.i18n.LocalizableMessage; 042import org.forgerock.i18n.slf4j.LocalizedLogger; 043import org.forgerock.opendj.config.server.ConfigChangeResult; 044import org.forgerock.opendj.config.server.ConfigException; 045import org.forgerock.opendj.ldap.ResultCode; 046import org.opends.server.admin.server.ConfigurationChangeListener; 047import org.opends.server.admin.std.server.PluggableBackendCfg; 048import org.opends.server.api.CompressedSchema; 049import org.opends.server.backends.pluggable.spi.AccessMode; 050import org.opends.server.backends.pluggable.spi.ReadOperation; 051import org.opends.server.backends.pluggable.spi.ReadableTransaction; 052import org.opends.server.backends.pluggable.spi.Storage; 053import org.opends.server.backends.pluggable.spi.StorageRuntimeException; 054import org.opends.server.backends.pluggable.spi.StorageStatus; 055import org.opends.server.backends.pluggable.spi.WriteOperation; 056import org.opends.server.backends.pluggable.spi.WriteableTransaction; 057import org.opends.server.core.SearchOperation; 058import org.opends.server.types.DN; 059import org.opends.server.types.DirectoryException; 060import org.opends.server.types.InitializationException; 061import org.opends.server.types.Operation; 062import org.opends.server.types.Privilege; 063 064/** 065 * Wrapper class for a backend "container". Root container holds all the entry 066 * containers for each base DN. It also maintains all the openings and closings 067 * of the entry containers. 068 */ 069public class RootContainer implements ConfigurationChangeListener<PluggableBackendCfg> 070{ 071 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 072 073 /** The tree storage. */ 074 private final Storage storage; 075 076 /** The ID of the backend to which this entry root container belongs. */ 077 private final String backendId; 078 /** The backend configuration. */ 079 private final PluggableBackendCfg config; 080 /** The monitor for this backend. */ 081 private BackendMonitor monitor; 082 083 /** The base DNs contained in this root container. */ 084 private final ConcurrentMap<DN, EntryContainer> entryContainers = new ConcurrentHashMap<>(); 085 086 /** Value of the next entryID to be assigned. */ 087 private AtomicLong nextEntryID = new AtomicLong(1); 088 089 /** The compressed schema manager for this backend. */ 090 private PersistentCompressedSchema compressedSchema; 091 092 /** 093 * Creates a new RootContainer object representing a storage. 094 * 095 * @param config 096 * The configuration of the backend. 097 * @param backendID 098 * A reference to the backend that is creating this root 099 * container. 100 */ 101 RootContainer(String backendID, Storage storage, PluggableBackendCfg config) 102 { 103 this.backendId = backendID; 104 this.storage = storage; 105 this.config = config; 106 107 getMonitorProvider().enableFilterUseStats(config.isIndexFilterAnalyzerEnabled()); 108 getMonitorProvider().setMaxEntries(config.getIndexFilterAnalyzerMaxFilters()); 109 110 config.addPluggableChangeListener(this); 111 } 112 113 /** 114 * Returns the underlying storage engine. 115 * 116 * @return the underlying storage engine 117 */ 118 Storage getStorage() 119 { 120 return storage; 121 } 122 123 /** 124 * Opens the root container. 125 * 126 * @param accessMode specifies how the container has to be opened (read-write or read-only) 127 * 128 * @throws StorageRuntimeException 129 * If an error occurs when opening the storage. 130 * @throws ConfigException 131 * If an configuration error occurs while opening the storage. 132 */ 133 void open(final AccessMode accessMode) throws StorageRuntimeException, ConfigException 134 { 135 try 136 { 137 storage.open(accessMode); 138 storage.write(new WriteOperation() 139 { 140 @Override 141 public void run(WriteableTransaction txn) throws Exception 142 { 143 compressedSchema = new PersistentCompressedSchema(storage, txn, accessMode); 144 openAndRegisterEntryContainers(txn, config.getBaseDN(), accessMode); 145 } 146 }); 147 } 148 catch(StorageRuntimeException e) 149 { 150 throw e; 151 } 152 catch (Exception e) 153 { 154 throw new StorageRuntimeException(e); 155 } 156 } 157 158 /** 159 * Opens the entry container for a base DN. If the entry container does not 160 * exist for the base DN, it will be created. The entry container will be 161 * opened with the same mode as the root container. Any entry containers 162 * opened in a read only root container will also be read only. Any entry 163 * containers opened in a non transactional root container will also be non 164 * transactional. 165 * 166 * @param baseDN 167 * The base DN of the entry container to open. 168 * @param txn 169 * The transaction 170 * @param accessMode specifies how the container has to be opened (read-write or read-only) 171 * @return The opened entry container. 172 * @throws StorageRuntimeException 173 * If an error occurs while opening the entry container. 174 * @throws ConfigException 175 * If an configuration error occurs while opening the entry container. 176 */ 177 EntryContainer openEntryContainer(DN baseDN, WriteableTransaction txn, AccessMode accessMode) 178 throws StorageRuntimeException, ConfigException 179 { 180 EntryContainer ec = new EntryContainer(baseDN, backendId, config, storage, this); 181 ec.open(txn, accessMode); 182 return ec; 183 } 184 185 /** 186 * Registers the entry container for a base DN. 187 * 188 * @param baseDN 189 * The base DN of the entry container to close. 190 * @param entryContainer 191 * The entry container to register for the baseDN. 192 * @throws InitializationException 193 * If an error occurs while opening the entry container. 194 */ 195 void registerEntryContainer(DN baseDN, EntryContainer entryContainer) throws InitializationException 196 { 197 EntryContainer ec = this.entryContainers.get(baseDN); 198 if (ec != null) 199 { 200 throw new InitializationException(ERR_ENTRY_CONTAINER_ALREADY_REGISTERED.get(ec.getTreePrefix(), baseDN)); 201 } 202 this.entryContainers.put(baseDN, entryContainer); 203 } 204 205 /** 206 * Opens the entry containers for multiple base DNs. 207 * 208 * @param baseDNs 209 * The base DNs of the entry containers to open. 210 * @param accessMode specifies how the containers have to be opened (read-write or read-only) 211 * 212 * @throws StorageRuntimeException 213 * If an error occurs while opening the entry container. 214 * @throws InitializationException 215 * If an initialization error occurs while opening the entry 216 * container. 217 * @throws ConfigException 218 * If a configuration error occurs while opening the entry 219 * container. 220 */ 221 private void openAndRegisterEntryContainers(WriteableTransaction txn, Set<DN> baseDNs, AccessMode accessMode) 222 throws StorageRuntimeException, InitializationException, ConfigException 223 { 224 EntryID highestID = null; 225 for (DN baseDN : baseDNs) 226 { 227 EntryContainer ec = openEntryContainer(baseDN, txn, accessMode); 228 EntryID id = ec.getHighestEntryID(txn); 229 registerEntryContainer(baseDN, ec); 230 if (highestID == null || id.compareTo(highestID) > 0) 231 { 232 highestID = id; 233 } 234 } 235 236 nextEntryID = new AtomicLong(highestID.longValue() + 1); 237 } 238 239 /** 240 * Unregisters the entry container for a base DN. 241 * 242 * @param baseDN 243 * The base DN of the entry container to close. 244 * @return The entry container that was unregistered or NULL if a entry 245 * container for the base DN was not registered. 246 */ 247 EntryContainer unregisterEntryContainer(DN baseDN) 248 { 249 return entryContainers.remove(baseDN); 250 } 251 252 /** 253 * Retrieves the compressed schema manager for this backend. 254 * 255 * @return The compressed schema manager for this backend. 256 */ 257 CompressedSchema getCompressedSchema() 258 { 259 return compressedSchema; 260 } 261 262 /** 263 * Get the BackendMonitor object used by this root container. 264 * 265 * @return The BackendMonitor object. 266 */ 267 BackendMonitor getMonitorProvider() 268 { 269 if (monitor == null) 270 { 271 monitor = new BackendMonitor(backendId + " Storage", this); 272 } 273 return monitor; 274 } 275 276 /** 277 * Preload the tree cache. There is no preload if the configured preload 278 * time limit is zero. 279 * 280 * @param timeLimit 281 * The time limit for the preload process. 282 */ 283 void preload(long timeLimit) 284 { 285 if (timeLimit > 0) 286 { 287 // Get a list of all the tree used by the backend. 288 final List<Tree> trees = new ArrayList<>(); 289 for (EntryContainer ec : entryContainers.values()) 290 { 291 ec.sharedLock.lock(); 292 try 293 { 294 trees.addAll(ec.listTrees()); 295 } 296 finally 297 { 298 ec.sharedLock.unlock(); 299 } 300 } 301 302 // Sort the list in order of priority. 303 Collections.sort(trees, new TreePreloadComparator()); 304 305 // Preload each tree until we reach the time limit or the cache is filled. 306 try 307 { 308 throw new UnsupportedOperationException("Not implemented exception"); 309 } 310 catch (StorageRuntimeException e) 311 { 312 logger.error(ERR_CACHE_PRELOAD, backendId, 313 stackTraceToSingleLineString(e.getCause() != null ? e.getCause() : e)); 314 } 315 } 316 } 317 318 /** 319 * Closes this root container. 320 * 321 * @throws StorageRuntimeException 322 * If an error occurs while attempting to close the root container. 323 */ 324 void close() throws StorageRuntimeException 325 { 326 for (DN baseDN : entryContainers.keySet()) 327 { 328 EntryContainer ec = unregisterEntryContainer(baseDN); 329 ec.exclusiveLock.lock(); 330 try 331 { 332 ec.close(); 333 } 334 finally 335 { 336 ec.exclusiveLock.unlock(); 337 } 338 } 339 config.removePluggableChangeListener(this); 340 if (storage != null) 341 { 342 storage.close(); 343 } 344 } 345 346 /** 347 * Return all the entry containers in this root container. 348 * 349 * @return The entry containers in this root container. 350 */ 351 public Collection<EntryContainer> getEntryContainers() 352 { 353 return entryContainers.values(); 354 } 355 356 /** 357 * Returns all the baseDNs this root container stores. 358 * 359 * @return The set of DNs this root container stores. 360 */ 361 Set<DN> getBaseDNs() 362 { 363 return entryContainers.keySet(); 364 } 365 366 /** 367 * Return the entry container for a specific base DN. 368 * 369 * @param baseDN 370 * The base DN of the entry container to retrieve. 371 * @return The entry container for the base DN. 372 */ 373 EntryContainer getEntryContainer(DN baseDN) 374 { 375 EntryContainer ec = null; 376 DN nodeDN = baseDN; 377 378 while (ec == null && nodeDN != null) 379 { 380 ec = entryContainers.get(nodeDN); 381 if (ec == null) 382 { 383 nodeDN = nodeDN.getParentDNInSuffix(); 384 } 385 } 386 387 return ec; 388 } 389 390 /** 391 * Get the total number of entries in this root container. 392 * 393 * @return The number of entries in this root container 394 * @throws StorageRuntimeException 395 * If an error occurs while retrieving the entry count. 396 */ 397 long getEntryCount() throws StorageRuntimeException 398 { 399 try 400 { 401 return storage.read(new ReadOperation<Long>() 402 { 403 @Override 404 public Long run(ReadableTransaction txn) throws Exception 405 { 406 long entryCount = 0; 407 for (EntryContainer ec : entryContainers.values()) 408 { 409 ec.sharedLock.lock(); 410 try 411 { 412 entryCount += ec.getNumberOfEntriesInBaseDN0(txn); 413 } 414 finally 415 { 416 ec.sharedLock.unlock(); 417 } 418 } 419 return entryCount; 420 } 421 }); 422 } 423 catch (Exception e) 424 { 425 throw new StorageRuntimeException(e); 426 } 427 } 428 429 /** 430 * Assign the next entry ID. 431 * 432 * @return The assigned entry ID. 433 */ 434 EntryID getNextEntryID() 435 { 436 return new EntryID(nextEntryID.getAndIncrement()); 437 } 438 439 /** Resets the next entry ID counter to zero. This should only be used after clearing all trees. */ 440 public void resetNextEntryID() 441 { 442 nextEntryID.set(1); 443 } 444 445 @Override 446 public boolean isConfigurationChangeAcceptable(PluggableBackendCfg configuration, 447 List<LocalizableMessage> unacceptableReasons) 448 { 449 // Storage has also registered a change listener, delegate to it. 450 return true; 451 } 452 453 @Override 454 public ConfigChangeResult applyConfigurationChange(PluggableBackendCfg configuration) 455 { 456 getMonitorProvider().enableFilterUseStats(configuration.isIndexFilterAnalyzerEnabled()); 457 getMonitorProvider().setMaxEntries(configuration.getIndexFilterAnalyzerMaxFilters()); 458 459 return new ConfigChangeResult(); 460 } 461 462 /** 463 * Checks the storage has enough resources for an operation. 464 * 465 * @param operation the current operation 466 * @throws DirectoryException if resources are in short supply 467 */ 468 void checkForEnoughResources(Operation operation) throws DirectoryException 469 { 470 StorageStatus status = storage.getStorageStatus(); 471 if (status.isUnusable() 472 || (status.isLockedDown() && hasBypassLockdownPrivileges(operation))) 473 { 474 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, status.getReason()); 475 } 476 } 477 478 private boolean hasBypassLockdownPrivileges(Operation operation) 479 { 480 return operation != null 481 // Read operations are always allowed in lock down mode 482 && !(operation instanceof SearchOperation) 483 && !operation.getClientConnection().hasPrivilege( 484 Privilege.BYPASS_LOCKDOWN, operation); 485 } 486}