/* $Id$ */ /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.manifoldcf.authorities.authgroups; import org.apache.manifoldcf.core.interfaces.*; import org.apache.manifoldcf.authorities.interfaces.*; import java.util.*; import org.apache.manifoldcf.authorities.interfaces.CacheKeyFactory; import org.apache.manifoldcf.authorities.system.ManifoldCF; import org.apache.manifoldcf.crawler.interfaces.IRepositoryConnectionManager; import org.apache.manifoldcf.crawler.interfaces.RepositoryConnectionManagerFactory; /** Implementation of the authority group manager functionality. * * <br><br> * <b>authgroups</b> * <table border="1" cellpadding="3" cellspacing="0"> * <tr class="TableHeadingColor"> * <th>Field</th><th>Type</th><th>Description        </th> * <tr><td>groupname</td><td>VARCHAR(32)</td><td>Primary Key</td></tr> * <tr><td>description</td><td>VARCHAR(255)</td><td></td></tr> * </table> * <br><br> * */ public class AuthorityGroupManager extends org.apache.manifoldcf.core.database.BaseTable implements IAuthorityGroupManager { public static final String _rcsid = "@(#)$Id$"; protected final static String nameField = "groupname"; protected final static String descriptionField = "description"; // Cache manager ICacheManager cacheManager; // Thread context IThreadContext threadContext; /** Constructor. *@param threadContext is the thread context. */ public AuthorityGroupManager(IThreadContext threadContext, IDBInterface database) throws ManifoldCFException { super(database,"authgroups"); cacheManager = CacheManagerFactory.make(threadContext); this.threadContext = threadContext; } /** Install the manager. */ @Override public void install() throws ManifoldCFException { // Always do a loop, in case upgrade needs it. while (true) { Map existing = getTableSchema(null,null); if (existing == null) { // Install the "objects" table. HashMap map = new HashMap(); map.put(nameField,new ColumnDescription("VARCHAR(32)",true,false,null,null,false)); map.put(descriptionField,new ColumnDescription("VARCHAR(255)",false,true,null,null,false)); performCreate(map,null); } else { // Upgrade, when needed } // Index management goes here break; } } /** Uninstall the manager. */ @Override public void deinstall() throws ManifoldCFException { performDrop(null); } /** Export configuration */ @Override public void exportConfiguration(java.io.OutputStream os) throws java.io.IOException, ManifoldCFException { // Write a version indicator ManifoldCF.writeDword(os,1); // Get the authority list IAuthorityGroup[] list = getAllGroups(); // Write the number of groups ManifoldCF.writeDword(os,list.length); // Loop through the list and write the individual group info int i = 0; while (i < list.length) { IAuthorityGroup conn = list[i++]; ManifoldCF.writeString(os,conn.getName()); ManifoldCF.writeString(os,conn.getDescription()); } } /** Import configuration */ @Override public void importConfiguration(java.io.InputStream is) throws java.io.IOException, ManifoldCFException { int version = ManifoldCF.readDword(is); if (version < 1 || version > 1) throw new java.io.IOException("Unknown authority group configuration version: "+Integer.toString(version)); int count = ManifoldCF.readDword(is); int i = 0; while (i < count) { IAuthorityGroup conn = create(); conn.setName(ManifoldCF.readString(is)); conn.setDescription(ManifoldCF.readString(is)); // Attempt to save this connection save(conn); i++; } } /** Obtain a list of the authority grouops, ordered by name. *@return an array of connection objects. */ @Override public IAuthorityGroup[] getAllGroups() throws ManifoldCFException { beginTransaction(); try { // Read all the tools StringSetBuffer ssb = new StringSetBuffer(); ssb.add(getAuthorityGroupsKey()); StringSet localCacheKeys = new StringSet(ssb); IResultSet set = performQuery("SELECT "+nameField+",lower("+nameField+") AS sortfield FROM "+getTableName()+" ORDER BY sortfield ASC",null, localCacheKeys,null); String[] names = new String[set.getRowCount()]; int i = 0; while (i < names.length) { IResultRow row = set.getRow(i); names[i] = row.getValue(nameField).toString(); i++; } return loadMultiple(names); } catch (ManifoldCFException e) { signalRollback(); throw e; } catch (Error e) { signalRollback(); throw e; } finally { endTransaction(); } } /** Load an authority group by name. *@param name is the name of the authority group. *@return the loaded group object, or null if not found. */ @Override public IAuthorityGroup load(String name) throws ManifoldCFException { return loadMultiple(new String[]{name})[0]; } /** Load multiple authority groups by name. *@param names are the names to load. *@return the loaded group objects. */ @Override public IAuthorityGroup[] loadMultiple(String[] names) throws ManifoldCFException { // Build description objects AuthorityGroupDescription[] objectDescriptions = new AuthorityGroupDescription[names.length]; int i = 0; StringSetBuffer ssb = new StringSetBuffer(); while (i < names.length) { ssb.clear(); ssb.add(getAuthorityGroupKey(names[i])); objectDescriptions[i] = new AuthorityGroupDescription(names[i],new StringSet(ssb)); i++; } AuthorityGroupExecutor exec = new AuthorityGroupExecutor(this,objectDescriptions); cacheManager.findObjectsAndExecute(objectDescriptions,null,exec,getTransactionID()); return exec.getResults(); } /** Create a new authority group object. *@return the new object. */ @Override public IAuthorityGroup create() throws ManifoldCFException { AuthorityGroup rval = new AuthorityGroup(); return rval; } /** Save an authority group object. *@param object is the object to save. *@return true if the object is created, false otherwise. */ @Override public boolean save(IAuthorityGroup object) throws ManifoldCFException { StringSetBuffer ssb = new StringSetBuffer(); ssb.add(getAuthorityGroupsKey()); ssb.add(getAuthorityGroupKey(object.getName())); StringSet cacheKeys = new StringSet(ssb); while (true) { long sleepAmt = 0L; try { ICacheHandle ch = cacheManager.enterCache(null,cacheKeys,getTransactionID()); try { beginTransaction(); try { //performLock(); ManifoldCF.noteConfigurationChange(); boolean isNew = object.getIsNew(); // See whether the instance exists ArrayList params = new ArrayList(); String query = buildConjunctionClause(params,new ClauseDescription[]{ new UnitaryClause(nameField,object.getName())}); IResultSet set = performQuery("SELECT * FROM "+getTableName()+" WHERE "+ query+" FOR UPDATE",params,null,null); HashMap values = new HashMap(); values.put(descriptionField,object.getDescription()); boolean isCreated; if (set.getRowCount() > 0) { // If the object is supposedly new, it is bad that we found one that already exists. if (isNew) throw new ManifoldCFException("Authority group '"+object.getName()+"' already exists"); isCreated = false; // Update params.clear(); query = buildConjunctionClause(params,new ClauseDescription[]{ new UnitaryClause(nameField,object.getName())}); performUpdate(values," WHERE "+query,params,null); } else { // If the object is not supposed to be new, it is bad that we did not find one. if (!isNew) throw new ManifoldCFException("Authority group '"+object.getName()+"' no longer exists"); isCreated = true; // Insert values.put(nameField,object.getName()); // We only need the general key because this is new. performInsert(values,null); } cacheManager.invalidateKeys(ch); return isCreated; } catch (ManifoldCFException e) { signalRollback(); throw e; } catch (Error e) { signalRollback(); throw e; } finally { endTransaction(); } } finally { cacheManager.leaveCache(ch); } } catch (ManifoldCFException e) { // Is this a deadlock exception? If so, we want to try again. if (e.getErrorCode() != ManifoldCFException.DATABASE_TRANSACTION_ABORT) throw e; sleepAmt = getSleepAmt(); } finally { sleepFor(sleepAmt); } } } /** Delete an authority group. *@param name is the name of the group to delete. If the * name does not exist, no error is returned. */ @Override public void delete(String name) throws ManifoldCFException { // Grab repository connection manager handle, to check on legality of deletion. IRepositoryConnectionManager repoManager = RepositoryConnectionManagerFactory.make(threadContext); IAuthorityConnectionManager authManager = AuthorityConnectionManagerFactory.make(threadContext); StringSetBuffer ssb = new StringSetBuffer(); ssb.add(getAuthorityGroupsKey()); ssb.add(getAuthorityGroupKey(name)); StringSet cacheKeys = new StringSet(ssb); ICacheHandle ch = cacheManager.enterCache(null,cacheKeys,getTransactionID()); try { beginTransaction(); try { // Check if anything refers to this group name if (repoManager.isGroupReferenced(name)) throw new ManifoldCFException("Can't delete authority group '"+name+"': existing repository connections refer to it"); if (authManager.isGroupReferenced(name)) throw new ManifoldCFException("Can't delete authority group '"+name+"': existing authority connections refer to it"); ManifoldCF.noteConfigurationChange(); ArrayList params = new ArrayList(); String query = buildConjunctionClause(params,new ClauseDescription[]{ new UnitaryClause(nameField,name)}); performDelete("WHERE "+query,params,null); cacheManager.invalidateKeys(ch); } catch (ManifoldCFException e) { signalRollback(); throw e; } catch (Error e) { signalRollback(); throw e; } finally { endTransaction(); } } finally { cacheManager.leaveCache(ch); } } /** Get the authority group name column. *@return the name column. */ @Override public String getGroupNameColumn() { return nameField; } /** Get the authority connection description column. *@return the description column. */ @Override public String getGroupDescriptionColumn() { return descriptionField; } // Caching strategy: Individual connection descriptions are cached, and there is a global cache key for the list of // repository connections. /** Construct a key which represents the general list of repository connectors. *@return the cache key. */ protected static String getAuthorityGroupsKey() { return CacheKeyFactory.makeAuthorityGroupsKey(); } /** Construct a key which represents an individual authority group. *@param groupName is the name of the group. *@return the cache key. */ protected static String getAuthorityGroupKey(String groupName) { return CacheKeyFactory.makeAuthorityGroupKey(groupName); } // Other utility methods. /** Fetch multiple authority groups at a single time. *@param groupNames are a list of group names. *@return the corresponding authority group objects. */ protected AuthorityGroup[] getAuthorityGroupsMultiple(String[] groupNames) throws ManifoldCFException { AuthorityGroup[] rval = new AuthorityGroup[groupNames.length]; HashMap returnIndex = new HashMap(); int i = 0; while (i < groupNames.length) { rval[i] = null; returnIndex.put(groupNames[i],new Integer(i)); i++; } beginTransaction(); try { i = 0; ArrayList params = new ArrayList(); int j = 0; int maxIn = maxClauseGetAuthorityGroupsChunk(); while (i < groupNames.length) { if (j == maxIn) { getAuthorityGroupsChunk(rval,returnIndex,params); params.clear(); j = 0; } params.add(groupNames[i]); i++; j++; } if (j > 0) getAuthorityGroupsChunk(rval,returnIndex,params); return rval; } catch (Error e) { signalRollback(); throw e; } catch (ManifoldCFException e) { signalRollback(); throw e; } finally { endTransaction(); } } /** Find the maximum number of clauses for getAuthorityConnectionsChunk. */ protected int maxClauseGetAuthorityGroupsChunk() { return findConjunctionClauseMax(new ClauseDescription[]{}); } /** Read a chunk of authority groups. *@param rval is the place to put the read policies. *@param returnIndex is a map from the object id (resource id) and the rval index. *@param params is the set of parameters. */ protected void getAuthorityGroupsChunk(AuthorityGroup[] rval, Map returnIndex, ArrayList params) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new MultiClause(nameField,params)}); IResultSet set = performQuery("SELECT * FROM "+getTableName()+" WHERE "+ query,list,null,null); int i = 0; while (i < set.getRowCount()) { IResultRow row = set.getRow(i++); String name = row.getValue(nameField).toString(); int index = ((Integer)returnIndex.get(name)).intValue(); AuthorityGroup rc = new AuthorityGroup(); rc.setIsNew(false); rc.setName(name); rc.setDescription((String)row.getValue(descriptionField)); rval[index] = rc; } } // The cached instance will be a AuthorityGroup. The cached version will be duplicated when it is returned // from the cache. // // The description object is based completely on the name. /** This is the object description for an authority group object. */ protected static class AuthorityGroupDescription extends org.apache.manifoldcf.core.cachemanager.BaseDescription { protected String groupName; protected String criticalSectionName; protected StringSet cacheKeys; public AuthorityGroupDescription(String groupName, StringSet invKeys) { super("authoritygroupcache"); this.groupName = groupName; criticalSectionName = getClass().getName()+"-"+groupName; cacheKeys = invKeys; } public String getGroupName() { return groupName; } public int hashCode() { return groupName.hashCode(); } public boolean equals(Object o) { if (!(o instanceof AuthorityGroupDescription)) return false; AuthorityGroupDescription d = (AuthorityGroupDescription)o; return d.groupName.equals(groupName); } public String getCriticalSectionName() { return criticalSectionName; } /** Get the cache keys for an object (which may or may not exist yet in * the cache). This method is called in order for cache manager to throw the correct locks. * @return the object's cache keys, or null if the object should not * be cached. */ public StringSet getObjectKeys() { return cacheKeys; } } /** This is the executor object for locating authority group objects. */ protected static class AuthorityGroupExecutor extends org.apache.manifoldcf.core.cachemanager.ExecutorBase { // Member variables protected AuthorityGroupManager thisManager; protected AuthorityGroup[] returnValues; protected HashMap returnMap = new HashMap(); /** Constructor. *@param manager is the AuthorityGroupManager. *@param objectDescriptions are the object descriptions. */ public AuthorityGroupExecutor(AuthorityGroupManager manager, AuthorityGroupDescription[] objectDescriptions) { super(); thisManager = manager; returnValues = new AuthorityGroup[objectDescriptions.length]; int i = 0; while (i < objectDescriptions.length) { returnMap.put(objectDescriptions[i].getGroupName(),new Integer(i)); i++; } } /** Get the result. *@return the looked-up or read cached instances. */ public AuthorityGroup[] getResults() { return returnValues; } /** Create a set of new objects to operate on and cache. This method is called only * if the specified object(s) are NOT available in the cache. The specified objects * should be created and returned; if they are not created, it means that the * execution cannot proceed, and the execute() method will not be called. * @param objectDescriptions is the set of unique identifier of the object. * @return the newly created objects to cache, or null, if any object cannot be created. * The order of the returned objects must correspond to the order of the object descriptinos. */ public Object[] create(ICacheDescription[] objectDescriptions) throws ManifoldCFException { // Turn the object descriptions into the parameters for the AuthorityGroup requests String[] groupNames = new String[objectDescriptions.length]; int i = 0; while (i < groupNames.length) { AuthorityGroupDescription desc = (AuthorityGroupDescription)objectDescriptions[i]; groupNames[i] = desc.getGroupName(); i++; } return thisManager.getAuthorityGroupsMultiple(groupNames); } /** Notify the implementing class of the existence of a cached version of the * object. The object is passed to this method so that the execute() method below * will have it available to operate on. This method is also called for all objects * that are freshly created as well. * @param objectDescription is the unique identifier of the object. * @param cachedObject is the cached object. */ public void exists(ICacheDescription objectDescription, Object cachedObject) throws ManifoldCFException { // Cast what came in as what it really is AuthorityGroupDescription objectDesc = (AuthorityGroupDescription)objectDescription; AuthorityGroup ci = (AuthorityGroup)cachedObject; // Duplicate it! if (ci != null) ci = ci.duplicate(); // In order to make the indexes line up, we need to use the hashtable built by // the constructor. returnValues[((Integer)returnMap.get(objectDesc.getGroupName())).intValue()] = ci; } /** Perform the desired operation. This method is called after either createGetObject() * or exists() is called for every requested object. */ public void execute() throws ManifoldCFException { // Does nothing; we only want to fetch objects in this cacher. } } }