org.olat.ims.qti.render.LocalizedXSLTransformer.java Source code

Java tutorial

Introduction

Here is the source code for org.olat.ims.qti.render.LocalizedXSLTransformer.java

Source

/**
 * 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.apache.velocity.runtime.RuntimeConstants;
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();
            p.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
                    "org.apache.velocity.runtime.log.SimpleLog4JLogSystem");
            p.setProperty("runtime.log.logsystem.log4j.category", "syslog");
            velocityEngine.init(p);
        } catch (final Exception e) {
            throw new OLATRuntimeException("config error with velocity properties::" + p.toString(), e);
        }
    }

    private final 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(final 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(final Locale locale) {
        LocalizedXSLTransformer instance = instanceHash.get(I18nManager.getInstance().getLocaleKey(locale));
        if (instance == null) {
            final Translator trans = Util.createPackageTranslator(QTIResultDetailsController.class, locale);
            final 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(final Element node) {
        try {
            Document doc = node.getDocument();
            if (doc == null) {
                doc = new DOMDocument();
                doc.add(node);
            }
            final DocumentSource xmlsource = new DocumentSource(node);

            // ByteArrayOutputStream baos = new ByteArrayOutputStream();
            final StringWriter sw = new StringWriter();
            final StreamResult result = new StreamResult(sw);
            synchronized (transformer) {// o_clusterOK by:fj transformer is per vm
                transformer.transform(xmlsource, result);
            }
            final String res = sw.toString();
            return new StringBuilder(res); // .append(result.getOutputStream());
        } catch (final 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(final Document doc) {
        return render(doc.getRootElement());
    }

    /**
     * Helper to create XSLT transformer for this instance
     */
    private void initTransformer() {
        // build new transformer
        final InputStream xslin = getClass().getResourceAsStream("/org/olat/ims/resources/xsl/" + XSLFILENAME);
        // translate xsl with velocity
        final Context vcContext = new VelocityContext();
        vcContext.put("t", pT);
        vcContext.put("staticPath", StaticMediaDispatcher.createStaticURIFor(""));
        String xslAsString = "";
        try {
            xslAsString = slurp(xslin);
        } catch (final IOException e) {
            log.error("Could not convert xsl to string!", e);
        }
        final String replacedOutput = evaluateValue(xslAsString, vcContext);
        final TransformerFactory tfactory = TransformerFactory.newInstance();
        XMLReader reader;
        try {
            reader = XMLReaderFactory.createXMLReader();
            reader.setEntityResolver(er);
            final Source xsltsource = new SAXSource(reader, new InputSource(new StringReader(replacedOutput)));
            this.transformer = tfactory.newTransformer(xsltsource);
        } catch (final SAXException e) {
            throw new OLATRuntimeException("Could not initialize transformer!", e);
        } catch (final 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(final String valToEval, final Context vcContext) {
        final StringWriter evaluatedValue = new StringWriter();
        // evaluate inputFieldValue to get a concatenated string
        try {
            velocityEngine.evaluate(vcContext, evaluatedValue, "vcUservalue", valToEval);
        } catch (final ParseErrorException e) {
            log.error("parsing of values in xsl-file of LocalizedXSLTransformer not possible!", e);
            return "ERROR";
        } catch (final MethodInvocationException e) {
            log.error("evaluating of values in xsl-file of LocalizedXSLTransformer not possible!", e);
            return "ERROR";
        } catch (final ResourceNotFoundException e) {
            log.error("xsl-file of LocalizedXSLTransformer not found!", e);
            return "ERROR";
        } catch (final 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(final InputStream in) throws IOException {
        final StringBuffer out = new StringBuffer();
        final byte[] b = new byte[4096];
        for (int n; (n = in.read(b)) != -1;) {
            out.append(new String(b, 0, n));
        }
        return out.toString();
    }

}