package org.juxtasoftware.resource; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.juxtasoftware.dao.ComparisonSetDao; import org.juxtasoftware.dao.WitnessDao; import org.juxtasoftware.model.ComparisonSet; import org.juxtasoftware.model.Witness; import org.juxtasoftware.service.SetRemover; import org.juxtasoftware.util.MetricsHelper; import org.restlet.data.Status; import org.restlet.representation.Representation; import org.restlet.resource.Delete; import org.restlet.resource.Get; import org.restlet.resource.Post; import org.restlet.resource.Put; 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.ExclusionStrategy; import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; /** * Get/Delete/update a specific instances of a <code>ComparisonSet</code> * * @author loufoster * */ @Service @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class ComparisonSetResource extends BaseResource { @Autowired private ComparisonSetDao comparionSetDao; @Autowired private WitnessDao witnessDao; @Autowired private Integer maxSetWitnesses; @Autowired private MetricsHelper metrics; @Autowired private SetRemover remover; private enum PostAction {INVALID, ADD_WITNESSES, DELETE_WITNESSES}; private ComparisonSet set; private PostAction postAction = PostAction.INVALID; @Override protected void doInit() throws ResourceException { super.doInit(); Long setId = getIdFromAttributes("id"); if ( setId == null ) { return; } this.set = this.comparionSetDao.find(setId); if ( validateModel(this.set) == false ) { return; } String lastSeg = getRequest().getResourceRef().getLastSegment().toUpperCase(); if ( lastSeg.equals("ADD")) { this.postAction = PostAction.ADD_WITNESSES; } else if (lastSeg.equals("DELETE")) { this.postAction = PostAction.DELETE_WITNESSES; } } @Get("html") public Representation toHtml() { List<Witness> witnesses = this.comparionSetDao.getWitnesses(this.set); Map<String,Object> map = new HashMap<String,Object>(); map.put("set", set); map.put("witnesses", witnesses); map.put("page", "set"); map.put("title", "Juxta Comparison Set: "+set.getName()); return toHtmlRepresentation("comparison_set.ftl",map); } @Get("json") public Representation toJson() { List<Witness> witnesses = this.comparionSetDao.getWitnesses(this.set); Gson gson = new GsonBuilder() .setExclusionStrategies( new SourceExclusion() ) .create(); JsonObject setJson = gson.toJsonTree(set).getAsJsonObject(); JsonElement jsonWitnesses = gson.toJsonTree(witnesses); setJson.add("witnesses", jsonWitnesses); return toJsonRepresentation(setJson.toString()); } /** /* Accept json data to update the name and/or status of a comparison set. /* format: { "name": "newName", "status": "newStatus" } * where newStatus can be: NOT_COLLATED, COLLATING, COLLATED, ERROR */ @Put("json") public Representation rename(final String jsonStr ) { JsonParser parser = new JsonParser(); JsonObject jsonObj = parser.parse(jsonStr).getAsJsonObject(); String name = null; String status = null; if (jsonObj.has("name")) { name = jsonObj.get("name").getAsString(); } if (jsonObj.has("status")) { status = jsonObj.get("status").getAsString(); } if ( name == null && status == null ) { setStatus(Status.CLIENT_ERROR_BAD_REQUEST); return toTextRepresentation("Missing name and/or status in json payload"); } ComparisonSet.Status setStatus = null; if ( status != null ) { setStatus = ComparisonSet.Status.valueOf(status.toUpperCase()); if ( setStatus == null ) { setStatus(Status.CLIENT_ERROR_BAD_REQUEST); return toTextRepresentation("Invalid status '"+status+"' specified in json payload"); } } // make sure name chages don't cause conflicts if ( name != null ) { if ( this.set.getName().equals(name) == false ) { if ( this.comparionSetDao.exists(this.workspace, name)) { setStatus(Status.CLIENT_ERROR_CONFLICT); return toTextRepresentation("Set "+name+" already exists in workspace " +this.workspace.getName()); } } this.set.setName( name ); } if ( status != null ) { this.set.setStatus(setStatus); } // update the set this.comparionSetDao.update( this.set ); return toTextRepresentation( Long.toString(this.set.getId()) ); } @Post("json") public Representation jsonPost( final String jsonData ) { if ( this.postAction.equals(PostAction.INVALID) ) { setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); return toTextRepresentation("set POST is not allowed"); } if ( jsonData == null || jsonData.length() == 0 ) { setStatus(Status.CLIENT_ERROR_BAD_REQUEST); return toTextRepresentation("Missing json payload in request"); } if ( postAction.equals(PostAction.ADD_WITNESSES)) { return addWitnesses( jsonData); } else { return deleteWitnesses( jsonData ); } } private Representation deleteWitnesses(String jsonWitnesses) { LOG.info("Delete Witnesses "+jsonWitnesses+" from set "+this.set.getId()); if ( this.set.getStatus().equals(ComparisonSet.Status.COLLATING)) { setStatus(Status.CLIENT_ERROR_CONFLICT); return toTextRepresentation("Cannot alter set; collation is in progress"); } JsonParser parser = new JsonParser(); JsonArray jsonArray = parser.parse(jsonWitnesses).getAsJsonArray(); List<Witness> currWits = this.comparionSetDao.getWitnesses(this.set); for ( Iterator<JsonElement> itr = jsonArray.iterator(); itr.hasNext(); ) { Long witId = itr.next().getAsLong(); Witness delWitness = null; for ( Witness w : currWits ) { if ( w.getId().equals(witId)) { delWitness = w; break; } } if ( delWitness != null ) { this.comparionSetDao.deleteWitness(this.set, delWitness) ; } else { setStatus(Status.CLIENT_ERROR_BAD_REQUEST); return toTextRepresentation("Witness "+witId+" does not exist in set "+this.set.getId()); } } Integer added = jsonArray.size(); this.metrics.setWitnessCountChanged(this.workspace); return toTextRepresentation( added.toString() ); } private Representation addWitnesses( final String jsonWitnesses ) { LOG.info("Add Witnesses "+jsonWitnesses+" to set "+this.set.getId()); if ( this.set.getStatus().equals(ComparisonSet.Status.COLLATING)) { setStatus(Status.CLIENT_ERROR_CONFLICT); return toTextRepresentation("Cannot alter set; collation is in progress"); } JsonParser parser = new JsonParser(); JsonArray jsonArray = parser.parse(jsonWitnesses).getAsJsonArray(); List<Witness> currWits = this.comparionSetDao.getWitnesses(this.set); int newWitnessTotal = currWits.size() + jsonArray.size(); if ( newWitnessTotal > this.maxSetWitnesses ) { setStatus(Status.CLIENT_ERROR_PRECONDITION_FAILED); return toTextRepresentation("Witnesses per set limit ("+this.maxSetWitnesses+") exceeded"); } Set<Witness> addWits = new HashSet<Witness>(); for ( Iterator<JsonElement> itr = jsonArray.iterator(); itr.hasNext(); ) { Long newWitId = itr.next().getAsLong(); for ( Witness w : currWits ) { if (w.getId().equals(newWitId) ) { setStatus(Status.CLIENT_ERROR_CONFLICT); return toTextRepresentation("Witness "+newWitId+" already exists in set "+this.set.getId()); } } // at this point, the witness ID does not exist. get the witnesss Witness w = this.witnessDao.find(newWitId); if ( w == null || w.getWorkspaceId().equals(this.workspace.getId()) == false ){ setStatus(Status.CLIENT_ERROR_BAD_REQUEST); return toTextRepresentation("Witness "+newWitId+" does not exist"); } addWits.add(w); } this.comparionSetDao.addWitnesses(this.set, addWits); Integer added = addWits.size(); this.metrics.setWitnessCountChanged(this.workspace); return toTextRepresentation( added.toString() ); } @Delete public Representation deleteComparisonSet() { LOG.info("Delete set "+this.set.getId()); try { this.remover.remove(this.workspace, this.set); return toTextRepresentation("ok"); } catch (ResourceException e) { Status statusCode = e.getStatus(); setStatus( statusCode); return toTextRepresentation( e.getStatus().getDescription() ); } } private static class SourceExclusion implements ExclusionStrategy { @Override public boolean shouldSkipField(FieldAttributes f) { return ( f.getName().equals("source") || f.getName().equals("text") || f.getName().equals("fragment")); } @Override public boolean shouldSkipClass(Class<?> clazz) { return false; } } }