001/* 002 * (C) Copyright 2006-2009 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Jean-Marc Orliaguet, Chalmers 016 * 017 * $Id$ 018 */ 019 020package org.nuxeo.theme.webengine.fm.extensions; 021 022import java.io.BufferedReader; 023import java.io.IOException; 024import java.io.StringReader; 025import java.net.URL; 026import java.util.Date; 027import java.util.HashMap; 028import java.util.Map; 029 030import javax.servlet.http.HttpServletRequest; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.nuxeo.ecm.platform.rendering.fm.extensions.BlockWriter; 035import org.nuxeo.ecm.webengine.WebEngine; 036import org.nuxeo.ecm.webengine.model.WebContext; 037import org.nuxeo.runtime.api.Framework; 038import org.nuxeo.theme.ApplicationType; 039import org.nuxeo.theme.Manager; 040import org.nuxeo.theme.NegotiationDef; 041import org.nuxeo.theme.negotiation.NegotiationException; 042import org.nuxeo.theme.themes.ThemeException; 043import org.nuxeo.theme.themes.ThemeManager; 044import org.nuxeo.theme.types.TypeFamily; 045import org.nuxeo.theme.webengine.negotiation.WebNegotiator; 046 047import freemarker.core.Environment; 048import freemarker.template.SimpleScalar; 049import freemarker.template.Template; 050import freemarker.template.TemplateDirectiveBody; 051import freemarker.template.TemplateDirectiveModel; 052import freemarker.template.TemplateException; 053import freemarker.template.TemplateModel; 054import freemarker.template.TemplateModelException; 055 056/** 057 * @author <a href="mailto:jmo@chalmers.se">Jean-Marc Orliaguet</a> 058 */ 059public class ThemeDirective implements TemplateDirectiveModel { 060 061 private static final Log log = LogFactory.getLog(ThemeDirective.class); 062 063 private final Map<URL, String> cachedThemes = new HashMap<URL, String>(); 064 065 private final Map<URL, Long> lastRefreshedMap = new HashMap<URL, Long>(); 066 067 public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) 068 throws TemplateException, IOException { 069 070 if (loopVars.length != 0) { 071 throw new TemplateModelException("This directive doesn't allow loop variables."); 072 } 073 if (body == null) { 074 throw new TemplateModelException("Expecting a body"); 075 } 076 077 WebContext ctx = WebEngine.getActiveContext(); 078 if (ctx == null) { 079 throw new IllegalStateException("Not In a Web Context"); 080 } 081 082 String strategy = null; 083 SimpleScalar strategyModel = (SimpleScalar) params.get("strategy"); 084 if (strategyModel != null) { 085 strategy = strategyModel.getAsString(); 086 } 087 088 final URL themeUrl = getThemeUrlAndSetupRequest(ctx, strategy); 089 if (themeUrl == null) { 090 return; 091 } 092 093 String rendered = ""; 094 try { 095 rendered = renderTheme(themeUrl); 096 } catch (ThemeException e) { 097 log.error("Theme rendering failed", e); 098 return; 099 } 100 101 // Render <@block> content 102 BlockWriter writer = (BlockWriter) env.getOut(); 103 writer.setSuppressOutput(true); 104 body.render(writer); 105 writer.setSuppressOutput(false); 106 107 // Apply the theme template 108 BufferedReader reader = new BufferedReader(new StringReader(rendered)); 109 Template tpl = new Template(themeUrl.toString(), reader, env.getConfiguration(), 110 env.getTemplate().getEncoding()); 111 env.include(tpl); 112 113 reader.close(); 114 } 115 116 public String renderTheme(URL themeUrl) throws ThemeException { 117 if (!needsToBeRefreshed(themeUrl) && cachedThemes.containsKey(themeUrl)) { 118 return cachedThemes.get(themeUrl); 119 } 120 String result = ThemeManager.renderElement(themeUrl); 121 if (result != null) { 122 cachedThemes.put(themeUrl, result); 123 lastRefreshedMap.put(themeUrl, new Date().getTime()); 124 } 125 return result; 126 } 127 128 protected boolean needsToBeRefreshed(URL themeUrl) { 129 if (themeUrl == null) { 130 return false; 131 } 132 if (Framework.isDevModeSet()) { 133 return true; 134 } 135 if (themeUrl.getProtocol().equals("nxtheme")) { 136 Long lastRefreshed = lastRefreshedMap.get(themeUrl); 137 if (lastRefreshed == null) { 138 lastRefreshed = 0L; 139 } 140 try { 141 if (themeUrl.openConnection().getLastModified() >= lastRefreshed) { 142 return true; 143 } 144 } catch (IOException e) { 145 return false; 146 } 147 } 148 return false; 149 } 150 151 private static URL getThemeUrlAndSetupRequest(WebContext context, String strategy) throws IOException { 152 HttpServletRequest request = context.getRequest(); 153 URL themeUrl = (URL) request.getAttribute("org.nuxeo.theme.url"); 154 if (themeUrl != null) { 155 return themeUrl; 156 } 157 158 final ApplicationType application = (ApplicationType) Manager.getTypeRegistry().lookup(TypeFamily.APPLICATION, 159 context.getModulePath(), context.getModule().getName()); 160 161 if (application == null) { 162 log.error(getErrorMessage("Application not set for: ", context)); 163 return null; 164 } 165 166 final NegotiationDef negotiation = application.getNegotiation(); 167 if (negotiation == null) { 168 log.error(getErrorMessage("Negotiation not set for: ", context)); 169 return null; 170 } 171 172 request.setAttribute("org.nuxeo.theme.default.theme", negotiation.getDefaultTheme()); 173 request.setAttribute("org.nuxeo.theme.default.engine", negotiation.getDefaultEngine()); 174 request.setAttribute("org.nuxeo.theme.default.perspective", negotiation.getDefaultPerspective()); 175 if (strategy == null) { 176 strategy = negotiation.getStrategy(); 177 } 178 179 if (strategy == null) { 180 log.error(getErrorMessage("Negotiation strategy not set for: ", context)); 181 return null; 182 } 183 184 try { 185 final String spec = new WebNegotiator(strategy, context, request).getSpec(); 186 themeUrl = new URL(spec); 187 } catch (NegotiationException e) { 188 log.error(getErrorMessage("Could not get negotiation information for: ", context)); 189 return null; 190 } 191 192 request.setAttribute("org.nuxeo.theme.url", themeUrl); 193 return themeUrl; 194 } 195 196 private static String getErrorMessage(String message, WebContext context) { 197 return context.getModulePath() + "," + context.getModule().getName(); 198 } 199}