/*
 * $Header: /esup-portail/injac/applications/SlideServer/Stores/uPortalStore/src/stores/org/apache/slide/store/uPortal/UPortalRolesStore.java,v 1.6 2005/07/27 08:44:27 tbellemb Exp $
 * $Revision: 1.6 $
 * $Date: 2005/07/27 08:44:27 $
 *
 * ====================================================================
 *
 * 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.uPortal;

import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.security.Principal;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import javax.xml.rpc.ServiceException;

import localhost.uPortal.services.PortalGroups.PortalGroups;
import localhost.uPortal.services.PortalGroups.PortalGroupsServiceLocator;

import org.apache.slide.authenticate.CredentialsToken;
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.SlideToken;
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.logger.Logger;

import fr.univrennes1.cri.util.cacheManagement.CacheManager;
import fr.univrennes1.cri.util.cacheManagement.Cacheable;

/**
 * @author tbellemb
 * @version 1.0
 * Creation date : 01.01.2005
 * Last modification date : 01.01.2005
 * Modifications :
 */

public class UPortalRolesStore
		extends AbstractXAService
		implements ContentStore, LockStore, NodeStore, RevisionDescriptorStore,
			RevisionDescriptorsStore, SecurityStore {

	public static final String CACHE_NAME              = UPortalRolesStore.class.getName();
	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_UPORTAL_WEBSERVICE_URL = "uportal.webservice.url";
	public static final String PARAM_UPORTAL_STORE_CACHE_DISABLECACHE = "uportal.store.cache.disableCache";
	public static final String PARAM_UPORTAL_STORE_CACHE_SLEEPINGTIME = "uportal.store.cache.cleaningThreadSleepingTimeInMinutes";
	public static final String PARAM_UPORTAL_STORE_CACHE_USERINFO_MTL = "uportal.store.cache.cachedUserInformationMinutesTolive";
	public static final String PARAM_UPORTAL_STORE_CACHE_OBJECTNODE_MTL = "uportal.store.cache.cachedUserInformationMinutesTolive";
			
	public static final String PARAM_LOG_VALIDATION_ERRORS = "log.validationerrors";
	
	// Default values
	//public static final String LDAP_NAMESPACE = "LDAP:";
	public static final String LOG_CHANNEL = UPortalRolesStore.class.getName();
	protected boolean logValidationErrors = false;
	
	/////////////
	// Parameters
	//
	// Disable the cache !
	public boolean disableCache;
	// Sleeping time between each cache cleaning cycle
	public int sleepingTime;
	// User information TTL in minutes
	public int userInfoMinutesToLive;
	// Object Node TTL in minutes
	public int objectNodeMinutesToLive;
	// Cache manager caching information returned by the web service
	public CacheManager cacheManager;
	
	////////////
	// Variables
	//
	// Usually /users, /roles /roles/uPortal ...
	private String usersPath; // not used for the moment
	private String rolesPath; // usually /roles
	private String roleScope; // usually /roles/uPortal
	
	// WebService variables
	private PortalGroups uPortalWSStore; // web service object
	private String uPortalWebserviceUrl; // web service URL
	
	// Other variables
	private String connectedUser; // connected user
	// variables used to process groups
	private Vector groupsOfConnectedUserVector; // groups of the connected user
	private Enumeration groupsOfConnectedUserEnumeration;
	private Vector nestedGroupsOfCurrentGroupVector; // nested groups of a given group
	private Enumeration nestedGroupsOfCurrentGroupEnumeration;
	private Vector rootGroupsVector; // portal root group
	private Enumeration rootGroupsEnumeration;
		
	
	/////////////////////////
	// get the connected user
	//
	private String getPrincipal(Uri uri){
		
		String resultPrincipal = "";
		
		SlideToken slideToken = uri.getToken();
		if (slideToken != null){
			CredentialsToken credentialsToken = slideToken.getCredentialsToken();
			if (credentialsToken != null){
				Principal principal = credentialsToken.getPrincipal();
				if (principal != null){
					resultPrincipal = principal.getName();
				}// if (principal != null)
			}// if (credentialsToken != null)
		}// if (slideToken != null)
		
		return resultPrincipal;
		
	}// getPrincipal(Uri uri)

	
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// build a group path like : groupA/subGroupA/subSubGroupA with a vector containing groupA|subGroupA|subSubGroupA
	//
	private String buildGroupHierarchy(Vector group){
		
		// return a new String if the parameter is null
		if (group == null) return new String();
		
		Enumeration enum = group.elements();
		StringBuffer result = new StringBuffer();
		
		// building the result - separating each element of the enumeration with a '/' with no '/' at the end of the String
		String nextElement = (String)enum.nextElement();
		while (nextElement != null) {
			result.append(nextElement);
			if(enum.hasMoreElements()){
				nextElement = (String)enum.nextElement();
				result.append("/"); //no '/' at the end of the String
			}// if(enum.hasMoreElements())
			else  nextElement = null;
		}// while (enum.hasMoreElements())
		
		return result.toString();
		
	}// buildGroupHierarchy
	
	
	////////////////////////////////////////////
	// convert a Vector of Strings into a String
	//
	private String convertVectorIntoString(Vector v){
		
		if (v == null) return new String("");
		
		StringBuffer result = new StringBuffer();
		Enumeration enum = v.elements();
		
		String nextElement;
		if (enum.hasMoreElements()) nextElement = (String)enum.nextElement();
		else return new String("");
		
		while (nextElement != null) {
			result.append(nextElement);
			if(enum.hasMoreElements()){
				nextElement = (String)enum.nextElement();
				result.append("|"); //no '|' at the end of the String
			}// if(enum.hasMoreElements())
			else  nextElement = null;
		}// while (enum.hasMoreElements())
		
		return result.toString();
		
	}// convertVectorIntoString
	
	
	///////////////////////////////////////////////////////
	// convert a Vector of Vectors of Strings into a String
	//
	private String convertVectorOfVectorIntoString(Vector v){
		
		if (v == null) return new String("");
		
		StringBuffer result = new StringBuffer();
		Enumeration enum = v.elements();
		
		Vector nextElement;
		if (enum.hasMoreElements())	nextElement = (Vector)enum.nextElement();
		else return new String("");	
		
		while (nextElement != null) {
			
			Enumeration enum2 = nextElement.elements();
		
			String nextElement2 = (String)enum2.nextElement();
			while (nextElement2 != null) {
				result.append(nextElement2);
				if(enum2.hasMoreElements()){
					nextElement2 = (String)enum2.nextElement();
					result.append("|"); //no '|' at the end of the String
				}// if(enum.hasMoreElements())
				else  nextElement2 = null;
			}// while (enum.hasMoreElements())
		
			if (enum.hasMoreElements()){
				nextElement = (Vector) enum.nextElement();
				result.append(" - ");
			}
			else nextElement = null;
				
		}// while (enum.hasMoreElements())
		
		return result.toString();
		
	}// convertVectorIntoString
	
	
	public UPortalRolesStore() {
	}
	
	// ----------------------------------------------------------- Service Methods --------

	public void initialize( NamespaceAccessToken token )
			throws ServiceInitializationFailedException {
		
		getLogger().log(getClass().getName()+":initialize", LOG_CHANNEL, Logger.INFO );
		
		super.initialize( token );
		
		// Setting usersPath and rolesPath
		usersPath = token.getNamespaceConfig().getUsersPath();
		rolesPath = token.getNamespaceConfig().getRolesPath();
		getLogger().log(getClass().getName()+":initialize:usersPath="+usersPath, LOG_CHANNEL, Logger.INFO );
		getLogger().log(getClass().getName()+":initialize:rolesPath="+rolesPath, LOG_CHANNEL, Logger.INFO );
		
	}// initialize

	
	public void setParameters( Hashtable parameters )
			throws ServiceParameterErrorException,
			ServiceParameterMissingException {
		
		getLogger().log(getClass().getName()+":setParameters", LOG_CHANNEL, Logger.INFO );
		
		// Setting cache parameters
		String tmp = (String)parameters.get( PARAM_UPORTAL_STORE_CACHE_DISABLECACHE );
		if (!tmp.equalsIgnoreCase("true") && !tmp.equalsIgnoreCase("false")) tmp = "false";
		Boolean bool = Boolean.valueOf(tmp);
		disableCache = bool.booleanValue();
		
		tmp = (String)parameters.get( PARAM_UPORTAL_STORE_CACHE_SLEEPINGTIME );
		sleepingTime = Integer.parseInt(tmp);
		if (sleepingTime < 1) sleepingTime = 1;
		sleepingTime = sleepingTime*60*1000; // min value for sleeping time = 1mn
		
		tmp = (String)parameters.get( PARAM_UPORTAL_STORE_CACHE_USERINFO_MTL );
		userInfoMinutesToLive = Integer.parseInt(tmp);
		if (userInfoMinutesToLive < 1) userInfoMinutesToLive = 1; // min value for TTL = 1mn
		
		tmp = (String)parameters.get( PARAM_UPORTAL_STORE_CACHE_OBJECTNODE_MTL );
		objectNodeMinutesToLive = Integer.parseInt(tmp);
		if (objectNodeMinutesToLive < 1) objectNodeMinutesToLive = 1; // min value for TTL = 1mn
		
		if (!disableCache ){
			getLogger().log(
					getClass().getName()+":setParameters:starting cache manager",
					LOG_CHANNEL,
					Logger.INFO );
			getLogger().log(
					getClass().getName()+":setParameters:"+
					PARAM_UPORTAL_STORE_CACHE_USERINFO_MTL+"="+userInfoMinutesToLive,
					LOG_CHANNEL,
					Logger.INFO );
			getLogger().log(
					getClass().getName()+":setParameters:"+
					PARAM_UPORTAL_STORE_CACHE_OBJECTNODE_MTL+"="+objectNodeMinutesToLive,
					LOG_CHANNEL,
					Logger.INFO );
			getLogger().log(
					getClass().getName()+":setParameters:"+
					PARAM_UPORTAL_STORE_CACHE_SLEEPINGTIME+"="+sleepingTime,
					LOG_CHANNEL,
					Logger.INFO );
			cacheManager = new CacheManager(sleepingTime); // milliseconds required here
		}else {
			getLogger().log(
					getClass().getName()+":setParameters:cache manager not started",
					LOG_CHANNEL,
					Logger.INFO );
		}// if (!disableCache )
		
		// Setting uPortalWebserviceUrl
		uPortalWebserviceUrl = (String)parameters.get( PARAM_UPORTAL_WEBSERVICE_URL );
		if ( uPortalWebserviceUrl == null || uPortalWebserviceUrl.length() == 0 ) {
			getLogger().log(
				getClass().getName()+":no value set for "+
				PARAM_UPORTAL_WEBSERVICE_URL,
				LOG_CHANNEL,
				Logger.CRITICAL );
		}else{
			getLogger().log(
					getClass().getName()+":setParameters:"+
					PARAM_UPORTAL_WEBSERVICE_URL+"="+uPortalWebserviceUrl,
					LOG_CHANNEL,
					Logger.INFO );
		}// if ( uPortalWebserviceUrl == null || uPortalWebserviceUrl.length() == 0 )
		
		// Initializing uPortalWSStore
		getLogger().log(getClass().getName()+"setParameters:connecting uPortalWSStore...", LOG_CHANNEL, Logger.INFO );
		try {
			uPortalWSStore =
				new PortalGroupsServiceLocator().getPortalGroups(new URL(uPortalWebserviceUrl));
		} catch (ServiceException e) {
			getLogger().log(
					getClass().getName()+":setParameters:"+
					e.getMessage(),
					LOG_CHANNEL,
					Logger.CRITICAL );
		} catch (MalformedURLException e) {
			getLogger().log(
					getClass().getName()+":setParameters:"+
					e.getMessage(),
					LOG_CHANNEL,
					Logger.CRITICAL );
		}
		
		// Setting logValidationErrors
		String temp = null;
		temp = (String)parameters.get( PARAM_LOG_VALIDATION_ERRORS );
		if ( "true".equalsIgnoreCase( temp ) ) {
			logValidationErrors = true;
		}
	}// setParameters
	
	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(getClass().getName()+":retrieveObject:uri="+uri, LOG_CHANNEL, Logger.INFO );
		
		////////////////////////////////////////
		// Trying to find this node in the cache
		//
		if (!disableCache) {
			getLogger().log(getClass().getName()+":retrieveObject:..getting node "+uri.toString()+" from the cache", LOG_CHANNEL, Logger.INFO );
			CachedObjectNode cachedObjectNode = (CachedObjectNode) CacheManager.getCache(uri.toString());		
			if (cachedObjectNode != null){ // node found in the cache - returning it
				getLogger().log(getClass().getName()+":retrieveObject:....node "+uri.toString()+" found in the cache", LOG_CHANNEL, Logger.INFO );
				return cachedObjectNode.getObjectNode();
			}// if (cachedObjectNode != null)
			// here, node not found in the cache
			getLogger().log(getClass().getName()+":retrieveObject:....node "+uri.toString()+" NOT found in the cache", LOG_CHANNEL, Logger.INFO );
		} // if (!disableCache)
		
		////////////////////////////////////////////////////
		// Getting the portal root group from the webservice
		//
		rootGroupsVector = new Vector();
		try {
			rootGroupsVector.add(uPortalWSStore.getRoot());
			getLogger().log(getClass().getName()+":retrieveObject:..portal root group="+convertVectorIntoString(rootGroupsVector), LOG_CHANNEL, Logger.DEBUG );
		} catch (RemoteException e) {
			getLogger().log(
					getClass().getName()+":retrieveObject:"+
					e.getMessage(),
					LOG_CHANNEL,
					Logger.CRITICAL );
		}
		
		/////////////////////////////////////////////
		// Getting the nested group keys of this node
		//
		String key = getObjectNameFromUri(uri);
		nestedGroupsOfCurrentGroupVector = new Vector();
		try {
			nestedGroupsOfCurrentGroupVector = uPortalWSStore.getNestedGroupsOfGroup(key);
			getLogger().log(getClass().getName()+":retrieveObject:..nested group keys="+convertVectorIntoString(nestedGroupsOfCurrentGroupVector), LOG_CHANNEL, Logger.DEBUG );
		} catch (RemoteException e) {
			getLogger().log(
					getClass().getName()+":retrieveObject:"+
					e.getMessage(),
					LOG_CHANNEL,
					Logger.CRITICAL );
		}
		
		/////////////////////////////////////////////////////////////////////////////////////////////
		// If the node is the store root then its nested group is the root returned by the webservice
		//
		// ex : rootPath=/roles
		//      storeRoot=/roles/uPortal < then the nested group of this group is the portal root group
		if (uri.isStoreRoot()){
			getLogger().log(getClass().getName()+":retrieveObject:..group "+key+" is store root", LOG_CHANNEL, Logger.DEBUG );
			nestedGroupsOfCurrentGroupVector = rootGroupsVector;
		}// if (uri.isStoreRoot())
			
		ObjectNode myObjectNode = getObject( uri );	
		return myObjectNode;
		
	}// retrieveObject

	//-------------------------------------------- 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( getClass().getName() + ":retrieveRevisionDescriptor:uri=" + uri.toString(),
			LOG_CHANNEL, Logger.INFO );

		// Initializing some variables TODO
		groupsOfConnectedUserVector = null; // probably not necessary
		groupsOfConnectedUserEnumeration = null; // probably not necessary
		nestedGroupsOfCurrentGroupVector = null; // probably not necessary
		nestedGroupsOfCurrentGroupEnumeration = null;

		// Getting the role scope
		roleScope = uri.getScope().toString();
		getLogger().log( getClass().getName() + ":retrieveRevisionDescriptor:role scope=" + roleScope.toString(),
				LOG_CHANNEL, Logger.DEBUG );
		
		// Getting the connected userName
		connectedUser = getPrincipal(uri);
		getLogger().log( getClass().getName() + ":retrieveRevisionDescriptor:connected user=" + connectedUser,
				LOG_CHANNEL, Logger.INFO );
		
		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		// Trying to find user information in the cache - if not present, requesting the web service and puting gathered info in the cache
		//
		Cacheable cachedUserInformation = null;
		// about the "cachedUserInformation != null" condition : when Slide starts, it calls the retrieveRevisionDescriptor method with no connected user
		// in this case, no need to call the webservice, neither the cache
		if (!disableCache && connectedUser != null && !connectedUser.equals("")){
			getLogger().log( getClass().getName() + ":retrieveRevisionDescriptor:trying to find info. in the cache for user " + connectedUser,
					LOG_CHANNEL, Logger.INFO );
			cachedUserInformation = CacheManager.getCache(connectedUser);
		}// if (!disableCache && connectedUser != null && !connectedUser.equals(""))
		if ((cachedUserInformation == null && connectedUser != null && !connectedUser.equals("")) || disableCache){ 
			// ==> no user information for the connected user found in the cache
			getLogger().log( getClass().getName() + ":retrieveRevisionDescriptor:no info. in the cache for user " + connectedUser,
					LOG_CHANNEL, Logger.INFO );
						
			// Getting the group(s) of the connected user from the WebService
			try {
				groupsOfConnectedUserVector = uPortalWSStore.getGroupsOfUser(connectedUser);
				getLogger().log( getClass().getName() + ":retrieveRevisionDescriptor:groups of connected user=" + convertVectorOfVectorIntoString(groupsOfConnectedUserVector),
						LOG_CHANNEL, Logger.DEBUG );
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			// ==> caching gathered information
			if (!disableCache) {
				getLogger().log( getClass().getName() + ":retrieveRevisionDescriptor:caching gathered info. for user " + connectedUser,
						LOG_CHANNEL, Logger.INFO );
				CacheManager.putCache(new CachedUserInformation(groupsOfConnectedUserVector, connectedUser, userInfoMinutesToLive));
			} // if (!disableCache)
		}
		else if (cachedUserInformation != null){
			// ==> user information for the connected user found in the cache
			getLogger().log( getClass().getName() + ":retrieveRevisionDescriptor:info. for user " + connectedUser + " found in the cache",
					LOG_CHANNEL, Logger.INFO );
			groupsOfConnectedUserVector = ((CachedUserInformation) cachedUserInformation).getGroupsOfUser();
		}// if (userInformation == null)
		
		// DEPRECATED
		// Getting the nested group(s) of the current group (uri) from the WebService
//		if ( !uri.isStoreRoot() ) {
//			try {
//				nestedGroupsOfCurrentGroupVector = uPortalWSStore.getNestedGroupsOfGroup(getObjectNameFromUri(uri));
//				getLogger().log( getClass().getName() + ":retrieveRevisionDescriptor:nested groups of "+uri+"=" + convertVectorIntoString(nestedGroupsOfCurrentGroupVector),
//						LOG_CHANNEL, Logger.DEBUG );
//			} catch (RemoteException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
//		}// if ( !uri.isStoreRoot() )
		
		// Building Enumeration to process the results sent by the WebService 
		if (groupsOfConnectedUserVector != null){
			Vector temp = new Vector();
			groupsOfConnectedUserEnumeration = groupsOfConnectedUserVector.elements();
			while (groupsOfConnectedUserEnumeration.hasMoreElements()) {
				Vector currentGroupVector = (Vector) groupsOfConnectedUserEnumeration.nextElement();
				temp.add(buildGroupHierarchy(currentGroupVector));
			}// while (groupsOfConnectedUserEnumeration.hasMoreElements())
			groupsOfConnectedUserEnumeration = temp.elements();
		}// if (groupsOfConnectedUserVector != null)
		
		// DEPRECATED
//		if (nestedGroupsOfCurrentGroupVector != null){
//		nestedGroupsOfCurrentGroupEnumeration = nestedGroupsOfCurrentGroupVector.elements();
//		}// if (nestedGroupsOfCurrentGroupVector != null)
		
		NodeRevisionDescriptor temp = getRevisionDescriptor( uri );
		
		return temp;
	}// retrieveRevisionDescriptor
	
	// --------------------------------------------- RevisionDescriptorsStore Methods -----

	public NodeRevisionDescriptors retrieveRevisionDescriptors( Uri uri )
			throws ServiceAccessException, RevisionDescriptorNotFoundException {
		getLogger().log( getClass().getName() + ":retrieveRevisionDescriptors:uri=" + 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 );
	}// retrieveRevisionDescriptors

	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 {}
	
	
	// -------------------------------------------------- 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", "/actions/read" ) );
		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 {
		
		getLogger().log( getClass().getName() + ":getObject:uri=" + uri.toString(),
				LOG_CHANNEL, Logger.DEBUG );
		
		// getting the parent uri and object name
		Uri parentUri = uri.getParentUri();
		String objectName = getObjectNameFromUri( uri );
		
		getLogger().log( getClass().getName() + ":getObject:parentUri=" + parentUri.toString(),
				LOG_CHANNEL, Logger.DEBUG );
		getLogger().log( getClass().getName() + ":getObject:objectName=" + objectName,
				LOG_CHANNEL, Logger.DEBUG );
		
		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() ) );
		}// if ( !uri.toString().equals( "/" ) )
		
		//////////////////////////////
		// Building the child bindings
		//
		getLogger().log( getClass().getName() + ":getObject:..creating ChildBindings for "+uri.toString(),
				LOG_CHANNEL, Logger.DEBUG );
		ObjectNode.Binding currentChildBinding;
		childBindings = new Vector();
		
		if (nestedGroupsOfCurrentGroupVector != null){ // no nested groups for the current group
			nestedGroupsOfCurrentGroupEnumeration = nestedGroupsOfCurrentGroupVector.elements(); // already initialized !!
			while (nestedGroupsOfCurrentGroupEnumeration.hasMoreElements()) {
				String element = (String) nestedGroupsOfCurrentGroupEnumeration.nextElement();
				String bindingName = element;
				String bindingUuri = uri.toString()+"/"+element;
				getLogger().log( getClass().getName() + ":getObject:....adding ChildBinding - bindingName="+bindingName+" bindingUuri="+bindingUuri,
						LOG_CHANNEL, Logger.DEBUG );
				currentChildBinding = new ObjectNode.Binding(bindingName, bindingUuri);
				childBindings.add(currentChildBinding);
			}// while (nestedGroupsOfCurrentGroupEnumeration.hasMoreElements())
		}
		else{
			getLogger().log( getClass().getName() + ":getObject:..no ChildBindings for "+uri.toString(),
					LOG_CHANNEL, Logger.DEBUG );
		}// if (nestedGroupsOfCurrentGroupVector != null)
		
		// Building the parent bindings
		String bindingName = getObjectNameFromUri(uri);
		String bindingUuri = getObjectPathFromUri(uri);
		ObjectNode.ParentBinding currentParentBinding= new ObjectNode.ParentBinding(bindingName, bindingUuri);
		parentBindings.add(currentParentBinding);
		
		getLogger().log( getClass().getName() + ":getObject:..creating node "+uri.toString(),
				LOG_CHANNEL, Logger.INFO );
		
		SubjectNode node = new SubjectNode(
				uri.toString(), childBindings, parentBindings, new Vector() );
		// Workaround for bug in ObjectNode.validate()
		node.setUri( uri.toString() );
		
		// Caching the node
		if (!disableCache){
			getLogger().log( getClass().getName() + ":getObject:..caching the node "+uri.toString(),
					LOG_CHANNEL, Logger.INFO );
			CacheManager.putCache(new CachedObjectNode(node, uri.toString(), objectNodeMinutesToLive));
		}// if (!disableCache)
		
		return node;
	}
	
