/*
* Constellation - An open source and standard compliant SDI
* http://www.constellation-sdi.org
*
* Copyright 2014 Geomatys.
*
* Licensed 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.constellation.scheduler;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.sis.util.logging.Logging;
import org.constellation.admin.SpringHelper;
import org.constellation.admin.dto.TaskStatusDTO;
import org.constellation.api.TaskState;
import org.constellation.business.IProcessBusiness;
import org.constellation.database.api.jooq.tables.pojos.Task;
import org.constellation.util.ParamUtilities;
import org.geotoolkit.process.ProcessEvent;
import org.geotoolkit.process.ProcessListener;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.util.InternationalString;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.geotoolkit.processing.quartz.ProcessJob;
import org.geotoolkit.processing.quartz.ProcessJobDetail;
/**
* Quartz Job listener attaching a listener on geotoolkit processes to track their state.
*
* @author Johann Sorel (Geomatys)
* @author Christophe Mourette (Geomatys)
*/
public class QuartzJobListener implements JobListener {
private static final Logger LOGGER = Logging.getLogger("org.constellation.scheduler");
private static final int ROUND_SCALE = 2;
public static final String PROPERTY_TASK = "task";
private IProcessBusiness processBusiness;
public QuartzJobListener() {
}
////////////////////////////////////////////////////////////////////////////
// Quartz listener /////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
@Override
public String getName() {
return "ConstellationJobTracker";
}
@Override
public synchronized void jobToBeExecuted(JobExecutionContext jec) {
if (processBusiness == null) {
this.processBusiness = SpringHelper.getBean(IProcessBusiness.class);
}
final Job job = jec.getJobInstance();
if(!(job instanceof ProcessJob)) return;
//attach a listener on the process
final ProcessJob pj = (ProcessJob) job;
final ProcessJobDetail detail = (ProcessJobDetail) jec.getJobDetail();
final QuartzTask quartzTask = (QuartzTask) detail.getJobDataMap().get(QuartzJobListener.PROPERTY_TASK);
final String quartzTaskId = quartzTask.getId();
final Task taskEntity = new Task();
taskEntity.setIdentifier(UUID.randomUUID().toString());
taskEntity.setState(TaskState.PENDING.name());
taskEntity.setTaskParameterId(quartzTask.getTaskParameterId());
taskEntity.setOwner(quartzTask.getUserId());
taskEntity.setType(""); // TODO
processBusiness.addTask(taskEntity);
final ProcessListener listener = new StateListener(taskEntity.getIdentifier(), quartzTask.getTitle() );
pj.addListener(listener);
LOGGER.log(Level.INFO, "Run task "+taskEntity.getIdentifier());
}
@Override
public void jobExecutionVetoed(JobExecutionContext jec) {
}
@Override
public void jobWasExecuted(JobExecutionContext jec, JobExecutionException jee) {
}
////////////////////////////////////////////////////////////////////////////
// Geotk process listener //////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/**
* Catch process events and set them in the TaskState.
*/
private static class StateListener implements ProcessListener{
private final String taskId;
private final String title;
private final Task taskEntity;
private IProcessBusiness processBusiness;
/** Used to store eventual warnings process could send us. */
private final ArrayList<ProcessEvent> warnings = new ArrayList<ProcessEvent>();
public StateListener(String taskId, String title) {
this.taskId = taskId;
if (processBusiness == null) {
this.processBusiness = SpringHelper.getBean(IProcessBusiness.class);
}
this.taskEntity = processBusiness.getTask(taskId);
this.title = title;
}
@Override
public void started(ProcessEvent event) {
taskEntity.setState(TaskState.RUNNING.name());
taskEntity.setDateStart(System.currentTimeMillis());
taskEntity.setMessage(toString(event.getTask()));
roundProgression(event);
updateTask(taskEntity);
}
@Override
public void progressing(ProcessEvent event) {
taskEntity.setState(TaskState.RUNNING.name());
taskEntity.setMessage(toString(event.getTask()));
roundProgression(event);
ParameterValueGroup output = event.getOutput();
if (output != null) {
try {
taskEntity.setTaskOutput(ParamUtilities.writeParameterJSON(output));
} catch (JsonProcessingException e) {
LOGGER.log(Level.WARNING, "Process output serialization failed", e);
}
}
if (event.getException() != null) {
warnings.add(event);
}
updateTask(taskEntity);
}
@Override
public void paused(ProcessEvent event) {
taskEntity.setState(TaskState.PAUSED.name());
taskEntity.setMessage(toString(event.getTask()));
roundProgression(event);
updateTask(taskEntity);
}
@Override
public void resumed(ProcessEvent event) {
taskEntity.setState(TaskState.RUNNING.name());
taskEntity.setMessage(toString(event.getTask()));
roundProgression(event);
updateTask(taskEntity);
}
@Override
public void completed(ProcessEvent event) {
taskEntity.setDateEnd(System.currentTimeMillis());
taskEntity.setMessage(toString(event.getTask()));
roundProgression(event);
ParameterValueGroup output = event.getOutput();
if (output != null) {
try {
taskEntity.setTaskOutput(ParamUtilities.writeParameterJSON(output));
} catch (JsonProcessingException e) {
LOGGER.log(Level.WARNING, "Process output serialization failed", e);
}
}
// If a warning occurred, send exception to the user.
if (!warnings.isEmpty()) {
taskEntity.setState(TaskState.WARNING.name());
taskEntity.setMessage(processWarningMessage());
} else {
taskEntity.setState(TaskState.SUCCEED.name());
}
updateTask(taskEntity);
}
@Override
public void failed(ProcessEvent event) {
taskEntity.setState(TaskState.FAILED.name());
taskEntity.setDateEnd(System.currentTimeMillis());
final Exception exception = event.getException();
final String exceptionStr = printException(exception);
taskEntity.setMessage(toString(event.getTask()) + " cause : " + exceptionStr);
//taskEntity.setProgress((double) event.getProgress());
updateTask(taskEntity);
}
private void updateTask(Task taskEntity) {
if (processBusiness == null) {
this.processBusiness = SpringHelper.getBean(IProcessBusiness.class);
}
//update in database
processBusiness.updateTask(taskEntity);
//send event
final TaskStatusDTO taskStatus = new TaskStatusDTO();
taskStatus.setId(taskEntity.getIdentifier());
taskStatus.setTaskId(taskEntity.getTaskParameterId());
taskStatus.setTitle(title);
taskStatus.setStatus(taskEntity.getState());
taskStatus.setMessage(taskEntity.getMessage());
taskStatus.setPercent(taskEntity.getProgress().floatValue());
taskStatus.setStart(taskEntity.getDateStart());
taskStatus.setEnd(taskEntity.getDateEnd());
taskStatus.setOutput(taskEntity.getTaskOutput());
SpringHelper.sendEvent(taskStatus);
}
/**
* Format :
* "
* Task1 description : exceptionMessage
* stacktrace
*
* Task2 description : exceptionMessage
* stacktrace
* "
* @return
*/
private String processWarningMessage() {
final StringBuilder warningStr = new StringBuilder();
for (ProcessEvent warning : warnings) {
warningStr.append(warning.getTask().toString()).append(" : ");
warningStr.append(printException(warning.getException()));
warningStr.append('\n');
}
return warningStr.toString();
}
/**
* Print an exception.
* Format :
* "message
* stacktrace"
* @param exception
* @return
*/
public String printException(Exception exception) {
StringWriter errors = new StringWriter();
if (exception != null) {
errors.append(exception.getMessage()).append('\n');
exception.printStackTrace(new PrintWriter(errors));
}
return errors.toString();
}
/**
* Round event progression value to {@link #ROUND_SCALE} before
* set to taskEntity object.
*
* @param event ProcessEvent
*/
private void roundProgression(ProcessEvent event) {
if (!Float.isNaN(event.getProgress()) && !Float.isInfinite(event.getProgress())) {
BigDecimal progress = new BigDecimal(event.getProgress());
progress = progress.setScale(ROUND_SCALE, BigDecimal.ROUND_HALF_UP);
taskEntity.setProgress(progress.doubleValue());
}
}
private String toString(InternationalString str){
if(str==null){
return "";
}else{
return str.toString();
}
}
}
}