/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.ims.qti.render;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Locale;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.dom.DOMDocument;
import org.dom4j.io.DocumentSource;
import org.olat.core.defaults.dispatcher.StaticMediaDispatcher;
import org.olat.core.gui.translator.Translator;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.Util;
import org.olat.core.util.i18n.I18nManager;
import org.olat.ims.qti.QTIResultDetailsController;
import org.olat.ims.resources.IMSEntityResolver;
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;
/**
* @author Mike Stock Comment:
* Initial Date: 04.06.2003
*/
public class LocalizedXSLTransformer {
private static ConcurrentHashMap<String, LocalizedXSLTransformer> instanceHash = new ConcurrentHashMap<String, LocalizedXSLTransformer>(5);
private static OLog log = Tracing.createLoggerFor(LocalizedXSLTransformer.class);
private static EntityResolver er = new IMSEntityResolver();
private static VelocityEngine velocityEngine;
static {
// init velocity engine
Properties p = null;
try {
velocityEngine = new VelocityEngine();
p = new Properties();
velocityEngine.init(p);
} catch (Exception e) {
throw new OLATRuntimeException("config error with velocity properties::" + p.toString(), e);
}
}
private Translator pT;
private Transformer transformer;
/**
* <code>RESULTS2HTML</code>
*/
private static final String XSLFILENAME = "results2html_generic.xsl";
/**
* Private constructor, use getInstance to get an instance of the
* LocalizedXSLTransformer
*
* @param trans
*/
private LocalizedXSLTransformer(Translator trans) {
pT = trans;
initTransformer();
}
/**
* Get a localized transformer instance.
*
* @param locale The locale for this transformer instance
* @return A localized transformer
*/
// cluster_ok only in VM
public synchronized static LocalizedXSLTransformer getInstance(Locale locale) {
LocalizedXSLTransformer instance = instanceHash.get(I18nManager.getInstance().getLocaleKey(locale));
if (instance == null) {
Translator trans = Util.createPackageTranslator(QTIResultDetailsController.class, locale);
LocalizedXSLTransformer newInstance = new LocalizedXSLTransformer(trans);
instance = instanceHash.putIfAbsent(I18nManager.getInstance().getLocaleKey(locale), newInstance); //see javadoc of ConcurrentHashMap
if(instance==null) { //newInstance was put into the map
instance = newInstance;
}
}
return instance;
}
/**
* Render with a localized stylesheet. The localized stylesheet is addressed
* by its name with appended locale. E.g. mystyle.xsl in DE locale is
* addressed by mystyle_de.xsl
*
* @param node The node to render
* @param styleSheetName The stylesheet to use.
* @return Results of XSL transformation
*/
private StringBuilder render(Element node) {
try {
Document doc = node.getDocument();
if (doc == null) {
doc = new DOMDocument();
doc.add(node);
}
DocumentSource xmlsource = new DocumentSource(node);
//ByteArrayOutputStream baos = new ByteArrayOutputStream();
StringWriter sw = new StringWriter();
StreamResult result = new StreamResult(sw);
synchronized (transformer) {//o_clusterOK by:fj transformer is per vm
transformer.transform(xmlsource, result);
}
String res = sw.toString();
return new StringBuilder(res); //.append(result.getOutputStream());
} catch (Exception e) {
throw new OLATRuntimeException(LocalizedXSLTransformer.class, "Error transforming XML.", e);
}
}
/**
* Render results processing document
*
* @param doc The <results/>document
* @return transformation results
*/
public StringBuilder renderResults(Document doc) {
return render(doc.getRootElement());
}
/**
* Helper to create XSLT transformer for this instance
*/
private void initTransformer() {
// build new transformer
InputStream xslin = getClass().getResourceAsStream("/org/olat/ims/resources/xsl/" + XSLFILENAME);
// translate xsl with velocity
Context vcContext = new VelocityContext();
vcContext.put("t", pT);
vcContext.put("staticPath", StaticMediaDispatcher.createStaticURIFor(""));
String xslAsString = "";
try {
xslAsString = slurp(xslin);
} catch (IOException e) {
log.error("Could not convert xsl to string!", e);
}
String replacedOutput = evaluateValue(xslAsString, vcContext);
TransformerFactory tfactory = TransformerFactory.newInstance();
XMLReader reader;
try {
reader = XMLReaderFactory.createXMLReader();
reader.setEntityResolver(er);
Source xsltsource = new SAXSource(reader, new InputSource(new StringReader(replacedOutput)));
this.transformer = tfactory.newTransformer(xsltsource);
} catch (SAXException e) {
throw new OLATRuntimeException("Could not initialize transformer!", e);
} catch (TransformerConfigurationException e) {
throw new OLATRuntimeException("Could not initialize transformer (wrong config)!", e);
}
}
/**
* Takes String with template and fills values from Translator in Context
*
* @param valToEval String with variables to replace
* @param vcContext velocity context containing a translator in this case
* @return input String where values from context were replaced
*/
private String evaluateValue(String valToEval, Context vcContext) {
StringWriter evaluatedValue = new StringWriter();
// evaluate inputFieldValue to get a concatenated string
try {
velocityEngine.evaluate(vcContext, evaluatedValue, "vcUservalue", valToEval);
} catch (ParseErrorException e) {
log.error("parsing of values in xsl-file of LocalizedXSLTransformer not possible!", e);
return "ERROR";
} catch (MethodInvocationException e) {
log.error("evaluating of values in xsl-file of LocalizedXSLTransformer not possible!", e);
return "ERROR";
} catch (ResourceNotFoundException e) {
log.error("xsl-file of LocalizedXSLTransformer not found!", e);
return "ERROR";
} catch (IOException e) {
log.error("could not read xsl-file of LocalizedXSLTransformer!", e);
return "ERROR";
}
return evaluatedValue.toString();
}
/**
* convert xsl InputStream to String
* @param in
* @return xsl as String
* @throws IOException
*/
private static String slurp (InputStream in) throws IOException {
StringBuffer out = new StringBuffer();
byte[] b = new byte[4096];
for (int n; (n = in.read(b)) != -1;) {
out.append(new String(b, 0, n));
}
return out.toString();
}
}