001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.templating;
019
020import io.stallion.Context;
021import io.stallion.assets.AssetsController;
022import io.stallion.dataAccess.ModelController;
023import io.stallion.exceptions.UsageException;
024import io.stallion.exceptions.WebException;
025import io.stallion.fileSystem.FileSystemWatcherService;
026import io.stallion.hooks.HookRegistry;
027
028import io.stallion.plugins.javascript.Sandbox;
029import io.stallion.requests.SandboxedRequest;
030import io.stallion.requests.Site;
031import io.stallion.services.Log;
032import io.stallion.settings.Settings;
033import io.stallion.users.EmptyUser;
034import io.stallion.utils.DateUtils;
035import io.stallion.utils.Sanitize;
036import io.stallion.utils.GeneralUtils;
037import org.apache.commons.lang3.exception.ExceptionUtils;
038
039import java.io.File;
040import java.lang.reflect.InvocationTargetException;
041import java.net.URL;
042import java.util.HashMap;
043import java.util.Map;
044
045import static io.stallion.Context.*;
046import static io.stallion.utils.Literals.*;
047
048
049public class TemplateRenderer {
050
051    private static TemplateRenderer _instance;
052    private JinjaTemplating templating;
053
054    {
055        templating = new JinjaTemplating(Settings.instance().getTargetFolder(), Settings.instance().getDevMode() == true);
056    }
057
058    public static TemplateRenderer instance() {
059        if (_instance == null) {
060            throw new UsageException("You must call TemplateRenderer.load() before calling instance.");
061        }
062        return _instance;
063    }
064
065    public static TemplateRenderer load() {
066        _instance = new TemplateRenderer();
067        if (new File(Settings.instance().getTargetFolder() + "/templates").isDirectory()) {
068            FileSystemWatcherService.instance().registerWatcher(new TemplateFileChangeEventHandler()
069                    .setWatchedFolder(Settings.instance().getTargetFolder() + "/templates")
070                    .setWatchTree(true)
071                    .setExtension(".jinja"));
072        }
073        return _instance;
074    }
075
076    public static void shutdown() {
077        JinjaResourceLocator.clearCache();
078        _instance = null;
079    }
080
081    public String render404Html() throws Exception {
082        Map<String, Object> context = getErrorContext();
083
084        if (getJinjaTemplating().templateExists("404.jinja")) {
085            return renderTemplate("404.jinja", context);
086        } else {
087            URL url = getClass().getClassLoader().getResource("templates/public/404.jinja");
088            return renderTemplate(url.toString(), context);
089        }
090    }
091
092    public String render500Html(Exception e) {
093        String friendlyMessage = "There was an error trying to handle your request.";
094        if (e instanceof WebException) {
095            friendlyMessage = ((WebException)e).getMessage();
096        } else if (e instanceof InvocationTargetException) {
097            if (((InvocationTargetException) e).getTargetException() != null) {
098                if (((InvocationTargetException) e).getTargetException() instanceof WebException) {
099                    friendlyMessage = ((InvocationTargetException) e).getTargetException().getMessage();
100                }
101            }
102        }
103        String error = "";
104        if (Context.getSettings().getDebug()) {
105            error += "\nStacktrace-----------------------------------\n\n";
106            error += e.toString() + "\n\n";
107            error += ExceptionUtils.getStackTrace(e);
108            error = error.replace("\n", "<br>\n");
109        }
110
111        try {
112            Map<String, Object> context = getErrorContext();
113            context.put("errorDebugMessage", error);
114            context.put("friendlyMessage", friendlyMessage);
115            if (getJinjaTemplating().templateExists("templates/public/500.jinja")) {
116                return renderTemplate("templates/public/500.jinja", context);
117            } else {
118                URL url = getClass().getClassLoader().getResource("templates/public/500.jinja");
119                return renderTemplate(url.toString(), context);
120            }
121        } catch (Exception e2) {
122            Log.warn("---------------Exception trying to render the 500 page-------------");
123            Log.exception(e2, "500 rendering exception");
124            Log.warn("---------------End 500 rendering exception-------------");
125            String error2 = "";
126            if (Context.getSettings().getDebug()) {
127                error2 += "\nSecond Stacktrace trying to render the error!-----------------------------------\n\n";
128                error2 += e2.toString() + "\n\n";
129                error2 += ExceptionUtils.getStackTrace(e2);
130                error2 = error2.replace("\n", "<br>\n");
131            }
132            String html = "The server encountered an error trying to handle your request. Please try again in a little bit." + error + error2;
133            return html;
134        }
135    }
136
137    public Map<String, Object> getErrorContext() {
138        Map<String, Object> context = new HashMap<>();
139
140        context.put("user", Context.getUser());
141        context.put("org", Context.getOrg());
142        context.put("request", Context.request());
143        Site site = new Site();
144        site.setUrl(Context.settings().getSiteUrl());
145        AssetsController assetsController = new AssetsController();
146        context.put("files", assetsController);
147        context.put("assets", assetsController);
148        context.put("site", site);
149        context.put("styleSettings", Settings.instance().getStyles());
150        return context;
151    }
152
153    public String renderTemplate(String path) {
154        HashMap<String, Object> context = new HashMap<String, Object>();
155        return renderTemplate(path, context);
156    }
157
158    public String renderTemplate(URL url, Map<String, Object> context) {
159        return renderTemplate(url.toString(), context);
160    }
161
162
163    public String renderTemplate(String path, Map<String, Object> context) {
164        if (empty(path)) {
165            throw new UsageException("No template selected for renderTemplate");
166        }
167        if (!context.containsKey("user")) {
168            context.put("user", Context.getUser());
169        }
170
171
172        context.put("utils", new GeneralUtils());
173        context.put("dateUtils", new DateUtils());
174        context.put("sanitize", new Sanitize());
175
176        context.put("request", Context.request());
177        context.put("now", DateUtils.localNow());
178        context.put("styleSettings", Settings.instance().getStyles());
179
180        if (Context.response() != null) {
181
182            Context.response().getMeta().getCssClasses().add("st-template-" + GeneralUtils.slugify(path));
183            context.put("meta", Context.response().getMeta());
184        } else {
185            context.put("meta", map());
186        }
187
188        Site site = new Site();
189        site.setTitle(Settings.instance().getDefaultTitle());
190        site.setName(Settings.instance().getSiteName());
191        site.setUrl(settings().getSiteUrl());
192        site.setMetaDescription(settings().getMetaDescription());
193        context.put("site", site);
194        context.put("env", Settings.instance().getEnv());
195        context.put("isProd", "prod".equals(Settings.instance().getEnv()));
196
197        context.put("files", AssetsController.instance());
198        context.put("assets", AssetsController.instance());
199
200        for(Map.Entry<String, ModelController> entry: dal().entrySet()) {
201            context.put(entry.getKey(), entry.getValue().getReadonlyWrapper());
202        }
203
204
205
206        // Dispatch context hydration hooks
207        HookRegistry.instance().dispatch(TemplateContextHookHandler.class, context);
208
209
210        if (path.endsWith(".html") || path.endsWith(".jinja") || path.contains("\n")) {
211            JinjaTemplating templating = getJinjaTemplating();
212            String html = templating.renderTemplate(path, context);
213            return html;
214        } else {
215            throw new UsageException("Unknown extension for template path: " + path);
216        }
217    }
218
219
220    public String renderSandboxedTemplate(Sandbox sandbox, String path, Map<String, Object> context) {
221        context.put("utils", new GeneralUtils());
222        context.put("dateUtils", new DateUtils());
223        context.put("sanitize", new Sanitize());
224        context.put("now", DateUtils.localNow());
225
226        if (Context.response() != null) {
227
228            Context.response().getMeta().getCssClasses().add("st-template-" + GeneralUtils.slugify(path));
229            context.put("meta", Context.response().getMeta());
230        } else {
231            context.put("meta", map());
232        }
233
234        Site site = new Site();
235        site.setTitle(Settings.instance().getDefaultTitle());
236        site.setName(Settings.instance().getSiteName());
237        site.setUrl(settings().getSiteUrl());
238        site.setMetaDescription(settings().getMetaDescription());
239        context.put("site", site);
240        context.put("env", Settings.instance().getEnv());
241        context.put("isProd", "prod".equals(Settings.instance().getEnv()));
242
243        context.put("styleSettings", Settings.instance().getStyles());
244        context.put("files", AssetsController.wrapper());
245        context.put("assets", AssetsController.wrapper());
246        // Request, user, both pre-inserted
247
248        context.put("request", new SandboxedRequest(sandbox, Context.request()));
249        if (sandbox.getUsers().isCanAccess()) {
250            context.put("user", Context.getUser());
251        } else {
252            context.put("user", new EmptyUser());
253        }
254        context.put("lb", "{");
255
256        for(Map.Entry<String, ModelController> entry: dal().entrySet()) {
257            if (sandbox.isCanReadAllData() ||
258                    sandbox.getWhitelist().getReadBuckets().contains(entry.getKey()) ||
259                    sandbox.getWhitelist().getWriteBuckets().contains(entry.getKey())) {
260                context.put(entry.getKey(), entry.getValue().getReadonlyWrapper());
261            }
262        }
263        if (path.endsWith(".html") || path.endsWith(".jinja") || path.contains("\n")) {
264            JinjaTemplating templating = getJinjaTemplating();
265            String html = templating.renderTemplate(path, context);
266            return html;
267        } else {
268            throw new UsageException("Unknown extension for template path: " + path);
269        }
270    }
271
272
273    public JinjaTemplating getJinjaTemplating() {
274        return templating;
275    }
276}