/* $Id: RepositoryConnectionManager.java 996524 2010-09-13 13:38:01Z kwright $ */
/**
* 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.crawler.repository;
import java.util.*;
import org.apache.manifoldcf.core.interfaces.*;
import org.apache.manifoldcf.crawler.interfaces.*;
import org.apache.manifoldcf.authorities.interfaces.*;
import org.apache.manifoldcf.crawler.interfaces.CacheKeyFactory;
import org.apache.manifoldcf.crawler.system.ManifoldCF;
/** This class is the manager of the repository connection description. Inside, multiple database tables are managed,
* with appropriate caching.
* Note well: The database handle is instantiated here using the DBInterfaceFactory. This is acceptable because the
* actual database that this table is located in is fixed.
*
* <br><br>
* <b>repoconnections</b>
* <table border="1" cellpadding="3" cellspacing="0">
* <tr class="TableHeadingColor">
* <th>Field</th><th>Type</th><th>Description </th>
* <tr><td>connectionname</td><td>VARCHAR(32)</td><td>Primary Key</td></tr>
* <tr><td>description</td><td>VARCHAR(255)</td><td></td></tr>
* <tr><td>classname</td><td>VARCHAR(255)</td><td></td></tr>
* <tr><td>groupname</td><td>VARCHAR(32)</td><td></td></tr>
* <tr><td>maxcount</td><td>BIGINT</td><td></td></tr>
* <tr><td>configxml</td><td>LONGTEXT</td><td></td></tr>
* </table>
* <br><br>
*
*/
public class RepositoryConnectionManager extends org.apache.manifoldcf.core.database.BaseTable implements IRepositoryConnectionManager
{
public static final String _rcsid = "@(#)$Id: RepositoryConnectionManager.java 996524 2010-09-13 13:38:01Z kwright $";
// Special field suffix
private final static String passwordSuffix = "password";
// Database fields
protected final static String nameField = "connectionname"; // Changed this to work around constraint bug in postgresql
protected final static String descriptionField = "description";
protected final static String classNameField = "classname";
protected final static String maxCountField = "maxcount";
protected final static String configField = "configxml";
protected final static String groupNameField = "groupname";
// Discontinued fields
protected final static String authorityNameField = "authorityname";
protected static Random random = new Random();
// Handle for repository history manager
protected final RepositoryHistoryManager historyManager;
// Handle for throttle spec storage
protected final ThrottleSpecManager throttleSpecManager;
// Cache manager
protected final ICacheManager cacheManager;
// Thread context
protected final IThreadContext threadContext;
// Lock manager
protected final ILockManager lockManager;
protected final String repositoriesLock = "REPOSITORIES_LOCK";
/** Constructor.
*@param threadContext is the thread context.
*/
public RepositoryConnectionManager(IThreadContext threadContext, IDBInterface database)
throws ManifoldCFException
{
super(database,"repoconnections");
historyManager = new RepositoryHistoryManager(threadContext,database);
throttleSpecManager = new ThrottleSpecManager(database);
cacheManager = CacheManagerFactory.make(threadContext);
lockManager = LockManagerFactory.make(threadContext);
this.threadContext = threadContext;
}
/** Install the manager.
*/
public void install()
throws ManifoldCFException
{
// First, get the authority manager table name and name column
IAuthorityGroupManager authMgr = AuthorityGroupManagerFactory.make(threadContext);
// Always use a loop, and no transaction, as we may need to retry due to upgrade
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));
map.put(classNameField,new ColumnDescription("VARCHAR(255)",false,false,null,null,false));
map.put(groupNameField,new ColumnDescription("VARCHAR(32)",false,true,
authMgr.getTableName(),authMgr.getGroupNameColumn(),false));
map.put(maxCountField,new ColumnDescription("BIGINT",false,false,null,null,false));
map.put(configField,new ColumnDescription("LONGTEXT",false,true,null,null,false));
performCreate(map,null);
}
else
{
// Upgrade code
}
// Install dependent tables.
historyManager.install(getTableName(),nameField);
throttleSpecManager.install(getTableName(),nameField);
// Index management
IndexDescription authorityIndex = new IndexDescription(false,new String[]{groupNameField});
IndexDescription classIndex = new IndexDescription(false,new String[]{classNameField});
// Get rid of indexes that shouldn't be there
Map indexes = getTableIndexes(null,null);
Iterator iter = indexes.keySet().iterator();
while (iter.hasNext())
{
String indexName = (String)iter.next();
IndexDescription id = (IndexDescription)indexes.get(indexName);
if (authorityIndex != null && id.equals(authorityIndex))
authorityIndex = null;
else if (classIndex != null && id.equals(classIndex))
classIndex = null;
else if (indexName.indexOf("_pkey") == -1)
// This index shouldn't be here; drop it
performRemoveIndex(indexName);
}
// Add the ones we didn't find
if (authorityIndex != null)
performAddIndex(null,authorityIndex);
if (classIndex != null)
performAddIndex(null,classIndex);
break;
}
}
/** Uninstall the manager.
*/
public void deinstall()
throws ManifoldCFException
{
beginTransaction();
try
{
throttleSpecManager.deinstall();
historyManager.deinstall();
performDrop(null);
}
catch (ManifoldCFException e)
{
signalRollback();
throw e;
}
catch (Error e)
{
signalRollback();
throw e;
}
finally
{
endTransaction();
}
}
/** Export configuration */
public void exportConfiguration(java.io.OutputStream os)
throws java.io.IOException, ManifoldCFException
{
// Write a version indicator
ManifoldCF.writeDword(os,1);
// Get the authority list
IRepositoryConnection[] list = getAllConnections();
// Write the number of authorities
ManifoldCF.writeDword(os,list.length);
// Loop through the list and write the individual repository connection info
int i = 0;
while (i < list.length)
{
IRepositoryConnection conn = list[i++];
ManifoldCF.writeString(os,conn.getName());
ManifoldCF.writeString(os,conn.getDescription());
ManifoldCF.writeString(os,conn.getClassName());
ManifoldCF.writeString(os,conn.getConfigParams().toXML());
ManifoldCF.writeString(os,conn.getACLAuthority());
ManifoldCF.writeDword(os,conn.getMaxConnections());
String[] throttles = conn.getThrottles();
ManifoldCF.writeDword(os,throttles.length);
int j = 0;
while (j < throttles.length)
{
String throttleName = throttles[j++];
ManifoldCF.writeString(os,throttleName);
ManifoldCF.writeString(os,conn.getThrottleDescription(throttleName));
ManifoldCF.writefloat(os,conn.getThrottleValue(throttleName));
}
}
}
/** Import configuration */
public void importConfiguration(java.io.InputStream is)
throws java.io.IOException, ManifoldCFException
{
int version = ManifoldCF.readDword(is);
if (version != 1)
throw new java.io.IOException("Unknown repository connection configuration version: "+Integer.toString(version));
int count = ManifoldCF.readDword(is);
int i = 0;
while (i < count)
{
IRepositoryConnection conn = create();
conn.setName(ManifoldCF.readString(is));
conn.setDescription(ManifoldCF.readString(is));
conn.setClassName(ManifoldCF.readString(is));
conn.getConfigParams().fromXML(ManifoldCF.readString(is));
conn.setACLAuthority(ManifoldCF.readString(is));
conn.setMaxConnections(ManifoldCF.readDword(is));
int throttleCount = ManifoldCF.readDword(is);
int j = 0;
while (j < throttleCount)
{
String throttleName = ManifoldCF.readString(is);
conn.addThrottleValue(throttleName,ManifoldCF.readString(is),ManifoldCF.readfloat(is));
j++;
}
// Attempt to save this connection
save(conn);
i++;
}
}
/** Obtain a list of the repository connections, ordered by name.
*@return an array of connection objects.
*/
public IRepositoryConnection[] getAllConnections()
throws ManifoldCFException
{
lockManager.enterReadLock(repositoriesLock);
try
{
// Read all the tools
StringSetBuffer ssb = new StringSetBuffer();
ssb.add(getRepositoryConnectionsKey());
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()];
for (int i = 0; i < names.length; i++)
{
IResultRow row = set.getRow(i);
names[i] = row.getValue(nameField).toString();
}
return loadMultiple(names);
}
finally
{
lockManager.leaveReadLock(repositoriesLock);
}
}
/** Load a repository connection by name.
*@param name is the name of the repository connection.
*@return the loaded connection object, or null if not found.
*/
public IRepositoryConnection load(String name)
throws ManifoldCFException
{
return loadMultiple(new String[]{name})[0];
}
protected final static int FETCH_MAX = 200;
/** Load multiple repository connections by name.
*@param names are the names to load.
*@return the loaded connection objects.
*/
public IRepositoryConnection[] loadMultiple(String[] names)
throws ManifoldCFException
{
IRepositoryConnection[] rval = new IRepositoryConnection[names.length];
if (names.length == 0)
return rval;
int inputIndex = 0;
int outputIndex = 0;
while (names.length - inputIndex > FETCH_MAX)
{
outputIndex = loadMultipleInternal(rval,outputIndex,names,inputIndex,FETCH_MAX);
inputIndex += FETCH_MAX;
}
loadMultipleInternal(rval,outputIndex,names,inputIndex,names.length-inputIndex);
return rval;
}
protected int loadMultipleInternal(IRepositoryConnection[] rval, int outputIndex, String[] fetchNames, int inputIndex, int length)
throws ManifoldCFException
{
// Build description objects
RepositoryConnectionDescription[] objectDescriptions = new RepositoryConnectionDescription[length];
StringSetBuffer ssb = new StringSetBuffer();
for (int i = 0; i < length; i++)
{
ssb.clear();
String name = fetchNames[inputIndex + i];
ssb.add(getRepositoryConnectionKey(name));
objectDescriptions[i] = new RepositoryConnectionDescription(name,new StringSet(ssb));
}
RepositoryConnectionExecutor exec = new RepositoryConnectionExecutor(this,objectDescriptions);
cacheManager.findObjectsAndExecute(objectDescriptions,null,exec,getTransactionID());
IRepositoryConnection[] results = exec.getResults();
for (IRepositoryConnection result : results)
{
rval[outputIndex++] = result;
}
return outputIndex;
}
/** Create a new repository connection object.
*@return the new object.
*/
public IRepositoryConnection create()
throws ManifoldCFException
{
RepositoryConnection rval = new RepositoryConnection();
return rval;
}
/** Save a repository connection object.
*@param object is the object to save.
*@return true if the object was created, false otherwise.
*/
public boolean save(IRepositoryConnection object)
throws ManifoldCFException
{
StringSetBuffer ssb = new StringSetBuffer();
ssb.add(getRepositoryConnectionsKey());
ssb.add(getRepositoryConnectionKey(object.getName()));
StringSet cacheKeys = new StringSet(ssb);
while (true)
{
// Catch deadlock condition
long sleepAmt = 0L;
try
{
lockManager.enterNonExWriteLock(repositoriesLock);
try
{
ICacheHandle ch = cacheManager.enterCache(null,cacheKeys,getTransactionID());
try
{
beginTransaction();
try
{
//performLock();
// Notify of a change to the configuration
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());
values.put(classNameField,object.getClassName());
values.put(groupNameField,object.getACLAuthority());
values.put(maxCountField,new Long((long)object.getMaxConnections()));
String configXML = object.getConfigParams().toXML();
values.put(configField,configXML);
boolean notificationNeeded = false;
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("Repository connection '"+object.getName()+"' already exists");
isCreated = false;
IResultRow row = set.getRow(0);
String oldXML = (String)row.getValue(configField);
if (oldXML == null || !oldXML.equals(configXML))
notificationNeeded = true;
// Update
params.clear();
query = buildConjunctionClause(params,new ClauseDescription[]{
new UnitaryClause(nameField,object.getName())});
performUpdate(values," WHERE "+query,params,null);
throttleSpecManager.deleteRows(object.getName());
}
else
{
// If the object is not supposed to be new, it is bad that we did not find one.
if (!isNew)
throw new ManifoldCFException("Repository connection '"+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);
}
// Write secondary table stuff
throttleSpecManager.writeRows(object.getName(),object);
// If notification required, do it.
if (notificationNeeded)
{
IJobManager jobManager = JobManagerFactory.make(threadContext);
jobManager.noteConnectionChange(object.getName());
}
cacheManager.invalidateKeys(ch);
return isCreated;
}
catch (ManifoldCFException e)
{
signalRollback();
throw e;
}
catch (Error e)
{
signalRollback();
throw e;
}
finally
{
endTransaction();
}
}
finally
{
cacheManager.leaveCache(ch);
}
}
finally
{
lockManager.leaveNonExWriteLock(repositoriesLock);
}
}
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 a repository connection.
*@param name is the name of the connection to delete. If the
* name does not exist, no error is returned.
*/
public void delete(String name)
throws ManifoldCFException
{
// Grab a job manager handle. We will need to check if any jobs refer to this connection.
IJobManager jobManager = JobManagerFactory.make(threadContext);
StringSetBuffer ssb = new StringSetBuffer();
ssb.add(getRepositoryConnectionsKey());
ssb.add(getRepositoryConnectionKey(name));
StringSet cacheKeys = new StringSet(ssb);
lockManager.enterNonExWriteLock(repositoriesLock);
try
{
ICacheHandle ch = cacheManager.enterCache(null,cacheKeys,getTransactionID());
try
{
beginTransaction();
try
{
// Check if any jobs refer to this connection name
if (jobManager.checkIfReference(name))
throw new ManifoldCFException("Can't delete repository connection '"+name+"': existing jobs refer to it");
ManifoldCF.noteConfigurationChange();
throttleSpecManager.deleteRows(name);
historyManager.deleteOwner(name);
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);
}
}
finally
{
lockManager.leaveNonExWriteLock(repositoriesLock);
}
}
/** Return true if the specified authority name is referenced.
*@param groupName is the group name.
*@return true if referenced, false otherwise.
*/
@Override
public boolean isGroupReferenced(String groupName)
throws ManifoldCFException
{
StringSetBuffer ssb = new StringSetBuffer();
ssb.add(getRepositoryConnectionsKey());
StringSet localCacheKeys = new StringSet(ssb);
ArrayList params = new ArrayList();
String query = buildConjunctionClause(params,new ClauseDescription[]{
new UnitaryClause(groupNameField,groupName)});
IResultSet set = performQuery("SELECT "+nameField+" FROM "+getTableName()+" WHERE "+query,params,
localCacheKeys,null);
return set.getRowCount() > 0;
}
/** Get a list of repository connections that share the same connector.
*@param className is the class name of the connector.
*@return the repository connections that use that connector.
*/
@Override
public String[] findConnectionsForConnector(String className)
throws ManifoldCFException
{
StringSetBuffer ssb = new StringSetBuffer();
ssb.add(getRepositoryConnectionsKey());
StringSet localCacheKeys = new StringSet(ssb);
ArrayList params = new ArrayList();
String query = buildConjunctionClause(params,new ClauseDescription[]{
new UnitaryClause(classNameField,className)});
IResultSet set = performQuery("SELECT "+nameField+" FROM "+getTableName()+" WHERE "+query,params,
localCacheKeys,null);
String[] rval = new String[set.getRowCount()];
int i = 0;
while (i < rval.length)
{
IResultRow row = set.getRow(i);
rval[i] = (String)row.getValue(nameField);
i++;
}
java.util.Arrays.sort(rval);
return rval;
}
/** Check if underlying connector exists.
*@param name is the name of the connection to check.
*@return true if the underlying connector is registered.
*/
@Override
public boolean checkConnectorExists(String name)
throws ManifoldCFException
{
beginTransaction();
try
{
StringSetBuffer ssb = new StringSetBuffer();
ssb.add(getRepositoryConnectionKey(name));
StringSet localCacheKeys = new StringSet(ssb);
ArrayList params = new ArrayList();
String query = buildConjunctionClause(params,new ClauseDescription[]{
new UnitaryClause(nameField,name)});
IResultSet set = performQuery("SELECT "+classNameField+" FROM "+getTableName()+" WHERE "+query,params,
localCacheKeys,null);
if (set.getRowCount() == 0)
throw new ManifoldCFException("No such connection: '"+name+"'");
IResultRow row = set.getRow(0);
String className = (String)row.getValue(classNameField);
IConnectorManager cm = ConnectorManagerFactory.make(threadContext);
return cm.isInstalled(className);
}
catch (ManifoldCFException e)
{
signalRollback();
throw e;
}
catch (Error e)
{
signalRollback();
throw e;
}
finally
{
endTransaction();
}
}
// Schema related
/** Return the name column.
*@return the name column.
*/
@Override
public String getConnectionNameColumn()
{
return nameField;
}
// Reporting and analysis related
/** Delete history rows related to a specific connection, upon user request.
*@param connectionName is the connection whose history records should be removed.
*/
@Override
public void cleanUpHistoryData(String connectionName)
throws ManifoldCFException
{
historyManager.deleteOwner(connectionName);
}
/** Delete history rows older than a specified timestamp.
*@param timeCutoff is the timestamp to delete older rows before.
*/
@Override
public void cleanUpHistoryData(long timeCutoff)
throws ManifoldCFException
{
historyManager.deleteOldRows(timeCutoff);
}
/** Record time-stamped information about the activity of the connection. This information can originate from
* either the connector or from the framework. The reason it is here is that it is viewed as 'belonging' to an
* individual connection, and is segregated accordingly.
*@param connectionName is the connection to which the record belongs. If the connection is deleted, the
* corresponding records will also be deleted. Cannot be null.
*@param startTime is either null or the time since the start of epoch in milliseconds (Jan 1, 1970). Every
* activity has an associated time; the startTime field records when the activity began. A null value
* indicates that the start time and the finishing time are the same.
*@param activityType is a string which is fully interpretable only in the context of the connector involved, which is
* used to categorize what kind of activity is being recorded. For example, a web connector might record a
* "fetch document" activity, while the framework might record "ingest document", "job start", "job finish",
* "job abort", etc. Cannot be null.
*@param dataSize is the number of bytes of data involved in the activity, or null if not applicable.
*@param entityIdentifier is a (possibly long) string which identifies the object involved in the history record.
* The interpretation of this field will differ from connector to connector. May be null.
*@param resultCode contains a terse description of the result of the activity. The description is limited in
* size to 255 characters, and can be interpreted only in the context of the current connector. May be null.
*@param resultDescription is a (possibly long) human-readable string which adds detail, if required, to the result
* described in the resultCode field. This field is not meant to be queried on. May be null.
*@param childIdentifiers is a set of child entity identifiers associated with this activity. May be null.
*/
public void recordHistory(String connectionName, Long startTime, String activityType, Long dataSize,
String entityIdentifier, String resultCode, String resultDescription, String[] childIdentifiers)
throws ManifoldCFException
{
long endTimeValue = System.currentTimeMillis();
long startTimeValue;
if (startTime == null)
startTimeValue = endTimeValue - 1L;
else
{
startTimeValue = startTime.longValue();
if (startTimeValue == endTimeValue)
startTimeValue -= 1L; // Zero-time events are not allowed.
}
long dataSizeValue;
if (dataSize == null)
dataSizeValue = 0L;
else
dataSizeValue = dataSize.longValue();
Long rowID = historyManager.addRow(connectionName,startTimeValue,endTimeValue,dataSizeValue,activityType,
entityIdentifier,resultCode,resultDescription);
// child identifiers are not stored, for now.
// MHL
}
/** Count the number of rows specified by a given set of criteria. This can be used to make decisions
* as to whether a query based on those rows will complete in an acceptable amount of time.
*@param connectionName is the name of the connection.
*@param criteria is the filtering criteria, which selects the records of interest.
*@return the number of rows included by the criteria.
*/
public long countHistoryRows(String connectionName, FilterCriteria criteria)
throws ManifoldCFException
{
return historyManager.countHistoryRows(connectionName,criteria);
}
/** Get the maximum number of rows a window-based report can work with.
*@return the maximum rows.
*/
public long getMaxRows()
throws ManifoldCFException
{
return historyManager.getMaxRows();
}
/** Generate a report, listing the start time, elapsed time, result code and description, number of bytes, and entity identifier.
* The records selected for this report are based on the filtering criteria object passed into this method.
* The record order is based on the sorting criteria object passed into this method.
* The resultset returned should have the following columns: "starttime","elapsedtime","resultcode","resultdesc","bytes","identifier".
*@param connectionName is the name of the connection.
*@param criteria is the filtering criteria, which selects the records of interest.
*@param sort is the sorting order, which can specify sort based on the result columns.
*@param startRow is the first row to include (beginning with 0)
*@param maxRowCount is the maximum number of rows to include.
*/
public IResultSet genHistorySimple(String connectionName, FilterCriteria criteria, SortOrder sort, int startRow, int maxRowCount)
throws ManifoldCFException
{
return historyManager.simpleReport(connectionName,criteria,sort,startRow,maxRowCount);
}
/** Generate a report, listing the start time, activity count, and identifier bucket, given
* a time slice (interval) size.
* The records selected for this report are based on the filtering criteria object passed into this method.
* The record order is based on the sorting criteria object passed into this method.
* The identifier bucket description is specified by the bucket description object.
* The resultset returned should have the following columns: "starttime","endtime","activitycount","idbucket".
*@param connectionName is the name of the connection.
*@param criteria is the filtering criteria, which selects the records of interest.
*@param sort is the sorting order, which can specify sort based on the result columns.
*@param idBucket is the description of the bucket based on processed entity identifiers.
*@param interval is the time interval, in milliseconds, to locate. There will be one row in the resultset
* for each distinct idBucket value, and the returned activity count will the maximum found over the
* specified interval size.
*@param startRow is the first row to include (beginning with 0)
*@param maxRowCount is the maximum number of rows to include.
*/
public IResultSet genHistoryActivityCount(String connectionName, FilterCriteria criteria, SortOrder sort, BucketDescription idBucket,
long interval, int startRow, int maxRowCount)
throws ManifoldCFException
{
return historyManager.maxActivityCountReport(connectionName,criteria,sort,idBucket,interval,startRow,maxRowCount);
}
/** Generate a report, listing the start time, bytes processed, and identifier bucket, given
* a time slice (interval) size.
* The records selected for this report are based on the filtering criteria object passed into this method.
* The record order is based on the sorting criteria object passed into this method.
* The identifier bucket description is specified by the bucket description object.
* The resultset returned should have the following columns: "starttime","endtime","bytecount","idbucket".
*@param connectionName is the name of the connection.
*@param criteria is the filtering criteria, which selects the records of interest.
*@param sort is the sorting order, which can specify sort based on the result columns.
*@param idBucket is the description of the bucket based on processed entity identifiers.
*@param interval is the time interval, in milliseconds, to locate. There will be one row in the resultset
* for each distinct idBucket value, and the returned activity count will the maximum found over the
* specified interval size.
*@param startRow is the first row to include (beginning with 0)
*@param maxRowCount is the maximum number of rows to include.
*/
public IResultSet genHistoryByteCount(String connectionName, FilterCriteria criteria, SortOrder sort, BucketDescription idBucket,
long interval, int startRow, int maxRowCount)
throws ManifoldCFException
{
return historyManager.maxByteCountReport(connectionName,criteria,sort,idBucket,interval,startRow,maxRowCount);
}
/** Generate a report, listing the result bucket and identifier bucket.
* The records selected for this report are based on the filtering criteria object passed into this method.
* The record order is based on the sorting criteria object passed into this method.
* The result code bucket description is specified by a bucket description object.
* The identifier bucket description is specified by a bucket description object.
* The resultset returned should have the following columns: "resultcodebucket","idbucket".
*@param connectionName is the name of the connection.
*@param criteria is the filtering criteria, which selects the records of interest.
*@param sort is the sorting order, which can specify sort based on the result columns.
*@param resultCodeBucket is the description of the bucket based on processed result codes.
*@param idBucket is the description of the bucket based on processed entity identifiers.
*@param startRow is the first row to include (beginning with 0)
*@param maxRowCount is the maximum number of rows to include.
*/
public IResultSet genHistoryResultCodes(String connectionName, FilterCriteria criteria, SortOrder sort,
BucketDescription resultCodeBucket, BucketDescription idBucket, int startRow, int maxRowCount)
throws ManifoldCFException
{
return historyManager.resultCodesReport(connectionName,criteria,sort,resultCodeBucket,idBucket,startRow,maxRowCount);
}
// 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 getRepositoryConnectionsKey()
{
return CacheKeyFactory.makeRepositoryConnectionsKey();
}
/** Construct a key which represents an individual repository connection.
*@param connectionName is the name of the connector.
*@return the cache key.
*/
protected static String getRepositoryConnectionKey(String connectionName)
{
return CacheKeyFactory.makeRepositoryConnectionKey(connectionName);
}
// Other utility methods.
/** Fetch multiple repository connections at a single time.
*@param connectionNames are a list of connection names.
*@return the corresponding repository connection objects.
*/
protected RepositoryConnection[] getRepositoryConnectionsMultiple(String[] connectionNames)
throws ManifoldCFException
{
RepositoryConnection[] rval = new RepositoryConnection[connectionNames.length];
HashMap returnIndex = new HashMap();
int i = 0;
while (i < connectionNames.length)
{
rval[i] = null;
returnIndex.put(connectionNames[i],new Integer(i));
i++;
}
beginTransaction();
try
{
i = 0;
ArrayList params = new ArrayList();
int j = 0;
int maxIn = maxClauseGetRepositoryConnectionsChunk();
while (i < connectionNames.length)
{
if (j == maxIn)
{
getRepositoryConnectionsChunk(rval,returnIndex,params);
params.clear();
j = 0;
}
params.add(connectionNames[i]);
i++;
j++;
}
if (j > 0)
getRepositoryConnectionsChunk(rval,returnIndex,params);
return rval;
}
catch (Error e)
{
signalRollback();
throw e;
}
catch (ManifoldCFException e)
{
signalRollback();
throw e;
}
finally
{
endTransaction();
}
}
/** Calculate how many repository connections to get at once.
*/
protected int maxClauseGetRepositoryConnectionsChunk()
{
return Math.min(findConjunctionClauseMax(new ClauseDescription[]{}),
throttleSpecManager.maxClauseGetRows());
}
/** Read a chunk of repository connections.
*@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 getRepositoryConnectionsChunk(RepositoryConnection[] 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();
RepositoryConnection rc = new RepositoryConnection();
rc.setIsNew(false);
rc.setName(name);
rc.setDescription((String)row.getValue(descriptionField));
rc.setClassName((String)row.getValue(classNameField));
rc.setACLAuthority((String)row.getValue(groupNameField));
rc.setMaxConnections((int)((Long)row.getValue(maxCountField)).longValue());
String xml = (String)row.getValue(configField);
if (xml != null && xml.length() > 0)
rc.getConfigParams().fromXML(xml);
rval[index] = rc;
}
// Do throttle part
throttleSpecManager.getRows(rval,returnIndex,params);
}
// The cached instance will be a RepositoryConnection. 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 a repository connection object.
*/
protected static class RepositoryConnectionDescription extends org.apache.manifoldcf.core.cachemanager.BaseDescription
{
protected String connectionName;
protected String criticalSectionName;
protected StringSet cacheKeys;
public RepositoryConnectionDescription(String connectionName, StringSet invKeys)
{
super("repositoryconnectioncache");
this.connectionName = connectionName;
criticalSectionName = getClass().getName()+"-"+connectionName;
cacheKeys = invKeys;
}
public String getConnectionName()
{
return connectionName;
}
public int hashCode()
{
return connectionName.hashCode();
}
public boolean equals(Object o)
{
if (!(o instanceof RepositoryConnectionDescription))
return false;
RepositoryConnectionDescription d = (RepositoryConnectionDescription)o;
return d.connectionName.equals(connectionName);
}
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 repository connection objects.
*/
protected static class RepositoryConnectionExecutor extends org.apache.manifoldcf.core.cachemanager.ExecutorBase
{
// Member variables
protected RepositoryConnectionManager thisManager;
protected RepositoryConnection[] returnValues;
protected HashMap returnMap = new HashMap();
/** Constructor.
*@param manager is the ToolManager.
*@param objectDescriptions are the object descriptions.
*/
public RepositoryConnectionExecutor(RepositoryConnectionManager manager, RepositoryConnectionDescription[] objectDescriptions)
{
super();
thisManager = manager;
returnValues = new RepositoryConnection[objectDescriptions.length];
int i = 0;
while (i < objectDescriptions.length)
{
returnMap.put(objectDescriptions[i].getConnectionName(),new Integer(i));
i++;
}
}
/** Get the result.
*@return the looked-up or read cached instances.
*/
public RepositoryConnection[] 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 ToolInstance requests
String[] connectionNames = new String[objectDescriptions.length];
int i = 0;
while (i < connectionNames.length)
{
RepositoryConnectionDescription desc = (RepositoryConnectionDescription)objectDescriptions[i];
connectionNames[i] = desc.getConnectionName();
i++;
}
return thisManager.getRepositoryConnectionsMultiple(connectionNames);
}
/** 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
RepositoryConnectionDescription objectDesc = (RepositoryConnectionDescription)objectDescription;
RepositoryConnection ci = (RepositoryConnection)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.getConnectionName())).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.
}
}
}