package org.juxtasoftware.resource;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.juxtasoftware.dao.ComparisonSetDao;
import org.juxtasoftware.dao.JuxtaXsltDao;
import org.juxtasoftware.dao.PageMarkDao;
import org.juxtasoftware.dao.SourceDao;
import org.juxtasoftware.dao.WitnessDao;
import org.juxtasoftware.model.ComparisonSet;
import org.juxtasoftware.model.JuxtaXslt;
import org.juxtasoftware.model.Source;
import org.juxtasoftware.model.Usage;
import org.juxtasoftware.model.Witness;
import org.juxtasoftware.service.SourceTransformer;
import org.juxtasoftware.service.importer.JuxtaXsltFactory;
import org.juxtasoftware.service.importer.ps.ParallelSegmentationImportImpl;
import org.juxtasoftware.util.ConversionUtils;
import org.juxtasoftware.util.RangedTextReader;
import org.restlet.data.MediaType;
import org.restlet.data.Status;
import org.restlet.representation.FileRepresentation;
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.ApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* REsource to get/update XSLT for a witness.
* Also get the generic XSLT template.
*
* @author loufoster
*
*/
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class XsltResource extends BaseResource {
@Autowired private JuxtaXsltDao xsltDao;
@Autowired private SourceDao sourceDao;
@Autowired private WitnessDao witnessDao;
@Autowired private SourceTransformer transformer;
@Autowired private ComparisonSetDao setDao;
@Autowired private ApplicationContext context;
@Autowired private PageMarkDao pageMarkDao;
private Long xsltId = null;
private Long witnessId = null;
private boolean templateRequest = false;
private boolean previewRequest = false;
@Override
protected void doInit() throws ResourceException {
super.doInit();
if ( getRequest().getAttributes().containsKey("id")) {
this.witnessId = Long.parseLong( (String)getRequest().getAttributes().get("id"));
}
if ( getRequest().getAttributes().containsKey("xsltId")) {
this.xsltId = Long.parseLong( (String)getRequest().getAttributes().get("xsltId"));
}
this.previewRequest = getQuery().getValuesMap().containsKey("preview");
String lastSeg = getRequest().getResourceRef().getLastSegment();
this.templateRequest = ( lastSeg.equalsIgnoreCase("template"));
validateParams();
}
private void validateParams() {
if ( this.templateRequest && (this.witnessId != null || this.xsltId != null) ) {
setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return;
}
if ( this.witnessId != null && this.xsltId != null) {
setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return;
}
if (this.witnessId != null ) {
Witness w = this.witnessDao.find(witnessId);
if ( validateModel(w) == false ) {
return;
}
this.xsltId = w.getXsltId();
}
}
@Get("json")
public Representation getJson() {
if ( this.templateRequest ) {
return getXsltTemplates();
}
if ( this.xsltId != null ) {
JuxtaXslt xslt = this.xsltDao.find(this.xsltId);
Gson gson = new Gson();
return toJsonRepresentation( gson.toJson(xslt));
}
// if all else has failed, just return the list of xslts
List<JuxtaXslt> list = this.xsltDao.list(this.workspace);
Gson gson = new Gson();
return toJsonRepresentation( gson.toJson(list));
}
private Representation getXsltTemplates() {
try {
Map<String,String> templates = new HashMap<String,String>();
templates.put("main", JuxtaXsltFactory.getGenericTemplate() );
templates.put("singleExclude", JuxtaXsltFactory.getSingleExclusionTemplate() );
templates.put("globalExclude", JuxtaXsltFactory.getGlobalExclusionTemplate() );
templates.put("breaks", JuxtaXsltFactory.getBreaksTemplate() );
templates.put("linebreak", "<xsl:value-of select=\"$display-linebreak\"/>");
Gson gson = new Gson();
return toJsonRepresentation( gson.toJson(templates));
} catch (IOException e ) {
setStatus(Status.SERVER_ERROR_INTERNAL);
return toTextRepresentation("Unable to retrieve XSLT templates: " +e.getMessage());
}
}
@Get("xml")
public Representation getXml() {
if ( this.xsltId != null ) {
JuxtaXslt xslt = this.xsltDao.find(this.xsltId);
if ( xslt == null ) {
setStatus(Status.CLIENT_ERROR_NOT_FOUND);
return toTextRepresentation("xslt "+this.xsltId+" does not exist");
}
return toXmlRepresentation( xslt.getXslt() );
}
setStatus(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE);
return null;
}
@Post("json")
public Representation createXslt( final String jsonData ) {
Gson gson = new Gson();
JuxtaXslt xslt = gson.fromJson(jsonData, JuxtaXslt.class);
if ( this.previewRequest ) {
return previewWitness(xslt);
}
xslt.setWorkspaceId(this.workspace.getId());
if ( xslt.getName() == null || xslt.getXslt() == null ) {
setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return toTextRepresentation("missing required data in json payload");
}
Long id = this.xsltDao.create(xslt);
return toTextRepresentation( id.toString() );
}
private Representation previewWitness(JuxtaXslt xslt) {
Witness w = this.witnessDao.find(this.witnessId);
Source src = this.sourceDao.find(this.workspace.getId(), w.getSourceId());
if (src.getType().equals(Source.Type.XML)) {
try {
final File out = doTransform(src, xslt);
Reader r = new FileReader(out);
File html = ConversionUtils.witnessToHtml(r, null, this.pageMarkDao.find(this.witnessId));
out.delete();
FileRepresentation rep = new FileRepresentation(html, MediaType.TEXT_HTML);
rep.setAutoDeleting(true);
return rep;
} catch (Exception e) {
LOG.error("Unable to preview XML witness", e);
setStatus(Status.SERVER_ERROR_INTERNAL);
return toTextRepresentation("Unable to preview witness at this time");
}
} else {
final RangedTextReader reader = new RangedTextReader();
try {
reader.read( this.sourceDao.getContentReader(src) );
return toTextRepresentation(reader.toString());
} catch (IOException e) {
LOG.error("Unable to preview TXT witness", e);
setStatus(Status.SERVER_ERROR_INTERNAL);
return toTextRepresentation("Unable to preview witness at this time");
}
}
}
private File doTransform(Source srcDoc, JuxtaXslt xslt) throws IOException, TransformerException, FileNotFoundException, SAXException {
// setup source, xslt and result
File outFile = File.createTempFile("xform"+srcDoc.getId(), "xml");
outFile.deleteOnExit();
XMLReader reader = XMLReaderFactory.createXMLReader();
reader.setEntityResolver(new EntityResolver() {
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (systemId.endsWith(".dtd") || systemId.endsWith(".ent")) {
StringReader stringInput = new StringReader(" ");
return new InputSource(stringInput);
}
else {
return null; // use default behavior
}
}
});
SAXSource xmlSource = new SAXSource(reader, new InputSource( this.sourceDao.getContentReader(srcDoc) ));
javax.xml.transform.Source xsltSource = new StreamSource( new StringReader(xslt.getXslt()) );
javax.xml.transform.Result result = new StreamResult( new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8"));
TransformerFactory factory = TransformerFactory.newInstance( );
Transformer transformer = factory.newTransformer(xsltSource);
transformer.setOutputProperty(OutputKeys.INDENT, "no");
transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
transformer.setOutputProperty(OutputKeys.MEDIA_TYPE, "text");
transformer.transform(xmlSource, result);
return outFile;
}
@Put("json")
public Representation updateXslt( final String json ) {
if ( this.templateRequest || this.xsltId == null ) {
setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return null;
}
JsonParser parser = new JsonParser();
JsonObject jsonObj = parser.parse(json).getAsJsonObject();
final String updatedXslt = jsonObj.get("xslt").getAsString();
final boolean isPs = jsonObj.get("tei_ps").getAsBoolean();
JuxtaXslt xslt = this.xsltDao.find(this.xsltId);
if ( validateModel(xslt) == false ) {
setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return toTextRepresentation("xslt "+this.xsltId+" does not exist");
}
try {
// make sure this doesn't break an in-process collation
List<Usage> usage = this.xsltDao.getUsage(xslt);
for (Usage u : usage) {
if ( u.getType().equals(Usage.Type.COMPARISON_SET)) {
ComparisonSet s = this.setDao.find(u.getId());
if ( s.getStatus().equals(ComparisonSet.Status.COLLATING)) {
setStatus(Status.CLIENT_ERROR_CONFLICT);
return toTextRepresentation("Cannot prepare witness; related set '"+s.getName()+"' is collating.");
}
}
}
// update the DB with new content for XSLT
this.xsltDao.update(this.xsltId, new StringReader(updatedXslt) );
// If this witness was generated from a TEI parallel segmented
// source, it must be handled differently. Re-Import the source!
if ( isPs ) {
Witness w = this.witnessDao.find(this.witnessId);
Source src = this.sourceDao.find(this.workspace.getId(), w.getSourceId());
for(Usage u : usage) {
if ( u.getType().equals(Usage.Type.COMPARISON_SET)) {
ComparisonSet set = this.setDao.find( u.getId());
ParallelSegmentationImportImpl importService = this.context.getBean(ParallelSegmentationImportImpl.class);
importService.reimportSource(set, src);
}
}
} else {
// FIRST PASS: clear collation data
for(Usage u : usage) {
if ( u.getType().equals(Usage.Type.COMPARISON_SET)) {
ComparisonSet set = this.setDao.find( u.getId());
this.setDao.clearCollationData(set);
}
}
// SECOND PASS: re-transform
for(Usage u : usage) {
if ( u.getType().equals(Usage.Type.WITNESS)) {
Witness origWit = this.witnessDao.find( u.getId() );
Source src = this.sourceDao.find(this.workspace.getId(), origWit.getSourceId());
this.transformer.redoTransform(src, origWit);
}
}
}
Gson gson = new Gson();
return toJsonRepresentation( gson.toJson(usage));
} catch (Exception e) {
setStatus(Status.SERVER_ERROR_INTERNAL);
return toTextRepresentation(e.getMessage());
}
}
@Delete
public void deletXslt( ) {
if ( this.xsltId != null ) {
JuxtaXslt xslt = this.xsltDao.find(this.xsltId);
if ( validateModel(xslt) != false ) {
try {
this.xsltDao.delete(xslt);
return;
} catch ( Exception e ) {
setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "cannot to delete xslt that is in use");
return;
}
}
}
setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
}
}