// DEPRECATED
//	protected SubjectNode getObject( Uri uri )
//	throws ObjectNotFoundException, ServiceAccessException {
//		
//		getLogger().log( getClass().getName() + ":getObject:uri=" + uri.toString(),
//				LOG_CHANNEL, Logger.DEBUG );
//		
//		Uri parentUri = uri.getParentUri();
//		String objectName = getObjectNameFromUri( uri );
//		getLogger().log( getClass().getName() + ":getObject:parentUri=" + parentUri.toString(),
//				LOG_CHANNEL, Logger.DEBUG );
//		getLogger().log( getClass().getName() + ":getObject:objectName=" + objectName,
//				LOG_CHANNEL, Logger.DEBUG );
//		
//		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.
//		// TB I agree... not required for role store
//		if ( !uri.toString().equals( "/" ) ) {
//			parentBindings.add( new ObjectNode.Binding( objectName, parentUri.toString() ) );
//		}// if ( !uri.toString().equals( "/" ) )
//		
//		if ( uri.isStoreRoot() ) {
//						
//			Vector childrenNamesVector = new Vector();
//			Enumeration childrenNames = rootGroupsVector.elements();
//			
//			while (childrenNames.hasMoreElements()) {
//				String value = (String) childrenNames.nextElement();
//				
//				getLogger().log( getClass().getName() + ":getObject:creating child binding for "+uri.toString()+" - child="+value,
//						LOG_CHANNEL, Logger.DEBUG );
//				
//				childBindings.add(
//						new ObjectNode.Binding( value, uri.toString() + "/" + value ) );
//				
//			}// while (childrenNames.hasMoreElements())
//			
//		} else {
//			// If the uri matches the scope + something try to do a lookup
//			// of the "+ something" in LDAP.
//			// TB just trying to check that the node exist
//			// TODO check that the ressource exist ?
//			
//			
//			//			Vector parentsNamesVector = new Vector();
//			//			//childrenNamesVector.add("root");
//			//			parentsNamesVector.add("roleTest");
//			//			Enumeration parentsNames = parentsNamesVector.elements();
//			//				
//			//			while (parentsNames.hasMoreElements()) {
//			//				String value = (String) parentsNames.nextElement();
//			//				
//			//				getLogger().log(
//			//						getClass().getName() + ": Creating child binding \"" + value + "\" for \"" +
//			//						uri.toString() + "\".",
//			//						LOG_CHANNEL, Logger.DEBUG );
//			//				
//			//				childBindings.add(
//			//						new ObjectNode.Binding( value, uri.toString() + "/" + value ) );
//			//				
//			//			}// while (childrenNames.hasMoreElements())
//			
//		}// if ( uri.isStoreRoot() )
//		
//		getLogger().log( getClass().getName() + ":getObject: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() );
//		
//		return node;
//	}// getObject

	
	protected NodeRevisionDescriptor getRevisionDescriptor( Uri uri )
	throws RevisionDescriptorNotFoundException, ServiceAccessException {
		
		getLogger().log( getClass().getName() + ":getRevisionDescriptor:uri=" + uri.toString(),
				LOG_CHANNEL, Logger.DEBUG );

		////////////////////////////////////////////
		// Getting the group name of the group "uri"
		//
		// Trying to find the group name in the cache
		String groupKey = getObjectNameFromUri(uri);
		String groupName = null;
		if (!disableCache) {
			getLogger().log(getClass().getName()+":getRevisionDescriptor:..trying to find the group name of the group "+groupKey+" in the cache", LOG_CHANNEL, Logger.INFO );
			CachedGroupName cachedGroupName = ((CachedGroupName)CacheManager.getCache(groupKey));
			if (cachedGroupName == null) {
				getLogger().log(getClass().getName()+":getRevisionDescriptor:....group name of the group "+groupKey+" NOT found in the cache", LOG_CHANNEL, Logger.INFO );
				//groupName = groupKey;
			}
			else{
				getLogger().log(getClass().getName()+":getRevisionDescriptor:....group name of the group "+groupKey+" found in the cache", LOG_CHANNEL, Logger.INFO );
				groupName = cachedGroupName.getGroupName();
			}
		}// if (!disableCache)
		
		if (groupName == null) {
			
		try {
				
				// group uri
				getLogger().log(getClass().getName()+":getRevisionDescriptor:..getting the group name of the group "+groupKey, LOG_CHANNEL, Logger.DEBUG );
				
				groupName = uPortalWSStore.getGroupName(groupKey);
				if (groupName == null || groupName.equals("null")) groupName = groupKey;
				if (groupKey != null){
					getLogger().log(getClass().getName()+":getRevisionDescriptor:....key="+groupKey+" name="+groupName, LOG_CHANNEL, Logger.DEBUG );
				}// if (groupName != null && groupKey != null)
				
				// caching the group name
				// using the same TTL as the one used for the objectNode
				if (!disableCache) {
					getLogger().log(getClass().getName()+":getRevisionDescriptor:..caching the group name of the group "+groupKey+ " name="+groupName, LOG_CHANNEL, Logger.INFO );
					CacheManager.putCache(new CachedGroupName(groupKey, groupName, objectNodeMinutesToLive));
				}// if (!disableCache)
				
			} catch (RemoteException e) {
				getLogger().log(
						getClass().getName()+":getRevisionDescriptor:"+
						e.getMessage(),
						LOG_CHANNEL,
						Logger.CRITICAL );
			}
		}// if (groupName == null)
		
		Hashtable props = new Hashtable();
		StringBuffer instantGroupMemberSet = new StringBuffer();
		
		String resourceType = "<collection/>";
		if ( !uri.isStoreRoot() ) {
			resourceType += "<principal/>";
		}// if ( !uri.isStoreRoot() )
		
		props.put(
				"DAV:resourcetype",
				new NodeProperty( "resourcetype", resourceType, "DAV:", "", false ) );
		props.put(
				"DAV:displayname",
				new NodeProperty( "displayname", groupName, "DAV:", "", false ) );
		
		if ( !uri.isStoreRoot() && connectedUser != null && !connectedUser.equals("")) { // /roles
			
			getLogger().log( getClass().getName() + ":getRevisionDescriptor:..finding if "+connectedUser+" is member of "+uri,
					LOG_CHANNEL, Logger.DEBUG );
			// Trying to find if the groupsOfConnectedUserEnumeration is in the uri
			// uri is like : /roles/scope/roleA/roleB
			// groupsOfConnectedUserEnumeration is like : /roleA/roleB/roleC
			// So if uri is in one of the groups of the connected user, it means that the connected user is member of the group "uri" (or of one of its subgroups) 
			boolean uriIsInGroupsOfConnectedUser = false;
			while (groupsOfConnectedUserEnumeration != null && groupsOfConnectedUserEnumeration.hasMoreElements() && !uriIsInGroupsOfConnectedUser){
				// Roles returned by the WebService does not contain the basedir (/roles)
				String currentRole = roleScope+"/"+groupsOfConnectedUserEnumeration.nextElement();
				
				if (currentRole.indexOf(uri.toString()) != -1) uriIsInGroupsOfConnectedUser=true;
				//if (currentRole.equals(uri.toString())) uriIsInGroupsOfConnectedUser=true;
				
				getLogger().log( getClass().getName() + ":getRevisionDescriptor:....uri="+uri,
						LOG_CHANNEL, Logger.DEBUG );
				getLogger().log( getClass().getName() + ":getRevisionDescriptor:....current user role="+currentRole,
						LOG_CHANNEL, Logger.DEBUG );
				getLogger().log( getClass().getName() + ":getRevisionDescriptor:....user is member of "+uri+"="+uriIsInGroupsOfConnectedUser,
						LOG_CHANNEL, Logger.DEBUG );
				
			}// while (groupsOfConnectedUserStringTokenizer.hasMoreElements())
			
			// If the uri is in groupsOfConnectedUserEnumeration, it means that the connected user is member of the role "uri"
			// So building a group-member-set for "uri" with the connected user
			if (uriIsInGroupsOfConnectedUser){
				getLogger().log( getClass().getName() + ":getRevisionDescriptor:..user "+connectedUser+" is member of "+uri,
						LOG_CHANNEL, Logger.DEBUG );
				instantGroupMemberSet.append( "<D:href xmlns:D='DAV:'>" );
				instantGroupMemberSet.append(usersPath+"/"+connectedUser);
				instantGroupMemberSet.append( "</D:href>" );
				getLogger().log( getClass().getName() + ":getRevisionDescriptor:..building instant group-member-set for "+uri,
						LOG_CHANNEL, Logger.DEBUG );
				getLogger().log( getClass().getName() + ":getRevisionDescriptor:....instantGroupMemberSet="+instantGroupMemberSet,
						LOG_CHANNEL, Logger.DEBUG );
			}// if (uriIsInGroupsOfConnectedUser)
			
			// DEPRECATED
			// If the current group has nested groups then adding them to the group-member-set property
			//			while (nestedGroupsOfCurrentGroupEnumeration != null && nestedGroupsOfCurrentGroupEnumeration.hasMoreElements()){
			//				instantGroupMemberSet.append( "<D:href xmlns:D='DAV:'>" );
			//				instantGroupMemberSet.append(uri.toString()+"/"+nestedGroupsOfCurrentGroupEnumeration.nextElement());
			//				instantGroupMemberSet.append( "</D:href>" );
			//				getLogger().log( getClass().getName() + "- getRevisionDescriptor() - Building instant group-member-set for "+uri,
			//						LOG_CHANNEL, Logger.DEBUG );
			//				getLogger().log( getClass().getName() + "- getRevisionDescriptor() - instantGroupMemberSet="+instantGroupMemberSet,
			//						LOG_CHANNEL, Logger.DEBUG );
			//			}// if (uriIsInGroupsOfConnectedUser)
			
			
			// cache management	
			
			boolean isGms = uriIsInGroupsOfConnectedUser;		
			
			// DEPRECATED
			//if ( isGms || nestedGroupsOfCurrentGroupEnumeration != null) {
			if ( isGms ) {
				getLogger().log(
						getClass().getName() + "getRevisionDescriptor:adding property \"group-member-set\" in namespace",
						LOG_CHANNEL, Logger.DEBUG );
				
				props.put(
						"DAV:group-member-set",
						new NodeProperty(
								"group-member-set",
								instantGroupMemberSet.toString(),
						"DAV:" ) );
			}// if ( isGms )
			//else {
			//					getLogger().log(
			//							getClass().getName() + ": Adding property "+ key.toString() +" in namespace " +
			//							"\"DAV:\" with value of \"" + valueString.toString() + "\" to " +
			//							uri.toString() + ".",
			//							LOG_CHANNEL, Logger.DEBUG );
			//					
			//					props.put(
			//							"DAV:"+valueString.toString(),
			//						new NodeProperty(
			//							key.toString(),
			//							valueString.toString(),
			//							"DAV:" ) );
			//}// if ( isGms )
			
		}// if ( !uri.isStoreRoot() )
		
		NodeRevisionDescriptor descriptor = new NodeRevisionDescriptor(
				new NodeRevisionNumber( 1, 0 ),
				"main",
				new Vector(),
				props );
		
		return descriptor;
	}// getRevisionDescriptor
	
	// --------------------------------------------------- Helper Methods ---------------
          
	protected String getObjectNameFromUri( Uri uri ) {
		String objectName = uri.toString().substring(
			uri.toString().lastIndexOf( "/" ) + 1 );
		return objectName;
	}
	
	protected String getObjectPathFromUri( Uri uri ) {
		String objectPath = uri.toString().substring(0,
			uri.toString().lastIndexOf( "/" ));
		return objectPath;
	}
	
	
	/**
	 * 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(
					getClass().getName() + ": Skipping child with name \"" + name + "\" because " +
					"it contains a /.",
					LOG_CHANNEL, Logger.ERROR );
			}
		}
		return valid;
	}

	//	 --------------------------------------------------------- XA Methods --------------
	
	/* (non-Javadoc)
	 * @see org.apache.slide.common.AbstractServiceBase#connect()
	 */
	public void connect() throws ServiceConnectionFailedException {
		// TODO Auto-generated method stub
		
	}

	/* (non-Javadoc)
	 * @see org.apache.slide.common.AbstractServiceBase#disconnect()
	 */
	public void disconnect() throws ServiceDisconnectionFailedException {
		// TODO Auto-generated method stub
		
	}

	/* (non-Javadoc)
	 * @see org.apache.slide.common.AbstractServiceBase#reset()
	 */
	public void reset() throws ServiceResetFailedException {
		// TODO Auto-generated method stub
		
	}

	/* (non-Javadoc)
	 * @see org.apache.slide.common.AbstractServiceBase#isConnected()
	 */
	public boolean isConnected() throws ServiceAccessException {
		// TODO Auto-generated method stub
		return false;
	}

	/* (non-Javadoc)
	 * @see javax.transaction.xa.XAResource#getTransactionTimeout()
	 */
	public int getTransactionTimeout() throws XAException {
		// TODO Auto-generated method stub
		return 0;
	}

	/* (non-Javadoc)
	 * @see javax.transaction.xa.XAResource#setTransactionTimeout(int)
	 */
	public boolean setTransactionTimeout(int arg0) throws XAException {
		// TODO Auto-generated method stub
		return false;
	}

	/* (non-Javadoc)
	 * @see javax.transaction.xa.XAResource#isSameRM(javax.transaction.xa.XAResource)
	 */
	public boolean isSameRM(XAResource arg0) throws XAException {
		// TODO Auto-generated method stub
		return false;
	}

	/* (non-Javadoc)
	 * @see javax.transaction.xa.XAResource#recover(int)
	 */
	public Xid[] recover(int arg0) throws XAException {
		// TODO Auto-generated method stub
		return null;
	}

	/* (non-Javadoc)
	 * @see javax.transaction.xa.XAResource#prepare(javax.transaction.xa.Xid)
	 */
	public int prepare(Xid arg0) throws XAException {
		// TODO Auto-generated method stub
		return 0;
	}

	/* (non-Javadoc)
	 * @see javax.transaction.xa.XAResource#forget(javax.transaction.xa.Xid)
	 */
	public void forget(Xid arg0) throws XAException {
		// TODO Auto-generated method stub
		
	}

	/* (non-Javadoc)
	 * @see javax.transaction.xa.XAResource#rollback(javax.transaction.xa.Xid)
	 */
	public void rollback(Xid arg0) throws XAException {
		// TODO Auto-generated method stub
		
	}

	/* (non-Javadoc)
	 * @see javax.transaction.xa.XAResource#end(javax.transaction.xa.Xid, int)
	 */
	public void end(Xid arg0, int arg1) throws XAException {
		// TODO Auto-generated method stub
		
	}

	/* (non-Javadoc)
	 * @see javax.transaction.xa.XAResource#start(javax.transaction.xa.Xid, int)
	 */
	public void start(Xid arg0, int arg1) throws XAException {
		// TODO Auto-generated method stub
		
	}

	/* (non-Javadoc)
	 * @see javax.transaction.xa.XAResource#commit(javax.transaction.xa.Xid, boolean)
	 */
	public void commit(Xid arg0, boolean arg1) throws XAException {
		// TODO Auto-generated method stub
		
	}
	
	//	 ----------------------------------------------- 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( CACHE_NAME );
//		if ( cache == null ) {
//			cache = new Cache(
//				CACHE_NAME,
//				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 \"" + CACHE_NAME + "\" to CacheManager.",
//					e,
//					LOG_CHANNEL,
//					Logger.ERROR);
//			} catch ( ObjectExistsException e ) {
//				getLogger().log(
//					name + ": Error adding cache \"" + CACHE_NAME + "\" to CacheManager.",
//					e,
//					LOG_CHANNEL,
//					Logger.ERROR);
//			} catch ( CacheException e ) {
//				getLogger().log(
//					name + ": Error adding cache \"" + CACHE_NAME + "\" 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;
//		}
//	}

}