CTSPersistentStore.java revision b334b83b7ac2d9a8c60d935cad0365506f13333b
/**
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2013 ForgeRock, Inc. All Rights Reserved
*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information:
*
* "Portions copyright [year] [name of copyright owner]".
*
*/
/**
* Core Token Service Persistent Store is responsible for the storage and retrieval of
* Tokens from the persistent store.
*
* The Core Token Service is exposed through a series of CRUDL operations which use TokenAdapters
* to convert from objects to be stored in the Core Token Service, to the Token format required
* for generic storage.
*
* The Core Token Service is responsible for the storage mechanism behind the Session fail-over
* feature.
*
* Persistence is currently provided by LDAP.
*
* @see com.sun.identity.sm.ldap.adapters.TokenAdapter
* @see Token
*
* @author steve
* @author jeff.schenk@forgerock.com
* @author jason.lemay@forgerock.com
* @author robert.wapshott@forgerock.com
*/
public class CTSPersistentStore extends GeneralTaskRunnable {
// Singleton Instance
private static Thread storeThread;
private final CoreTokenConfig coreTokenConfig;
private final LDAPDataConversion dataConversion;
private final TokenBlobStrategy strategy;
private final DataLayerConnectionFactory connectionFactory;
/**
* Globals private Constants, so not to pollute entire product.
*/
private static final String ANY_OBJECTCLASS_FILTER = "(" + OBJECTCLASS + Constants.EQUALS + Constants.ASTERISK + ")";
/**
* Shared SM Data Layer Accessor.
*/
private static volatile CTSDataLayer CTSDataLayer;
/**
* Service Globals
*/
private static volatile boolean shutdown = false;
/**
* Configuration Definitions
*/
/**
* Define Global DN and Container Constants
*/
// Our default Top Level Root Suffix.
// This is resolved during Initialization process.
private static String BASE_ROOT_DN;
// Our default for TOKEN_ROOT_SUFFIX = ou=tokens
private static final String TOKEN_ROOT_SUFFIX =
// Our default for TOKEN_ROOT = ou=tokens,dc=openam,dc=forgerock,dc=org
// Our default for TOKEN_SESSION_HA_ROOT_SUFFIX = ou=openam-session
private static final String TOKEN_SESSION_HA_ROOT_SUFFIX =
// Our default for TOKEN_SAML2_HA_ROOT_SUFFIX = ou=openam-saml2
private static final String TOKEN_SAML2_HA_ROOT_SUFFIX =
// Our default for TOKEN_OAUTH2_HA_ROOT_SUFFIX = ou-openam-oauth2
private static final String TOKEN_OAUTH2_HA_ROOT_SUFFIX =
/**
* Define Session DN Constants
*/
private static final String SESSION_FAILOVER_HA_BASE_DN =
/**
* Define SAML2 DN Constants
*/
private static final String SAML2_HA_BASE_DN =
/**
* Define OAUTH2 DN Constants
*/
private static final String OAUTH2_HA_BASE_DN =
/**
* Private restricted to preserve Singleton Instantiation.
*/
this.coreTokenConfig = coreTokenConfig;
this.dataConversion = dataConversion;
this.connectionFactory = connectionFactory;
}
/**
* Provide Service Instance Access to our Singleton
*
* @return CTSPersistentStore Singleton Instance.
*/
public static final CTSPersistentStore getInstance() {
synchronized (CTSPersistentStore.class) {
instance = new CTSPersistentStore(
// Proceed With Initialization of Service.
try {
// Initialize the Initial Singleton Service Instance.
initialize();
} catch (StoreException se) {
}
}
} // End of synchronized block.
// Return Instance.
return instance;
}
/**
* Perform Initialization
*/
private synchronized static void initialize()
throws StoreException {
// Initialize this Service
if (DEBUG.messageEnabled()) {
DEBUG.message("Initializing Configuration for the OpenAM Session Repository using Implementation Class: " +
CTSPersistentStore.class.getSimpleName());
}
// *******************************
// Set up Shutdown Thread Hook.
if (shutdownManager.acquireValidLock()) {
try {
public void shutdown() {
}
});
} finally {
}
}
// *************************************************************
// Obtain our Directory Connection and ensure we can access our
// Internal Directory Connection or use an External Source as
// per configuration.
//
// ******************************************
// Start our AM Repository Store Thread.
storeThread.start();
// Finish Initialization
if (DEBUG.messageEnabled()) {
DEBUG.message("Successful Configuration Initialization for the OpenAM Session Repository using Implementation Class: " +
CTSPersistentStore.class.getSimpleName());
}
}
/**
* Prepare our BackEnd Persistence Store.
*
* @throws StoreException - Exception thrown if Error condition exists.
*/
private synchronized static void prepareCTSPersistenceStore() throws StoreException {
try {
if (CTSDataLayer == null) {
throw new StoreException("Unable to obtain BackEnd Persistence Store for CTS Services.");
}
} catch (Exception e) {
throw new StoreException(e);
}
// Obtain our Root DN to use as out Base DN.
throw new StoreException("Unable to obtain Base Root DN from SMSEntry.getRootSuffix() method call!");
}
// Show Informational Message.
if (DEBUG.messageEnabled()) {
}
}
/**
* Provides an instance of the CoreTokenAdapter the first time this function is called.
* Otherwise returns the same instance for all subsequent calls.
*
* @return Non null instance of the CoreTokenAdapter.
* @throws IllegalStateException If the connection to the LDAP database could not be established.
*/
private CoreTokenLDAPAdapter getAdapter() {
synchronized (this) {
}
}
}
return adapter;
}
/**
* Create a Token in the persistent store. If the Token already exists in the store then this
* function will throw a CoreTokenException. Instead it is recommended to use the update function.
*
* @see CTSPersistentStore#update(com.sun.identity.sm.ldap.api.tokens.Token)
*
* @param token Non null Token to create.
* @throws CoreTokenException If there was a non-recoverable error during the operation or if
* the Token already exists in the store.
*/
try {
} catch (TokenStrategyFailedException e) {
throw new CoreTokenException("Failed to perform Token Blob strategy.", e);
}
}
/**
* Read a Token from the persistent store.
*
* @param tokenId The non null Token Id that the Token was created with.
* @return Null if there was no matching Token. Otherwise a fully populated Token will be returned.
* @throws CoreTokenException If there was a non-recoverable error during the operation.
*/
try {
} catch (TokenStrategyFailedException e) {
throw new CoreTokenException("Failed to reverse Token Blob strategy.", e);
}
return token;
}
/**
* Update an existing Token in the store. If the Token does not exist in the store then a
* Token is created. If the Token did exist in the store then it is updated.
*
* Not all fields on the Token can be updated, see the Token class for more details.
*
* @see Token
*
* @param token Non null Token to update.
* @throws CoreTokenException If there was a non-recoverable error during the operation.
*/
try {
} catch (TokenStrategyFailedException e) {
throw new CoreTokenException("Failed to perform Token Blob strategy.", e);
}
if (DEBUG.messageEnabled()) {
"Update: {0} updated",
token.getTokenId()));
}
}
/**
* Delete the Token from the store.
*
* @param token Non null Token to be deleted from the store.
* @throws CoreTokenException If there was a non-recoverable error during the operation.
*/
}
/**
* Delete the Token from the store based on its id.
*
* Note: It is often more efficient to delete the token based on the Id if you already
* have this information, rather than reading the Token first before removing it.
*
* @param tokenId The non null Token Id of the token to remove.
* @throws CoreTokenException If there was a non-recoverable error during the operation.
*/
if (DEBUG.messageEnabled()) {
"Delete: {0} deleted",
tokenId));
}
}
/**
* Delete a collection of Tokens from the Token Store using a filter to narrow down the
* Tokens to be deleted.
*
* Note: This operation is linear in its execution time so the more Tokens being deleted, the
* longer it will take.
*
* @param query Non null filters which will be combined logically using AND.
*
* @return total number of tokens deleted by query.
*
* @throws DeleteFailedException If the delete failed for any reason.
*/
}
.query()
try {
}
if (DEBUG.messageEnabled()) {
"Delete: {0} deleted",
}
} catch (CoreTokenException e) {
throw new DeleteFailedException(builder, e);
}
}
/**
* Perform a query based on a collection of queryable parameters.
*
* The query will be an AND query where each matching Token must match on all query parameters
* provided.
*
* @param query A mapping of CoreTokenField keys to values.
* @return A non null, but possibly empty collection of Tokens.
* @throws CoreTokenException If there was a non-recoverable error during the operation.
*/
// Verify all types are safe to cast.
}
if (DEBUG.messageEnabled()) {
"List: {0} Tokens listed",
}
return tokens;
}
/**
* Performs a list operation against the Core Token Service with a predefined filter. This
* allows more complex filters to be constructed and is intended to be used with the
* QueryFilter fluent class.
*
* @see QueryFilter
*
* @param filter A non null OpenDJ LDAP Filter to use to control the results returned.
* @return A non null, but possible empty collection of Tokens.
* @throws CoreTokenException If there was an unrecoverable error.
*/
return tokens;
}
/**
* Handles the decrypting of tokens when needed.
*
* @param tokens A non null collection of Tokens.
* @return A new collection of Tokens with their byte contents decrypted if necessary.
*/
try {
} catch (TokenStrategyFailedException e) {
throw new CoreTokenException("Failed to reverse Token Blob strategy.", e);
}
}
}
/**
* Perform Service Shutdown.
*/
public void shutdown() {
}
/**
* Internal Service Shutdown Process.
*/
protected static void internalShutdown() {
shutdown = true;
}
/**
* Service Thread Run Process Loop.
*/
@SuppressWarnings("SleepWhileInLoop")
public void run() {
synchronized (LOCK) {
while (!shutdown) {
try {
boolean more = deleteExpired();
// Trigger a rerun of the loop if it is clear there are more to delete.
if (more) {
continue;
}
// Wait for next tick or interrupt.
} catch (InterruptedException ie) {
// very important, when Exception thrown, Interrupt is actually cleared.
// Need to ensure all child loop iterations get killed.
break;
} catch (CoreTokenException e) {
} catch (Exception e) {
}
}
}
}
/**
* Default Service Method, used to satisfy GeneralTaskRunnable extending.
*
* @param obj
* @return
*/
return false;
}
/**
* Default Service Method, used to satisfy GeneralTaskRunnable extending.
*
* @param obj
* @return
*/
return false;
}
/**
* Default Service Method, used to satisfy GeneralTaskRunnable extending.
*
* @return
*/
public boolean isEmpty() {
return true;
}
/**
* Return Service Run Period.
*
* @return long current run period.
*/
public long getRunPeriod() {
return coreTokenConfig.getRunPeriod();
}
/**
* Returns the expiration information of all sessions belonging to a user.
* The returned value will be a Map (sid->expiration_time).
*
* @param uuid User's universal unique ID.
* @return Map of all Session for the user
* @throws Exception if there is any problem with accessing the session
* repository.
*/
if (DEBUG.messageEnabled()) {
"Querying Sessions by User Id. Found {0} Sessions.\n" +
"UUID: {1}",
uuid));
}
}
return sessions;
}
/**
* Protected Common Helper Method to Obtain an LDAP Connection
* from the SMDataLayer Pool.
*
* @return LDAPConnection - Obtained Directory Connection from Pool.
* @throws StoreException
*/
if (ldapConnection == null) {
throw new StoreException("CTSPersistenceStore.prepareCTSPersistenceStore: Unable to Obtain Directory Connection!");
}
// Return Obtain Connection from Pool.
return ldapConnection;
}
/**
* Protected Common Helper Method for external parallel layer components @see CTSDataUtils
*
* @param ldapConnection
* @throws StoreException
*/
protected void releaseDirectoryConnection(LDAPConnection ldapConnection, LDAPException lastLDAPException) throws StoreException {
if (ldapConnection != null) {
// Release the Connection.
}
}
protected static String getAnyObjectclassFilter() {
return ANY_OBJECTCLASS_FILTER;
}
protected static String getBASE_ROOT_DN() {
}
protected static String getTokenRoot() {
}
protected static String getTokenSessionHaRootDn() {
}
protected static String getTokenSaml2HaRootDn() {
}
protected static String getTokenOauth2HaRootDn() {
}
protected static String getSessionFailoverHaBaseDn() {
}
protected static String getSaml2HaBaseDn() {
}
protected static String getOauth2HaBaseDn() {
}
/**
* Helper method to correctly format a String with a name,value pair.
* This uses the include Open Source @see MapFormat Source.
*
* @param template
* @param name - Can be Null.
* @param value
* @return String of Formatted Template with DN Names resolved.
*/
}
}
/**
* Delete all Expired Sessions, within Default Limits.
*
* @return True if there are more tokens to delete.
*
* @throws CoreTokenException If there was a problem performing the delete.
*/
private boolean deleteExpired() throws CoreTokenException {
}
if (DEBUG.messageEnabled()) {
"Delete Expired: {0} expired tokens deleted",
}
}
}