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