UNPKG

8.36 kBJavaScriptView Raw
1var path = require('path');
2var fs = require('fs');
3var http = require('http');
4
5var util = require("../util/util");
6
7var dust = require("dustjs-linkedin");
8require("dustjs-helpers");
9
10// we always set dust cache to false
11// this is because dust cache is file path dependent (could collide across tenants)s
12dust.config.cache = false;
13
14// instead we manage our own template cache
15var TEMPLATE_CACHE = {};
16
17if (process.env.CLOUDCMS_APPSERVER_MODE === "production") {
18 dust.debugLevel = "INFO";
19} else {
20 dust.debugLevel = "DEBUG";
21 dust.config.cache = false;
22}
23
24if (process.env.CLOUDCMS_APPSERVER_MODE !== "production") {
25 dust.config.whitespace = true;
26}
27
28if (process.env.DUST_DEBUG_LEVEL) {
29 dust.debugLevel = (process.env.DUST_DEBUG_LEVEL + "").toUpperCase();
30}
31
32/**
33 * Override Dust's isThenable() function so that Gitana driver chainable objects aren't included.
34 *
35 * @param elem
36 * @returns {*|boolean}
37 */
38dust.isThenable = function(elem) {
39 return elem &&
40 typeof elem === 'object' &&
41 typeof elem.then === 'function' && !elem.objectType;
42};
43
44/**
45 * Override Dust's onLoad() function so that templates are loaded from the store.
46 * The cache key is also determined to include the appId.
47 *
48 * @param templatePath
49 * @param options
50 * @param callback
51 */
52var loadTemplate = dust.onLoad = function(templatePath, options, callback)
53{
54 //var log = options.log;
55
56 // `templateName` is the name of the template requested by dust.render / dust.stream
57 // or via a partial include, like {> "hello-world" /}
58 // `options` can be set as part of a Context. They will be explored later
59 // `callback` is a Node-like callback that takes (err, data)
60
61 var store = options.store;
62
63 store.existsFile(templatePath, function(exists) {
64
65 if (!exists) {
66 return callback({
67 "message": "Dust cannot find file: " + templatePath
68 });
69 }
70
71 store.readFile(templatePath, function(err, data) {
72
73 if (err) {
74 return callback(err);
75 }
76
77 callback(null, "" + data);
78 });
79 });
80};
81
82/**
83 * Provides a convenience interface into the Dust subsystem that Cloud CMS uses to process in-page tags.
84 */
85var exports = module.exports;
86
87exports.getDust = function()
88{
89 return dust;
90};
91
92var populateContext = function(req, context, model, templateFilePath)
93{
94 if (model)
95 {
96 for (var k in model)
97 {
98 context[k] = model[k];
99 }
100 }
101
102 // assume no user information
103
104 // if we have a req.user, add this in
105 if (req.user)
106 {
107 context.user = req.user;
108 context.userId = req.user.id;
109 context.userName = req.user.name;
110 }
111
112 // populate request information
113 var qs = {};
114 if (req.query)
115 {
116 for (var name in req.query)
117 {
118 var value = req.query[name];
119 if (value)
120 {
121 qs[name] = value;
122 }
123 }
124 }
125 if (!context.request) {
126 context.request = {};
127 }
128 context.request.uri = req.originalUrl;
129 context.request.path = req.path;
130 context.request.qs = qs;
131
132 context.gitana = req.gitana;
133 context.templateFilePaths = [templateFilePath];
134 context.req = req;
135 context.messages = {};
136
137 // populate any flash messages
138 if (req.flash)
139 {
140 var info = req.flash("info");
141 if (info)
142 {
143 context.messages.info = info;
144 }
145 }
146
147 if (req.helpers)
148 {
149 context.helpers = req.helpers;
150 }
151
152 // push base tracker instance for tracking dependencies
153 // TODO: add user information?
154 context["__tracker"] = {
155 "requires": {},
156 "produces": {}
157 };
158
159 // TODO: add user information?
160 // this isn't clear... not all pages in a user authenticated web site will require per-user page caching...
161 // if we were to do it, we'd do it manually like this
162 //context["__tracker"]["requires"]["userId"] = [req.userId];
163
164 // used to generate fragment IDs
165 context["fragmentIdGenerator"] = function(url) {
166 var counter = 0;
167 return function() {
168 return util.hashSignature("fragment-" + url + "-" + (++counter));
169 };
170 }(req.url);
171};
172
173exports.invalidateCacheForApp = function(applicationId)
174{
175 console.log("Invalidating dust cache for application: " + applicationId);
176
177 var prefix = applicationId + ":";
178
179 var badKeys = [];
180 for (var k in TEMPLATE_CACHE)
181 {
182 if (k.indexOf(prefix) === 0) {
183 badKeys.push(k);
184 }
185 }
186 for (var i = 0; i < badKeys.length; i++)
187 {
188 console.log("Removing dust cache key: " + badKeys[i]);
189 delete TEMPLATE_CACHE[badKeys[i]];
190 }
191};
192
193exports.execute = function(req, store, filePath, model, callback)
194{
195 if (typeof(model) === "function")
196 {
197 callback = model;
198 model = {};
199 }
200
201 ensureInit(store);
202
203 var templatePath = filePath.split(path.sep).join("/");
204
205 // build context
206 var contextObject = {};
207 populateContext(req, contextObject, model, templatePath);
208 var context = dust.context(contextObject, {
209 "req": req,
210 "store": store,
211 "log": dust.log,
212 "dust": dust
213 });
214
215 // hold on to this instance so that we can get at it once we're done
216 var tracker = context.get("__tracker");
217
218 var executeTemplate = function(template, templatePath, context, callback)
219 {
220 // execute template
221 var t1 = new Date().getTime();
222 dust.render(template, context, function(err, out) {
223 var t2 = new Date().getTime();
224
225 if (err)
226 {
227 req.log("An error was caught while rendering dust template: " + templatePath + ", error: " + JSON.stringify(err, null, " "));
228 }
229
230 // clean up - help out the garbage collector
231 context.req = null;
232 context.gitana = null;
233 context.user = null;
234
235 var dependencies = {
236 "requires": tracker.requires,
237 "produces": tracker.produces
238 };
239
240 var stats = {
241 "dustExecutionTime": (t2 - t1)
242 };
243
244 // callback with dependencies
245 callback(err, out, dependencies, stats);
246 });
247 };
248
249 // does the template exist in the cache?
250 var templateCacheKey = store.id + "_" + templatePath;
251 var template = TEMPLATE_CACHE[templateCacheKey];
252 if (template)
253 {
254 //console.log("FOUND IN TEMPLATE CACHE: " + templatePath);
255 return executeTemplate(template, templatePath, context, callback);
256 }
257
258 // load and compile template by hand
259 // we do this by hand in case it has a bug in it - we don't want it crashing the entire node process
260 loadTemplate(templatePath, {
261 "store": store
262 }, function(err, text) {
263
264 if (err) {
265 return callback(err);
266 }
267
268 // compile and store into dust.cache
269 try {
270 //console.log("WRITE TO TEMPLATE CACHE: " + templatePath);
271 template = dust.loadSource(dust.compile(text));
272 TEMPLATE_CACHE[templateCacheKey] = template;
273 } catch (e) {
274 delete TEMPLATE_CACHE[templateCacheKey];
275 return callback(e);
276 }
277
278 // proceed
279 executeTemplate(template, templatePath, context, callback);
280 });
281};
282
283/**
284 * Init function that runs on the first call to duster.execute().
285 * This ensures that templates are being watched properly.
286 */
287var _init = false;
288var ensureInit = function(store)
289{
290 if (_init)
291 {
292 return;
293 }
294
295 _init = true;
296
297 if (process.env.CLOUDCMS_APPSERVER_MODE !== "production")
298 {
299 // watch everything in web store
300 store.watchDirectory("/", function (f, curr, prev) {
301
302 console.log("Template changes detected - invalidating dust cache");
303 var badKeys = [];
304 for (var k in TEMPLATE_CACHE)
305 {
306 if (k.indexOf(store.id) === 0)
307 {
308 badKeys.push(k);
309 }
310 }
311 for (var i = 0; i < badKeys.length; i++)
312 {
313 console.log("Invalidating watched key: " + badKeys[i]);
314 delete TEMPLATE_CACHE[badKeys[i]];
315 }
316
317 });
318 }
319};
\No newline at end of file