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}