/** * 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. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. * <p> */ package org.olat.core.gui.render.velocity; import java.io.StringWriter; import java.io.Writer; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.velocity.Template; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.context.Context; import org.apache.velocity.exception.MethodInvocationException; import org.apache.velocity.runtime.RuntimeConstants; import org.olat.core.helpers.Settings; import org.olat.core.logging.AssertException; import org.olat.core.logging.LogDelegator; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.util.WebappHelper; /** * Initial Date: 01.12.2003 * @author Mike Stock */ public class VelocityHelper extends LogDelegator { private static final VelocityHelper INSTANCE = new VelocityHelper(); private VelocityEngine ve; private Set<String> resourcesNotFound = ConcurrentHashMap.newKeySet(); /** * */ private VelocityHelper() { super(); init(); } /** * @return VelocityHelper */ public static VelocityHelper getInstance() { return INSTANCE; } private void init() { Properties p = null; try { ve = 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"); p.setProperty(RuntimeConstants.INPUT_ENCODING, VelocityModule.getInputEncoding()); p.setProperty(RuntimeConstants.OUTPUT_ENCODING, VelocityModule.getOutputEncoding()); p.setProperty(RuntimeConstants.PARSER_POOL_SIZE, VelocityModule.getParserPoolSize()); p.setProperty(RuntimeConstants.RESOURCE_MANAGER_CACHE_CLASS, "org.olat.core.gui.render.velocity.InfinispanResourceCache"); if (Settings.isDebuging()) { p.setProperty(RuntimeConstants.RESOURCE_LOADER, "file, classpath"); // config for file lookup from webapp classpath p.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader"); p.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, WebappHelper.getSourcePath()); p.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "false"); p.setProperty("file.resource.loader.modificationCheckInterval", "3"); } else { p.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); p.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "true"); } //for jars: use the classpathloader p.setProperty("classpath.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); // caching is on normally p.setProperty("classpath.resource.loader.cache", Settings.isDebuging() ? "false" : "true"); p.setProperty(RuntimeConstants.RESOURCE_MANAGER_LOGWHENFOUND, "false"); p.setProperty(RuntimeConstants.VM_LIBRARY, "velocity/olat_velocimacros.vm"); p.setProperty(RuntimeConstants.VM_LIBRARY_AUTORELOAD, "false"); ve.init(p); } catch (Exception e) { throw new RuntimeException("config error " + p.toString()); } } /** * @param template e.g. org/olat/demo/_content/index.html * @param c the context * @param theme the theme e.g. "accessibility" or "printing". may be null if the default theme ("") should be taken * @return String the rendered template */ private void merge(String template, Context c, Writer wOut, String theme) { try { Template vtemplate = null; if (isLogDebugEnabled()) logDebug("Merging template::" + template + " for theme::" + theme, null); if (theme != null) { // try the theme first, if resource not found exception, fallback to normal resource. // e.g. try /_accessibility/index.html first, if not found, try /index.html. // this allows for themes to only provide the delta to the default templates // todo we could avoid those string operations, if the performance gain is measureable int latestSlash = template.lastIndexOf('/'); StringBuilder sb = new StringBuilder(template.substring(0,latestSlash)); sb.append("/_").append(theme).append("/").append(template.substring(latestSlash+1)); String themedTemplatePath = sb.toString(); // check cache boolean notFound = resourcesNotFound.contains(themedTemplatePath); if (!notFound) { // never tried before -> try to load it if (!ve.resourceExists(themedTemplatePath)) { // remember not found (since velocity doesn't) then try fallback. // this will happen once for each theme when a resource does not exist in its themed variant but only in the default theme. if (!Settings.isDebuging()) { resourcesNotFound.add(themedTemplatePath); } // for debugging, allow introduction of themed files without restarting the application } else { // template exists -> load it vtemplate = ve.getTemplate(themedTemplatePath, VelocityModule.getInputEncoding()); } } // if not found, fallback to standard if (vtemplate == null) { vtemplate = ve.getTemplate(template, VelocityModule.getInputEncoding()); } } else { // no theme, load the standard template vtemplate = ve.getTemplate(template, VelocityModule.getInputEncoding()); } vtemplate.merge(c, wOut); } catch (MethodInvocationException me) { throw new OLATRuntimeException(VelocityHelper.class, "MethodInvocationException occured while merging template: methName:" + me.getMethodName() + ", refName:" + me.getReferenceName(), me.getWrappedThrowable()); } catch (Exception e) { throw new OLATRuntimeException(VelocityHelper.class, "exception occured while merging template: " + e.getMessage(), e); } } /** * @param path * @param c * @return String */ public void mergeContent(String path, Context c, Writer writer, String theme) { if (path == null) throw new AssertException("velocity path was null"); merge(path, c, writer, theme); } /** * @param vtlInput * @param c * @return String */ public String evaluateVTL(String vtlInput, Context c) { StringWriter wOut = new StringWriter(10000); try { ve.evaluate(c, wOut, "internalEvaluator", vtlInput); } catch (MethodInvocationException me) { throw new OLATRuntimeException(VelocityHelper.class, "MethodInvocationException occured while merging template: methName:" + me.getMethodName() + ", refName:" + me.getReferenceName(), me); } catch (Exception e) { throw new OLATRuntimeException(VelocityHelper.class, "exception occured while merging template: " + e.getMessage(), e); } return wOut.toString(); } }