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}