/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package edu.harvard.iq.dataverse.timer;
import edu.harvard.iq.dataverse.DatasetServiceBean;
import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.DataverseServiceBean;
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUser;
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
import edu.harvard.iq.dataverse.harvest.client.HarvestingClient;
import edu.harvard.iq.dataverse.harvest.client.HarvestTimerInfo;
import edu.harvard.iq.dataverse.harvest.client.HarvesterServiceBean;
import edu.harvard.iq.dataverse.harvest.client.HarvestingClientServiceBean;
import edu.harvard.iq.dataverse.harvest.server.OAISetServiceBean;
import edu.harvard.iq.dataverse.util.SystemConfig;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.http.HttpServletRequest;
/**
*
* This is a largely intact DVN3 implementation.
* original
* @author roberttreacy
* ported by
* @author Leonid Andreev
*/
//@Stateless
@Singleton
@Startup
public class DataverseTimerServiceBean implements Serializable {
@Resource
javax.ejb.TimerService timerService;
@PersistenceContext(unitName = "VDCNet-ejbPU")
private EntityManager em;
private static final Logger logger = Logger.getLogger("edu.harvard.iq.dataverse.timer.DataverseTimerServiceBean");
@EJB
HarvesterServiceBean harvesterService;
@EJB
DataverseServiceBean dataverseService;
@EJB
HarvestingClientServiceBean harvestingClientService;
@EJB
AuthenticationServiceBean authSvc;
@EJB
DatasetServiceBean datasetService;
@EJB
OAISetServiceBean oaiSetService;
@EJB
SystemConfig systemConfig;
// The init method that wipes and recreates all the timers on startup
//@PostConstruct
@PostConstruct
public void init() {
logger.info("PostConstruct timer check.");
if (systemConfig.isTimerServer()) {
logger.info("I am the dedicated timer server. Initializing mother timer.");
removeAllTimers();
// create mother timer:
createMotherTimer();
// And the export timer (there is only one)
createExportTimer();
} else {
logger.info("Skipping timer server init (I am not the dedicated timer server)");
}
}
public void createTimer(Date initialExpiration, long intervalDuration, Serializable info) {
try {
logger.log(Level.INFO,"Creating timer on " + InetAddress.getLocalHost().getCanonicalHostName());
} catch (UnknownHostException ex) {
Logger.getLogger(DataverseTimerServiceBean.class.getName()).log(Level.SEVERE, null, ex);
}
timerService.createTimer(initialExpiration, intervalDuration, info);
}
/**
* This method is called whenever an EJB Timer goes off.
* Check to see if this is a Harvest Timer, and if it is
* Run the harvest for the given (scheduled) dataverse
* @param timer
*/
@Timeout
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void handleTimeout(javax.ejb.Timer timer) {
// We have to put all the code in a try/catch block because
// if an exception is thrown from this method, Glassfish will automatically
// call the method a second time. (The minimum number of re-tries for a Timer method is 1)
if (!systemConfig.isTimerServer()) {
//logger.info("I am not the timer server! - bailing out of handleTimeout()");
Logger.getLogger(DataverseTimerServiceBean.class.getName()).log(Level.WARNING, null, "I am not the timer server! - but handleTimeout() got called. Please investigate!");
}
try {
logger.log(Level.INFO,"Handling timeout on " + InetAddress.getLocalHost().getCanonicalHostName());
} catch (UnknownHostException ex) {
Logger.getLogger(DataverseTimerServiceBean.class.getName()).log(Level.SEVERE, null, ex);
}
if (timer.getInfo() instanceof MotherTimerInfo) {
logger.info("Behold! I am the Master Timer, king of all timers! I'm here to create all the lesser timers!");
removeHarvestTimers();
for (HarvestingClient client : harvestingClientService.getAllHarvestingClients()) {
createHarvestTimer(client);
}
} else if (timer.getInfo() instanceof HarvestTimerInfo) {
HarvestTimerInfo info = (HarvestTimerInfo) timer.getInfo();
try {
logger.log(Level.INFO, "running a harvesting client: id=" + info.getHarvestingClientId());
// Timer batch jobs are run by the main Admin user.
// TODO: revisit how we retrieve the superuser here.
// Should it be configurable somewhere, which superuser
// runs these jobs? Should there be a central mechanism for obtaining
// the "major", builtin superuser for this Dataverse instance?
// -- L.A. 4.5, Aug. 2016
AuthenticatedUser adminUser = authSvc.getAdminUser(); // getAuthenticatedUser("admin");
if (adminUser == null) {
logger.info("Scheduled harvest: failed to locate the admin user! Exiting.");
throw new IOException("Scheduled harvest: failed to locate the admin user");
}
logger.info("found admin user "+adminUser.getName());
DataverseRequest dataverseRequest = new DataverseRequest(adminUser, (HttpServletRequest)null);
harvesterService.doHarvest(dataverseRequest, info.getHarvestingClientId());
} catch (Throwable e) {
// Harvester Service should be handling any error notifications,
// if/when things go wrong.
// (TODO: -- verify this logic; harvesterService may still be able
// to throw an IOException, if it could not run the harvest at all,
// or could not for whatever reason modify the database record...
// in this case we should, probably, log the error and try to send
// a mail notification. -- L.A. 4.4)
//dataverseService.setHarvestResult(info.getHarvestingDataverseId(), harvesterService.HARVEST_RESULT_FAILED);
//mailService.sendHarvestErrorNotification(dataverseService.find().getSystemEmail(), dataverseService.find().getName());
logException(e, logger);
}
} else if (timer.getInfo() instanceof ExportTimerInfo) {
try {
ExportTimerInfo info = (ExportTimerInfo) timer.getInfo();
logger.info("Timer Service: Running a scheduled export job.");
// try to export all unexported datasets:
datasetService.exportAll();
// and update all oai sets:
oaiSetService.exportAllSets();
} catch (Throwable e) {
logException(e, logger);
}
}
}
public void removeAllTimers() {
logger.info("Removing ALL existing timers.");
int i = 0;
for (Iterator it = timerService.getTimers().iterator(); it.hasNext();) {
Timer timer = (Timer) it.next();
logger.info("Removing timer " + i + ";");
timer.cancel();
i++;
}
logger.info("Done!");
}
public void removeHarvestTimers() {
// Remove all the harvest timers, if exist:
//
// (the logging messages below are set to level INFO; it's ok,
// since this code is only called on startup of the application,
// and it may be useful to know what existing timers were encountered).
logger.log(Level.INFO,"Removing existing harvest timers..");
int i = 1;
for (Iterator it = timerService.getTimers().iterator(); it.hasNext();) {
Timer timer = (Timer) it.next();
logger.log(Level.INFO, "HarvesterService: checking timer "+i);
if (timer.getInfo() instanceof HarvestTimerInfo) {
logger.log(Level.INFO, "HarvesterService: timer "+i+" is a harvesting one; removing.");
timer.cancel();
}
i++;
}
}
public void createMotherTimer() {
MotherTimerInfo info = new MotherTimerInfo();
Calendar initExpiration = Calendar.getInstance();
long intervalDuration = 60 * 60 * 1000; // every hour
initExpiration.set(Calendar.MINUTE, 50);
initExpiration.set(Calendar.SECOND, 0);
Date initExpirationDate = initExpiration.getTime();
Date currTime = new Date();
if (initExpirationDate.before(currTime)) {
initExpirationDate.setTime(initExpiration.getTimeInMillis() + intervalDuration);
}
logger.info("Setting the \"Mother Timer\", initial expiration: " + initExpirationDate);
createTimer(initExpirationDate, intervalDuration, info);
}
public void createHarvestTimer(HarvestingClient harvestingClient) {
if (harvestingClient.isScheduled()) {
long intervalDuration = 0;
Calendar initExpiration = Calendar.getInstance();
initExpiration.set(Calendar.MINUTE, 0);
initExpiration.set(Calendar.SECOND, 0);
if (harvestingClient.getSchedulePeriod().equals(HarvestingClient.SCHEDULE_PERIOD_DAILY)) {
intervalDuration = 1000 * 60 * 60 * 24;
initExpiration.set(Calendar.HOUR_OF_DAY, harvestingClient.getScheduleHourOfDay());
} else if (harvestingClient.getSchedulePeriod().equals(harvestingClient.SCHEDULE_PERIOD_WEEKLY)) {
intervalDuration = 1000 * 60 * 60 * 24 * 7;
initExpiration.set(Calendar.HOUR_OF_DAY, harvestingClient.getScheduleHourOfDay());
initExpiration.set(Calendar.DAY_OF_WEEK, harvestingClient.getScheduleDayOfWeek());
} else {
logger.log(Level.WARNING, "Could not set timer for harvesting client id=" + harvestingClient.getId() + ", unknown schedule period: " + harvestingClient.getSchedulePeriod());
return;
}
Date initExpirationDate = initExpiration.getTime();
Date currTime = new Date();
if (initExpirationDate.before(currTime)) {
initExpirationDate.setTime(initExpiration.getTimeInMillis() + intervalDuration);
}
logger.log(Level.INFO, "Setting timer for harvesting client " + harvestingClient.getName() + ", initial expiration: " + initExpirationDate);
createTimer(initExpirationDate, intervalDuration, new HarvestTimerInfo(harvestingClient.getId(), harvestingClient.getName(), harvestingClient.getSchedulePeriod(), harvestingClient.getScheduleHourOfDay(), harvestingClient.getScheduleDayOfWeek()));
}
}
public void updateHarvestTimer(HarvestingClient harvestingClient) {
removeHarvestTimer(harvestingClient);
createHarvestTimer(harvestingClient);
}
public void removeHarvestTimer(HarvestingClient harvestingClient) {
// Clear dataverse timer, if one exists
try {
logger.log(Level.INFO,"Removing harvest timer on " + InetAddress.getLocalHost().getCanonicalHostName());
} catch (UnknownHostException ex) {
Logger.getLogger(DataverseTimerServiceBean.class.getName()).log(Level.SEVERE, null, ex);
}
for (Iterator it = timerService.getTimers().iterator(); it.hasNext();) {
Timer timer = (Timer) it.next();
if (timer.getInfo() instanceof HarvestTimerInfo) {
HarvestTimerInfo info = (HarvestTimerInfo) timer.getInfo();
if (info.getHarvestingClientId().equals(harvestingClient.getId())) {
timer.cancel();
}
}
}
}
public void createExportTimer() {
ExportTimerInfo info = new ExportTimerInfo();
Calendar initExpiration = Calendar.getInstance();
long intervalDuration = 24 * 60 * 60 * 1000; // every day
initExpiration.set(Calendar.MINUTE, 0);
initExpiration.set(Calendar.SECOND, 0);
initExpiration.set(Calendar.HOUR_OF_DAY, 2); // 2AM, fixed.
Date initExpirationDate = initExpiration.getTime();
Date currTime = new Date();
if (initExpirationDate.before(currTime)) {
initExpirationDate.setTime(initExpiration.getTimeInMillis() + intervalDuration);
}
logger.info("Setting the Export Timer, initial expiration: " + initExpirationDate);
createTimer(initExpirationDate, intervalDuration, info);
}
public void createExportTimer(Dataverse dataverse) {
/* Not yet implemented. The DVN 3 implementation can be used as a model */
}
public void removeExportTimer() {
/* Not yet implemented. The DVN 3 implementation can be used as a model */
}
/* Utility methods: */
private void logException(Throwable e, Logger logger) {
boolean cause = false;
String fullMessage = "";
do {
String message = e.getClass().getName() + " " + e.getMessage();
if (cause) {
message = "\nCaused By Exception.................... " + e.getClass().getName() + " " + e.getMessage();
}
StackTraceElement[] ste = e.getStackTrace();
message += "\nStackTrace: \n";
for (int m = 0; m < ste.length; m++) {
message += ste[m].toString() + "\n";
}
fullMessage += message;
cause = true;
} while ((e = e.getCause()) != null);
logger.severe(fullMessage);
}
}