/* $HeadURL:: $ * $Id$ * * Copyright (c) 2006-2010 by Public Library of Science * http://plos.org * http://ambraproject.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ambraproject.freemarker; import java.io.IOException; import java.io.Reader; import java.io.File; import java.util.StringTokenizer; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts2.views.freemarker.FreemarkerManager; import org.apache.struts2.views.freemarker.ScopesHashModel; import org.apache.struts2.views.freemarker.StrutsClassTemplateLoader; import org.apache.struts2.ServletActionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.ambraproject.util.AuthorNameAbbreviationDirective; import org.ambraproject.util.ArticleFormattingDirective; import org.ambraproject.util.RandomNumberDirective; import org.ambraproject.util.SimpleTextDirective; import org.ambraproject.util.URLParametersDirective; import org.ambraproject.util.VersionedCSSDirective; import org.ambraproject.util.VersionedJSDirective; import org.ambraproject.web.VirtualJournalContext; import org.ambraproject.configuration.ConfigurationStore; import org.springframework.beans.factory.annotation.Required; import com.opensymphony.xwork2.util.ValueStack; import freemarker.cache.TemplateLoader; import freemarker.cache.StatefulTemplateLoader; import freemarker.cache.FileTemplateLoader; import freemarker.cache.MultiTemplateLoader; import freemarker.cache.WebappTemplateLoader; import freemarker.template.TemplateException; import freemarker.template.Configuration; /** * Custom Freemarker Manager to load up the configuration files for css, javascript, and titles of * pages * * @author Stephen Cheng */ public class AmbraFreemarkerManager extends FreemarkerManager { private static final Logger log = LoggerFactory.getLogger(AmbraFreemarkerManager.class); private AmbraFreemarkerConfig freemarkerConfig; private org.apache.commons.configuration.Configuration configuration; public AmbraFreemarkerManager() { } /** * Sets the custom configuration object via Spring injection * */ @Required public void setAmbraFreemarkerConfig(AmbraFreemarkerConfig fmConfig) { this.freemarkerConfig = fmConfig; } @Required public void setAmbraConfiguration( org.apache.commons.configuration.Configuration configuration) { this.configuration = configuration; } /** * Subclass from parent to add the freemarker configuratio object globally * * @see org.apache.struts2.views.freemarker.FreemarkerManager */ @Override protected void populateContext(ScopesHashModel model, ValueStack stack, Object action, HttpServletRequest request, HttpServletResponse response) { super.populateContext(model, stack, action, request, response); model.put("freemarker_config", freemarkerConfig); } @Override protected TemplateLoader createTemplateLoader(final ServletContext context, String templatePath) { return new StatefulTemplateLoader() { private final TemplateLoader templateLoader; // anonymous initializer { String journalTemplatePath = configuration.getString(ConfigurationStore.JOURNAL_TEMPLATE_DIR, null); FileTemplateLoader templatePathLoader = null; try { templatePathLoader = new FileTemplateLoader(new File(journalTemplatePath)); } catch (IOException e) { log.error("Invalid template path " + journalTemplatePath + " in " + ConfigurationStore.JOURNAL_TEMPLATE_DIR); } this.templateLoader = templatePathLoader != null ? new MultiTemplateLoader(new TemplateLoader[]{ templatePathLoader, new WebappTemplateLoader(context), new StrutsClassTemplateLoader() }) : new MultiTemplateLoader(new TemplateLoader[]{ new WebappTemplateLoader(context), new StrutsClassTemplateLoader() }); } public void closeTemplateSource(Object source) throws IOException { templateLoader.closeTemplateSource(source); } // requests are in form journals/<journal_name>/<package>/template.ftl public Object findTemplateSource(String templatePath) throws IOException { // First: look into journal-specific templates Object templateSource = getJournalSpecificTemplate(templatePath); if (templateSource != null) return templateSource; String trimmedPath = AmbraFreemarkerConfig.trimJournalFromTemplatePath(templatePath); // Second: look into site-specific override templates if (freemarkerConfig.getDefaultJournalName() != null) { templateSource = getDefaultTemplate(trimmedPath); if (templateSource != null) return templateSource; } // Third: look in the ambra default folders templateSource = templateLoader.findTemplateSource(trimmedPath); if (templateSource != null) return templateSource; /* * Fifth: try struts default theme * FIXME: theme inheritance is hard coded * NOTE: The real fix is in struts. See WW-1832 */ if (templatePath.indexOf("ambra-theme") >= 0) return templateLoader.findTemplateSource(templatePath.replace("ambra-theme", "simple")); else return null; } private Object getDefaultTemplate(String templatePath) throws IOException { return templateLoader.findTemplateSource(defaultPath(templatePath)); } private Object getJournalSpecificTemplate(String templatePath) throws IOException { String journalSpecificTemplatePath = templatePath; // If path doesn't start with journal append current journal context if (templatePath.startsWith("journals") || templatePath.startsWith("/journals")) { journalSpecificTemplatePath = addWebappToPath(journalSpecificTemplatePath); } else { journalSpecificTemplatePath = journalizePath(journalSpecificTemplatePath); } return templateLoader.findTemplateSource(journalSpecificTemplatePath); } public long getLastModified(Object source) { return templateLoader.getLastModified(source); } public Reader getReader(Object source, String encoding) throws IOException { return templateLoader.getReader(source, encoding); } public void resetState() { if (templateLoader instanceof StatefulTemplateLoader) ((StatefulTemplateLoader) templateLoader).resetState(); } /** * Add journal mapping prefix to template path * @param templatePath Template path * @return Template path in form /journals/journalName/webapp/templatePath */ private String journalizePath(String templatePath) { VirtualJournalContext journalContext = (VirtualJournalContext)ServletActionContext.getRequest(). getAttribute(VirtualJournalContext.PUB_VIRTUALJOURNAL_CONTEXT); // Not inside a journal context if (journalContext == null) return templatePath; StringBuilder journalizedPath = new StringBuilder(); journalizedPath.append("/journals/") .append(journalContext.getJournal()) .append((templatePath.startsWith("/") ? "/webapp" : "/webapp/")) .append(templatePath); return journalizedPath.toString(); } private String addWebappToPath(String templatePath) { StringTokenizer tokenizer = new StringTokenizer(templatePath, "/"); StringBuilder stringBuilder = new StringBuilder(); boolean addToNext = false; while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); stringBuilder.append('/').append(token); if (addToNext && token.equals("webapp")) addToNext = false; if (addToNext) { stringBuilder.append("/webapp"); addToNext = false; } else if (token.equals("journals")) addToNext = true; } return stringBuilder.toString(); } private String defaultPath(String templatePath) { StringBuilder defaultPath = new StringBuilder(); defaultPath.append("/journals/") .append(freemarkerConfig.getDefaultJournalName()) .append("/webapp") .append((templatePath.startsWith("/") ? "" : "/")) .append(templatePath); return defaultPath.toString(); } }; } /** * Attaches custom Freemarker directives as shared variables. * * @param servletContext Servlet context. * @return Freemarker configuration. * @throws TemplateException */ @Override protected Configuration createConfiguration(ServletContext servletContext) throws TemplateException { Configuration configuration = super.createConfiguration(servletContext); /* configuration.setServletContextForTemplateLoading(servletContext, "/"); try { configuration.setDirectoryForTemplateLoading(new File("/")); } catch (IOException e) { throw new TemplateException("Error setting root directory", e, Environment.getCurrentEnvironment()); } */ configuration.setCacheStorage(new AmbraTemplateStorage( freemarkerConfig.getCache_storage_strong(), freemarkerConfig.getCache_storage_soft())); configuration.setTemplateUpdateDelay(freemarkerConfig.getTemplateUpdateDelay()); configuration.setSharedVariable("abbreviation", new AuthorNameAbbreviationDirective()); configuration.setSharedVariable("articleFormat", new ArticleFormattingDirective()); configuration.setSharedVariable("simpleText", new SimpleTextDirective()); configuration.setSharedVariable("URLParameters", new URLParametersDirective()); configuration.setSharedVariable("randomNumber", new RandomNumberDirective()); configuration.setSharedVariable("versionedCSS", new VersionedCSSDirective()); configuration.setSharedVariable("versionedJS", new VersionedJSDirective()); return configuration; } }