/* $Id: Jobs.java 991295 2010-08-31 19:12:14Z 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.jobs; import org.apache.manifoldcf.core.interfaces.*; import org.apache.manifoldcf.agents.interfaces.*; import org.apache.manifoldcf.crawler.interfaces.*; import org.apache.manifoldcf.crawler.system.ManifoldCF; import org.apache.manifoldcf.crawler.interfaces.CacheKeyFactory; import java.util.*; /** This class manages the jobs table. * * <br><br> * <b>jobs</b> * <table border="1" cellpadding="3" cellspacing="0"> * <tr class="TableHeadingColor"> * <th>Field</th><th>Type</th><th>Description        </th> * <tr><td>id</td><td>BIGINT</td><td>Primary Key</td></tr> * <tr><td>description</td><td>VARCHAR(255)</td><td></td></tr> * <tr><td>status</td><td>CHAR(1)</td><td>operational field</td></tr> * <tr><td>lasttime</td><td>BIGINT</td><td>operational field</td></tr> * <tr><td>starttime</td><td>BIGINT</td><td>operational field</td></tr> * <tr><td>lastchecktime</td><td>BIGINT</td><td>operational field</td></tr> * <tr><td>seedingversion</td><td>LONGTEXT</td><td>operational field</td></tr> * <tr><td>endtime</td><td>BIGINT</td><td>operational field</td></tr> * <tr><td>docspec</td><td>LONGTEXT</td><td></td></tr> * <tr><td>connectionname</td><td>VARCHAR(32)</td><td>Reference:repoconnections.connectionname</td></tr> * <tr><td>type</td><td>CHAR(1)</td><td></td></tr> * <tr><td>intervaltime</td><td>BIGINT</td><td></td></tr> * <tr><td>maxintervaltime</td><td>BIGINT</td><td></td></tr> * <tr><td>expirationtime</td><td>BIGINT</td><td></td></tr> * <tr><td>windowend</td><td>BIGINT</td><td></td></tr> * <tr><td>priority</td><td>BIGINT</td><td></td></tr> * <tr><td>startmethod</td><td>CHAR(1)</td><td></td></tr> * <tr><td>errortext</td><td>LONGTEXT</td><td></td></tr> * <tr><td>reseedinterval</td><td>BIGINT</td><td></td></tr> * <tr><td>reseedtime</td><td>BIGINT</td><td></td></tr> * <tr><td>hopcountmode</td><td>CHAR(1)</td><td></td></tr> * <tr><td>processid</td><td>VARCHAR(16)</td><td></td></tr> * <tr><td>failtime</td><td>BIGINT</td><td></td></tr> * <tr><td>failcount</td><td>BIGINT</td><td></td></tr> * <tr><td>assessmentstate</td><td>CHAR(1)</td><td></td></tr> * </table> * <br><br> * */ public class Jobs extends org.apache.manifoldcf.core.database.BaseTable { public static final String _rcsid = "@(#)$Id: Jobs.java 991295 2010-08-31 19:12:14Z kwright $"; // Status field values // // There are some themes in this state diagram. For instance, for all active states there's a corresponding "active seeding" state, which // indicates that automatic seeding is taking place at that time. Also, the "pause a job" sequence goes from "pausing" to "paused", while // "resuming a job" goes from "resuming" to "active". This is complicated by the fact that entering the "activewait" state is very similar // to entering the "paused" state, in that the job must go first to the "active waiting" state and then to the "active wait" state, but it // is completely orthogonal to the "paused" state, because one is triggered automatically and the other by human control. Similarly, // resuming from an "active wait" state must transition through another state to return to the "active" state. // // However, it is only certain transitions that require special states. For example, leaving a waited/paused state and returning to "active" // does something, as does entering either active wait or paused. Also transitions between these states do NOT require intermediates. // The state diagram reflects that. But it also has to reflect that during the "pausing" stage, "activewait" may occur, and visa versa. public static final int STATUS_INACTIVE = 0; // Not running public static final int STATUS_ACTIVE = 1; // Active, within a valid window public static final int STATUS_ACTIVESEEDING = 2; // Same as active, but seeding process is currently active also. public static final int STATUS_ACTIVEWAITING = 3; // In the process of waiting due to window expiration; will enter STATUS_ACTIVEWAIT when done. public static final int STATUS_ACTIVEWAITINGSEEDING = 4; // In the process of waiting due to window exp, also seeding; will enter STATUS_ACTIVEWAITSEEDING when done. public static final int STATUS_ACTIVEWAIT = 5; // Active, but paused due to window expiration public static final int STATUS_ACTIVEWAITSEEDING = 6; // Same as active wait, but seeding process is currently active also. public static final int STATUS_PAUSING = 7; // In the process of pausing a job; will enter STATUS_PAUSED when done. public static final int STATUS_PAUSINGSEEDING = 8; // In the process of pausing a job, but seeding also; will enter STATUS_PAUSEDSEEDING when done. public static final int STATUS_PAUSINGWAITING = 9; // In the process of pausing a job; will enter STATUS_PAUSEDWAIT when done. public static final int STATUS_PAUSINGWAITINGSEEDING = 10; // In the process of pausing a job, but seeding also; will enter STATUS_PAUSEDWAITSEEDING when done. public static final int STATUS_PAUSED = 11; // Paused, but within a valid window public static final int STATUS_PAUSEDSEEDING = 12; // Same as paused, but seeding process is currently active also. public static final int STATUS_PAUSEDWAIT = 13; // Paused, and outside of window expiration public static final int STATUS_PAUSEDWAITSEEDING = 14; // Same as paused wait, but seeding process is currently active also. public static final int STATUS_SHUTTINGDOWN = 15; // Done, except for process cleanup public static final int STATUS_RESUMING = 16; // In the process of resuming a paused or waited job; will enter STATUS_ACTIVE when done public static final int STATUS_RESUMINGSEEDING = 17; // In the process of resuming a paused or waited job, seeding process active too; will enter STATUS_ACTIVESEEDING when done. public static final int STATUS_ABORTING = 18; // Aborting (not yet aborted because documents still being processed) public static final int STATUS_STARTINGUP = 19; // Loading the queue (will go into ACTIVE if successful, or INACTIVE if not) public static final int STATUS_STARTINGUPMINIMAL = 20; // Loading the queue for minimal job run (will go into ACTIVE if successful, or INACTIVE if not) public static final int STATUS_READYFORSTARTUP = 23; // Job is marked for minimal startup; startup thread has not taken it yet. public static final int STATUS_READYFORSTARTUPMINIMAL = 24; // Job is marked for startup; startup thread has not taken it yet. public static final int STATUS_READYFORDELETE = 25; // Job is marked for delete; delete thread has not taken it yet. public static final int STATUS_ABORTINGFORRESTART = 27; // Same as aborting, except after abort is complete startup will happen. public static final int STATUS_ABORTINGFORRESTARTMINIMAL = 28; // Same as aborting, except after abort is complete startup will happen. public static final int STATUS_ABORTINGFORRESTARTSEEDING = 29; // Seeding version of aborting for restart public static final int STATUS_ABORTINGFORRESTARTSEEDINGMINIMAL = 30; // Seeding version of aborting for restart public static final int STATUS_ABORTINGSTARTINGUPFORRESTART = 31; // Starting up version of aborting for restart public static final int STATUS_ABORTINGSTARTINGUPFORRESTARTMINIMAL = 32; // Starting up version of aborting for restart public static final int STATUS_READYFORNOTIFY = 33; // Job is ready to be notified of completion public static final int STATUS_NOTIFYINGOFCOMPLETION = 34; // Notifying connector of terminating job (either aborted, or finished) public static final int STATUS_DELETING = 35; // The job is deleting. public static final int STATUS_DELETESTARTINGUP = 36; // The delete is starting up. public static final int STATUS_ABORTINGSHUTTINGDOWN = 37; // Aborting the cleanup phase. public static final int STATUS_READYFORDELETENOTIFY = 38; // Job is ready for delete notification public static final int STATUS_NOTIFYINGOFDELETION = 39; // Notifying connector of job deletion // These statuses have to do with whether a job has an installed underlying connector or not. // There are two reasons to have a special state here: (1) if the behavior of the crawler differs, or (2) if the // UI would present something different. // // For the base states, such as for an inactive job, I've chosen not to have an "uninstalled" version of the state, for now. // This is because I believe that feedback will be adequate without such states. // But, since there is no indication in the jobs table of an uninstalled connector for such jobs, the code which starts // jobs up (or otherwise would enter any state that has a corresponding special state) must check to see if the underlying // connector exists before deciding what state to put the job into. public static final int STATUS_ACTIVE_UNINSTALLED = 40; // Active, but repository connector not installed public static final int STATUS_ACTIVESEEDING_UNINSTALLED = 41; // Active and seeding, but repository connector not installed public static final int STATUS_DELETING_NOOUTPUT = 42; // Job is being deleted but there's no output connector installed // Deprecated states. These states should never be used; they're defined only for upgrade purposes public static final int STATUS_ACTIVE_NOOUTPUT = 100; // Active, but output connector not installed public static final int STATUS_ACTIVESEEDING_NOOUTPUT = 101; // Active and seeding, but output connector not installed public static final int STATUS_ACTIVE_NEITHER = 102; // Active, but neither repository connector nor output connector installed public static final int STATUS_ACTIVESEEDING_NEITHER = 103; // Active and seeding, but neither repository connector nor output connector installed // Need Connector Assessment states public static final int ASSESSMENT_KNOWN = 0; // State is known. public static final int ASSESSMENT_UNKNOWN = 1; // State is unknown, and job needs assessment // Type field values public static final int TYPE_CONTINUOUS = IJobDescription.TYPE_CONTINUOUS; public static final int TYPE_SPECIFIED = IJobDescription.TYPE_SPECIFIED; // Start method field values public static final int START_WINDOWBEGIN = IJobDescription.START_WINDOWBEGIN; public static final int START_WINDOWINSIDE = IJobDescription.START_WINDOWINSIDE; public static final int START_DISABLE = IJobDescription.START_DISABLE; // Hopcount mode values public static final int HOPCOUNT_ACCURATE = IJobDescription.HOPCOUNT_ACCURATE; public static final int HOPCOUNT_NODELETE = IJobDescription.HOPCOUNT_NODELETE; public static final int HOPCOUNT_NEVERDELETE = IJobDescription.HOPCOUNT_NEVERDELETE; // Field names public final static String idField = "id"; public final static String descriptionField = "description"; public final static String documentSpecField = "docspec"; public final static String connectionNameField = "connectionname"; public final static String typeField = "type"; /** This is the minimum reschedule interval for a document being crawled adaptively (in ms.) */ public final static String intervalField = "intervaltime"; /** This is the maximum reschedule interval for a document being crawled adaptively (in ms.) */ public final static String maxIntervalField = "maxintervaltime"; /** This is the expiration time of documents for a given job (in ms.) */ public final static String expirationField = "expirationtime"; /** This is the job's priority vs. other jobs. */ public final static String priorityField = "priority"; /** How/when to start the job */ public final static String startMethodField = "startmethod"; /** If this is an adaptive job, what should the reseed interval be (in milliseconds) */ public final static String reseedIntervalField = "reseedinterval"; // These fields are NOT part of the definition, but are operational /** Status of this job. */ public final static String statusField = "status"; /** The last time this job was assessed, in ms. since epoch. */ public final static String lastTimeField = "lasttime"; /** If active, paused, activewait, or pausedwait, the start time of the current session, else null. */ public final static String startTimeField = "starttime"; /** This text data represents the seeding version string, which for many connectors is simply the last time seeding was done */ public final static String seedingVersionField = "seedingversion"; /** If inactive, the end time of the LAST session, if any. */ public final static String endTimeField = "endtime"; /** If non-null, this is the time that the current execution window closes, in ms since epoch. */ public final static String windowEndField = "windowend"; /** If non-null, this is the last error that occurred (which aborted the last task, either running the job or doing the * delete cleanup). */ public final static String errorField = "errortext"; /** For an adaptive job, this is the next time to reseed the job. */ public final static String reseedTimeField = "reseedtime"; /** For a job whose connector supports hopcounts, this describes how those hopcounts are handled. */ public final static String hopcountModeField = "hopcountmode"; /** Process id field, for keeping track of which process owns transient state */ public final static String processIDField = "processid"; /** When non-null, indicates the time that, when a ServiceInterruption occurs, the attempt will be considered to have actually failed */ public static final String failTimeField = "failtime"; /** When non-null, indicates the number of retries remaining, after which the attempt will be considered to have actually failed */ public static final String failCountField = "failcount"; /** Set to N when the job needs connector-installed assessment */ public static final String assessmentStateField = "assessmentstate"; protected static Map<String,Integer> statusMap; protected static Map<String,Integer> typeMap; protected static Map<String,Integer> startMap; protected static Map<String,Integer> hopmodeMap; protected static Map<String,Integer> assessmentMap; static { statusMap = new HashMap<String,Integer>(); statusMap.put("N",new Integer(STATUS_INACTIVE)); statusMap.put("A",new Integer(STATUS_ACTIVE)); statusMap.put("P",new Integer(STATUS_PAUSED)); statusMap.put("S",new Integer(STATUS_SHUTTINGDOWN)); statusMap.put("s",new Integer(STATUS_READYFORNOTIFY)); statusMap.put("n",new Integer(STATUS_NOTIFYINGOFCOMPLETION)); statusMap.put("d",new Integer(STATUS_READYFORDELETENOTIFY)); statusMap.put("j",new Integer(STATUS_NOTIFYINGOFDELETION)); statusMap.put("W",new Integer(STATUS_ACTIVEWAIT)); statusMap.put("Z",new Integer(STATUS_PAUSEDWAIT)); statusMap.put("X",new Integer(STATUS_ABORTING)); statusMap.put("B",new Integer(STATUS_STARTINGUP)); statusMap.put("b",new Integer(STATUS_STARTINGUPMINIMAL)); statusMap.put("C",new Integer(STATUS_READYFORSTARTUP)); statusMap.put("c",new Integer(STATUS_READYFORSTARTUPMINIMAL)); statusMap.put("E",new Integer(STATUS_READYFORDELETE)); statusMap.put("V",new Integer(STATUS_DELETESTARTINGUP)); statusMap.put("e",new Integer(STATUS_DELETING)); statusMap.put("Y",new Integer(STATUS_ABORTINGFORRESTART)); statusMap.put("M",new Integer(STATUS_ABORTINGFORRESTARTMINIMAL)); statusMap.put("T",new Integer(STATUS_ABORTINGSTARTINGUPFORRESTART)); statusMap.put("t",new Integer(STATUS_ABORTINGSTARTINGUPFORRESTARTMINIMAL)); statusMap.put("a",new Integer(STATUS_ACTIVESEEDING)); statusMap.put("p",new Integer(STATUS_PAUSEDSEEDING)); statusMap.put("w",new Integer(STATUS_ACTIVEWAITSEEDING)); statusMap.put("z",new Integer(STATUS_PAUSEDWAITSEEDING)); statusMap.put("y",new Integer(STATUS_ABORTINGFORRESTARTSEEDING)); statusMap.put("m",new Integer(STATUS_ABORTINGFORRESTARTSEEDINGMINIMAL)); statusMap.put("v",new Integer(STATUS_ABORTINGSHUTTINGDOWN)); statusMap.put("H",new Integer(STATUS_ACTIVEWAITING)); statusMap.put("h",new Integer(STATUS_ACTIVEWAITINGSEEDING)); statusMap.put("F",new Integer(STATUS_PAUSING)); statusMap.put("f",new Integer(STATUS_PAUSINGSEEDING)); statusMap.put("G",new Integer(STATUS_PAUSINGWAITING)); statusMap.put("g",new Integer(STATUS_PAUSINGWAITINGSEEDING)); statusMap.put("I",new Integer(STATUS_RESUMING)); statusMap.put("i",new Integer(STATUS_RESUMINGSEEDING)); // These are the uninstalled states. The values, I'm afraid, are pretty random. statusMap.put("R",new Integer(STATUS_ACTIVE_UNINSTALLED)); statusMap.put("r",new Integer(STATUS_ACTIVESEEDING_UNINSTALLED)); statusMap.put("D",new Integer(STATUS_DELETING_NOOUTPUT)); // These are deprecated states; we may be able to reclaim them statusMap.put("O",new Integer(STATUS_ACTIVE_NOOUTPUT)); statusMap.put("o",new Integer(STATUS_ACTIVESEEDING_NOOUTPUT)); statusMap.put("U",new Integer(STATUS_ACTIVE_NEITHER)); statusMap.put("u",new Integer(STATUS_ACTIVESEEDING_NEITHER)); typeMap = new HashMap<String,Integer>(); typeMap.put("C",new Integer(TYPE_CONTINUOUS)); typeMap.put("S",new Integer(TYPE_SPECIFIED)); startMap = new HashMap<String,Integer>(); startMap.put("B",new Integer(START_WINDOWBEGIN)); startMap.put("I",new Integer(START_WINDOWINSIDE)); startMap.put("D",new Integer(START_DISABLE)); hopmodeMap = new HashMap<String,Integer>(); hopmodeMap.put("A",new Integer(HOPCOUNT_ACCURATE)); hopmodeMap.put("N",new Integer(HOPCOUNT_NODELETE)); hopmodeMap.put("V",new Integer(HOPCOUNT_NEVERDELETE)); assessmentMap = new HashMap<String,Integer>(); assessmentMap.put("Y",new Integer(ASSESSMENT_KNOWN)); assessmentMap.put("N",new Integer(ASSESSMENT_UNKNOWN)); } /* Transient vs. non-transient states *================================== * There are two kinds of states. The first kind of state is that which is considered permanent; if the * whole world restarts, the state would not change. Such states reflect the underlying status of the job. * The second kind of state is that which is clearly transient but is attached to a given process. So if the * process is killed, the job should leave the transient state and return to a permanent state. For example, * when a given thread decided to undertake seeding, the job is annotated with the process ID of the thread * that is performing the seeding operation. These states are called process-transient. * * The permanent states are: * STATUS_INACTIVE * STATUS_ACTIVE * STATUS_ACTIVEWAIT * STATUS_PAUSEDWAIT * STATUS_PAUSED * STATUS_READYFORNOTIFY * STATUS_READYFORDELETENOTIFY * STATUS_READYFORDELETE * STATUS_DELETING * STATUS_READYFORSTARTUP * STATUS_READYFORSTARTUPMINIMAL * STATUS_SHUTTINGDOWN * STATUS_ABORTINGFORRESTART * STATUS_ABORTINGFORRESTARTMINIMAL * STATUS_ACTIVE_UNINSTALLED * STATUS_ACTIVE_NOOUTPUT * STATUS_ACTIVE_NEITHER * STATUS_DELETING_NOOUTPUT * STATUS_ABORTING * STATUS_ACTIVEWAITING * STATUS_PAUSING * STATUS_PAUSINGWAITING * STATUS_RESUMING * STATUS_ABORTINGSHUTTINGDOWN * * These are the process-transient states: * STATUS_DELETESTARTINGUP * STATUS_NOTIFYINGOFCOMPLETION * STATUS_NOTIFYINGOFDELETION * STATUS_STARTINGUP * STATUS_STARTINGUPMINIMAL * STATUS_ABORTINGSTARTINGUPFORRESTART * STATUS_ABORTINGSTARTINGUPFORRESTARTMINIMAL * STATUS_ACTIVEWAITINGSEEDING * STATUS_ACTIVEWAITSEEDING * STATUS_ACTIVESEEDING * STATUS_PAUSINGSEEDING * STATUS_PAUSINGWAITINGSEEDING * STATUS_PAUSEDSEEDING * STATUS_PAUSEDWAITSEEDING * STATUS_RESUMINGSEEDING * STATUS_ABORTINGFORRESTARTSEEDING * STATUS_ABORTINGFORRESTARTSEEDINGMINIMAL * STATUS_ACTIVESEEDING_UNINSTALLED * STATUS_ACTIVESEEDING_NOOUTPUT * STATUS_ACTIVESEEDING_NEITHER * * */ // Local variables protected final ICacheManager cacheManager; protected final ScheduleManager scheduleManager; protected final HopFilterManager hopFilterManager; protected final PipelineManager pipelineManager; protected final NotificationManager notificationManager; protected final IOutputConnectionManager outputMgr; protected final IRepositoryConnectionManager connectionMgr; protected final ITransformationConnectionManager transMgr; protected final ILockManager lockManager; protected final IThreadContext threadContext; protected final static String jobsLock = "JOBS_LOCK"; /** Constructor. *@param database is the database handle. */ public Jobs(IThreadContext threadContext, IDBInterface database) throws ManifoldCFException { super(database,"jobs"); this.threadContext = threadContext; scheduleManager = new ScheduleManager(threadContext,database); hopFilterManager = new HopFilterManager(threadContext,database); pipelineManager = new PipelineManager(threadContext,database); notificationManager = new NotificationManager(threadContext,database); cacheManager = CacheManagerFactory.make(threadContext); lockManager = LockManagerFactory.make(threadContext); outputMgr = OutputConnectionManagerFactory.make(threadContext); connectionMgr = RepositoryConnectionManagerFactory.make(threadContext); transMgr = TransformationConnectionManagerFactory.make(threadContext); } /** Install or upgrade this table. */ public void install(String transTableName, String transNameField, String outputTableName, String outputNameField, String connectionTableName, String connectionNameField, String notificationConnectionTableName, String notificationConnectionNameField) throws ManifoldCFException { // Standard practice: Have a loop around everything, in case upgrade needs it. while (true) { // These are fields we want to get rid of. String oldOutputSpecField = "outputspec"; String oldOutputNameField = "outputname"; String oldLastCheckTimeField = "lastchecktime"; // A place to keep the outputs we find, so we can add them into the pipeline at the end. IResultSet outputSet = null; Map existing = getTableSchema(null,null); if (existing == null) { HashMap map = new HashMap(); map.put(idField,new ColumnDescription("BIGINT",true,false,null,null,false)); map.put(descriptionField,new ColumnDescription("VARCHAR(255)",false,false,null,null,false)); map.put(statusField,new ColumnDescription("CHAR(1)",false,false,null,null,false)); map.put(lastTimeField,new ColumnDescription("BIGINT",false,false,null,null,false)); map.put(startTimeField,new ColumnDescription("BIGINT",false,true,null,null,false)); map.put(seedingVersionField,new ColumnDescription("LONGTEXT",false,true,null,null,false)); map.put(endTimeField,new ColumnDescription("BIGINT",false,true,null,null,false)); map.put(documentSpecField,new ColumnDescription("LONGTEXT",false,true,null,null,false)); map.put(this.connectionNameField,new ColumnDescription("VARCHAR(32)",false,false,connectionTableName,connectionNameField,false)); map.put(typeField,new ColumnDescription("CHAR(1)",false,false,null,null,false)); map.put(intervalField,new ColumnDescription("BIGINT",false,true,null,null,false)); map.put(maxIntervalField,new ColumnDescription("BIGINT",false,true,null,null,false)); map.put(expirationField,new ColumnDescription("BIGINT",false,true,null,null,false)); map.put(windowEndField,new ColumnDescription("BIGINT",false,true,null,null,false)); map.put(priorityField,new ColumnDescription("BIGINT",false,false,null,null,false)); map.put(startMethodField,new ColumnDescription("CHAR(1)",false,false,null,null,false)); map.put(errorField,new ColumnDescription("LONGTEXT",false,true,null,null,false)); map.put(reseedIntervalField,new ColumnDescription("BIGINT",false,true,null,null,false)); map.put(reseedTimeField,new ColumnDescription("BIGINT",false,true,null,null,false)); map.put(hopcountModeField,new ColumnDescription("CHAR(1)",false,true,null,null,false)); map.put(processIDField,new ColumnDescription("VARCHAR(16)",false,true,null,null,false)); map.put(failTimeField,new ColumnDescription("BIGINT",false,true,null,null,false)); map.put(failCountField,new ColumnDescription("BIGINT",false,true,null,null,false)); map.put(assessmentStateField,new ColumnDescription("CHAR(1)",false,true,null,null,false)); performCreate(map,null); } else { // Do any needed upgrades } // Handle related tables pipelineManager.install(getTableName(),idField,outputTableName,outputNameField,transTableName,transNameField); if (outputSet != null) { // Go through set and add pipeline stages corresponding to outputs for (int k = 0; k < outputSet.getRowCount(); k++) { IResultRow row = outputSet.getRow(k); Long id = (Long)row.getValue(idField); String outputConnectionName = (String)row.getValue(oldOutputNameField); String outputConnectionSpec = (String)row.getValue(oldOutputSpecField); pipelineManager.writeOutputStage(id,outputConnectionName,outputConnectionSpec); } } notificationManager.install(getTableName(),idField,notificationConnectionTableName,notificationConnectionNameField); scheduleManager.install(getTableName(),idField); hopFilterManager.install(getTableName(),idField); // Index management IndexDescription statusIndex = new IndexDescription(false,new String[]{statusField,idField,priorityField}); IndexDescription statusProcessIndex = new IndexDescription(false,new String[]{statusField,processIDField}); IndexDescription connectionIndex = new IndexDescription(false,new String[]{connectionNameField}); IndexDescription failTimeIndex = new IndexDescription(false,new String[]{failTimeField}); // 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 (statusIndex != null && id.equals(statusIndex)) statusIndex = null; else if (statusProcessIndex != null && id.equals(statusProcessIndex)) statusProcessIndex = null; else if (connectionIndex != null && id.equals(connectionIndex)) connectionIndex = null; else if (failTimeIndex != null && id.equals(failTimeIndex)) failTimeIndex = 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 (statusIndex != null) performAddIndex(null,statusIndex); if (statusProcessIndex != null) performAddIndex(null,statusProcessIndex); if (connectionIndex != null) performAddIndex(null,connectionIndex); if (failTimeIndex != null) performAddIndex(null,failTimeIndex); break; } } /** Uninstall. */ public void deinstall() throws ManifoldCFException { beginTransaction(); try { hopFilterManager.deinstall(); scheduleManager.deinstall(); notificationManager.deinstall(); pipelineManager.deinstall(); performDrop(null); } catch (ManifoldCFException e) { signalRollback(); throw e; } catch (Error e) { signalRollback(); throw e; } finally { endTransaction(); } } /** Come up with a maximum time (in minutes) for re-analyzing tables. *@return the time, in minutes. */ public int getAnalyzeTime() throws ManifoldCFException { // Since we never expect jobs to grow rapidly, every 24hrs should always be fine return 24 * 60; } /** Analyze job tables that need analysis. */ public void analyzeTables() throws ManifoldCFException { analyzeTable(); } /** Find a list of jobs matching specified notification names. */ public Long[] findJobsMatchingNotifications(List<String> notificationConnectionNames) throws ManifoldCFException { StringBuilder query = new StringBuilder(); ArrayList params = new ArrayList(); query.append("SELECT ").append(idField) .append(" FROM ").append(getTableName()).append(" t1 WHERE EXISTS("); notificationManager.buildNotificationQueryClause(query,params,"t1."+idField,notificationConnectionNames); query.append(")"); IResultSet set = performQuery(query.toString(),params,null,null); Long[] rval = new Long[set.getRowCount()]; for (int i = 0; i < rval.length; i++) { IResultRow row = set.getRow(i); rval[i] = (Long)row.getValue(idField); } return rval; } /** Find a list of jobs matching specified transformation names. */ public Long[] findJobsMatchingTransformations(List<String> transformationConnectionNames) throws ManifoldCFException { StringBuilder query = new StringBuilder(); ArrayList params = new ArrayList(); query.append("SELECT ").append(idField) .append(" FROM ").append(getTableName()).append(" t1 WHERE EXISTS("); pipelineManager.buildTransformationQueryClause(query,params,"t1."+idField,transformationConnectionNames); query.append(")"); IResultSet set = performQuery(query.toString(),params,null,null); Long[] rval = new Long[set.getRowCount()]; for (int i = 0; i < rval.length; i++) { IResultRow row = set.getRow(i); rval[i] = (Long)row.getValue(idField); } return rval; } /** Find a list of jobs matching specified output names. */ public Long[] findJobsMatchingOutputs(List<String> outputConnectionNames) throws ManifoldCFException { StringBuilder query = new StringBuilder(); ArrayList params = new ArrayList(); query.append("SELECT ").append(idField) .append(" FROM ").append(getTableName()).append(" t1 WHERE EXISTS("); pipelineManager.buildOutputQueryClause(query,params,"t1."+idField,outputConnectionNames); query.append(")"); IResultSet set = performQuery(query.toString(),params,null,null); Long[] rval = new Long[set.getRowCount()]; for (int i = 0; i < rval.length; i++) { IResultRow row = set.getRow(i); rval[i] = (Long)row.getValue(idField); } return rval; } /** Read schedule records for a specified set of jobs. Cannot use caching! */ public ScheduleRecord[][] readScheduleRecords(Long[] jobIDs) throws ManifoldCFException { Map uniqueIDs = new HashMap(); int i = 0; while (i < jobIDs.length) { Long jobID = jobIDs[i++]; uniqueIDs.put(jobID,jobID); } HashMap returnValues = new HashMap(); beginTransaction(); try { ArrayList params = new ArrayList(); int j = 0; int maxIn = scheduleManager.maxClauseGetRowsAlternate(); Iterator iter = uniqueIDs.keySet().iterator(); while (iter.hasNext()) { if (j == maxIn) { scheduleManager.getRowsAlternate(returnValues,params); params.clear(); j = 0; } params.add(iter.next()); j++; } if (j > 0) scheduleManager.getRowsAlternate(returnValues,params); } catch (Error e) { signalRollback(); throw e; } catch (ManifoldCFException e) { signalRollback(); throw e; } finally { endTransaction(); } // Fill the return array ScheduleRecord[][] rval = new ScheduleRecord[jobIDs.length][]; i = 0; while (i < jobIDs.length) { ArrayList al = (ArrayList)returnValues.get(jobIDs[i]); ScheduleRecord[] srList; if (al == null) srList = new ScheduleRecord[0]; else { srList = new ScheduleRecord[al.size()]; int k = 0; while (k < srList.length) { srList[k] = (ScheduleRecord)al.get(k); k++; } } rval[i++] = srList; } return rval; } // Only fetch 200 jobs at a time, for resource reasons protected final int FETCH_MAX = 200; /** Get a list of all jobs which are not in the process of being deleted already. *@return the array of all jobs. */ public IJobDescription[] getAll() throws ManifoldCFException { // Begin transaction lockManager.enterReadLock(jobsLock); try { // Put together cache key StringSetBuffer ssb = new StringSetBuffer(); ssb.add(getJobsKey()); ssb.add(getJobStatusKey()); StringSet cacheKeys = new StringSet(ssb); ArrayList list = new ArrayList(); list.add(statusToString(STATUS_READYFORDELETE)); list.add(statusToString(STATUS_DELETESTARTINGUP)); list.add(statusToString(STATUS_DELETING)); list.add(statusToString(STATUS_DELETING_NOOUTPUT)); IResultSet set = performQuery("SELECT "+idField+","+descriptionField+" FROM "+ getTableName()+" WHERE "+statusField+"!=? AND "+statusField+"!=? AND "+statusField+"!=? AND "+statusField+"!=?"+ " ORDER BY "+descriptionField+" ASC",list,cacheKeys,null); Long[] ids = new Long[set.getRowCount()]; boolean[] readOnlies = new boolean[set.getRowCount()]; for (int i = 0; i < set.getRowCount(); i++) { IResultRow row = set.getRow(i); ids[i] = (Long)row.getValue(idField); readOnlies[i] = true; } return loadMultiple(ids,readOnlies); } finally { lockManager.leaveReadLock(jobsLock); } } /** Get a list of active job identifiers and their associated connection names. *@return a resultset with "jobid" and "connectionname" fields. */ public IResultSet getActiveJobConnections() throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new MultiClause(statusField,new Object[]{ statusToString(STATUS_ACTIVE), statusToString(STATUS_ACTIVESEEDING)})}); return performQuery("SELECT "+idField+" AS jobid,"+connectionNameField+" AS connectionname FROM "+getTableName()+" WHERE "+ query,list,null,null); } /** Get unique connection names for all active jobs. *@return the array of connection names corresponding to active jobs. */ public String[] getActiveConnectionNames() throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new MultiClause(statusField,new Object[]{ statusToString(STATUS_ACTIVE), statusToString(STATUS_ACTIVESEEDING)})}); IResultSet set = performQuery("SELECT DISTINCT "+connectionNameField+" FROM "+getTableName()+" WHERE "+ query,list,null,null); String[] rval = new String[set.getRowCount()]; int i = 0; while (i < set.getRowCount()) { IResultRow row = set.getRow(i); rval[i] = (String)row.getValue(connectionNameField); i++; } return rval; } /** Are there any jobs that have a specified priority level */ public boolean hasPriorityJobs(int priority) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new MultiClause(statusField,new Object[]{ statusToString(STATUS_ACTIVE), statusToString(STATUS_ACTIVESEEDING)})}); IResultSet set = performQuery("SELECT * FROM "+getTableName()+" WHERE "+query+" AND "+priorityField+"="+Integer.toString(priority)+ " "+constructOffsetLimitClause(0,1),list,null,null,1); return set.getRowCount() > 0; } /** Create a job. */ public IJobDescription create() throws ManifoldCFException { JobDescription rval = new JobDescription(); rval.setIsNew(true); rval.setID(new Long(IDFactory.make(threadContext))); return rval; } /** Delete a job. This is not enough by itself; the queue entries also need to be deleted. *@param id is the job id. */ public void delete(Long id) throws ManifoldCFException { lockManager.enterNonExWriteLock(jobsLock); try { StringSetBuffer ssb = new StringSetBuffer(); ssb.add(getJobsKey()); ssb.add(getJobStatusKey()); ssb.add(getJobIDKey(id)); StringSet cacheKeys = new StringSet(ssb); beginTransaction(); try { scheduleManager.deleteRows(id); hopFilterManager.deleteRows(id); pipelineManager.deleteRows(id); notificationManager.deleteRows(id); ArrayList params = new ArrayList(); String query = buildConjunctionClause(params,new ClauseDescription[]{ new UnitaryClause(idField,id)}); performDelete("WHERE "+query,params,cacheKeys); } catch (ManifoldCFException e) { signalRollback(); throw e; } catch (Error e) { signalRollback(); throw e; } finally { endTransaction(); } } finally { lockManager.leaveNonExWriteLock(jobsLock); } } /** Load a job for editing. *@param id is the job id. *@return the editable job description. */ public IJobDescription load(Long id, boolean readOnly) throws ManifoldCFException { return loadMultiple(new Long[]{id}, new boolean[]{readOnly})[0]; } /** Load multiple jobs for editing. *@param ids is the set of id's to load *@param readOnlies are boolean values, set to true if the job description to be read is to be designated "read only". *@return the array of objects, in order. */ public IJobDescription[] loadMultiple(Long[] ids, boolean[] readOnlies) throws ManifoldCFException { IJobDescription[] rval = new IJobDescription[ids.length]; if (ids.length == 0) return rval; int inputIndex = 0; int outputIndex = 0; while (ids.length - inputIndex > FETCH_MAX) { outputIndex = loadMultipleInternal(rval,outputIndex,ids,readOnlies,inputIndex,FETCH_MAX); inputIndex += FETCH_MAX; } loadMultipleInternal(rval,outputIndex,ids,readOnlies,inputIndex,ids.length-inputIndex); return rval; } protected int loadMultipleInternal(IJobDescription[] rval, int outputIndex, Long[] ids, boolean[] readOnlies, int inputIndex, int length) throws ManifoldCFException { // Build description objects JobObjectDescription[] objectDescriptions = new JobObjectDescription[length]; StringSetBuffer ssb = new StringSetBuffer(); for (int i = 0; i < length; i++) { Long id = ids[inputIndex + i]; ssb.clear(); ssb.add(getJobIDKey(id)); objectDescriptions[i] = new JobObjectDescription(id,new StringSet(ssb)); } JobObjectExecutor exec = new JobObjectExecutor(this,objectDescriptions); cacheManager.findObjectsAndExecute(objectDescriptions,null,exec,getTransactionID()); IJobDescription[] results = exec.getResults(readOnlies,inputIndex); for (IJobDescription result : results) { rval[outputIndex++] = result; } return outputIndex; } /** Save a job description. *@param jobDescription is the job description. */ public void save(IJobDescription jobDescription) throws ManifoldCFException { // The invalidation keys for this are both the general and the specific. Long id = jobDescription.getID(); StringSetBuffer ssb = new StringSetBuffer(); ssb.add(getJobsKey()); ssb.add(getJobStatusKey()); ssb.add(getJobIDKey(id)); StringSet invKeys = new StringSet(ssb); while (true) { long sleepAmt = 0L; try { lockManager.enterNonExWriteLock(jobsLock); try { ICacheHandle ch = cacheManager.enterCache(null,invKeys,getTransactionID()); try { beginTransaction(); try { //performLock(); // See whether the instance exists ArrayList params = new ArrayList(); String query = buildConjunctionClause(params,new ClauseDescription[]{ new UnitaryClause(idField,id)}); IResultSet set = performQuery("SELECT * FROM "+getTableName()+" WHERE "+ query+" FOR UPDATE",params,null,null); HashMap values = new HashMap(); values.put(descriptionField,jobDescription.getDescription()); values.put(connectionNameField,jobDescription.getConnectionName()); String newXML = jobDescription.getSpecification().toXML(); values.put(documentSpecField,newXML); values.put(typeField,typeToString(jobDescription.getType())); values.put(startMethodField,startMethodToString(jobDescription.getStartMethod())); values.put(intervalField,jobDescription.getInterval()); values.put(maxIntervalField,jobDescription.getMaxInterval()); values.put(reseedIntervalField,jobDescription.getReseedInterval()); values.put(expirationField,jobDescription.getExpiration()); values.put(priorityField,new Integer(jobDescription.getPriority())); values.put(hopcountModeField,hopcountModeToString(jobDescription.getHopcountMode())); if (set.getRowCount() > 0) { // Update // We need to reset the seedingVersionField if there are any changes that // could affect what set of documents we allow!!! IResultRow row = set.getRow(0); // Determine whether we need to reset the scan time for documents. // Basically, any change to job parameters that could affect ingestion should clear isSame so that we // relook at all the documents, not just the recent ones. boolean isSame = pipelineManager.compareRows(id,jobDescription); if (!isSame) { int currentStatus = stringToStatus((String)row.getValue(statusField)); if (currentStatus == STATUS_ACTIVE || currentStatus == STATUS_ACTIVESEEDING || currentStatus == STATUS_ACTIVE_UNINSTALLED || currentStatus == STATUS_ACTIVESEEDING_UNINSTALLED) values.put(assessmentStateField,assessmentStateToString(ASSESSMENT_UNKNOWN)); } // Changing notifications should never reset seeding. /* if (isSame) { isSame = notificationManager.compareRows(id,jobDescription); } */ if (isSame) { String oldDocSpecXML = (String)row.getValue(documentSpecField); if (!oldDocSpecXML.equals(newXML)) isSame = false; } if (isSame) isSame = hopFilterManager.compareRows(id,jobDescription); if (!isSame) { //System.out.println("Setting version field to null"); values.put(seedingVersionField,null); } else { //System.out.println("NOT setting version field to null"); } params.clear(); query = buildConjunctionClause(params,new ClauseDescription[]{ new UnitaryClause(idField,id)}); performUpdate(values," WHERE "+query,params,null); pipelineManager.deleteRows(id); notificationManager.deleteRows(id); scheduleManager.deleteRows(id); hopFilterManager.deleteRows(id); } else { // Insert values.put(startTimeField,null); values.put(seedingVersionField,null); values.put(endTimeField,null); values.put(statusField,statusToString(STATUS_INACTIVE)); values.put(lastTimeField,new Long(System.currentTimeMillis())); values.put(idField,id); performInsert(values,null); } // Write pipeline rows pipelineManager.writeRows(id,jobDescription); // Write notification rows notificationManager.writeRows(id,jobDescription); // Write schedule records scheduleManager.writeRows(id,jobDescription); // Write hop filter rows hopFilterManager.writeRows(id,jobDescription); cacheManager.invalidateKeys(ch); break; } catch (ManifoldCFException e) { signalRollback(); throw e; } catch (Error e) { signalRollback(); throw e; } finally { endTransaction(); } } finally { cacheManager.leaveCache(ch); } } finally { lockManager.leaveNonExWriteLock(jobsLock); } } catch (ManifoldCFException e) { if (e.getErrorCode() != ManifoldCFException.DATABASE_TRANSACTION_ABORT) throw e; sleepAmt = getSleepAmt(); continue; } finally { sleepFor(sleepAmt); } } } /** Clear seeding state for a job. *@param jobID is the job whose state should be cleared. */ public void clearSeedingState(Long jobID) throws ManifoldCFException { Map values = new HashMap(); values.put(seedingVersionField,null); ArrayList params = new ArrayList(); String query = buildConjunctionClause(params,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); performUpdate(values,"WHERE "+query,params,null); } /** This method is called on a restart. *@param processID is the process to be restarting. */ public void restart(String processID) throws ManifoldCFException { StringSet invKey = new StringSet(getJobStatusKey()); ArrayList list = new ArrayList(); HashMap map = new HashMap(); String query; // Starting up delete goes back to just being ready for delete query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_DELETESTARTINGUP)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_READYFORDELETE)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); // Notifying of completion goes back to just being ready for notify list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_NOTIFYINGOFCOMPLETION)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_READYFORNOTIFY)); map.put(processIDField,null); map.put(failTimeField,null); map.put(failCountField,null); performUpdate(map,"WHERE "+query,list,invKey); // Notifying of deletion goes back to just being ready for delete notify list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_NOTIFYINGOFDELETION)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_READYFORDELETENOTIFY)); map.put(processIDField,null); map.put(failTimeField,null); map.put(failCountField,null); performUpdate(map,"WHERE "+query,list,invKey); // Starting up or aborting starting up goes back to just being ready list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new MultiClause(statusField,new Object[]{ statusToString(STATUS_STARTINGUP)}), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_READYFORSTARTUP)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); // Starting up or aborting starting up goes back to just being ready list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new MultiClause(statusField,new Object[]{ statusToString(STATUS_STARTINGUPMINIMAL)}), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_READYFORSTARTUPMINIMAL)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); // Aborting starting up for restart state goes to ABORTINGFORRESTART list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ABORTINGSTARTINGUPFORRESTART)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ABORTINGFORRESTART)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); // Aborting starting up for restart state goes to ABORTINGFORRESTART list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ABORTINGSTARTINGUPFORRESTARTMINIMAL)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ABORTINGFORRESTARTMINIMAL)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); // All seeding values return to pre-seeding values list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ACTIVESEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ACTIVE)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_PAUSINGSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_PAUSING)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ACTIVEWAITINGSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ACTIVEWAITING)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_PAUSINGWAITINGSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_PAUSINGWAITING)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_RESUMINGSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_RESUMING)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ABORTINGFORRESTARTSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ABORTINGFORRESTART)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ABORTINGFORRESTARTSEEDINGMINIMAL)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ABORTINGFORRESTARTMINIMAL)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_PAUSEDSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_PAUSED)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ACTIVEWAITSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ACTIVEWAIT)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_PAUSEDWAITSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_PAUSEDWAIT)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ACTIVESEEDING_UNINSTALLED)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ACTIVE_UNINSTALLED)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); } /** Clean up after all process IDs. */ public void restart() throws ManifoldCFException { StringSet invKey = new StringSet(getJobStatusKey()); ArrayList list = new ArrayList(); HashMap map = new HashMap(); String query; // All jobs with non-null failTime and failCount get set to null failTime and failCount // Starting up delete goes back to just being ready for delete query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_DELETESTARTINGUP))}); map.put(statusField,statusToString(STATUS_READYFORDELETE)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); // Notifying of completion goes back to just being ready for notify list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_NOTIFYINGOFCOMPLETION))}); map.put(statusField,statusToString(STATUS_READYFORNOTIFY)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); // Notifying of deletion goes back to just being ready for delete notify list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_NOTIFYINGOFDELETION))}); map.put(statusField,statusToString(STATUS_READYFORDELETENOTIFY)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); // Starting up or aborting starting up goes back to just being ready list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new MultiClause(statusField,new Object[]{ statusToString(STATUS_STARTINGUP)})}); map.put(statusField,statusToString(STATUS_READYFORSTARTUP)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); // Starting up or aborting starting up goes back to just being ready list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new MultiClause(statusField,new Object[]{ statusToString(STATUS_STARTINGUPMINIMAL)})}); map.put(statusField,statusToString(STATUS_READYFORSTARTUPMINIMAL)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); // Aborting starting up for restart state goes to ABORTINGFORRESTART list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ABORTINGSTARTINGUPFORRESTART))}); map.put(statusField,statusToString(STATUS_ABORTINGFORRESTART)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); // Aborting starting up for restart state goes to ABORTINGFORRESTART list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ABORTINGSTARTINGUPFORRESTARTMINIMAL))}); map.put(statusField,statusToString(STATUS_ABORTINGFORRESTARTMINIMAL)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); // All seeding values return to pre-seeding values list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ACTIVESEEDING))}); map.put(statusField,statusToString(STATUS_ACTIVE)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_PAUSINGSEEDING))}); map.put(statusField,statusToString(STATUS_PAUSING)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ACTIVEWAITINGSEEDING))}); map.put(statusField,statusToString(STATUS_ACTIVEWAITING)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_PAUSINGWAITINGSEEDING))}); map.put(statusField,statusToString(STATUS_PAUSINGWAITING)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_RESUMINGSEEDING))}); map.put(statusField,statusToString(STATUS_RESUMING)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ABORTINGFORRESTARTSEEDING))}); map.put(statusField,statusToString(STATUS_ABORTINGFORRESTART)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ABORTINGFORRESTARTSEEDINGMINIMAL))}); map.put(statusField,statusToString(STATUS_ABORTINGFORRESTARTMINIMAL)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_PAUSEDSEEDING))}); map.put(statusField,statusToString(STATUS_PAUSED)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ACTIVEWAITSEEDING))}); map.put(statusField,statusToString(STATUS_ACTIVEWAIT)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_PAUSEDWAITSEEDING))}); map.put(statusField,statusToString(STATUS_PAUSEDWAIT)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ACTIVESEEDING_UNINSTALLED))}); map.put(statusField,statusToString(STATUS_ACTIVE_UNINSTALLED)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); } public void restartCluster() throws ManifoldCFException { HashMap map = new HashMap(); map.put(failTimeField,null); ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new NullCheckClause(failTimeField,false)}); performUpdate(map,"WHERE "+query,list,null); } /** Invalidate current state with respect to installed connectors, as a result of registration. */ public void invalidateCurrentUnregisteredState(Long jobID, int oldStatusValue) throws ManifoldCFException { // If we are in a state that cares about the connector state, then we have to signal we need an assessment. if (oldStatusValue == STATUS_ACTIVE_UNINSTALLED || oldStatusValue == STATUS_ACTIVESEEDING_UNINSTALLED || oldStatusValue == STATUS_DELETING_NOOUTPUT) { // Assessment state is not cached, so no cache invalidation needed ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(assessmentStateField,assessmentStateToString(ASSESSMENT_UNKNOWN)); performUpdate(map,"WHERE "+query,list,null); } } /** Signal to a job that an underlying transformation connector has gone away. *@param jobID is the identifier of the job. *@param oldStatusValue is the current status value for the job. */ public void noteTransformationConnectorDeregistration(Long jobID, int oldStatusValue) throws ManifoldCFException { int newStatusValue; // The following states are special, in that when the underlying connector goes away, the jobs // in such states are switched away to something else. There are TWO reasons that a state may be in // this category: EITHER we don't want the job in this state to be treated in the same way if its // connector is uninstalled, OR we need feedback for the user interface. If it's the latter situation, // then all usages of the corresponding states will be identical - and that's in fact precisely where we // start with in all the code. switch (oldStatusValue) { case STATUS_ACTIVE: newStatusValue = STATUS_ACTIVE_UNINSTALLED; break; case STATUS_ACTIVESEEDING: newStatusValue = STATUS_ACTIVESEEDING_UNINSTALLED; break; default: newStatusValue = oldStatusValue; break; } if (newStatusValue == oldStatusValue) return; StringSet invKey = new StringSet(getJobStatusKey()); HashMap newValues = new HashMap(); newValues.put(statusField,statusToString(newStatusValue)); newValues.put(assessmentStateField,assessmentStateToString(ASSESSMENT_KNOWN)); ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); performUpdate(newValues,"WHERE "+query,list,invKey); } /** Signal to a job that an underlying transformation connector has been registered. *@param jobID is the identifier of the job. *@param oldStatusValue is the current status value for the job. */ public void noteTransformationConnectorRegistration(Long jobID, int oldStatusValue) throws ManifoldCFException { invalidateCurrentUnregisteredState(jobID,oldStatusValue); } /** Signal to a job that its underlying output connector has gone away. *@param jobID is the identifier of the job. *@param oldStatusValue is the current status value for the job. */ public void noteOutputConnectorDeregistration(Long jobID, int oldStatusValue) throws ManifoldCFException { int newStatusValue; // The following states are special, in that when the underlying connector goes away, the jobs // in such states are switched away to something else. There are TWO reasons that a state may be in // this category: EITHER we don't want the job in this state to be treated in the same way if its // connector is uninstalled, OR we need feedback for the user interface. If it's the latter situation, // then all usages of the corresponding states will be identical - and that's in fact precisely where we // start with in all the code. switch (oldStatusValue) { case STATUS_ACTIVE: newStatusValue = STATUS_ACTIVE_UNINSTALLED; break; case STATUS_ACTIVESEEDING: newStatusValue = STATUS_ACTIVESEEDING_UNINSTALLED; break; case STATUS_DELETING: newStatusValue = STATUS_DELETING_NOOUTPUT; break; default: newStatusValue = oldStatusValue; break; } if (newStatusValue == oldStatusValue) return; StringSet invKey = new StringSet(getJobStatusKey()); HashMap newValues = new HashMap(); newValues.put(statusField,statusToString(newStatusValue)); newValues.put(assessmentStateField,assessmentStateToString(ASSESSMENT_KNOWN)); ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); performUpdate(newValues,"WHERE "+query,list,invKey); } /** Signal to a job that its underlying output connector has returned. *@param jobID is the identifier of the job. *@param oldStatusValue is the current status value for the job. */ public void noteOutputConnectorRegistration(Long jobID, int oldStatusValue) throws ManifoldCFException { if (oldStatusValue == STATUS_DELETING_NOOUTPUT) { // We can do the state transition now. StringSet invKey = new StringSet(getJobStatusKey()); HashMap newValues = new HashMap(); newValues.put(statusField,statusToString(STATUS_DELETING)); newValues.put(assessmentStateField,assessmentStateToString(ASSESSMENT_KNOWN)); ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); performUpdate(newValues,"WHERE "+query,list,invKey); return; } // Otherwise, we don't know the state, and can't do the work now because we'd deadlock invalidateCurrentUnregisteredState(jobID,oldStatusValue); } /** Signal to a job that its underlying notification connector has gone away. *@param jobID is the identifier of the job. *@param oldStatusValue is the current status value for the job. */ public void noteNotificationConnectorDeregistration(Long jobID, int oldStatusValue) throws ManifoldCFException { // MHL } /** Signal to a job that its underlying connector has gone away. *@param jobID is the identifier of the job. *@param oldStatusValue is the current status value for the job. */ public void noteConnectorDeregistration(Long jobID, int oldStatusValue) throws ManifoldCFException { int newStatusValue; // The following states are special, in that when the underlying connector goes away, the jobs // in such states are switched away to something else. There are TWO reasons that a state may be in // this category: EITHER we don't want the job in this state to be treated in the same way if its // connector is uninstalled, OR we need feedback for the user interface. If it's the latter situation, // then all usages of the corresponding states will be identical - and that's in fact precisely where we // start with in all the code. switch (oldStatusValue) { case STATUS_ACTIVE: newStatusValue = STATUS_ACTIVE_UNINSTALLED; break; case STATUS_ACTIVESEEDING: newStatusValue = STATUS_ACTIVESEEDING_UNINSTALLED; break; default: newStatusValue = oldStatusValue; break; } if (newStatusValue == oldStatusValue) return; StringSet invKey = new StringSet(getJobStatusKey()); HashMap newValues = new HashMap(); newValues.put(statusField,statusToString(newStatusValue)); newValues.put(assessmentStateField,assessmentStateToString(ASSESSMENT_KNOWN)); ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); performUpdate(newValues,"WHERE "+query,list,invKey); } /** Signal to a job that its underlying notification connector has returned. *@param jobID is the identifier of the job. *@param oldStatusValue is the current status value for the job. */ public void noteNotificationConnectorRegistration(Long jobID, int oldStatusValue) throws ManifoldCFException { // MHL } /** Signal to a job that its underlying connector has returned. *@param jobID is the identifier of the job. *@param oldStatusValue is the current status value for the job. */ public void noteConnectorRegistration(Long jobID, int oldStatusValue) throws ManifoldCFException { invalidateCurrentUnregisteredState(jobID,oldStatusValue); } /** Note a change in connection configuration. * This method will be called whenever a connection's configuration is modified, or when an external repository change * is signalled. */ public void noteConnectionChange(String connectionName) throws ManifoldCFException { // No cache keys need invalidation, since we're changing the start time, not the status. HashMap newValues = new HashMap(); newValues.put(seedingVersionField,null); ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(connectionNameField,connectionName)}); performUpdate(newValues,"WHERE "+query,list,null); } /** Note a change in notification connection configuration. * This method will be called whenever a notification connection's configuration is modified, or when an external repository change * is signalled. */ public void noteNotificationConnectionChange(String connectionName) throws ManifoldCFException { // Notification configuration change does not change anything in a crawl } /** Note a change in output connection configuration. * This method will be called whenever a connection's configuration is modified, or when an external target config change * is signalled. */ public void noteOutputConnectionChange(String connectionName) throws ManifoldCFException { // No cache keys need invalidation, since we're changing the start time, not the status. HashMap newValues = new HashMap(); newValues.put(seedingVersionField,null); ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new JoinClause(getTableName()+"."+idField,pipelineManager.ownerIDField), new UnitaryClause(pipelineManager.outputNameField,connectionName)}); performUpdate(newValues,"WHERE EXISTS(SELECT 'x' FROM "+pipelineManager.getTableName()+" WHERE "+query+")",list,null); } /** Note a change in transformation connection configuration. * This method will be called whenever a connection's configuration is modified. */ public void noteTransformationConnectionChange(String connectionName) throws ManifoldCFException { // No cache keys need invalidation, since we're changing the start time, not the status. HashMap newValues = new HashMap(); newValues.put(seedingVersionField,null); ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new JoinClause(getTableName()+"."+idField,pipelineManager.ownerIDField), new UnitaryClause(pipelineManager.transformationNameField,connectionName)}); performUpdate(newValues,"WHERE EXISTS(SELECT 'x' FROM "+pipelineManager.getTableName()+" WHERE "+query+")",list,null); } /** Check whether a job's status indicates that it is in ACTIVE or ACTIVESEEDING state. */ public boolean checkJobActive(Long jobID) throws ManifoldCFException { StringSet cacheKeys = new StringSet(getJobStatusKey()); ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); IResultSet set = performQuery("SELECT "+statusField+" FROM "+ getTableName()+" WHERE "+query,list,cacheKeys,null); if (set.getRowCount() == 0) return false; IResultRow row = set.getRow(0); String statusValue = (String)row.getValue(statusField); int status = stringToStatus(statusValue); // Any active state in the lifecycle will do: seeding, active, active_seeding return (status == STATUS_ACTIVE || status == STATUS_ACTIVESEEDING || status == STATUS_STARTINGUP || status == STATUS_STARTINGUPMINIMAL); } /** Reset delete startup worker thread status. */ public void resetDeleteStartupWorkerStatus(String processID) throws ManifoldCFException { // This handles everything that the delete startup thread would resolve. ArrayList list = new ArrayList(); HashMap map = new HashMap(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_DELETESTARTINGUP)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_READYFORDELETE)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Reset notification worker thread status. */ public void resetNotificationWorkerStatus(String processID) throws ManifoldCFException { // This resets everything that the job notification thread would resolve. ArrayList list = new ArrayList(); HashMap map = new HashMap(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_NOTIFYINGOFCOMPLETION)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_READYFORNOTIFY)); map.put(processIDField,null); map.put(failTimeField,null); map.put(failCountField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); list.clear(); map.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_NOTIFYINGOFDELETION)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_READYFORDELETENOTIFY)); map.put(processIDField,null); map.put(failTimeField,null); map.put(failCountField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Reset startup worker thread status. */ public void resetStartupWorkerStatus(String processID) throws ManifoldCFException { // We have to handle all states that the startup thread would resolve, and change them to something appropriate. ArrayList list = new ArrayList(); HashMap map = new HashMap(); String query; list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_STARTINGUP)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_READYFORSTARTUP)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_STARTINGUPMINIMAL)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_READYFORSTARTUPMINIMAL)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ABORTINGSTARTINGUPFORRESTART)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ABORTINGFORRESTART)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ABORTINGSTARTINGUPFORRESTARTMINIMAL)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ABORTINGFORRESTARTMINIMAL)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Reset as part of restoring seeding worker threads. */ public void resetSeedingWorkerStatus(String processID) throws ManifoldCFException { StringSet invKey = new StringSet(getJobStatusKey()); ArrayList list = new ArrayList(); HashMap map = new HashMap(); String query; // All seeding values return to pre-seeding values list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ACTIVESEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ACTIVE)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_PAUSINGSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_PAUSING)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ACTIVEWAITINGSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ACTIVEWAITING)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_PAUSINGWAITINGSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_PAUSINGWAITING)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_RESUMINGSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_RESUMING)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ABORTINGFORRESTARTSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ABORTINGFORRESTART)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ABORTINGFORRESTARTSEEDINGMINIMAL)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ABORTINGFORRESTARTMINIMAL)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_PAUSEDSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_PAUSED)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ACTIVEWAITSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ACTIVEWAIT)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_PAUSEDWAITSEEDING)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_PAUSEDWAIT)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); list.clear(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_ACTIVESEEDING_UNINSTALLED)), new UnitaryClause(processIDField,processID)}); map.put(statusField,statusToString(STATUS_ACTIVE_UNINSTALLED)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,invKey); } /** Retry startup. *@param jobID is the job identifier. *@param requestMinimum is true for a minimal crawl. *@param failTime is the fail time, -1 == none *@param failCount is the fail count to use, -1 == none. */ public void retryStartup(Long jobID, boolean requestMinimum, long failTime, int failCount) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); if (requestMinimum) map.put(statusField,statusToString(STATUS_READYFORSTARTUPMINIMAL)); else map.put(statusField,statusToString(STATUS_READYFORSTARTUP)); if (failTime == -1L) map.put(failTimeField,null); else map.put(failTimeField,new Long(failTime)); if (failCount == -1) map.put(failCountField,null); else map.put(failCountField,failCount); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Retry seeding. *@param jobID is the job identifier. *@param failTime is the fail time, -1 == none *@param failCount is the fail count to use, -1 == none. */ public void retrySeeding(Long jobID, long failTime, int failCount) throws ManifoldCFException { beginTransaction(); try { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); // Map the field values we see to those we want. // This is exactly equivalent to finishing seeding, except for fail time etc. IResultSet set = performQuery("SELECT "+statusField+" FROM "+getTableName()+" WHERE "+ query+" FOR UPDATE",list,null,null); if (set.getRowCount() == 0) throw new ManifoldCFException("Can't find job "+jobID.toString()); IResultRow row = set.getRow(0); int status = stringToStatus((String)row.getValue(statusField)); int newStatus; switch (status) { case STATUS_ACTIVESEEDING: newStatus = STATUS_ACTIVE; break; case STATUS_PAUSINGSEEDING: newStatus = STATUS_PAUSING; break; case STATUS_ACTIVEWAITINGSEEDING: newStatus = STATUS_ACTIVEWAITING; break; case STATUS_PAUSINGWAITINGSEEDING: newStatus = STATUS_PAUSINGWAITING; break; case STATUS_ACTIVESEEDING_UNINSTALLED: newStatus = STATUS_ACTIVE_UNINSTALLED; break; case STATUS_ACTIVEWAITSEEDING: newStatus = STATUS_ACTIVEWAIT; break; case STATUS_PAUSEDSEEDING: newStatus = STATUS_PAUSED; break; case STATUS_PAUSEDWAITSEEDING: newStatus = STATUS_PAUSEDWAIT; break; case STATUS_ABORTINGFORRESTARTSEEDING: newStatus = STATUS_ABORTINGFORRESTART; break; case STATUS_ABORTINGFORRESTARTSEEDINGMINIMAL: newStatus = STATUS_ABORTINGFORRESTARTMINIMAL; break; default: throw new ManifoldCFException("Unexpected job status encountered: "+Integer.toString(status)); } HashMap map = new HashMap(); map.put(statusField,statusToString(newStatus)); if (failTime == -1L) map.put(failTimeField,null); else map.put(failTimeField,new Long(failTime)); if (failCount == -1) map.put(failCountField,null); else map.put(failCountField,failCount); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } catch (Error e) { signalRollback(); throw e; } catch (RuntimeException e) { signalRollback(); throw e; } catch (ManifoldCFException e) { signalRollback(); throw e; } finally { endTransaction(); } } /** Retry notification. *@param jobID is the job identifier. *@param failTime is the fail time, -1 == none *@param failCount is the fail count to use, -1 == none. */ public void retryNotification(Long jobID, long failTime, int failCount) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(statusField,statusToString(STATUS_READYFORNOTIFY)); if (failTime == -1L) map.put(failTimeField,null); else map.put(failTimeField,new Long(failTime)); if (failCount == -1) map.put(failCountField,null); else map.put(failCountField,failCount); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Retry delete notification. *@param jobID is the job identifier. *@param failTime is the fail time, -1 == none *@param failCount is the fail count to use, -1 == none. */ public void retryDeleteNotification(Long jobID, long failTime, int failCount) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(statusField,statusToString(STATUS_READYFORDELETENOTIFY)); if (failTime == -1L) map.put(failTimeField,null); else map.put(failTimeField,new Long(failTime)); if (failCount == -1) map.put(failCountField,null); else map.put(failCountField,failCount); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Write job status and window end, and clear the endtime field. (The start time will be written * when the job enters the "active" state.) *@param jobID is the job identifier. *@param windowEnd is the window end time, if any *@param requestMinimum is true if a minimal job run is requested */ public void startJob(Long jobID, Long windowEnd, boolean requestMinimum) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(statusField,statusToString(requestMinimum?STATUS_READYFORSTARTUPMINIMAL:STATUS_READYFORSTARTUP)); map.put(endTimeField,null); // Make sure error is removed (from last time) map.put(errorField,null); map.put(windowEndField,windowEnd); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Assess all marked jobs to determine if they can be reactivated. */ public void assessMarkedJobs() throws ManifoldCFException { ArrayList newList = new ArrayList(); String query = buildConjunctionClause(newList,new ClauseDescription[]{ new UnitaryClause(assessmentStateField,assessmentStateToString(ASSESSMENT_UNKNOWN))}); // Query for the matching jobs, and then for each job potentially adjust the state based on the connector status IResultSet set = performQuery("SELECT "+idField+","+statusField+","+connectionNameField+" FROM "+ getTableName()+" WHERE "+query+" FOR UPDATE", newList,null,null); for (int i = 0; i < set.getRowCount(); i++) { IResultRow row = set.getRow(i); Long jobID = (Long)row.getValue(idField); String connectionName = (String)row.getValue(connectionNameField); String[] transformationNames = pipelineManager.getTransformationConnectionNames(jobID); String[] outputNames = pipelineManager.getOutputConnectionNames(jobID); int statusValue = stringToStatus((String)row.getValue(statusField)); int newValue; // Based on status value, see what we need to know to determine if the state can be switched switch (statusValue) { case STATUS_DELETING_NOOUTPUT: if (checkOutputsInstalled(outputNames)) newValue = STATUS_DELETING; else return; break; case STATUS_ACTIVE_UNINSTALLED: if (connectionMgr.checkConnectorExists(connectionName) && checkTransformationsInstalled(transformationNames) && checkOutputsInstalled(outputNames)) newValue = STATUS_ACTIVE; else return; break; case STATUS_ACTIVESEEDING_UNINSTALLED: if (connectionMgr.checkConnectorExists(connectionName) && checkTransformationsInstalled(transformationNames) && checkOutputsInstalled(outputNames)) newValue = STATUS_ACTIVESEEDING; else return; break; default: return; } ArrayList list = new ArrayList(); query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(assessmentStateField,assessmentStateToString(ASSESSMENT_KNOWN)); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } } /** Put job back into active state, from the shutting-down state. *@param jobID is the job identifier. */ public void returnJobToActive(Long jobID) throws ManifoldCFException { beginTransaction(); try { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); IResultSet set = performQuery("SELECT "+statusField+","+connectionNameField+" FROM "+getTableName()+" WHERE "+ query+" FOR UPDATE",list,null,null); if (set.getRowCount() == 0) throw new ManifoldCFException("Can't find job "+jobID.toString()); IResultRow row = set.getRow(0); int status = stringToStatus((String)row.getValue(statusField)); int newStatus; switch (status) { case STATUS_SHUTTINGDOWN: String[] transformationConnectionNames = pipelineManager.getTransformationConnectionNames(jobID); String[] outputConnectionNames = pipelineManager.getOutputConnectionNames(jobID); String connectionName = (String)row.getValue(connectionNameField); // Want either STATUS_ACTIVE, or STATUS_ACTIVE_UNINSTALLED if (!checkTransformationsInstalled(transformationConnectionNames) || !checkOutputsInstalled(outputConnectionNames) || !connectionMgr.checkConnectorExists(connectionName)) newStatus = STATUS_ACTIVE_UNINSTALLED; else newStatus = STATUS_ACTIVE; break; default: // Complain! throw new ManifoldCFException("Unexpected job status encountered: "+Integer.toString(status)); } HashMap map = new HashMap(); map.put(statusField,statusToString(newStatus)); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } catch (ManifoldCFException e) { signalRollback(); throw e; } catch (RuntimeException e) { signalRollback(); throw e; } catch (Error e) { signalRollback(); throw e; } finally { endTransaction(); } } protected boolean checkTransformationsInstalled(String[] transformationNames) throws ManifoldCFException { for (String transformationName : transformationNames) { if (!transMgr.checkConnectorExists(transformationName)) return false; } return true; } protected boolean checkOutputsInstalled(String[] outputNames) throws ManifoldCFException { for (String outputName : outputNames) { if (!outputMgr.checkConnectorExists(outputName)) return false; } return true; } /** Put job into "deleting" state, and set the start time field. *@param jobID is the job identifier. *@param startTime is the current time in milliseconds from start of epoch. */ public void noteJobDeleteStarted(Long jobID, long startTime) throws ManifoldCFException { beginTransaction(); try { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); IResultSet set = performQuery("SELECT "+statusField+" FROM "+getTableName()+" WHERE "+ query+" FOR UPDATE",list,null,null); if (set.getRowCount() == 0) throw new ManifoldCFException("Can't find job "+jobID.toString()); IResultRow row = set.getRow(0); int status = stringToStatus((String)row.getValue(statusField)); String[] outputNames = pipelineManager.getOutputConnectionNames(jobID); int newStatus; switch (status) { case STATUS_DELETESTARTINGUP: if (checkOutputsInstalled(outputNames)) newStatus = STATUS_DELETING; else newStatus = STATUS_DELETING_NOOUTPUT; break; default: // Complain! throw new ManifoldCFException("Unexpected job status encountered: "+Integer.toString(status)); } HashMap map = new HashMap(); map.put(statusField,statusToString(newStatus)); if (newStatus == STATUS_DELETING || newStatus == STATUS_DELETING_NOOUTPUT) { map.put(startTimeField,new Long(startTime)); } // Clear out seeding version, in case we wind up keeping the job and rerunning it map.put(seedingVersionField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } catch (ManifoldCFException e) { signalRollback(); throw e; } catch (RuntimeException e) { signalRollback(); throw e; } catch (Error e) { signalRollback(); throw e; } finally { endTransaction(); } } /** Make job active, and set the start time field. *@param jobID is the job identifier. *@param startTime is the current time in milliseconds from start of epoch. *@param seedVersionString is the version string to record for the seeding. */ public void noteJobStarted(Long jobID, long startTime, String seedVersionString) throws ManifoldCFException { beginTransaction(); try { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); IResultSet set = performQuery("SELECT "+statusField+","+connectionNameField+" FROM "+getTableName()+" WHERE "+ query+" FOR UPDATE",list,null,null); if (set.getRowCount() == 0) throw new ManifoldCFException("Can't find job "+jobID.toString()); IResultRow row = set.getRow(0); int status = stringToStatus((String)row.getValue(statusField)); int newStatus; switch (status) { case STATUS_STARTINGUP: case STATUS_STARTINGUPMINIMAL: String[] transformationConnectionNames = pipelineManager.getTransformationConnectionNames(jobID); String[] outputConnectionNames = pipelineManager.getOutputConnectionNames(jobID); String connectionName = (String)row.getValue(connectionNameField); // Need to set either STATUS_ACTIVE, or STATUS_ACTIVE_UNINSTALLED if (!checkTransformationsInstalled(transformationConnectionNames) || !checkOutputsInstalled(outputConnectionNames) || !connectionMgr.checkConnectorExists(connectionName)) newStatus = STATUS_ACTIVE_UNINSTALLED; else newStatus = STATUS_ACTIVE; break; case STATUS_ABORTINGSTARTINGUPFORRESTART: newStatus = STATUS_ABORTINGFORRESTART; break; case STATUS_ABORTINGSTARTINGUPFORRESTARTMINIMAL: newStatus = STATUS_ABORTINGFORRESTARTMINIMAL; break; default: // Complain! throw new ManifoldCFException("Unexpected job status encountered: "+Integer.toString(status)); } HashMap map = new HashMap(); map.put(statusField,statusToString(newStatus)); if (newStatus == STATUS_ACTIVE || newStatus == STATUS_ACTIVE_UNINSTALLED) { map.put(startTimeField,new Long(startTime)); } // The seeding was complete or we wouldn't have gotten called, so at least note that. map.put(seedingVersionField,seedVersionString); // Clear out the retry fields we might have set map.put(failTimeField,null); map.put(failCountField,null); map.put(processIDField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } catch (ManifoldCFException e) { signalRollback(); throw e; } catch (RuntimeException e) { signalRollback(); throw e; } catch (Error e) { signalRollback(); throw e; } finally { endTransaction(); } } /** Note job seeded. *@param jobID is the job id. *@param seedVersionString is the job seed version string. */ public void noteJobSeeded(Long jobID, String seedVersionString) throws ManifoldCFException { // We have to convert the current status to the non-seeding equivalent beginTransaction(); try { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); IResultSet set = performQuery("SELECT "+statusField+" FROM "+getTableName()+" WHERE "+ query+" FOR UPDATE",list,null,null); if (set.getRowCount() == 0) throw new ManifoldCFException("Can't find job "+jobID.toString()); IResultRow row = set.getRow(0); int status = stringToStatus((String)row.getValue(statusField)); int newStatus; switch (status) { case STATUS_ACTIVESEEDING: newStatus = STATUS_ACTIVE; break; case STATUS_PAUSINGSEEDING: newStatus = STATUS_PAUSING; break; case STATUS_ACTIVEWAITINGSEEDING: newStatus = STATUS_ACTIVEWAITING; break; case STATUS_PAUSINGWAITINGSEEDING: newStatus = STATUS_PAUSINGWAITING; break; case STATUS_ACTIVESEEDING_UNINSTALLED: newStatus = STATUS_ACTIVE_UNINSTALLED; break; case STATUS_ACTIVEWAITSEEDING: newStatus = STATUS_ACTIVEWAIT; break; case STATUS_PAUSEDSEEDING: newStatus = STATUS_PAUSED; break; case STATUS_PAUSEDWAITSEEDING: newStatus = STATUS_PAUSEDWAIT; break; case STATUS_ABORTINGFORRESTARTSEEDING: newStatus = STATUS_ABORTINGFORRESTART; break; case STATUS_ABORTINGFORRESTARTSEEDINGMINIMAL: newStatus = STATUS_ABORTINGFORRESTARTMINIMAL; break; default: throw new ManifoldCFException("Unexpected job status encountered: "+Integer.toString(status)); } HashMap map = new HashMap(); map.put(statusField,statusToString(newStatus)); map.put(processIDField,null); map.put(seedingVersionField,seedVersionString); map.put(failTimeField,null); map.put(failCountField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } catch (Error e) { signalRollback(); throw e; } catch (RuntimeException e) { signalRollback(); throw e; } catch (ManifoldCFException e) { signalRollback(); throw e; } finally { endTransaction(); } } /** Cause job that is in "wait" state to become un-waited. *@param jobID is the job identifier. *@param newStatus is the new status (either STATUS_ACTIVE or STATUS_PAUSED or * STATUS_ACTIVESEEDING or STATUS_PAUSEDSEEDING) *@param windowEnd is the window end time, if any. */ public void unwaitJob(Long jobID, int newStatus, Long windowEnd) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(statusField,statusToString(newStatus)); map.put(windowEndField,windowEnd); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Cause job that is in active or paused state to become waited. *@param jobID is the job identifier. *@param newStatus is the new status (either STATUS_ACTIVEWAIT or STATUS_PAUSEDWAIT or * STATUS_ACTIVEWAITSEEDING or STATUS_PAUSEDWAITSEEDING) */ public void waitJob(Long jobID, int newStatus) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(statusField,statusToString(newStatus)); map.put(assessmentStateField,assessmentStateToString(ASSESSMENT_KNOWN)); map.put(windowEndField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Abort a job. *@param jobID is the job id. *@param errorText is the error, or null if none. *@return true if there wasn't an abort already logged for this job. */ public boolean abortJob(Long jobID, String errorText) throws ManifoldCFException { // Get the current job status ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); IResultSet set = performQuery("SELECT "+statusField+" FROM "+getTableName()+ " WHERE "+query+" FOR UPDATE",list,null,null); if (set.getRowCount() == 0) throw new ManifoldCFException("Job does not exist: "+jobID); IResultRow row = set.getRow(0); int status = stringToStatus(row.getValue(statusField).toString()); if (status == STATUS_ABORTING || status == STATUS_ABORTINGSHUTTINGDOWN) return false; int newStatus; switch (status) { case STATUS_NOTIFYINGOFCOMPLETION: newStatus = STATUS_INACTIVE; break; case STATUS_STARTINGUP: case STATUS_ABORTINGSTARTINGUPFORRESTART: newStatus = STATUS_ABORTING; break; case STATUS_STARTINGUPMINIMAL: case STATUS_ABORTINGSTARTINGUPFORRESTARTMINIMAL: newStatus = STATUS_ABORTING; break; case STATUS_SHUTTINGDOWN: newStatus = STATUS_ABORTINGSHUTTINGDOWN; break; case STATUS_READYFORSTARTUP: case STATUS_READYFORSTARTUPMINIMAL: case STATUS_ACTIVE: case STATUS_ACTIVE_UNINSTALLED: case STATUS_ACTIVEWAIT: case STATUS_PAUSING: case STATUS_ACTIVEWAITING: case STATUS_PAUSINGWAITING: case STATUS_PAUSED: case STATUS_PAUSEDWAIT: case STATUS_ABORTINGFORRESTART: case STATUS_ABORTINGFORRESTARTMINIMAL: newStatus = STATUS_ABORTING; break; case STATUS_ACTIVESEEDING: case STATUS_ACTIVESEEDING_UNINSTALLED: case STATUS_ACTIVEWAITSEEDING: case STATUS_PAUSINGSEEDING: case STATUS_ACTIVEWAITINGSEEDING: case STATUS_PAUSINGWAITINGSEEDING: case STATUS_PAUSEDSEEDING: case STATUS_PAUSEDWAITSEEDING: case STATUS_ABORTINGFORRESTARTSEEDING: case STATUS_ABORTINGFORRESTARTSEEDINGMINIMAL: newStatus = STATUS_ABORTING; break; default: throw new ManifoldCFException("Job "+jobID+" is not active"); } // Pause the job HashMap map = new HashMap(); map.put(statusField,statusToString(newStatus)); map.put(errorField,errorText); map.put(processIDField,null); map.put(failTimeField,null); map.put(failCountField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); return true; } /** Restart a job. Finish off what's currently happening, and then start the job up again. *@param jobID is the job id. *@param requestMinimum is true if the minimal job run is requested. */ public void abortRestartJob(Long jobID, boolean requestMinimum) throws ManifoldCFException { // Get the current job status ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); IResultSet set = performQuery("SELECT "+statusField+" FROM "+getTableName()+ " WHERE "+query+" FOR UPDATE",list,null,null); if (set.getRowCount() == 0) throw new ManifoldCFException("Job does not exist: "+jobID); IResultRow row = set.getRow(0); int status = stringToStatus(row.getValue(statusField).toString()); if (status == STATUS_ABORTINGFORRESTART || status == STATUS_ABORTINGFORRESTARTSEEDING || status == STATUS_ABORTINGSTARTINGUPFORRESTART || status == STATUS_ABORTINGFORRESTARTMINIMAL || status == STATUS_ABORTINGFORRESTARTSEEDINGMINIMAL || status == STATUS_ABORTINGSTARTINGUPFORRESTARTMINIMAL) return; int newStatus; switch (status) { case STATUS_STARTINGUP: case STATUS_STARTINGUPMINIMAL: newStatus = requestMinimum?STATUS_ABORTINGSTARTINGUPFORRESTARTMINIMAL:STATUS_ABORTINGSTARTINGUPFORRESTART; break; case STATUS_READYFORSTARTUP: case STATUS_READYFORSTARTUPMINIMAL: case STATUS_ACTIVE: case STATUS_ACTIVE_UNINSTALLED: case STATUS_ACTIVEWAIT: case STATUS_PAUSING: case STATUS_ACTIVEWAITING: case STATUS_PAUSINGWAITING: case STATUS_PAUSED: case STATUS_PAUSEDWAIT: newStatus = requestMinimum?STATUS_ABORTINGFORRESTARTMINIMAL:STATUS_ABORTINGFORRESTART; break; case STATUS_ACTIVESEEDING: case STATUS_ACTIVESEEDING_UNINSTALLED: case STATUS_ACTIVEWAITSEEDING: case STATUS_PAUSINGSEEDING: case STATUS_ACTIVEWAITINGSEEDING: case STATUS_PAUSINGWAITINGSEEDING: case STATUS_PAUSEDSEEDING: case STATUS_PAUSEDWAITSEEDING: newStatus = requestMinimum?STATUS_ABORTINGFORRESTARTSEEDINGMINIMAL:STATUS_ABORTINGFORRESTARTSEEDING; break; default: throw new ManifoldCFException("Job "+jobID+" is not restartable"); } // reset the job HashMap map = new HashMap(); map.put(statusField,statusToString(newStatus)); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Pause a job. *@param jobID is the job id. */ public void pauseJob(Long jobID) throws ManifoldCFException { // Get the current job status ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); IResultSet set = performQuery("SELECT "+statusField+" FROM "+getTableName()+ " WHERE "+query+" FOR UPDATE",list,null,null); if (set.getRowCount() == 0) throw new ManifoldCFException("Job does not exist: "+jobID); IResultRow row = set.getRow(0); int status = stringToStatus(row.getValue(statusField).toString()); int newStatus; switch (status) { case STATUS_ACTIVE: case STATUS_ACTIVE_UNINSTALLED: newStatus = STATUS_PAUSING; break; case STATUS_ACTIVEWAITING: newStatus = STATUS_PAUSINGWAITING; break; case STATUS_ACTIVEWAIT: newStatus = STATUS_PAUSEDWAIT; break; case STATUS_ACTIVESEEDING: case STATUS_ACTIVESEEDING_UNINSTALLED: newStatus = STATUS_PAUSINGSEEDING; break; case STATUS_ACTIVEWAITINGSEEDING: newStatus = STATUS_PAUSINGWAITINGSEEDING; break; case STATUS_ACTIVEWAITSEEDING: newStatus = STATUS_PAUSEDWAITSEEDING; break; default: throw new ManifoldCFException("Job "+jobID+" is not active"); } // Pause the job HashMap map = new HashMap(); map.put(statusField,statusToString(newStatus)); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Restart a job. *@param jobID is the job id. */ public void restartJob(Long jobID) throws ManifoldCFException { // Get the current job status ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); IResultSet set = performQuery("SELECT "+statusField+" FROM "+getTableName()+ " WHERE "+query+" FOR UPDATE",list,null,null); if (set.getRowCount() == 0) throw new ManifoldCFException("Job does not exist: "+jobID); IResultRow row = set.getRow(0); int status = stringToStatus(row.getValue(statusField).toString()); int newStatus; switch (status) { case STATUS_PAUSED: newStatus = STATUS_RESUMING; break; case STATUS_PAUSEDWAIT: newStatus = STATUS_ACTIVEWAIT; break; case STATUS_PAUSEDSEEDING: newStatus = STATUS_RESUMINGSEEDING; break; case STATUS_PAUSEDWAITSEEDING: newStatus = STATUS_ACTIVEWAITSEEDING; break; default: throw new ManifoldCFException("Job "+jobID+" is not paused"); } // Pause the job HashMap map = new HashMap(); map.put(statusField,statusToString(newStatus)); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Update a job's status, and its reseed time. *@param jobID is the job id. *@param status is the desired status. *@param reseedTime is the reseed time. */ public void writeTransientStatus(Long jobID, int status, Long reseedTime, String processID) throws ManifoldCFException { writeStatus(jobID, status, reseedTime, processID, false); } /** Update a job's status, and its reseed time. *@param jobID is the job id. *@param status is the desired status. *@param reseedTime is the reseed time. */ public void writePermanentStatus(Long jobID, int status, Long reseedTime) throws ManifoldCFException { writeStatus(jobID, status, reseedTime, null, false); } /** Update a job's status, and its reseed time. *@param jobID is the job id. *@param status is the desired status. *@param reseedTime is the reseed time. */ public void writePermanentStatus(Long jobID, int status, Long reseedTime, boolean clearFailTime) throws ManifoldCFException { writeStatus(jobID, status, reseedTime, null, clearFailTime); } /** Update a job's status, and its reseed time. *@param jobID is the job id. *@param status is the desired status. *@param reseedTime is the reseed time. */ protected void writeStatus(Long jobID, int status, Long reseedTime, String processID, boolean clearFailTime) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(statusField,statusToString(status)); map.put(processIDField,processID); map.put(reseedTimeField,reseedTime); if (clearFailTime) { map.put(failTimeField,null); map.put(failCountField,null); } performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Update a job's status. *@param jobID is the job id. *@param status is the desired status. */ public void writeTransientStatus(Long jobID, int status, String processID) throws ManifoldCFException { writeStatus(jobID, status, processID, false); } /** Update a job's status. *@param jobID is the job id. *@param status is the desired status. */ public void writePermanentStatus(Long jobID, int status) throws ManifoldCFException { writeStatus(jobID, status, null, false); } /** Update a job's status. *@param jobID is the job id. *@param status is the desired status. */ public void writePermanentStatus(Long jobID, int status, boolean clearFailTime) throws ManifoldCFException { writeStatus(jobID, status, null, clearFailTime); } /** Update a job's status. *@param jobID is the job id. *@param status is the desired status. */ protected void writeStatus(Long jobID, int status, String processID, boolean clearFailTime) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(statusField,statusToString(status)); map.put(processIDField,processID); if (clearFailTime) { map.put(failTimeField,null); map.put(failCountField,null); } performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Update a job's last-time field. *@param jobID is the job id. *@param currentTime is the current time. */ public void updateLastTime(Long jobID, long currentTime) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(lastTimeField,new Long(currentTime)); performUpdate(map,"WHERE "+query,list,null); } /** Finish a job. * Write completion and the current time. *@param jobID is the job id. *@param finishTime is the finish time. */ public void finishJob(Long jobID, long finishTime) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(statusField,statusToString(STATUS_READYFORNOTIFY)); map.put(errorField,null); map.put(endTimeField,new Long(finishTime)); map.put(lastTimeField,new Long(finishTime)); map.put(windowEndField,null); map.put(reseedTimeField,null); performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Finish job cleanup. * Write completion and the current time. *@param jobID is the job id. */ public void finishJobCleanup(Long jobID) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(statusField,statusToString(STATUS_READYFORDELETENOTIFY)); map.put(errorField,null); // Anything else? // MHL performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** Resume a stopped job (from a pause or activewait). * Updates the job record in a manner consistent with the job's state. */ public void finishResumeJob(Long jobID, long currentTime) throws ManifoldCFException { beginTransaction(); try { // Get the current job status ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); IResultSet set = performQuery("SELECT "+statusField+","+connectionNameField+" FROM "+getTableName()+ " WHERE "+query+" FOR UPDATE",list,null,null); if (set.getRowCount() == 0) throw new ManifoldCFException("Job does not exist: "+jobID); IResultRow row = set.getRow(0); int status = stringToStatus(row.getValue(statusField).toString()); String[] transformationConnectionNames = pipelineManager.getTransformationConnectionNames(jobID); String[] outputConnectionNames = pipelineManager.getOutputConnectionNames(jobID); String connectionName = (String)row.getValue(connectionNameField); int newStatus; HashMap map = new HashMap(); switch (status) { case STATUS_RESUMING: // Want either STATUS_ACTIVE or STATUS_ACTIVE_UNINSTALLED if (!checkTransformationsInstalled(transformationConnectionNames) || !checkOutputsInstalled(outputConnectionNames) || !connectionMgr.checkConnectorExists(connectionName)) newStatus = STATUS_ACTIVE_UNINSTALLED; else newStatus = STATUS_ACTIVE; map.put(statusField,statusToString(newStatus)); break; case STATUS_RESUMINGSEEDING: // Want either STATUS_ACTIVESEEDING or STATUS_ACTIVESEEDING_UNINSTALLED if (!checkTransformationsInstalled(transformationConnectionNames) || !checkOutputsInstalled(outputConnectionNames) || !connectionMgr.checkConnectorExists(connectionName)) newStatus = STATUS_ACTIVESEEDING_UNINSTALLED; else newStatus = STATUS_ACTIVESEEDING; map.put(statusField,statusToString(newStatus)); break; default: throw new ManifoldCFException("Unexpected value for job status: "+Integer.toString(status)); } performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } catch (ManifoldCFException e) { signalRollback(); throw e; } catch (Error e) { signalRollback(); throw e; } finally { endTransaction(); } } /** Stop a job suddenly (abort, pause, activewait). * Updates the job record in a manner consistent with the job's state. */ public void finishStopJob(Long jobID, long currentTime) throws ManifoldCFException { beginTransaction(); try { // Get the current job status ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); IResultSet set = performQuery("SELECT "+statusField+" FROM "+getTableName()+ " WHERE "+query+" FOR UPDATE",list,null,null); if (set.getRowCount() == 0) throw new ManifoldCFException("Job does not exist: "+jobID); IResultRow row = set.getRow(0); int status = stringToStatus(row.getValue(statusField).toString()); HashMap map = new HashMap(); switch (status) { case STATUS_ABORTINGSHUTTINGDOWN: case STATUS_ABORTING: // Mark status of job as "inactive" map.put(statusField,statusToString(STATUS_READYFORNOTIFY)); map.put(endTimeField,null); map.put(lastTimeField,new Long(currentTime)); map.put(windowEndField,null); map.put(reseedTimeField,null); break; case STATUS_ABORTINGFORRESTART: map.put(statusField,statusToString(STATUS_READYFORSTARTUP)); map.put(endTimeField,null); // Make sure error is removed (from last time) map.put(errorField,null); map.put(windowEndField,null); break; case STATUS_ABORTINGFORRESTARTMINIMAL: map.put(statusField,statusToString(STATUS_READYFORSTARTUPMINIMAL)); map.put(endTimeField,null); // Make sure error is removed (from last time) map.put(errorField,null); map.put(windowEndField,null); break; case STATUS_PAUSING: map.put(statusField,statusToString(STATUS_PAUSED)); break; case STATUS_PAUSINGSEEDING: map.put(statusField,statusToString(STATUS_PAUSEDSEEDING)); break; case STATUS_ACTIVEWAITING: map.put(statusField,statusToString(STATUS_ACTIVEWAIT)); break; case STATUS_ACTIVEWAITINGSEEDING: map.put(statusField,statusToString(STATUS_ACTIVEWAITSEEDING)); break; case STATUS_PAUSINGWAITING: map.put(statusField,statusToString(STATUS_PAUSEDWAIT)); break; case STATUS_PAUSINGWAITINGSEEDING: map.put(statusField,statusToString(STATUS_PAUSEDWAITSEEDING)); break; default: throw new ManifoldCFException("Unexpected value for job status: "+Integer.toString(status)); } performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } catch (ManifoldCFException e) { signalRollback(); throw e; } catch (Error e) { signalRollback(); throw e; } finally { endTransaction(); } } /** Mark job as having properly notified the output connector of completion. *@param jobID is the job id. */ public void notificationComplete(Long jobID) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(idField,jobID)}); HashMap map = new HashMap(); map.put(statusField,statusToString(STATUS_INACTIVE)); map.put(processIDField,null); map.put(failTimeField,null); map.put(failCountField,null); // Leave everything else around from the abort/finish. performUpdate(map,"WHERE "+query,list,new StringSet(getJobStatusKey())); } /** See if there's a reference to a connection name. *@param connectionName is the name of the connection. *@return true if there is a reference, false otherwise. */ public boolean checkIfReference(String connectionName) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(connectionNameField,connectionName)}); IResultSet set = performQuery("SELECT "+idField+" FROM "+getTableName()+ " WHERE "+query,list,new StringSet(getJobsKey()),null); return set.getRowCount() > 0; } /** See if there's a reference to a notification connection name. *@param connectionName is the name of the connection. *@return true if there is a reference, false otherwise. */ public boolean checkIfNotificationReference(String connectionName) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(notificationManager.notificationNameField,connectionName)}); IResultSet set = performQuery("SELECT "+notificationManager.ownerIDField+" FROM "+notificationManager.getTableName()+ " WHERE "+query,list,new StringSet(getJobsKey()),null); return set.getRowCount() > 0; } /** See if there's a reference to an output connection name. *@param connectionName is the name of the connection. *@return true if there is a reference, false otherwise. */ public boolean checkIfOutputReference(String connectionName) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(pipelineManager.outputNameField,connectionName)}); IResultSet set = performQuery("SELECT "+pipelineManager.ownerIDField+" FROM "+pipelineManager.getTableName()+ " WHERE "+query,list,new StringSet(getJobsKey()),null); return set.getRowCount() > 0; } /** See if there's a reference to a transformation connection name. *@param connectionName is the name of the connection. *@return true if there is a reference, false otherwise. */ public boolean checkIfTransformationReference(String connectionName) throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(pipelineManager.transformationNameField,connectionName)}); IResultSet set = performQuery("SELECT "+pipelineManager.ownerIDField+" FROM "+pipelineManager.getTableName()+ " WHERE "+query,list,new StringSet(getJobsKey()),null); return set.getRowCount() > 0; } /** Get the job IDs associated with a given connection name. *@param connectionName is the name of the connection. *@return the set of job id's associated with that connection. */ public IJobDescription[] findJobsForConnection(String connectionName) throws ManifoldCFException { lockManager.enterReadLock(jobsLock); try { // Put together cache key StringSetBuffer ssb = new StringSetBuffer(); ssb.add(getJobsKey()); StringSet cacheKeys = new StringSet(ssb); ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(connectionNameField,connectionName)}); IResultSet set = performQuery("SELECT "+idField+","+descriptionField+" FROM "+ getTableName()+" WHERE "+query+ " ORDER BY "+descriptionField+" ASC",list,cacheKeys,null); // Convert to an array of id's, and then load them Long[] ids = new Long[set.getRowCount()]; boolean[] readOnlies = new boolean[set.getRowCount()]; for (int i = 0; i < ids.length; i++) { IResultRow row = set.getRow(i); ids[i] = (Long)row.getValue(idField); readOnlies[i] = true; } return loadMultiple(ids,readOnlies); } finally { lockManager.leaveReadLock(jobsLock); } } /** Return true if there is a job in the DELETING state. (This matches the * conditions for values to be returned from * getNextDeletableDocuments). *@return true if such jobs exist. */ public boolean deletingJobsPresent() throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_DELETING))}); IResultSet set = performQuery("SELECT "+idField+" FROM "+getTableName()+" WHERE "+ query+" "+constructOffsetLimitClause(0,1), list,new StringSet(getJobStatusKey()),null,1); return set.getRowCount() > 0; } /** Return true if there is a job in the * SHUTTINGDOWN state. (This matches the conditions for values to be returned from * getNextCleanableDocuments). *@return true if such jobs exist. */ public boolean cleaningJobsPresent() throws ManifoldCFException { ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new UnitaryClause(statusField,statusToString(STATUS_SHUTTINGDOWN))}); IResultSet set = performQuery("SELECT "+idField+" FROM "+getTableName()+" WHERE "+ query+" "+constructOffsetLimitClause(0,1), list,new StringSet(getJobStatusKey()),null,1); return set.getRowCount() > 0; } /** Return true if there is a job in either the ACTIVE or the ACTIVESEEDING state. * (This matches the conditions for values to be returned from getNextDocuments). *@return true if such jobs exist. */ public boolean activeJobsPresent() throws ManifoldCFException { // To improve the postgres CPU usage of the system at rest, we do a *fast* check to be // sure there are ANY jobs in an active state. ArrayList list = new ArrayList(); String query = buildConjunctionClause(list,new ClauseDescription[]{ new MultiClause(statusField,new Object[]{ statusToString(STATUS_ACTIVE), statusToString(STATUS_ACTIVESEEDING)})}); IResultSet set = performQuery("SELECT "+idField+" FROM "+getTableName()+" WHERE "+ query+" "+constructOffsetLimitClause(0,1),list,new StringSet(getJobStatusKey()),null,1); return set.getRowCount() > 0; } // These functions map from status to a string and back /** Go from string to status. *@param value is the string. *@return the status value. */ public static int stringToStatus(String value) throws ManifoldCFException { Integer x = (Integer)statusMap.get(value); if (x == null) throw new ManifoldCFException("Bad status value: '"+value+"'"); return x.intValue(); } /** Go from status to string. *@param status is the status. *@return the string. */ public static String statusToString(int status) throws ManifoldCFException { switch (status) { case STATUS_INACTIVE: return "N"; case STATUS_ACTIVE: return "A"; case STATUS_PAUSED: return "P"; case STATUS_SHUTTINGDOWN: return "S"; case STATUS_NOTIFYINGOFCOMPLETION: return "n"; case STATUS_READYFORNOTIFY: return "s"; case STATUS_NOTIFYINGOFDELETION: return "j"; case STATUS_READYFORDELETENOTIFY: return "d"; case STATUS_ACTIVEWAIT: return "W"; case STATUS_PAUSEDWAIT: return "Z"; case STATUS_ABORTING: return "X"; case STATUS_ABORTINGFORRESTART: return "Y"; case STATUS_ABORTINGFORRESTARTMINIMAL: return "M"; case STATUS_STARTINGUP: return "B"; case STATUS_STARTINGUPMINIMAL: return "b"; case STATUS_ABORTINGSTARTINGUPFORRESTART: return "T"; case STATUS_ABORTINGSTARTINGUPFORRESTARTMINIMAL: return "t"; case STATUS_READYFORSTARTUP: return "C"; case STATUS_READYFORSTARTUPMINIMAL: return "c"; case STATUS_READYFORDELETE: return "E"; case STATUS_DELETESTARTINGUP: return "V"; case STATUS_DELETING: return "e"; case STATUS_ACTIVESEEDING: return "a"; case STATUS_PAUSEDSEEDING: return "p"; case STATUS_ACTIVEWAITSEEDING: return "w"; case STATUS_PAUSEDWAITSEEDING: return "z"; case STATUS_ABORTINGFORRESTARTSEEDING: return "y"; case STATUS_ABORTINGFORRESTARTSEEDINGMINIMAL: return "m"; case STATUS_ACTIVE_UNINSTALLED: return "R"; case STATUS_ACTIVESEEDING_UNINSTALLED: return "r"; case STATUS_DELETING_NOOUTPUT: return "D"; case STATUS_ACTIVEWAITING: return "H"; case STATUS_ACTIVEWAITINGSEEDING: return "h"; case STATUS_PAUSING: return "F"; case STATUS_PAUSINGSEEDING: return "f"; case STATUS_PAUSINGWAITING: return "G"; case STATUS_PAUSINGWAITINGSEEDING: return "g"; case STATUS_RESUMING: return "I"; case STATUS_RESUMINGSEEDING: return "i"; case STATUS_ABORTINGSHUTTINGDOWN: return "v"; // These are deprecated case STATUS_ACTIVE_NOOUTPUT: return "O"; case STATUS_ACTIVESEEDING_NOOUTPUT: return "o"; case STATUS_ACTIVE_NEITHER: return "U"; case STATUS_ACTIVESEEDING_NEITHER: return "u"; default: throw new ManifoldCFException("Bad status value: "+Integer.toString(status)); } } /** Go from string to type. *@param value is the string. *@return the type value. */ public static int stringToType(String value) throws ManifoldCFException { Integer x = (Integer)typeMap.get(value); if (x == null) throw new ManifoldCFException("Bad type value: '"+value+"'"); return x.intValue(); } /** Go from type to string. *@param type is the type. *@return the string. */ public static String typeToString(int type) throws ManifoldCFException { switch (type) { case TYPE_CONTINUOUS: return "C"; case TYPE_SPECIFIED: return "S"; default: throw new ManifoldCFException("Bad type: "+Integer.toString(type)); } } /** Go from string to assessment state. */ public static int stringToAssessmentState(String value) throws ManifoldCFException { if (value == null || value.length() == 0) return ASSESSMENT_KNOWN; Integer x = assessmentMap.get(value); if (x == null) throw new ManifoldCFException("Bad assessment value: '"+value+"'"); return x.intValue(); } /** Go from assessment state to string. */ public static String assessmentStateToString(int value) throws ManifoldCFException { switch(value) { case ASSESSMENT_KNOWN: return "Y"; case ASSESSMENT_UNKNOWN: return "N"; default: throw new ManifoldCFException("Unknown assessment state value "+Integer.toString(value)); } } /** Go from string to hopcount mode. */ public static int stringToHopcountMode(String value) throws ManifoldCFException { if (value == null || value.length() == 0) return HOPCOUNT_ACCURATE; Integer x = hopmodeMap.get(value); if (x == null) throw new ManifoldCFException("Bad hopcount mode value: '"+value+"'"); return x.intValue(); } /** Go from hopcount mode to string. */ public static String hopcountModeToString(int value) throws ManifoldCFException { switch(value) { case HOPCOUNT_ACCURATE: return "A"; case HOPCOUNT_NODELETE: return "N"; case HOPCOUNT_NEVERDELETE: return "V"; default: throw new ManifoldCFException("Unknown hopcount mode value "+Integer.toString(value)); } } /** Go from string to start method. *@param value is the string. *@return the start method value. */ public static int stringToStartMethod(String value) throws ManifoldCFException { Integer x = (Integer)startMap.get(value); if (x == null) throw new ManifoldCFException("Bad start method value: '"+value+"'"); return x.intValue(); } /** Get from start method to string. *@param startMethod is the start method. *@return a string. */ public static String startMethodToString(int startMethod) throws ManifoldCFException { switch(startMethod) { case START_WINDOWBEGIN: return "B"; case START_WINDOWINSIDE: return "I"; case START_DISABLE: return "D"; default: throw new ManifoldCFException("Bad start method: "+Integer.toString(startMethod)); } } /** Go from string to enumerated value. *@param value is the input. *@return the enumerated value. */ public static EnumeratedValues stringToEnumeratedValue(String value) throws ManifoldCFException { if (value == null) return null; try { ArrayList valStore = new ArrayList(); if (!value.equals("*")) { int curpos = 0; while (true) { int newpos = value.indexOf(",",curpos); if (newpos == -1) { valStore.add(new Integer(value.substring(curpos))); break; } valStore.add(new Integer(value.substring(curpos,newpos))); curpos = newpos+1; } } return new EnumeratedValues(valStore); } catch (NumberFormatException e) { throw new ManifoldCFException("Bad number: '"+value+"'",e); } } /** Go from enumerated value to string. *@param values is the enumerated value. *@return the string value. */ public static String enumeratedValueToString(EnumeratedValues values) { if (values == null) return null; if (values.size() == 0) return "*"; StringBuilder rval = new StringBuilder(); Iterator iter = values.getValues(); boolean first = true; while (iter.hasNext()) { if (first) first = false; else rval.append(','); rval.append(((Integer)iter.next()).toString()); } return rval.toString(); } // Cache key picture for jobs. There is one global job cache key, and a cache key for each individual job (which is based on // id). protected static String getJobsKey() { return CacheKeyFactory.makeJobsKey(); } protected static String getJobIDKey(Long jobID) { return CacheKeyFactory.makeJobIDKey(jobID.toString()); } protected static String getJobStatusKey() { return CacheKeyFactory.makeJobStatusKey(); } /** Get multiple jobs (without caching) *@param ids is the set of ids to get jobs for. *@return the corresponding job descriptions. */ protected JobDescription[] getJobsMultiple(Long[] ids) throws ManifoldCFException { // Fetch all the jobs, but only once for each ID. Then, assign each one by id into the final array. Set<Long> uniqueIDs = new HashSet<Long>(); for (Long id : ids) { uniqueIDs.add(id); } Map<Long,JobDescription> returnValues = new HashMap<Long,JobDescription>(); beginTransaction(); try { StringBuilder sb = new StringBuilder(); ArrayList params = new ArrayList(); int j = 0; int maxIn = getMaxInClause(); for (Long uniqueID : uniqueIDs) { if (j == maxIn) { getJobsChunk(returnValues,sb.toString(),params); sb.setLength(0); params.clear(); j = 0; } if (j > 0) sb.append(','); sb.append('?'); params.add(uniqueID); j++; } if (j > 0) getJobsChunk(returnValues,sb.toString(),params); } catch (Error e) { signalRollback(); throw e; } catch (ManifoldCFException e) { signalRollback(); throw e; } finally { endTransaction(); } // Build the return array JobDescription[] rval = new JobDescription[ids.length]; for (int i = 0; i < rval.length; i++) { Long id = ids[i]; JobDescription jd = returnValues.get(id); if (jd != null) jd.makeReadOnly(); rval[i] = jd; } return rval; } /** Read a chunk of repository connections. *@param returnValues is keyed by id and contains a JobDescription value. *@param idList is the list of id's. *@param params is the set of parameters. */ protected void getJobsChunk(Map<Long,JobDescription> returnValues, String idList, ArrayList params) throws ManifoldCFException { try { IResultSet set; set = performQuery("SELECT * FROM "+getTableName()+" WHERE "+ idField+" IN ("+idList+")",params,null,null); int i = 0; while (i < set.getRowCount()) { IResultRow row = set.getRow(i++); Long id = (Long)row.getValue(idField); JobDescription rc = new JobDescription(); rc.setID(id); rc.setIsNew(false); rc.setDescription(row.getValue(descriptionField).toString()); rc.setConnectionName(row.getValue(connectionNameField).toString()); rc.setType(stringToType(row.getValue(typeField).toString())); rc.setStartMethod(stringToStartMethod(row.getValue(startMethodField).toString())); rc.setHopcountMode(stringToHopcountMode((String)row.getValue(hopcountModeField))); rc.getSpecification().fromXML(row.getValue(documentSpecField).toString()); Object x; rc.setInterval((Long)row.getValue(intervalField)); rc.setMaxInterval((Long)row.getValue(maxIntervalField)); rc.setReseedInterval((Long)row.getValue(reseedIntervalField)); rc.setExpiration((Long)row.getValue(expirationField)); rc.setPriority(Integer.parseInt(row.getValue(priorityField).toString())); returnValues.put(id,rc); } // Fill in schedules for jobs pipelineManager.getRows(returnValues,idList,params); notificationManager.getRows(returnValues,idList,params); scheduleManager.getRows(returnValues,idList,params); hopFilterManager.getRows(returnValues,idList,params); } catch (NumberFormatException e) { throw new ManifoldCFException("Bad number: "+e.getMessage(),e); } } /** Job object description class. This class describes an object in the cache. */ protected static class JobObjectDescription extends org.apache.manifoldcf.core.cachemanager.BaseDescription { protected Long jobID; protected String criticalSectionName; protected StringSet cacheKeys; public JobObjectDescription(Long jobID, StringSet invKeys) { super("jobdescriptioncache"); this.jobID = jobID; criticalSectionName = getClass().getName()+"-"+jobID.toString(); cacheKeys = invKeys; } public Long getJobID() { return jobID; } public int hashCode() { return jobID.hashCode() ; } public boolean equals(Object o) { if (!(o instanceof JobObjectDescription)) return false; JobObjectDescription d = (JobObjectDescription)o; return d.jobID.equals(jobID); } 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 job objects. */ protected static class JobObjectExecutor extends org.apache.manifoldcf.core.cachemanager.ExecutorBase { // Member variables protected Jobs thisManager; protected JobDescription[] returnValues; protected HashMap returnMap = new HashMap(); /** Constructor. *@param manager is the ToolManager. *@param objectDescriptions are the object descriptions. */ public JobObjectExecutor(Jobs manager, JobObjectDescription[] objectDescriptions) { super(); thisManager = manager; returnValues = new JobDescription[objectDescriptions.length]; int i = 0; while (i < objectDescriptions.length) { returnMap.put(objectDescriptions[i].getJobID(),new Integer(i)); i++; } } /** Get the result. *@return the looked-up or read cached instances. */ public JobDescription[] getResults(boolean[] readOnlies, int inputIndex) { JobDescription[] rval = new JobDescription[returnValues.length]; for (int i = 0; i < rval.length; i++) { JobDescription jd = returnValues[i]; if (jd != null) rval[i] = jd.duplicate(readOnlies[inputIndex + i]); else rval[i] = null; } return rval; } /** 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 Long[] ids = new Long[objectDescriptions.length]; int i = 0; while (i < ids.length) { JobObjectDescription desc = (JobObjectDescription)objectDescriptions[i]; ids[i] = desc.getJobID(); i++; } return thisManager.getJobsMultiple(ids); } /** 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 JobObjectDescription objectDesc = (JobObjectDescription)objectDescription; JobDescription ci = (JobDescription)cachedObject; // All objects stored in this cache are read-only; no need to duplicate them at this level. // In order to make the indexes line up, we need to use the hashtable built by // the constructor. returnValues[((Integer)returnMap.get(objectDesc.getJobID())).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. } } }