package jeffaschenk.commons.frameworks.cnxidx.utility.pool;
import jeffaschenk.commons.exceptions.ResourceProviderException;
import jeffaschenk.commons.frameworks.cnxidx.utility.ldap.DataMappingConstants;
import jeffaschenk.commons.frameworks.cnxidx.utility.ldap.IcosDirContext;
import jeffaschenk.commons.frameworks.cnxidx.utility.logging.FrameworkLoggerLevel;
import jeffaschenk.commons.frameworks.cnxidx.utility.logging.FrameworkLogger;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
/**
* This class provides access to a pool of DirContext resources for clients
* executing outside of the Application Server's JVM and is
* implemented as a singleton / delegator idiom.
* <p> The ResourcePool uses several different properties from the
* -Dconfigfile properties file.
* <p/>
* <p> The following parameters are used to connect to the LDAP server:<br>
* <p> <li><b>LDAPHost</b> - The LDAP Server's address. No default.
* <li><b>LDAPPort</b> - The LDAP Server's port. Defaults to <i>389</i>
* <li><b>LDAPAuthentication</b> - Defaults to <i>simple</i>
* <li><b>LDAPUser</b> - The LDAP Server's admin account. No default.
* <li><b>LDAPPassword</b> - The LDAP Server's admin password. No default.
* <p> Notes: Notice, by giving default <i>(invalid)</i> values to
* LDAPUser, and LDAPPassword - we are
* imposing the requirement that the App and LDAP servers are
* userid/password protected!
*/
class DirContextResourceProviderDC implements ResourceProvider, DataMappingConstants {
/**
* This class name used for debug purposes.
*/
private static final String CLASS_NAME =
DirContextResourceProviderDC.class.getName();
/**
* The LDAP directory url used to create a
* DirContext object.
*/
private String ldapFactory;
/**
* The LDAP directory url used to create a
* DirContext object.
*/
private String ldapHost;
/**
* The LDAP directory port used to create a
* DirContext object.
*/
private String ldapPort;
/**
* The LDAP directory authentication mechanism used to create a
* DirContext object.
*/
private String ldapAuthentication;
/**
* The LDAP directory user id used to create a
* DirContext object.
*/
private String ldapUser;
private static int DEFAULT_RETRY_COUNT = 10;
private static long DEFAULT_RETRY_SLEEP_MILLIS = 150;
/**
* Count of how many times to retry when ldap fails with a 51 (busy)
*/
private int ldapRetryCount = DEFAULT_RETRY_COUNT;
/**
* How long to wait between retries when ldap fails with a 51 (busy)
*/
private long ldapRetrySleepMillis = DEFAULT_RETRY_SLEEP_MILLIS;
/**
* The LDAP directory user password used to create a
* DirContext object.
*/
private String ldapPassword;
/**
* The search controls object to use in the isValid() method
*/
private SearchControls isValidSrchCtrls = new SearchControls();
/**
* The No Attributes to be Returned, only a DN.
*/
private String[] NO_Attributes = {"1.1"};
/**
* Default Constructor
*
* @param props Properties required for accessing the LDAP
* server and creating DirContexts.
*/
public DirContextResourceProviderDC(Properties props)
throws ResourceProviderException {
loadFromProperties(props);
isValidSrchCtrls.setSearchScope(SearchControls.OBJECT_SCOPE);
isValidSrchCtrls.setReturningAttributes(NO_Attributes);
} // End of Constructor
/**
* Creates a DirContext object.
*
* @param props Properties which are looked up and used to create
* the resource.
* @return A resource.
* @throws ResourceProviderException
* Thrown if the DirContext cannot be created.
*/
public Object create(Properties props)
throws ResourceProviderException {
final String METHOD = "create(TypedProperties)";
DirContext resource = null;
try {
resource = createDirContext();
} catch (NamingException ne) {
FrameworkLogger.log(CLASS_NAME, "create", FrameworkLoggerLevel.ERROR,
MessageConstants.ICOS_UTIL_DIRECTORY_CONTEXT_RESOURCE_PROVIDER_CANT_CREATE_RESOURCE,
ne);
throw new ResourceProviderException(MessageConstants.ICOS_UTIL_DIRECTORY_CONTEXT_RESOURCE_PROVIDER_CANT_CREATE_RESOURCE,
ne);
}
return resource;
}
/**
* Shuts down or closes the DirContext
*
* @param resource The DirContext object to destroy.
* @throws ResourceProviderException
* Thrown if the resource provider fails.
*/
public void destroy(Object resource)
throws ResourceProviderException {
final String METHOD = "destroy(resource)";
try {
if (resource != null)
((DirContext) resource).close();
} catch (NamingException ne) {
FrameworkLogger.log(CLASS_NAME, "destroy", FrameworkLoggerLevel.WARNING,
MessageConstants.ICOS_UTIL_DIRECTORY_CONTEXT_RESOURCE_PROVIDER_CANT_CLOSE_RESOURCE,
ne);
throw new ResourceProviderException(MessageConstants.ICOS_UTIL_DIRECTORY_CONTEXT_RESOURCE_PROVIDER_CANT_CLOSE_RESOURCE,
ne);
}
}
/**
* Determines if the DirContext object is valid and can be used.
* We check our DSA Entry, which for since is protected
* we should not find anything but will indicate whether or
* not we have a valid DirContext or not.
*
* @param resource The DirContext object to check
* @return A boolean indicating validity
*/
public boolean isValid(Object resource) {
// ***************************************
// Do we have a Context?
if (resource == null) {
return false;
}
// ***************************************
// Is the Context Alive?
try {
((DirContext) resource).search(ldapUser, OC_ALL_FILTER, isValidSrchCtrls);
return true;
} catch (NameNotFoundException nnfe) {
// ********************************
// Name not found condition will
// Possible exist due to ACI
// However, the context is hot and alive
// So return we are ok.
return true;
} catch (Exception e) {
// ********************************
// Exception other than a not found
// indicates we have a bad context.
// Should perform RootCause Here.
FrameworkLogger.log(CLASS_NAME, "isValid", FrameworkLoggerLevel.WARNING,
MessageConstants.ICOS_UTIL_DIRECTORY_CONTEXT_RESOURCE_PROVIDER_RESOURCE_INVALID);
} // End of Exception.
return false;
} // End of isValid Method.
/**
* Utility that creates a DirContext object.
*
* @return A DirContext object.
* @throws javax.naming.NamingException Thrown if
* the DirContext object cannot be created.
*/
private DirContext createDirContext() throws NamingException {
// This is interesting code and an example that this method
// was built from was provided by SilverStream support.
final String METHOD = "getDirContext()";
// Now - get an LDAP DirContext off of the Context...
FrameworkLogger.log(CLASS_NAME, "createDirContext", FrameworkLoggerLevel.DEBUG,
MessageConstants.ICOS_UTIL_DIRECTORY_CONTEXT_RESOURCE_PROVIDER_GET_CONTEXT);
try {
Properties props = new Properties();
// *********************************************************
// Now add userid/password for ldap security
// Notice how we are imposing the requirement that the LDAP
// server be password protected here...
//
// TODO FIX this for SSL Protocol Support.
//
props.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
props.setProperty(Context.PROVIDER_URL,
"ldap://" + ldapHost + ":" + ldapPort);
props.setProperty(Context.SECURITY_AUTHENTICATION,
ldapAuthentication);
props.setProperty(Context.SECURITY_PRINCIPAL, ldapUser);
props.setProperty(Context.SECURITY_CREDENTIALS, ldapPassword);
// *******************************************
// Initialize Additional LDAP Properties.
// *******************************************
// Specify any non-String Attributes not in
// Standard supplied JNDI specification.
// The property must be in sync with
// idxIRRSchema class as well as our
// Directory Schema.
//
// Specify Each attribute deliminted by a space.
// See DataMappingContants.
//
props.setProperty("java.naming.ldap.attributes.binary", BIN_ATTRIBUTE_NAMES);
// *******************************************
// Specify deleteRDN on a Rename.
//
// java.naming.ldap.deleterdn:
// When a JNDI Rename occurs, take the default
// and remove the RDN attribute of the old
// naming attribute.
//
props.setProperty("java.naming.ldap.deleterdn", "true");
// *******************************************
// Specify our DeReferencing Alias Property.
//
// java.naming.ldap.derefAliases:
// Specifies DeReferencing Rule:
// always = Always Dereference Aliases, DEFAULT.
// never = Never Dereference Aliases.
// finding = Dereferences Aliases only during name resolution.
// searching = Dereferences Aliases only after name resolution.
//
props.setProperty("java.naming.ldap.derefAliases",
System.getProperty("java.naming.ldap.derefAliases", "always"));
// *******************************************
// Initialize Additional Naming Properties.
// *******************************************
// Specify BatchSize.
//
// java.naming.batchsize (Context.BATCHSIZE):
// Sets the recommended size limit for the number of search results
// held by a returned NamingEnumeration.
// If not specified then the default batchsize is 1.
// This helps to ensure the smallest memory footprint possible
// by the class library.
// A value of 0 disables batchsize and indicates that a
// search will block until all results are collected.
//
props.setProperty("java.naming.batchsize", "0");
// *******************************************
// Specify REFERRAL.
//
// java.naming.referral (Context.REFERRAL):
// Sets how the application will handle REFERRALS
// when provided from the Directory Server.
//
// Since the DCL Directory implementation
// specifies that REFERRALS will be chained,
// then if we receive a referral, there is a
// potential that a Realm or subordinate
// Directory instance is not running or down.
//
// If we set this to "follow", and a
// Directory Instance is down, then response
// time for all Search operations will be
// very long.
//
// Currently this is set to Ignore.
// Meaning, if a REFERRAL is received from
// a request, than the REFERRAL Entity is
// Simple Ignored and normal processing will
// continue.
// We will rely on the DCL Directory Implementation
// to provide automatic following of the
// Referrals, before we get information returned
// to us.
//
// Otherwise the default is to throw the
// Exception which is not correct in our
// environment.
// Another option is to "follow" the
// Referral, but in our case if a Referral
// is received we know a Directory Instance
// is down.
//
props.setProperty("java.naming.referral",
System.getProperty("java.naming.referral", "ignore"));
// *******************************************
// Specify our Object Factories.
//
// java.naming.factory.object:
// specified Classes must implement
// the ObjectFactory or DirObjectFactory interface.
//
props.setProperty("java.naming.factory.object",
System.getProperty("java.naming.factory.object", ""));
// *******************************************
// Specify our State Factories.
//
// java.naming.factory.state:
// specified Classes must implement
// the StateFactory or DirStateFactory interface.
//
props.setProperty("java.naming.factory.state",
System.getProperty("java.naming.factory.state", ""));
// *******************************************
// Specify our Control Factories.
//
// java.naming.factory.control:
// specified Classes must implement
// the ControlFactory interface.
//
props.setProperty("java.naming.factory.control",
System.getProperty("java.naming.factory.control", ""));
// *************************************
// Return new IcosDirContext
return new IcosDirContext(props, ldapRetryCount, ldapRetrySleepMillis);
} catch (NamingException ne) {
throw ne;
}
}
// Initialize the properties by loading properties from the system
// and the properties file.
// Pay attention in here!
private void loadFromProperties(Properties props)
throws ResourceProviderException {
final String METHOD = "loadFromProperties";
String key = null;
try {
key = "LDAPHost";
ldapHost = (String) props.get(key);
key = "LDAPPort";
ldapPort = (String) props.get(key);
key = "LDAPAuthentication";
ldapAuthentication = (String) props.get(key);
key = "LDAPUser";
ldapUser = (String) props.get(key);
key = "LDAPPassword";
ldapPassword = (String) props.get(key);
try {
key = "LDAPRetryCount";
ldapRetryCount = Integer.parseInt((String)props.get(key));
key = "LDAPRetrySleepMillis";
ldapRetrySleepMillis = Long.parseLong((String)props.get(key));
} catch (Exception ignore) {
ldapRetryCount = DEFAULT_RETRY_COUNT;
ldapRetrySleepMillis = DEFAULT_RETRY_SLEEP_MILLIS;
}
} catch (Exception ipe) {
String[] arguments = new String[]{key};
FrameworkLogger.log(CLASS_NAME, "loadFromProperties", FrameworkLoggerLevel.SEVERE,
MessageConstants.ICOS_UTIL_DIRECTORY_CONTEXT_RESOURCE_PROVIDER_MISSING_PROPERTY,
arguments, ipe);
throw new ResourceProviderException(MessageConstants.ICOS_UTIL_DIRECTORY_CONTEXT_RESOURCE_PROVIDER_MISSING_PROPERTY,
arguments, ipe);
}
}
}