JNDIPrincipalStore.java
| Index Score | ||
|---|---|---|
![]() |
![]() |
org.apache.slide.store.txjndi |
![]() |
![]() |
Jakarta Slide |
View: Reasons, Metrics, Source Code
These are the metrics that contribute to the Enerjy Score for this file, ranked by impact. So the metrics listed at the top influence the score to a greater extent that the metrics listed at the bottom.
/*
* $Header$
* $Revision: 467526 $
* $Date: 2006-10-24 20:46:09 -0400 (Tue, 24 Oct 2006) $
*
* ====================================================================
*
* Copyright 1999-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.slide.store.txjndi;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.Vector;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.ObjectExistsException;
import org.apache.slide.common.AbstractXAService;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.ServiceConnectionFailedException;
import org.apache.slide.common.ServiceDisconnectionFailedException;
import org.apache.slide.common.ServiceInitializationFailedException;
import org.apache.slide.common.ServiceParameterErrorException;
import org.apache.slide.common.ServiceParameterMissingException;
import org.apache.slide.common.ServiceResetFailedException;
import org.apache.slide.common.Uri;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.NodeRevisionNumber;
import org.apache.slide.content.RevisionAlreadyExistException;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.content.RevisionNotFoundException;
import org.apache.slide.lock.LockTokenNotFoundException;
import org.apache.slide.lock.NodeLock;
import org.apache.slide.security.NodePermission;
import org.apache.slide.store.ContentStore;
import org.apache.slide.store.LockStore;
import org.apache.slide.store.NodeStore;
import org.apache.slide.store.RevisionDescriptorStore;
import org.apache.slide.store.RevisionDescriptorsStore;
import org.apache.slide.store.SecurityStore;
import org.apache.slide.structure.ObjectAlreadyExistsException;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.structure.SubjectNode;
import org.apache.slide.util.CacheManagerFactory;
import org.apache.slide.util.logger.Logger;
/**
* <p>
* This is a read-only Store implementation for retrieving Slide users
* and roles from an LDAP server. It has been tested with Novell's eDirectory
* version 8.6.2. Other LDAP servers should work.
* </p>
*
* <h3>Prerequisites</h3>
* <p>
* To use this Store your app server must be setup to authenticate
* users using the LDAP server. For Tomcat 5 see
* <a href="http://jakarta.apache.org/tomcat/tomcat-5.0-doc/realm-howto.html#JNDIRealm">this</a>.
* You cannot use the SlideRealm to authenticate users because this Store
* does not expose a <span style="font-style: italic;">password</span>
* property.
* </p>
*
* <h3>Store Parameters</h3>
* <p>
* Parameters used in Domain.xml when setting up the Store.
* </p>
* <dl>
* <dt>cache.name</dt>
* <dd>
* The name of the EHCache cache that should be used for this Store. If more
* than one JNDIPrincipalStore is used in a single Namespace this parameter
* can be used to make each one use a different cache. The default value is
* <em>org.apache.slide.store.txjndi.JNDIPrincipalStore</em>.
* See <a href="#cacherefreshing">Caching</a> for more information.
* </dd>
*
* <dt>cache.refresh.checkrate</dt>
* <dd>
* How often, in <em>seconds</em>, the cache refresh thread should check for Uris in the cache
* that need to be refreshed. The default value is "15".
* See <a href="#cacherefreshing">Cache Refreshing</a> for more information.
* </dd>
*
* <dt>cache.refresh.rate</dt>
* <dd>
* How frequently, in <em>seconds</em>, Uris that are marked for refreshing should be refreshed.
* This value must be less than the TimeToLive and TimeToIdle (whichever is least) parameters
* specified for the EHCache in order for the items to never expire. The default value is "800".
* See <a href="#caching">Caching</a> and <a href="#cacherefreshing">Cache Refreshing</a> for
* more information.
* </dd>
*
* <dt>cache.refresh.threshold</dt>
* <dd>
* The maximum amount of time, in <em>milliseconds</em>, that retrieve* methods can take before
* the Uri they are retrieving is scheduled for refreshing. By tuning this parameter you can keep
* smaller, infrequently accessed Uris from perpetually remaining in the cache. This may improve
* cache performance.
* See <a href="#cacherefreshing">Cache Refreshing</a> for more information.
* </dd>
*
* <dt>jndi.container</dt>
* <dd>
* The base LDAP context you wish to search. Example: <em>ou=Users,o=Company</em>
* </dd>
*
* <dt>jndi.attributes.rdn</dt>
* <dd>
* The attribute used to uniquely identify the objects you're fetching. Usually uid or cn.
* </dd>
*
* <dt>jndi.attributes.userprincipalname</dt>
* <dd>
* The attribute used to provide a user/role name which is mapped into Slide instead of the
* path name. This attribute is optional.
* </dd>
*
* <dt>jndi.search.filter</dt>
* <dd>
* The filter string to use for the search. Example: <em>(objectClass=inetOrgPerson)</em>.
* The default value is <em>(objectClass=*)</em>.
* See the <a href="http://java.sun.com/j2se/1.4.2/docs/api/javax/naming/directory/DirContext.html#search(javax.naming.Name,%20java.lang.String,%20javax.naming.directory.SearchControls)">DirContext.search()</a> javadoc.
* </dd>
*
* <dt>jndi.search.scope</dt>
* <dd>
* The Scope of the search. Can be one of <em>OBJECT_SCOPE</em>, <em>ONELEVEL_SCOPE</em>,
* <em>SUBTREE_SCOPE</em>. The default value is <em>ONELEVEL_SCOPE</em>.
* See the <a href="http://java.sun.com/j2se/1.4.2/docs/api/javax/naming/directory/SearchControls.html#OBJECT_SCOPE">SearchControls</a> javadoc.
* </dd>
*
* <dt>jndi.search.attributes</dt>
* <dd>
* A comma delimited list of the attributes you want returned with your search results.
* Example: <em>givenName, uid, mail</em>. The default value is <em>cn</em>.
* </dd>
*
* <dt>java.naming.*</dt>
* <dd>
* Parameters for connecting to the LDAP server.
* See the <a href="http://java.sun.com/j2se/1.4.2/docs/api/javax/naming/InitialContext.html">InitialContext</a> javadoc.
* </dd>
* </dl>
*
* <h3><a name="caching">Caching</a></h3>
* <p>
* This Store makes use of <a href="http://ehcache.sourceforge.net/">EHCache</a>.
* When initialized the default CacheManager is used to find the Cache named
* in the <em>cache.name</em> parameter. If there is no Cache found with this
* name then a Cache is created with these default values:
* </p>
* <ul>
* <li>name = org.apache.slide.store.txjndi.JNDIPrincipalStore</li>
* <li>maxElementsInMemory = 200</li>
* <li>eternal = false</li>
* <li>timeToIdleSeconds = 900</li>
* <li>timeToLiveSeconds = 900</li>
* <li>overflowToDisk = true</li>
* </ul>
* <p>
* To override these values you will need to create a configuration file for EHCache with
* the cache named by the <em>cache.name</em> parameter that has the settings you
* wish. See the documentation at the <a href="http://ehcache.sourceforge.net/">EHCache website</a>
* for instructions.
* </p>
*
* <h3><a name="cacherefreshing">Cache Refreshing</a></h3>
* <p>
* Because the data delivered by this Store is managed externally to Slide the data cache must be
* periodically expired to pick up any changes. Because creating object Nodes can take a long time
* for LDAP queries that return a lot of objects, larger queries need to be preemptively refreshed
* before a user makes a request to an expired object.
* </p>
* <p>
* The retrieveObject() and retrieveNodeDescriptor() methods monitor the amount of time it takes
* them to return for each Uri. If the method takes longer than a specified amount of time
* (configured with the <em>cache.refresh.threshold</em> parameter) the Uri is marked as needing
* to be refreshed. Upon initialization the Store spawns a child thread that periodically checks
* for Uris that need to be refreshed.
* </p>
*
* <h3>TODO:</h3>
* <ol>
* <li>
* I'd like to see this implemented as a ResourceManager rather than
* a stand-alone Store. I think it would fit into Slide's framework better
* that way and mean less duplicated code.
* </li>
* <li>
* I think there's still room for a full-fledged LDAP store. The way
* LDAP exposes a directory as a graph-of-objects-with-properties and
* Slide exposes a repository as a graph-of-objects-with-properties seems
* very similar to me ;). However, adapting the structure of most LDAP
* servers to the user/role structure that Slide uses would be a bit of a
* pain, so I don't think this kind of Store would be useful for
* users/roles in Slide. I have heard of people using LDAP to keep track
* of server inventories and things like that, though, and I think it
* would work well there.
* </li>
* </ol>
*
*/
public class JNDIPrincipalStore
extends AbstractXAService
implements ContentStore, LockStore, NodeStore, RevisionDescriptorStore,
RevisionDescriptorsStore, SecurityStore {
public static final String CACHE_OBJECT_PREFIX = "object: ";
public static final String CACHE_DESCRIPTOR_PREFIX = "descriptor: ";
public static final String JNDI_PROPERTY_PREFIX = "java.naming";
// Parameter keys
public static final String PARAM_CACHE_NAME = "cache.name";
public static final String PARAM_CACHE_REFRESH_CHECK_RATE = "cache.refresh.checkrate";
public static final String PARAM_CACHE_REFRESH_RATE = "cache.refresh.rate";
public static final String PARAM_CACHE_REFRESH_THRESHOLD = "cache.refresh.threshold";
public static final String PARAM_JNDI_CONTAINER = "jndi.container";
public static final String PARAM_JNDI_FILTER = "jndi.search.filter";
public static final String PARAM_JNDI_GROUPMEMBERSET = "jndi.attributes.groupmemberset";
public static final String PARAM_JNDI_RDN_ATTRIBUTE = "jndi.attributes.rdn";
public static final String PARAM_JNDI_SEARCH_ATTRIBUTES = "jndi.search.attributes";
public static final String PARAM_JNDI_SEARCH_SCOPE = "jndi.search.scope";
public static final String PARAM_JNDI_USERPRINCIPALNAME = "jndi.attributes.userprincipalname";
public static final String PARAM_LOG_VALIDATION_ERRORS = "log.validationerrors";
// Default values
public static final String DEFAULT_CACHE_NAME = JNDIPrincipalStore.class.getName();
public static final int DEFAULT_CACHE_SIZE = 200;
public static final boolean DEFAULT_CACHE_OVERFLOW_TO_DISK = true;
public static final boolean DEFAULT_CACHE_ETERNAL = false;
public static final long DEFAULT_CACHE_TTL = 900L; // seconds
public static final long DEFAULT_CACHE_TTI = 900L; // seconds
public static final long DEFAULT_CACHE_REFRESH_CHECK_RATE = 15L; // seconds
public static final long DEFAULT_CACHE_REFRESH_RATE = 800L; // seconds
public static final long DEFAULT_CACHE_REFRESH_THRESHOLD = 200L; // milliseconds!!
public static final String DEFAULT_JNDI_SEARCH_ATTRIBUTES = "cn";
public static final String DEFAULT_JNDI_FILTER = "(objectClass=*)";
public static final String LDAP_NAMESPACE = "LDAP:";
public static final String LOG_CHANNEL = JNDIPrincipalStore.class.getName();
protected Hashtable ctxParameters;
protected boolean isConnected = false;
protected boolean logValidationErrors = false;
protected Cache cache;
protected TreeSet refreshList;
protected long refreshRate = DEFAULT_CACHE_REFRESH_RATE;
protected long refreshThreadSleepTime = DEFAULT_CACHE_REFRESH_CHECK_RATE;
protected long refreshThreshold = DEFAULT_CACHE_REFRESH_THRESHOLD;
protected RefreshThread refresher;
protected String container;
protected String[] descriptorAttributes;
protected String filter;
protected String groupMemberSet;
protected String rdnAttribute;
protected int searchScope;
protected String principalNameAttribute;
private String cacheName = DEFAULT_CACHE_NAME;
private String name;
private String usersPath;
private Map objectNameMap; // Uri-String -> LDAP lookup name
public JNDIPrincipalStore() {
ctxParameters = new Hashtable();
name = "";
refreshList = new TreeSet();
refresher = new RefreshThread();
objectNameMap = new HashMap();
}
// ----------------------------------------------------------- Service Methods --------
public void initialize( NamespaceAccessToken token )
throws ServiceInitializationFailedException {
name = "JNDIPrincipalStore[" + scope.toString() + "]";
getLogger().log( "Initializing " + name, LOG_CHANNEL, Logger.DEBUG );
super.initialize( token );
usersPath = token.getNamespaceConfig().getUsersPath();
}
public void setParameters( Hashtable parameters )
throws ServiceParameterErrorException,
ServiceParameterMissingException {
getLogger().log( "Setting parameters for " + name, LOG_CHANNEL, Logger.DEBUG );
String temp = null;
// Set ctxParameters
Iterator keys = parameters.keySet().iterator();
while ( keys.hasNext() ) {
temp = (String)keys.next();
if ( temp.startsWith( JNDI_PROPERTY_PREFIX ) ) {
ctxParameters.put( temp, parameters.get( temp ) );
}
}
// Set container
container = (String)parameters.get( PARAM_JNDI_CONTAINER );
if ( container == null || container.length() == 0 ) {
getLogger().log(
"Error during " + name + " setup: No value set for " +
PARAM_JNDI_CONTAINER,
LOG_CHANNEL,
Logger.CRITICAL );
}
// Set filter
filter = (String)parameters.get( PARAM_JNDI_FILTER );
if ( filter == null || filter.length() == 0 ) {
filter = DEFAULT_JNDI_FILTER;
}
// Set rdnAttribute
rdnAttribute = (String)parameters.get( PARAM_JNDI_RDN_ATTRIBUTE );
if ( rdnAttribute == null || rdnAttribute.length() == 0 ) {
getLogger().log(
"Error during " + name + " setup: No value set for " +
PARAM_JNDI_RDN_ATTRIBUTE,
LOG_CHANNEL,
Logger.CRITICAL );
}
// Set searchScope
temp = (String)parameters.get( PARAM_JNDI_SEARCH_SCOPE );
if ( "OBJECT_SCOPE".equalsIgnoreCase( temp ) ) {
searchScope = SearchControls.OBJECT_SCOPE;
} else if ( "ONELEVEL_SCOPE".equalsIgnoreCase( temp ) ) {
searchScope = SearchControls.ONELEVEL_SCOPE;
} else if ( "SUBTREE_SCOPE".equalsIgnoreCase( temp ) ) {
searchScope = SearchControls.SUBTREE_SCOPE;
} else {
searchScope = SearchControls.ONELEVEL_SCOPE;
}
// Set descriptorAttributes and groupMemberSet
temp = (String)parameters.get( PARAM_JNDI_SEARCH_ATTRIBUTES );
if ( temp == null || temp.length() == 0 ) {
temp = DEFAULT_JNDI_SEARCH_ATTRIBUTES;
}
ArrayList searchAttributesList = new ArrayList();
StringTokenizer tok = new StringTokenizer( temp, "," );
while ( tok.hasMoreTokens() ) {
searchAttributesList.add( tok.nextToken().trim() );
}
String gms = (String)parameters.get( PARAM_JNDI_GROUPMEMBERSET );
if ( gms != null ) {
searchAttributesList.add( gms );
groupMemberSet = gms;
} else {
groupMemberSet = "";
}
descriptorAttributes = (String[])searchAttributesList.toArray( new String[0] );
// Set refreshRate
temp = (String)parameters.get( PARAM_CACHE_REFRESH_RATE );
if ( temp != null ) {
refreshRate = Long.parseLong( temp ) * 1000L;
} else {
refreshRate = DEFAULT_CACHE_REFRESH_RATE * 1000L;
}
// Set refreshThreadSleepTime
temp = (String)parameters.get( PARAM_CACHE_REFRESH_CHECK_RATE );
if ( temp != null ) {
refreshThreadSleepTime = Long.parseLong( temp ) * 1000L;
} else {
refreshThreadSleepTime = DEFAULT_CACHE_REFRESH_CHECK_RATE * 1000L;
}
// Set refreshThreshold
temp = (String)parameters.get( PARAM_CACHE_REFRESH_THRESHOLD );
if ( temp != null ) {
refreshThreshold = Long.parseLong( temp );
} else {
refreshThreshold = DEFAULT_CACHE_REFRESH_THRESHOLD;
}
// Set logValidationErrors
temp = (String)parameters.get( PARAM_LOG_VALIDATION_ERRORS );
if ( "true".equalsIgnoreCase( temp ) ) {
logValidationErrors = true;
}
//Set attribute which contains the user principal name for authentication
principalNameAttribute = (String)parameters.get(PARAM_JNDI_USERPRINCIPALNAME);
// Set cacheName
temp = (String)parameters.get( PARAM_CACHE_NAME );
if ( temp != null ) {
cacheName = temp;
} else {
cacheName = DEFAULT_CACHE_NAME;
}
cache = getCache();
}
public boolean cacheResults() {
return false;
}
//------------------------------------------------------ NodeStore Methods ----------
public void storeObject( Uri uri, ObjectNode object )
throws ServiceAccessException, ObjectNotFoundException {}
public void createObject( Uri uri, ObjectNode object )
throws ServiceAccessException, ObjectAlreadyExistsException {}
public void removeObject( Uri uri, ObjectNode object )
throws ServiceAccessException, ObjectNotFoundException {}
public ObjectNode retrieveObject( Uri uri ) throws ServiceAccessException,
ObjectNotFoundException {
getLogger().log( name + ": Retrieving Object " + uri.toString() + ".",
LOG_CHANNEL, Logger.DEBUG );
String cacheKey = CACHE_OBJECT_PREFIX + uri.toString();
Element cachedNode = null;
try {
if ( cache != null ) {
cachedNode = cache.get( cacheKey );
}
} catch ( CacheException e ) {
getLogger().log(
name + ": Error while getting \"" + cacheKey + "\" from cache.",
e,
LOG_CHANNEL,
Logger.ERROR);
}
if ( cachedNode != null ) {
getLogger().log(
name + ": ObjectNode for \"" + uri.toString() + "\" found in cache.",
LOG_CHANNEL,
Logger.DEBUG );
return (ObjectNode)cachedNode.getValue();
} else {
return getObject( uri );
}
}
//-------------------------------------------- RevisionDescriptorStore Methods --------
public void createRevisionDescriptor( Uri uri, NodeRevisionDescriptor revisionDescriptor )
throws ServiceAccessException {}
public void storeRevisionDescriptor( Uri uri, NodeRevisionDescriptor revisionDescriptor )
throws ServiceAccessException, RevisionDescriptorNotFoundException {}
public void removeRevisionDescriptor( Uri uri, NodeRevisionNumber revisionNumber )
throws ServiceAccessException {}
public NodeRevisionDescriptor retrieveRevisionDescriptor( Uri uri,
NodeRevisionNumber revisionNumber ) throws ServiceAccessException,
RevisionDescriptorNotFoundException {
getLogger().log( name + ": Retrieving Revision Descriptor for " + uri.toString() + ".",
LOG_CHANNEL, Logger.DEBUG );
String cacheKey = CACHE_DESCRIPTOR_PREFIX + uri.toString();
Element cachedDescriptor = null;
try {
if ( cache != null ) {
cachedDescriptor = cache.get( cacheKey );
}
} catch ( CacheException e ) {
getLogger().log(
name + ": Error while getting \"" + cacheKey + "\" from cache.",
e,
LOG_CHANNEL,
Logger.ERROR);
}
if ( cachedDescriptor != null ) {
getLogger().log(
name + ": NodeRevisionDescriptor for \"" + uri.toString() + "\" found in cache.",
LOG_CHANNEL,
Logger.DEBUG );
return (NodeRevisionDescriptor)cachedDescriptor.getValue();
} else {
return getRevisionDescriptor( uri );
}
}
// --------------------------------------------- RevisionDescriptorsStore Methods -----
public NodeRevisionDescriptors retrieveRevisionDescriptors( Uri uri )
throws ServiceAccessException, RevisionDescriptorNotFoundException {
getLogger().log( name + ": Retrieving Revision Descriptors for " + uri.toString() + ".",
LOG_CHANNEL, Logger.DEBUG );
NodeRevisionNumber rev = new NodeRevisionNumber( 1, 0 );
Hashtable workingRevisions = new Hashtable();
workingRevisions.put( "1.0", rev );
Hashtable latestRevisionNumbers = new Hashtable();
latestRevisionNumbers.put( "1.0", rev );
// From looking at NodeRevisionDescriptors.cloneObject I see branchNames is
// supposed to store a Vector. I'm guessing the Vector is of revision numbers
Vector branches = new Vector();
branches.add( rev );
Hashtable branchNames = new Hashtable();
branchNames.put( "main", branches );
return new NodeRevisionDescriptors(
uri.toString(),
rev,
workingRevisions,
latestRevisionNumbers,
branchNames,
false );
}
public void createRevisionDescriptors( Uri uri, NodeRevisionDescriptors revisionDescriptors )
throws ServiceAccessException {}
public void storeRevisionDescriptors( Uri uri, NodeRevisionDescriptors revisionDescriptors )
throws ServiceAccessException, RevisionDescriptorNotFoundException {}
public void removeRevisionDescriptors( Uri uri ) throws ServiceAccessException {}
// --------------------------------------------------------- XA Methods --------------
public void connect() throws ServiceConnectionFailedException {
if ( !refresher.isAlive() ) {
refresher.start();
}
}
public void disconnect() throws ServiceDisconnectionFailedException {
if ( refresher.isAlive() ) {
refresher.halt();
}
}
public void reset() throws ServiceResetFailedException {}
public boolean isConnected() throws ServiceAccessException {
return true;
}
public int getTransactionTimeout() throws XAException {
return 0;
}
public boolean setTransactionTimeout( int seconds ) throws XAException {
return false;
}
public boolean isSameRM( XAResource rm ) throws XAException {
return false;
}
public Xid[] recover( int flag ) throws XAException {
return new Xid[0];
}
public int prepare( Xid txId ) throws XAException {
return XA_RDONLY;
}
public void forget( Xid txId ) throws XAException {}
public void rollback( Xid txId ) throws XAException {}
public void end( Xid txId, int flags ) throws XAException {}
public void start( Xid txId, int flags ) throws XAException {}
public void commit( Xid txId, boolean onePhase ) throws XAException {}
// -------------------------------------------------- ContentStore Methods ----------
public NodeRevisionContent retrieveRevisionContent(
Uri uri, NodeRevisionDescriptor revisionDescriptor )
throws ServiceAccessException, RevisionNotFoundException {
NodeRevisionContent nrc = new NodeRevisionContent();
nrc.setContent( new char[0] );
return nrc;
}
public void createRevisionContent(
Uri uri,
NodeRevisionDescriptor revisionDescriptor,
NodeRevisionContent revisionContent )
throws ServiceAccessException, RevisionAlreadyExistException {}
public void storeRevisionContent(
Uri uri,
NodeRevisionDescriptor revisionDescriptor,
NodeRevisionContent revisionContent )
throws ServiceAccessException, RevisionNotFoundException {}
public void removeRevisionContent( Uri uri, NodeRevisionDescriptor revisionDescriptor )
throws ServiceAccessException {}
// --------------------------------------------------- Security Store Methods ---------------
public void grantPermission(Uri uri, NodePermission permission) throws ServiceAccessException {
// TODO Auto-generated method stub
}
public void revokePermission(Uri uri, NodePermission permission) throws ServiceAccessException {
// TODO Auto-generated method stub
}
public void revokePermissions(Uri uri) throws ServiceAccessException {
// TODO Auto-generated method stub
}
/**
* Always returns read access for all users.
*/
public Enumeration enumeratePermissions(Uri uri) throws ServiceAccessException {
Vector permissions = new Vector();
permissions.add(
new NodePermission(
uri.toString(),
"all",
uri.getNamespace().getConfig().getReadObjectAction().getUri() ) );
return permissions.elements();
}
// --------------------------------------------------- LockStore Methods ---------------
public void putLock(Uri uri, NodeLock lock) throws ServiceAccessException {
// TODO Auto-generated method stub
}
public void renewLock(Uri uri, NodeLock lock) throws ServiceAccessException, LockTokenNotFoundException {
// TODO Auto-generated method stub
}
public void removeLock(Uri uri, NodeLock lock) throws ServiceAccessException, LockTokenNotFoundException {
// TODO Auto-generated method stub
}
public void killLock(Uri uri, NodeLock lock) throws ServiceAccessException, LockTokenNotFoundException {
// TODO Auto-generated method stub
}
public Enumeration enumerateLocks(Uri uri) throws ServiceAccessException {
return new Vector().elements();
}
// --------------------------------------------------- Worker Methods ---------------
protected SubjectNode getObject( Uri uri )
throws ObjectNotFoundException, ServiceAccessException {
long start = System.currentTimeMillis();
DirContext ctx = null;
try {
ctx = getContext();
} catch ( ServiceConnectionFailedException e ) {
throw new ServiceAccessException(this, e);
}
Uri parentUri = uri.getParentUri();
String objectName = getObjectNameFromUri( uri );
Vector parentBindings = new Vector();
Vector childBindings = new Vector();
// As long as this node isn't the root node create a parent binding.
// This doesn't appear to do anything, but just in case.
if ( !uri.toString().equals( "/" ) ) {
parentBindings.add( new ObjectNode.Binding( objectName, parentUri.toString() ) );
}
SearchControls controls = new SearchControls();
controls.setSearchScope( searchScope );
// If the uri matches the scope create a SubjectNode with bindings for all
// of the results from a jndi search
if ( uri.isStoreRoot() ) {
try {
NamingEnumeration results = ctx.search(
container,
filter,
controls );
if ( !results.hasMore() ) {
getLogger().log(
name + ": No objects found in container " + container +
" that match filter " + filter + ".",
LOG_CHANNEL,
Logger.WARNING );
}
while ( results.hasMore() ) {
SearchResult result = null;
try {
result = (SearchResult)results.next();
} catch ( NamingException e ) {
getLogger().log(
name + ": Error getting next search result.",
e, LOG_CHANNEL, Logger.ERROR );
}
String name = result.getName();
if ( !validatePathName( name ) ) {
continue;
}
String value = parseLdapName(name);
if (principalNameAttribute != null) {
String uriValue = ((String)result.getAttributes().get(principalNameAttribute).get()).toLowerCase();
objectNameMap.put(uriValue, value);
value = uriValue;
}
getLogger().log(
name + ": Creating child binding \"" + value + "\" for \"" +
uri.toString() + "\".",
LOG_CHANNEL, Logger.DEBUG );
childBindings.add(
new ObjectNode.Binding( value, uri.toString() + "/" + value ) );
}
} catch ( NamingException e ) {
getLogger().log(
name + ": Error during search.",
e, LOG_CHANNEL, Logger.ERROR );
}
} else {
// If the uri matches the scope + something try to do a lookup
// of the "+ something" in LDAP.
try {
if (principalNameAttribute != null && objectNameMap.get(objectName) == null)
retrieveObject(parentUri);
NamingEnumeration results = ctx.search(
container,
rdnAttribute + "=" + (principalNameAttribute != null ? (String)objectNameMap.get(objectName) : objectName),
controls);
if ( !results.hasMore() ) {
if (ctx != null) {
closeContext(ctx);
}
throw new ObjectNotFoundException( uri );
}
} catch ( NamingException e ) {
getLogger().log(
name + ": Error retrieving " + uri.toString(),
e, LOG_CHANNEL, Logger.ERROR );
if (ctx != null) {
closeContext(ctx);
}
throw new ServiceAccessException( this, e );
}
}
getLogger().log( name + ": Creating SubjectNode for \"" + uri.toString() + "\".",
LOG_CHANNEL, Logger.DEBUG );
SubjectNode node = new SubjectNode(
uri.toString(), childBindings, parentBindings, new Vector() );
// Workaround for bug in ObjectNode.validate()
node.setUri( uri.toString() );
if ( cache != null ) {
getLogger().log(
name + ": Putting ObjectNode for " + uri.toString() + " to cache.",
LOG_CHANNEL,
Logger.DEBUG );
Element cachedNode = new Element( CACHE_OBJECT_PREFIX + uri.toString(), node );
cache.put(cachedNode);
}
long elapsed = System.currentTimeMillis() - start;
if ( elapsed > refreshThreshold ) {
addRefreshee( uri, Refreshee.REFRESH_OBJECT );
}
if (ctx != null) {
closeContext(ctx);
}
return node;
}
protected NodeRevisionDescriptor getRevisionDescriptor( Uri uri )
throws RevisionDescriptorNotFoundException, ServiceAccessException {
long start = System.currentTimeMillis();
DirContext ctx = null;
try {
ctx = getContext();
} catch ( ServiceConnectionFailedException e ) {
throw new ServiceAccessException(this, e);
}
String objectName = getObjectNameFromUri( uri );
Hashtable props = new Hashtable();
String resourceType = "<collection/>";
if ( !uri.isStoreRoot() ) {
resourceType += "<principal/>";
}
props.put(
"DAV:resourcetype",
new NodeProperty( "resourcetype", resourceType, "DAV:", "", false ) );
props.put(
"DAV:displayname",
new NodeProperty( "displayname", (!uri.isStoreRoot() && principalNameAttribute != null?(String)objectNameMap.get(objectName):objectName), "DAV:", "", false ) );
// The storeRoot isn't a real object so it doesn't have any parameters to look up
if ( !uri.isStoreRoot() ) {
String localFilter = rdnAttribute + "=" + (principalNameAttribute != null?(String)objectNameMap.get(objectName):objectName);
SearchControls controls = new SearchControls();
controls.setSearchScope( searchScope );
controls.setReturningAttributes( descriptorAttributes );
try {
NamingEnumeration results = ctx.search(
container,
localFilter,
controls );
if ( !results.hasMore() ) {
if (ctx != null) {
closeContext(ctx);
}
throw new RevisionDescriptorNotFoundException( uri.toString() );
}
while ( results.hasMore() ) {
SearchResult result = null;
try {
result = (SearchResult)results.next();
} catch ( NamingException e ) {
getLogger().log(
name + ": Error getting search result with filter: " + localFilter +
" from container: " + container + ".",
LOG_CHANNEL, Logger.ERROR );
if (ctx != null) {
closeContext(ctx);
}
throw new ServiceAccessException( this, e );
}
NamingEnumeration attributes = result.getAttributes().getAll();
while ( attributes.hasMore() ) {
Attribute attribute = (Attribute)attributes.next();
StringBuffer valueString = new StringBuffer();
boolean isGms = attribute.getID().equals( groupMemberSet );
boolean isMva = attribute.size() > 1;
for ( int i = 0; i < attribute.size(); i++ ) {
try {
Object value = attribute.get( i );
if ( !( value instanceof String ) ) {
getLogger().log(
name + ": Non-string value found for " +
attribute.getID() + ".",
LOG_CHANNEL,
Logger.DEBUG );
continue;
}
if ( isGms ) {
valueString.append( "<D:href xmlns:D='DAV:'>" );
valueString.append( usersPath ).append( "/" );
String name = parseLdapName(value.toString());
if (principalNameAttribute != null) {
// lookup LDAP user entry
controls.setReturningAttributes(new String[] { principalNameAttribute });
NamingEnumeration roleResults =
ctx.search(container, rdnAttribute + "=" + name, controls);
if (roleResults.hasMore()) {
SearchResult userObject = (SearchResult)roleResults.next();
name = ((String)userObject.getAttributes().get(principalNameAttribute).get()).toLowerCase();
}
}
valueString.append(name);
valueString.append( "</D:href>" );
} else {
if ( isMva ) {
valueString.append( "<mva xmlns=\"" )
.append( LDAP_NAMESPACE ).append( "\">" );
valueString.append( value.toString() );
valueString.append( "</mva>" );
} else {
valueString.append( value.toString() );
}
}
} catch ( NamingException e ) {
getLogger().log(
name + ": Error fetching next attribute value for attribute " +
attribute.getID() + ".",
e, LOG_CHANNEL, Logger.DEBUG );
}
}
if ( isGms ) {
getLogger().log(
name + ": Adding property \"group-member-set\" in namespace " +
"\"DAV:\" with value of \"" + valueString.toString() + "\" to " +
uri.toString() + ".",
LOG_CHANNEL, Logger.DEBUG );
props.put(
"DAV:group-member-set",
new NodeProperty(
"group-member-set",
valueString.toString(),
"DAV:" ) );
} else {
getLogger().log(
name + ": Adding property \"" + attribute.getID() +
"\" in namespace \"" + LDAP_NAMESPACE + "\" " +
"with value of \"" +
valueString.toString() + "\" to " + uri.toString() + ".",
LOG_CHANNEL, Logger.DEBUG );
props.put(
LDAP_NAMESPACE + attribute.getID(),
new NodeProperty(
attribute.getID(),
valueString.toString(),
LDAP_NAMESPACE ) );
}
}
}
} catch ( NamingException e ) {
getLogger().log(
name + ": Error during search.",
e, LOG_CHANNEL, Logger.ERROR );
}
}
NodeRevisionDescriptor descriptor = new NodeRevisionDescriptor(
new NodeRevisionNumber( 1, 0 ),
"main",
new Vector(),
props );
if ( cache != null ) {
getLogger().log(
name + ": Putting NodeRevisionDescriptor for " + uri.toString() + " to cache.",
LOG_CHANNEL,
Logger.DEBUG );
Element cachedDescriptor = new Element(
CACHE_DESCRIPTOR_PREFIX + uri.toString(), descriptor );
cache.put(cachedDescriptor);
}
long elapsed = System.currentTimeMillis() - start;
if ( elapsed > refreshThreshold ) {
addRefreshee( uri, Refreshee.REFRESH_DESCRIPTOR );
}
if (ctx != null) {
closeContext(ctx);
}
return descriptor;
}
// --------------------------------------------------- Helper Methods ---------------
/**
* Closes a JNDI connection.
* @param ctx the Context to close
*/
private void closeContext(DirContext ctx) {
getLogger().log( name + ": Disconnecting from LDAP server.", LOG_CHANNEL, Logger.DEBUG );
try {
ctx.close();
} catch ( NamingException e ) {
getLogger().log( name + ": Error disconnecting from LDAP",
e, LOG_CHANNEL, Logger.WARNING );
ctx = null;
}
}
/**
* Gets a JNDI Context using the connection parameters specified for
* this Store in the Domain.
* @throws ServiceConnectionFailedException
*/
private DirContext getContext() throws ServiceConnectionFailedException {
getLogger().log( name + ": Connecting to LDAP server.", LOG_CHANNEL, Logger.DEBUG );
try {
DirContext ctx = new InitialDirContext( ctxParameters );
if ( ctx != null ) {
return ctx;
} else {
throw new ServiceConnectionFailedException(
this, "Invalid JNDI connection parameters." );
}
} catch ( NamingException e ) {
getLogger().log( name + ": Error Connecting to LDAP Server",
e,
LOG_CHANNEL,
Logger.CRITICAL );
throw new ServiceConnectionFailedException( this, e );
}
}
protected String getObjectNameFromUri( Uri uri ) {
String objectName = uri.toString().substring(
uri.toString().lastIndexOf( "/" ) + 1 );
return objectName.toLowerCase();
}
protected String parseLdapName( String name ) {
// Since attribute values can contain pretty much anything, parsing
// name to get the attribute value isn't terribly accurate.
// The slow way is to find a value for the attribute that matches the
// results from getName(), but that is horribly horribly slow.
// On the assumption that "," is more likely to be in the value than
// "=", this should work most of the time and be faster... I hope.
if (name.equals("")) return name;
int firstEqual = name.indexOf("=");
int secondEqual = name.substring( firstEqual + 1 ).indexOf( "=" );
if ( secondEqual < 0 ) {
secondEqual = name.length() - 1;
} else {
secondEqual = secondEqual + firstEqual + 1;
}
int end = name.substring( 0, secondEqual ).lastIndexOf( "," );
if ( end < 0 ) {
end = name.length();
}
String value = name.substring( firstEqual + 1, end ).toLowerCase();
return value;
}
/**
* Checks a String to make sure it is a valid path element.
*
* @param name the name to validate
* @return false if any of the validation checks failed.
*/
protected boolean validatePathName( String name ) {
boolean valid = true;
if ( name.indexOf("/") > -1 ) {
// Skip names that contain "/". They're evil!! (and they break things)
valid = false;
if ( logValidationErrors ) {
getLogger().log(
name + ": Skipping child with name \"" + name + "\" because " +
"it contains a /.",
LOG_CHANNEL, Logger.ERROR );
}
}
return valid;
}
// ----------------------------------------------- Cache Methods/Classes ---------------
protected synchronized void addRefreshee( Uri uri, int refreshType ) {
getLogger().log(
name + ": Adding refreshee for \"" + uri.toString() + "\" of type \"" +
(refreshType == Refreshee.REFRESH_OBJECT ? "object" : "descriptor") + "\".",
LOG_CHANNEL,
Logger.DEBUG );
refreshList.add(
new Refreshee( uri, System.currentTimeMillis() + refreshRate, refreshType ) );
}
protected Cache getCache() {
CacheManager cacheManager = null;
Cache cache = null;
try {
cacheManager = CacheManagerFactory.getDefaultCacheManager();
} catch ( CacheException e ) {
getLogger().log(
name + ": Error getting default CacheManager.",
e,
LOG_CHANNEL,
Logger.ERROR );
return null;
}
cache = cacheManager.getCache( cacheName );
if ( cache == null ) {
cache = new Cache(
cacheName,
DEFAULT_CACHE_SIZE,
DEFAULT_CACHE_OVERFLOW_TO_DISK,
DEFAULT_CACHE_ETERNAL,
DEFAULT_CACHE_TTL,
DEFAULT_CACHE_TTI );
try {
cacheManager.addCache(cache);
} catch ( IllegalStateException e ) {
getLogger().log(
name + ": Error adding cache \"" + cacheName + "\" to CacheManager.",
e,
LOG_CHANNEL,
Logger.ERROR);
} catch ( ObjectExistsException e ) {
getLogger().log(
name + ": Error adding cache \"" + cacheName + "\" to CacheManager.",
e,
LOG_CHANNEL,
Logger.ERROR);
} catch ( CacheException e ) {
getLogger().log(
name + ": Error adding cache \"" + cacheName + "\" to CacheManager.",
e,
LOG_CHANNEL,
Logger.ERROR);
}
}
return cache;
}
protected synchronized Refreshee getNextRefreshee() {
Refreshee refreshee = null;
try {
refreshee = (Refreshee)refreshList.last();
} catch ( NoSuchElementException e ) {
// expected when the list is emtpy
}
return refreshee;
}
protected void refreshCache() {
Refreshee oldest = getNextRefreshee();
long now = System.currentTimeMillis();
while ( oldest != null && oldest.getRefreshTime() < now ) {
getLogger().log(
name + ": Refreshing cache for \"" + oldest.getUri().toString() + "\" of type \"" +
(oldest.getRefreshType() == Refreshee.REFRESH_OBJECT ? "object" : "descriptor") +
"\".",
LOG_CHANNEL,
Logger.DEBUG );
removeRefreshee( oldest );
try {
Cache cache = getCache();
if ( cache != null ) {
switch( oldest.getRefreshType() ) {
case Refreshee.REFRESH_OBJECT:
getObject( oldest.getUri() );
break;
case Refreshee.REFRESH_DESCRIPTOR:
getRevisionDescriptor( oldest.getUri() );
break;
}
}
} catch ( ObjectNotFoundException e ) {
getLogger().log(
name + ": Error refreshing cache for \"" + oldest.getUri().toString() + "\".",
e,
LOG_CHANNEL,
Logger.ERROR );
} catch ( ServiceAccessException e ) {
getLogger().log(
name + ": Error refreshing cache for \"" + oldest.getUri().toString() + "\".",
e,
LOG_CHANNEL,
Logger.ERROR );
} catch ( RevisionDescriptorNotFoundException e ) {
getLogger().log(
name + ": Error refreshing cache for \"" + oldest.getUri().toString() + "\".",
e,
LOG_CHANNEL,
Logger.ERROR );
}
oldest = getNextRefreshee();
}
}
protected synchronized void removeRefreshee( Refreshee refreshee ) {
refreshList.remove( refreshee );
}
/**
* Implements Comparable so that the older the object, the sooner it needs to be refreshed,
* and therefor the "greater" it is. When used with a SortedSet this ensures objects are
* refreshed in the proper order.
*
*/
protected class Refreshee implements Comparable {
public static final int REFRESH_OBJECT = 0;
public static final int REFRESH_DESCRIPTOR = 1;
private long refreshTime;
private Uri uri;
private int refreshType;
// No default constructor
private Refreshee() {}
public Refreshee( Uri uri, long refreshTime, int refreshType ) {
this.refreshTime = refreshTime;
this.uri = uri;
this.refreshType = refreshType;
}
public int compareTo( Object object ) {
if ( object instanceof Refreshee ) {
Refreshee other = (Refreshee)object;
int result = compare( this.getRefreshTime(), other.getRefreshTime() );
if ( result != 0 ) {
return result;
}
}
return compare( hashCode(), object.hashCode() );
}
public long getRefreshTime() {
return refreshTime;
}
public int getRefreshType() {
return refreshType;
}
public Uri getUri() {
return uri;
}
private int compare( long first, long second ) {
if (first < second) {
return 1;
}
if (second < first) {
return -1;
}
return 0;
}
}
protected class RefreshThread extends Thread {
private boolean run;
public RefreshThread() {
super( "RefreshThread-" + name );
setPriority( Thread.MIN_PRIORITY );
setDaemon( false );
}
public synchronized void halt() {
run = false;
}
public void run() {
run = true;
while ( keepRunning() ) {
try {
Thread.sleep( refreshThreadSleepTime );
} catch ( InterruptedException e ) {}
refreshCache();
}
}
private synchronized boolean keepRunning() {
return run;
}
}
}
The table below shows all metrics for JNDIPrincipalStore.java.




