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