package org.juxtasoftware.resource;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import org.juxtasoftware.dao.ComparisonSetDao;
import org.juxtasoftware.model.CollatorConfig;
import org.juxtasoftware.model.ComparisonSet;
import org.juxtasoftware.model.Witness;
import org.juxtasoftware.service.ComparisonSetCollator;
import org.juxtasoftware.service.Tokenizer;
import org.juxtasoftware.util.BackgroundTask;
import org.juxtasoftware.util.BackgroundTaskCanceledException;
import org.juxtasoftware.util.BackgroundTaskStatus;
import org.juxtasoftware.util.MetricsHelper;
import org.juxtasoftware.util.TaskManager;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.resource.Get;
import org.restlet.resource.Post;
import org.restlet.resource.ResourceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import com.google.gson.Gson;
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class CollatorResource extends BaseResource {
private enum Action {
COLLATE, CONFIG
}
@Autowired private ComparisonSetDao setDao;
@Autowired private Tokenizer tokenizer;
@Autowired private ComparisonSetCollator collator;
@Autowired private TaskManager taskManager;
@Autowired private MetricsHelper metrics;
private Action action;
private ComparisonSet set;
@Override
protected void doInit() throws ResourceException {
super.doInit();
// make sure set exists and is in workspace
Long id = getIdFromAttributes("id");
if ( id == null ) {
return;
}
this.set = this.setDao.find(id);
if (validateModel(this.set) == false) {
return;
}
// validate the action requested
String act = getRequest().getResourceRef().getLastSegment().toUpperCase();
if (act.equals("COLLATE")) {
this.action = Action.COLLATE;
} else if (act.equals("COLLATOR")) {
this.action = Action.CONFIG;
} else {
setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Invalid collation action specified");
}
}
@Post("json")
public Representation acceptPost(final String json) {
if (this.action.equals(Action.CONFIG)) {
return configureCollator(json);
} else if (this.action.equals(Action.COLLATE)) {
return doCollation();
} else {
setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return toTextRepresentation("Invalid collation action specified");
}
}
private Representation doCollation() {
if (this.set.getStatus().equals(ComparisonSet.Status.COLLATING) ||
this.set.getStatus().equals(ComparisonSet.Status.TOKENIZING) ) {
LOG.error("Attempt to collate "+this.set+" when it is aalready collating");
setStatus(Status.CLIENT_ERROR_CONFLICT);
return toTextRepresentation("Set " + this.set.getId() + " is currently collating");
}
List<Witness> witnesses = this.setDao.getWitnesses(this.set);
if (witnesses.size() < 2) {
LOG.error("Attempt to collate "+this.set+" that has less than 2 witnesses");
setStatus(Status.CLIENT_ERROR_FAILED_DEPENDENCY);
return toTextRepresentation("Set " + this.set.getId()
+ " has fewer than 2 witnesses; cannot collate");
}
final String taskId = generateTaskName(this.set.getId());
this.taskManager.submit(new CollateTask(taskId));
return toTextRepresentation(taskId);
}
private Representation configureCollator(String json) {
Gson gson = new Gson();
CollatorConfig cfg = gson.fromJson(json, CollatorConfig.class);
this.setDao.updateCollatorConfig(this.set, cfg);
return toTextRepresentation(this.set.getId().toString());
}
@Get("json")
public Representation handeGet() {
if (this.action.equals(Action.CONFIG)) {
return getCollationConfig();
}
setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return toTextRepresentation("Invalid collation action specified");
}
private String generateTaskName(final Long setId) {
final int prime = 31;
int result = 1;
result = prime * result + setId.hashCode();
return "collate-"+result;
}
private Representation getCollationConfig() {
CollatorConfig cfg = this.setDao.getCollatorConfig(this.set);
Gson gson = new Gson();
String json = gson.toJson(cfg);
return toJsonRepresentation(json);
}
/**
* Task to asynchronously execute the collation
*/
private class CollateTask implements BackgroundTask {
private final String name;
private BackgroundTaskStatus status;
private final CollatorConfig config;
private Date startDate;
private Date endDate;
public CollateTask(final String name) {
this.name = name;
this.status = new BackgroundTaskStatus(this.name);
this.config = CollatorResource.this.setDao.getCollatorConfig(set);;
this.startDate = new Date();
}
@Override
public Type getType() {
return BackgroundTask.Type.COLLATE;
}
@Override
public void run() {
try {
LOG.info("Begin collation task " + this.name);
this.status.begin();
if ( set.getStatus().equals(ComparisonSet.Status.TOKENIZED)) {
CollatorResource.this.collator.collate(set, this.config, this.status);
} else {
CollatorResource.this.metrics.collationStarted(CollatorResource.this.workspace, CollatorResource.this.set);
LOG.info(this.name+" tokenizing....");
CollatorResource.this.tokenizer.tokenize( CollatorResource.this.set, this.config, this.status);
LOG.info(this.name+" collating....");
CollatorResource.this.collator.collate(set, this.config, this.status);
}
LOG.info("collation task " + this.name + " COMPLETE");
metrics.collationFinished(workspace,set);
this.endDate = new Date();
} catch (IOException e) {
LOG.error(this.name + " task failed", e.toString());
this.status.fail(e.toString());
this.endDate = new Date();
set.setStatus(ComparisonSet.Status.ERROR);
setDao.update(set);
} catch (BackgroundTaskCanceledException e) {
LOG.info(this.name + " task was canceled");
this.endDate = new Date();
set.setStatus(ComparisonSet.Status.NOT_COLLATED);
setDao.update(set);
} catch (Exception e) {
LOG.error(this.name + " task failed", e);
this.status.fail(e.toString());
this.endDate = new Date();
set.setStatus(ComparisonSet.Status.ERROR);
setDao.update(set);
}
}
@Override
public void cancel() {
this.status.cancel();
}
@Override
public BackgroundTaskStatus.Status getStatus() {
return this.status.getStatus();
}
@Override
public String getName() {
return this.name;
}
@Override
public Date getEndTime() {
return this.endDate;
}
@Override
public Date getStartTime() {
return this.startDate;
}
@Override
public String getMessage() {
return this.status.getNote();
}
}
}