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}