/* See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Esri Inc. 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 com.esri.gpt.control.webharvest.engine;
import com.esri.gpt.catalog.harvest.repository.HrRecord;
import com.esri.gpt.catalog.harvest.repository.HrRecords;
import com.esri.gpt.catalog.harvest.repository.HrSelectRequest;
import com.esri.gpt.framework.collection.StringAttributeMap;
import com.esri.gpt.framework.context.ApplicationConfiguration;
import com.esri.gpt.framework.context.ApplicationContext;
import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.util.TimePeriod;
import com.esri.gpt.framework.util.Val;
import java.sql.SQLException;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Schedules new harvesting task according to the repository harvest frequency.
*/
abstract class AutoSelector implements Runnable {
private static final int DEFAULT_MAX_ATTEMPTS = 1;
/** logger */
private static final Logger LOGGER = Logger.getLogger(AutoSelector.class.getCanonicalName());
/** current thread */
private Thread workerThread;
/** shutdown flag */
private volatile boolean shutdown;
/** autoselect frequency */
private long autoSelectFrequency;
/** suspended */
private volatile boolean suspended;
/**
* Creates instance of auto selector.
* @param autoSelectFrequency auto select frequency (milliseconds)
*/
public AutoSelector(long autoSelectFrequency) {
this.autoSelectFrequency = autoSelectFrequency;
}
@Override
public void run() {
workerThread = Thread.currentThread();
LOGGER.info("[SYNCHRONIZER] AutoSelector activated.");
int attempt = 0;
do {
long duration = autoSelectFrequency;
if (!suspended) {
LOGGER.finer("[SYNCHRONIZER] AutoSelector entered run mode.");
try {
HrRecords records = selectRecords();
// selecting all records with harvest date due now
HrRecords recordsDueNow = records.findHarvestDue();
LOGGER.log(Level.FINER, "[SYNCHRONIZER] AutoSelector selected {0} records with harvest date due now.", recordsDueNow.size());
// process all with harvest date due now
for (HrRecord r : recordsDueNow) {
if (shutdown || suspended) break;
// this is it; do something in overriden method
onSelect(r);
}
// get the one record with the closes due date but not due yet
HrRecord nextDue = records.findNextDue();
// caluclate duration in milliseconds
if (nextDue!=null) {
Date nextHarvestDate = nextDue.getNextHarvestDate();
duration = nextHarvestDate.getTime() - (new Date()).getTime() ;
LOGGER.log(Level.INFO,"[SYNCHRONIZER] Next synchronization time : "+nextHarvestDate.toString()+" has been determined based on scheduling of "+nextDue.getUuid()+"/\""+nextDue.getName()+"\" harvesting site.");
} else {
duration = autoSelectFrequency;
LOGGER.log(Level.INFO,"[SYNCHRONIZER] Next synchronization time couldn't been determined at this time.");
}
// clear attempt counter
attempt = 0;
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "[SYNCHRONIZER] Error selecting harvesting sites for harvest.", ex);
attempt++;
if (attempt<=getMaxAttempts()) {
LOGGER.log(Level.SEVERE, "[SYNCHRONIZER] Error selecting harvesting sites for harvest.", ex);
}
}
}
if (shutdown) break;
// wait for calculated duration or until interrupted
synchronized (this) {
try {
if (isSuspendedWithAck()) {
LOGGER.finer("[SYNCHRONIZER] AutoSelector suspended mode");
wait();
} else {
TimePeriod period = new TimePeriod();
period.setValue(duration);
LOGGER.log(Level.FINER, "[SYNCHRONIZER] AutoSelector enters wait mode for {0}", period);
wait(duration);
}
} catch (InterruptedException ex) {
if (shutdown)
break;
}
}
} while (true);
}
/**
* Shuts down selector.
*/
public synchronized void shutdown() {
LOGGER.info("[SYNCHRONIZER] Shutting down AutoSelector.");
this.shutdown = true;
if (workerThread != null) {
workerThread.interrupt();
}
}
/**
* Forces to reselect harvesting sites.
*/
public void reselect() {
if (workerThread != null) {
workerThread.interrupt();
}
}
/**
* Called on every repository selected for harvest.
* @param context request context
* @param repository harvest repository
*/
protected abstract void onSelect(HrRecord repository);
/**
* Selects records.
* @return records
* @throws SQLException if accessing database failed
*/
private HrRecords selectRecords() throws SQLException {
RequestContext context = RequestContext.extract(null);
try {
// get all harveting records
HrSelectRequest selectRequest = new HrSelectRequest(context);
selectRequest.execute();
return selectRequest.getQueryResult().getRecords();
} finally {
context.onExecutionPhaseCompleted();
}
}
public synchronized void safeSuspend() {
if (!suspended) {
LOGGER.info("[SYNCHRONIZER] Suspending AutoSelector");
suspended = true;
notify();
} else {
LOGGER.info("[SYNCHRONIZER] AutoSelector already suspended");
}
}
public synchronized void safeResume() {
if (suspended) {
LOGGER.info("[SYNCHRONIZER] Resuming AutoSelector");
suspended = false;
notify();
} else {
LOGGER.info("[SYNCHRONIZER] AutoSelector already resumed");
}
}
private boolean isSuspendedWithAck() {
if (suspended) {
LOGGER.info("[SYNCHRONIZER] AutoSelector acknowledged suspension");
}
return suspended;
}
private int getMaxAttempts() {
ApplicationContext appCtx = ApplicationContext.getInstance();
ApplicationConfiguration appCfg = appCtx.getConfiguration();
StringAttributeMap parameters = appCfg.getCatalogConfiguration().getParameters();
return Val.chkInt(parameters.getValue("webharvester.maxAttempts"),DEFAULT_MAX_ATTEMPTS);
}
}