cloudcms-server 4.0.0-beta.2 → 4.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +0 -5
  2. package/cloudcms-server.iml +1 -0
  3. package/index.js +58 -32
  4. package/middleware/authentication/authentication.js +40 -12
  5. package/middleware/authentication/providers/saml.js +8 -4
  6. package/middleware/awareness/awareness.js +8 -7
  7. package/middleware/awareness/plugins/api_event.js +105 -0
  8. package/middleware/awareness/plugins/editorial.js +54 -3
  9. package/middleware/awareness/plugins/resources.js +13 -5
  10. package/middleware/config/adapter.js +0 -44
  11. package/middleware/deployment/deployment.js +22 -24
  12. package/middleware/driver/driver.js +24 -1
  13. package/middleware/driver-config/driver-config.js +0 -6
  14. package/middleware/modules/modules.js +11 -5
  15. package/middleware/perf/perf.js +3 -2
  16. package/middleware/registration/registration.js +0 -5
  17. package/middleware/stores/engines/empty.js +0 -4
  18. package/middleware/stores/engines/fs-caching-adapter.js +0 -5
  19. package/middleware/stores/engines/fs.js +0 -9
  20. package/middleware/stores/engines/s3.js +0 -5
  21. package/middleware/stores/engines/s3fs.js +0 -5
  22. package/middleware/stores/multistore.js +0 -29
  23. package/middleware/stores/store.js +0 -10
  24. package/middleware/stores/stores.js +2 -2
  25. package/middleware/virtual-config/virtual-config.js +253 -206
  26. package/middleware/virtual-files/virtual-files.js +0 -3
  27. package/middleware/welcome/welcome.js +0 -3
  28. package/notifications/notifications.js +72 -10
  29. package/notifications/providers/kafka.js +182 -0
  30. package/notifications/providers/stomp.js +4 -0
  31. package/package.json +40 -56
  32. package/server/index.js +216 -123
  33. package/server/standalone.js +1 -6
  34. package/util/auth.js +10 -4
  35. package/util/cloudcms.js +77 -35
  36. package/util/loaders.js +113 -0
  37. package/util/proxy-factory.js +143 -168
  38. package/util/request.js +6 -2
  39. package/util/workqueue.js +111 -0
  40. package/.last_command +0 -7
  41. package/duster/helpers/core/cloudcms/associations.js +0 -34
  42. package/duster/helpers/core/cloudcms/beta/markdown.js +0 -46
  43. package/duster/helpers/core/cloudcms/beta/nodeAttachmentText.js +0 -46
  44. package/duster/helpers/core/cloudcms/beta/params.js +0 -33
  45. package/duster/helpers/core/cloudcms/beta/processTemplate.js +0 -82
  46. package/duster/helpers/core/cloudcms/content.js +0 -34
  47. package/duster/helpers/core/cloudcms/expand.js +0 -38
  48. package/duster/helpers/core/cloudcms/form.js +0 -34
  49. package/duster/helpers/core/cloudcms/query.js +0 -34
  50. package/duster/helpers/core/cloudcms/queryOne.js +0 -34
  51. package/duster/helpers/core/cloudcms/relatives.js +0 -34
  52. package/duster/helpers/core/cloudcms/search.js +0 -34
  53. package/duster/helpers/core/cloudcms/searchOne.js +0 -34
  54. package/duster/helpers/core/cloudcms/wcm/dependency.js +0 -83
  55. package/duster/helpers/core/cloudcms/wcm/fragment.js +0 -34
  56. package/duster/helpers/core/dev/debug.js +0 -42
  57. package/duster/helpers/core/dom/block.js +0 -49
  58. package/duster/helpers/core/dom/include.js +0 -38
  59. package/duster/helpers/core/dom/layout.js +0 -49
  60. package/duster/helpers/core/dom/link.js +0 -81
  61. package/duster/helpers/core/dom/resource.js +0 -77
  62. package/duster/helpers/core/engine.js +0 -1580
  63. package/duster/helpers/core/ice/value.js +0 -65
  64. package/duster/helpers/core/index.js +0 -49
  65. package/duster/helpers/core/operators/if.js +0 -64
  66. package/duster/helpers/core/operators/iter.js +0 -45
  67. package/duster/helpers/core/operators/iterate.js +0 -129
  68. package/duster/helpers/sample/nyt.js +0 -114
  69. package/duster/index.js +0 -319
  70. package/duster/support.js +0 -436
  71. package/duster/tracker.js +0 -262
  72. package/middleware/authentication/providers/cas.js +0 -73
  73. package/middleware/authentication/providers/facebook.js +0 -120
  74. package/middleware/authentication/providers/github.js +0 -88
  75. package/middleware/authentication/providers/linkedin.js +0 -112
  76. package/middleware/authentication/providers/twitter.js +0 -120
  77. package/middleware/server-tags/server-tags.js +0 -113
  78. package/middleware/wcm/wcm.js +0 -1437
@@ -1,1437 +0,0 @@
1
- var path = require('path');
2
- var fs = require('fs');
3
- var http = require('http');
4
- var util = require("../../util/util");
5
- var cloudcms = require("../../util/cloudcms");
6
- var duster = require("../../duster/index");
7
- var async = require("async");
8
-
9
- var support = require("../../duster/support")(duster.getDust());
10
-
11
- var renditions = require("../../util/renditions");
12
-
13
- /**
14
- * WCM middleware.
15
- *
16
- * Serves up HTML pages based on WCM configuration. Applies duster tag processing.
17
- *
18
- * @type {Function}
19
- */
20
- exports = module.exports = function()
21
- {
22
- // cache keys
23
- var WCM_PAGES = "wcmPages";
24
- var WCM_PAGES_CACHE_TIME = "wcmPagesTime";
25
-
26
- var isEmpty = function(thing)
27
- {
28
- return (typeof(thing) === "undefined") || (thing === null);
29
- };
30
-
31
- var startsWith = function(text, prefix) {
32
- return text.substr(0, prefix.length) === prefix;
33
- };
34
-
35
- var executeMatch = function(matcher, text)
36
- {
37
- // strip matcher from "/a/b/c" to "a/b/c"
38
- if (matcher && matcher.length > 0 && matcher.substring(0,1) === "/")
39
- {
40
- matcher = matcher.substring(1);
41
- }
42
-
43
- // strip text from "/a/b/c" to "a/b/c"
44
- if (text && text.length > 0 && text.substring(0,1) === "/")
45
- {
46
- text = text.substring(1);
47
- }
48
-
49
- var tokens = {};
50
-
51
- var printDebug = function()
52
- {
53
- if (process.env.NODE_ENV === "production") {
54
- // skip
55
- } else {
56
- process.log("Matched - pattern: " + matcher + ", text: " + text + ", tokens: " + JSON.stringify(tokens));
57
- }
58
- };
59
-
60
- var array1 = [];
61
- if (matcher)
62
- {
63
- array1 = matcher.split("/");
64
- }
65
- var array2 = [];
66
- if (text)
67
- {
68
- array2 = text.split("/");
69
- }
70
-
71
- // short cut - zero length matches
72
- if ((array1.length === 0) && (array2.length === 0))
73
- {
74
- printDebug();
75
- return tokens;
76
- }
77
-
78
- if (matcher)
79
- {
80
- // short cut - **
81
- if (matcher === "**")
82
- {
83
- // it's a match, pull out wildcard token
84
- tokens["**"] = text;
85
- printDebug();
86
- return tokens;
87
- }
88
-
89
- // if matcher has no wildcards or tokens...
90
- if ((matcher.indexOf("{") === -1) && (matcher.indexOf("*") === -1))
91
- {
92
- // if they're equal...
93
- if (matcher === text)
94
- {
95
- // it's a match, no tokens
96
- tokens["_exact"] = true;
97
- printDebug();
98
- return tokens;
99
- }
100
- }
101
- }
102
-
103
- var pattern = null;
104
- var value = null;
105
- do
106
- {
107
- pattern = array1.shift();
108
- value = array2.shift();
109
-
110
- var patternEmpty = (isEmpty(pattern) || pattern === "");
111
- var valueEmpty = (isEmpty(value) || value === "");
112
-
113
- // if there are remaining pattern and value elements
114
- if (!patternEmpty && !valueEmpty)
115
- {
116
- if (pattern === "*")
117
- {
118
- // wildcard - element matches
119
- }
120
- else if (pattern === "**")
121
- {
122
- // wildcard - match everything else, so break out
123
- tokens["**"] = "/" + [].concat(value, array2).join("/");
124
- break;
125
- }
126
- else if (pattern.indexOf("{") > -1)
127
- {
128
- var startIndex = pattern.indexOf("{");
129
- var stopIndex = pattern.indexOf("}");
130
-
131
- var prefix = null;
132
- if (startIndex > 0)
133
- {
134
- prefix = pattern.substring(0, startIndex);
135
- }
136
-
137
- var suffix = null;
138
- if (stopIndex < pattern.length - 1)
139
- {
140
- suffix = pattern.substring(stopIndex);
141
- }
142
-
143
- if (prefix)
144
- {
145
- value = value.substring(prefix.length);
146
- }
147
-
148
- if (suffix)
149
- {
150
- value = value.substring(0, value.length - suffix.length + 1);
151
- }
152
-
153
- var key = pattern.substring(startIndex + 1, stopIndex);
154
-
155
- // assign to token collection
156
- if (value)
157
- {
158
- // URL decode the value
159
- value = decodeURIComponent(value);
160
-
161
- // assign to tokens
162
- tokens[key] = value;
163
- }
164
- }
165
- else
166
- {
167
- // check for exact match
168
- if (matchCase() && pattern === value)
169
- {
170
- // exact match
171
- }
172
- else if (!matchCase() && (pattern+"").toLowerCase() === (value+"").toLowerCase())
173
- {
174
- // case insensitive match
175
- }
176
- else
177
- {
178
- // not a match, thus fail
179
- return null;
180
- }
181
- }
182
- }
183
- else
184
- {
185
- // if we expected a pattern but empty value or we have a value but no pattern
186
- // then it is a mismatch
187
- if ((pattern && valueEmpty) || (patternEmpty && value))
188
- {
189
- return null;
190
- }
191
- }
192
- }
193
- while (!isEmpty(pattern) && !isEmpty(value));
194
-
195
- printDebug();
196
- return tokens;
197
- };
198
-
199
- var findMatchingPage = function(pages, offsetPath, callback)
200
- {
201
- // walk through the routes and find one that matches this URI and method
202
- var discoveredTokensArray = [];
203
- var discoveredPages = [];
204
- var discoveredPageOffsetPaths = [];
205
- for (var pageOffsetPath in pages)
206
- {
207
- var matchedTokens = executeMatch(pageOffsetPath, offsetPath);
208
- if (matchedTokens)
209
- {
210
- discoveredPages.push(pages[pageOffsetPath]);
211
- discoveredTokensArray.push(matchedTokens);
212
- discoveredPageOffsetPaths.push(pageOffsetPath);
213
- }
214
- }
215
-
216
- // pick the closest page (overrides are sorted first)
217
- var discoveredPage = null;
218
- var discoveredTokens = null;
219
- var discoveredPageOffsetPath = null;
220
- if (discoveredPages.length > 0)
221
- {
222
- // find the index with the greatest # of tokens
223
- var index = 0;
224
- var maxLen = 0;
225
- for (var i = 0; i < discoveredTokensArray.length; i++)
226
- {
227
- var len = discoveredTokensArray[i].length;
228
- if (len > maxLen) {
229
- index = i;
230
- maxLen = len;
231
- }
232
-
233
- if (discoveredTokensArray[i]["_exact"])
234
- {
235
- index = i;
236
- break;
237
- }
238
- }
239
-
240
- // hand back the discovered page that matches the greatest # of tokens
241
- discoveredPage = discoveredPages[index];
242
- discoveredTokens = discoveredTokensArray[index];
243
- discoveredPageOffsetPath = discoveredPageOffsetPaths[index];
244
- }
245
-
246
- callback(null, discoveredPage, discoveredTokens, discoveredPageOffsetPath);
247
- };
248
-
249
- var preloadPages = function(req, finished)
250
- {
251
- var WCM_PAGES_CACHE_TTL = getPagesCacheTTL();
252
-
253
- // retry in 30 seconds if Cloud CMS unavailable for page cache reload
254
- var WCM_PAGES_CACHE_RETRY_TIME_MS = getPagesCacheRetryTimeout();
255
-
256
- // set driver timeout to 30 seconds
257
- var Gitana = require("gitana");
258
- Gitana.HTTP_TIMEOUT = 30 * 1000;
259
-
260
- var ensureInvalidate = function(callback) {
261
-
262
- // allow for forced invalidation via req param
263
- if (req.query["invalidate"]) {
264
- req.cache.remove(WCM_PAGES, function() {
265
- callback();
266
- });
267
- return;
268
- }
269
-
270
- callback();
271
- };
272
-
273
- ensureInvalidate(function() {
274
-
275
- var now = new Date().getTime();
276
-
277
- req.cache.read(WCM_PAGES, function (err, cachedPages) {
278
- req.cache.read(WCM_PAGES_CACHE_TIME, function(err, cachedPagesTime) {
279
-
280
- // if we received cachedPages, try to determine whether they're dirty (in which case we should reload)
281
- // or whether we can serve them back
282
- var load = (cachedPages ? false : true);
283
- if (cachedPages && !cachedPagesTime)
284
- {
285
- cachedPages = null;
286
- load = true;
287
- }
288
- if (cachedPages && typeof(cachedPagesTime.ms) === "undefined")
289
- {
290
- cachedPages = null;
291
- load = true;
292
- }
293
- if (cachedPages && cachedPagesTime && cachedPagesTime.ms > -1)
294
- {
295
- if ((cachedPagesTime.ms + WCM_PAGES_CACHE_TTL) < now)
296
- {
297
- load = true;
298
- }
299
- }
300
- if (!load && cachedPages)
301
- {
302
- return finished(null, cachedPages);
303
- }
304
-
305
-
306
- // at this point, we MAY need to reload pages
307
- // we take out a lock and so that only one thread loads at at time per application
308
-
309
- req.application(function (err, application) {
310
-
311
- var loadingPagesCacheKey = application._doc + "-wcm-loading-pages";
312
- _LOCK(null, loadingPagesCacheKey, function (err, releaseLockFn) {
313
-
314
- // check again inside lock in case another request preloaded this before we arrived
315
- req.cache.read(WCM_PAGES, function (err, cachedPages) {
316
- req.cache.read(WCM_PAGES_CACHE_TIME, function (err, cachedPagesTime) {
317
-
318
- // if we received cachedPages, try to determine whether they're dirty (in which case we should reload)
319
- // or whether we can serve them back
320
-
321
- var load = (cachedPages ? false : true);
322
- if (cachedPages && !cachedPagesTime)
323
- {
324
- cachedPages = null;
325
- load = true;
326
- }
327
- if (cachedPages && typeof(cachedPagesTime.ms) === "undefined")
328
- {
329
- cachedPages = null;
330
- load = true;
331
- }
332
- if (cachedPages && cachedPagesTime && cachedPagesTime.ms > -1)
333
- {
334
- if (cachedPagesTime.ms + WCM_PAGES_CACHE_TTL < now)
335
- {
336
- load = true;
337
- }
338
- }
339
- if (!load && cachedPages)
340
- {
341
- releaseLockFn();
342
- return finished(null, cachedPages);
343
- }
344
-
345
- req.log("Loading Web Pages into cache");
346
-
347
- // handles after the load either completed or failed
348
- var afterHandler = function (err, loadedPages) {
349
-
350
- if (err)
351
- {
352
- req.log("Error while loading web pages: " + JSON.stringify(err));
353
-
354
- // if we have cached pages, just use those
355
- if (cachedPages)
356
- {
357
- req.log("Falling back to using cached pages, will retain for " + WCM_PAGES_CACHE_RETRY_TIME_MS + " ms before trying again");
358
- req.cache.write(WCM_PAGES_CACHE_TIME, {
359
- "ms": (new Date().getTime() + WCM_PAGES_CACHE_RETRY_TIME_MS)
360
- });
361
-
362
- return finished(null, cachedPages);
363
- }
364
-
365
- req.log("No cached pages were present, throwing error");
366
-
367
- return finished(err);
368
- }
369
-
370
- // hand back the loaded pages
371
- return finished(null, loadedPages);
372
- };
373
-
374
- // load all wcm pages from the server
375
- req.branch(function (err, branch) {
376
-
377
- if (err)
378
- {
379
- // release the lock
380
- releaseLockFn();
381
-
382
- // fire the error handler with no loaded pages
383
- return afterHandler(err);
384
- }
385
-
386
- // build out pages
387
- var loadedPages = {};
388
-
389
- var queryTimeMs = new Date().getTime();
390
-
391
- branch.trap(function (err) {
392
-
393
- // release the lock
394
- releaseLockFn();
395
-
396
- // fire the error handler with no loaded pages
397
- afterHandler(err);
398
-
399
- return false;
400
- }).then(function () {
401
-
402
- var fns = [];
403
-
404
- req.log("Querying for WCM web pages");
405
-
406
- // load all of the pages
407
- this.trap(function (err) {
408
-
409
- // release the lock
410
- releaseLockFn();
411
-
412
- // fire the error handler with no loaded pages
413
- afterHandler(err);
414
-
415
- return false;
416
-
417
- }).queryNodes({
418
- _type: "wcm:page",
419
- _fields: {
420
- title: 1,
421
- template: 1,
422
- templatePath: 1,
423
- uris: 1
424
- }
425
- }, {
426
- limit: -1,
427
- metadata: false,
428
- full: false
429
- }).each(function () {
430
-
431
- // THIS = wcm:page
432
- var page = this;
433
-
434
- // if the page has a template
435
- if (page.template)
436
- {
437
- var fn = function (branch, page) {
438
- return function (allDone) {
439
-
440
- var completionFn = function () {
441
-
442
- if (page.templatePath)
443
- {
444
- if (page.uris)
445
- {
446
- // merge into our pages collection
447
- for (var i = 0; i < page.uris.length; i++)
448
- {
449
- loadedPages[page.uris[i]] = page;
450
- }
451
- }
452
- }
453
-
454
- allDone();
455
- };
456
-
457
- // is the template a GUID or a path to the template file?
458
- if ((page.template.indexOf("/") > -1) || (page.template.indexOf(".") > -1))
459
- {
460
- page.templatePath = page.template;
461
- completionFn();
462
- }
463
- else
464
- {
465
- // load the template
466
- Chain(branch).trap(function (e2) {
467
- // skip it
468
- completionFn();
469
- return false;
470
- }).readNode(page.template).then(function () {
471
-
472
- // THIS = wcm:template
473
- var template = this;
474
-
475
- if (template.path)
476
- {
477
- page.templatePath = template.path;
478
- }
479
-
480
- completionFn();
481
-
482
- });
483
- }
484
- };
485
- }(branch, page);
486
- fns.push(fn);
487
- }
488
-
489
- }).then(function () {
490
-
491
- req.log("Loading " + fns.length + " web pages");
492
-
493
- async.series(fns, function (err) {
494
-
495
- for (var uri in loadedPages)
496
- {
497
- req.log("Loaded Web Page -> " + uri);
498
- }
499
-
500
- req.log("Web Page loading complete");
501
-
502
- // write to cache
503
- req.cache.write(WCM_PAGES, loadedPages);
504
- req.cache.write(WCM_PAGES_CACHE_TIME, {
505
- "ms": queryTimeMs
506
- });
507
-
508
- releaseLockFn();
509
-
510
- afterHandler(null, loadedPages);
511
- });
512
-
513
- });
514
- });
515
- });
516
- });
517
- });
518
- });
519
- });
520
- });
521
- });
522
- });
523
- };
524
-
525
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
526
- //
527
- // PAGE CACHE (WITH DEPENDENCIES)
528
- //
529
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
530
-
531
- var matchCase = function()
532
- {
533
- if (!process.configuration.wcm) {
534
- process.configuration.wcm = {};
535
- }
536
-
537
- if (typeof(process.configuration.wcm.matchCase) === "undefined") {
538
- process.configuration.wcm.matchCase = true;
539
- }
540
-
541
- return process.configuration.wcm.matchCase;
542
- };
543
-
544
- var isEnabled = function()
545
- {
546
- if (!process.configuration.wcm) {
547
- process.configuration.wcm = {};
548
- }
549
-
550
- if (typeof(process.configuration.wcm.enabled) === "undefined") {
551
- process.configuration.wcm.enabled = false;
552
- }
553
-
554
- return process.configuration.wcm.enabled;
555
- };
556
-
557
- /**
558
- * Determines whether to use the page cache. We use this cache if we're instructed to and if we're in
559
- * production model.
560
- *
561
- * @returns {boolean}
562
- */
563
- var isPageCacheEnabled = function(req)
564
- {
565
- if (!process.configuration.wcm) {
566
- process.configuration.wcm = {};
567
- }
568
-
569
- if (typeof(process.configuration.wcm.enabled) === "undefined") {
570
- process.configuration.wcm.enabled = false;
571
- }
572
-
573
- // some helpful compatibility
574
- if (typeof(process.configuration.wcm.enabled) === "false") {
575
- process.configuration.wcm.enabled = false;
576
- }
577
-
578
- if (typeof(process.configuration.wcm.cache) === "undefined") {
579
- process.configuration.wcm.cache = false;
580
- }
581
-
582
- // some helpful compatibility
583
- if (typeof(process.configuration.wcm.enabled) === "false") {
584
- process.configuration.wcm.enabled = false;
585
- }
586
-
587
-
588
- if (process.env.FORCE_CLOUDCMS_WCM_PAGE_CACHE === "true")
589
- {
590
- process.configuration.wcm.cache = true;
591
- }
592
- else if (process.env.FORCE_CLOUDCMS_WCM_PAGE_CACHE === "false")
593
- {
594
- process.configuration.wcm.cache = false;
595
- }
596
-
597
- if (process.env.CLOUDCMS_APPSERVER_MODE !== "production")
598
- {
599
- process.configuration.wcm.cache = false;
600
- }
601
-
602
- var enabled = (process.configuration.wcm.enabled && process.configuration.wcm.cache);
603
-
604
- // is it disabled per request?
605
- if (req && req.disablePageCache)
606
- {
607
- enabled = false;
608
- }
609
-
610
- return enabled;
611
- };
612
-
613
- var getPageCacheKeyConfig = function(req)
614
- {
615
- if (!process.configuration.wcm) {
616
- process.configuration.wcm = {};
617
- }
618
-
619
- if (!process.configuration.wcm.cacheKey) {
620
- process.configuration.wcm.cacheKey = {};
621
- }
622
-
623
- if (!process.configuration.wcm.cacheKey.params) {
624
- process.configuration.wcm.cacheKey.params = {};
625
- }
626
-
627
- if (!process.configuration.wcm.cacheKey.params.includes) {
628
- process.configuration.wcm.cacheKey.params.includes = [];
629
- }
630
-
631
- if (!process.configuration.wcm.cacheKey.params.excludes) {
632
- process.configuration.wcm.cacheKey.params.excludes = [];
633
- }
634
-
635
- return process.configuration.wcm.cacheKey;
636
- };
637
-
638
- var getPagesCacheTTL = function()
639
- {
640
- if (!process.configuration.wcm) {
641
- process.configuration.wcm = {};
642
- }
643
-
644
- if (!(process.configuration.wcm.pageCacheTTL) || process.configuration.wcm.pageCacheTTL === -1)
645
- {
646
- // assume 60 seconds (development mode)
647
- process.configuration.wcm.pageCacheTTL = 60 * 1000;
648
-
649
- // in production, assume 24 hours
650
- if (process.env.CLOUDCMS_APPSERVER_MODE === "production")
651
- {
652
- process.configuration.wcm.pageCacheTTL = (24 * 60 * 60) * 1000;
653
- }
654
- }
655
-
656
- return process.configuration.wcm.pageCacheTTL;
657
- };
658
-
659
- var getPagesCacheRetryTimeout = function()
660
- {
661
- if (!process.configuration.wcm) {
662
- process.configuration.wcm = {};
663
- }
664
-
665
- if (!(process.configuration.wcm.pageCacheRetryTimeout) || process.configuration.wcm.pageCacheRetryTimeout === -1)
666
- {
667
- // default to 30 seconds
668
- process.configuration.wcm.pageCacheRetryTimeout = 30 * 1000;
669
- }
670
-
671
- return process.configuration.wcm.pageCacheRetryTimeout;
672
- };
673
-
674
-
675
- var cleanup = function(store, pageFilePath, cacheFilePath, afterCleanup)
676
- {
677
- cloudcms.safeRemove(store, pageFilePath, function() {
678
- cloudcms.safeRemove(store, cacheFilePath, function() {
679
-
680
- if (afterCleanup) {
681
- afterCleanup();
682
- }
683
- });
684
- });
685
- };
686
-
687
- var handleCachePageWrite = function(req, res, descriptor, pageBasePath, dependencies, text, callback)
688
- {
689
- // if page cache isn't enabled, return right away
690
- if (!isPageCacheEnabled(req))
691
- {
692
- return callback();
693
- }
694
-
695
- var contentStore = req.stores.content;
696
-
697
- // mark the rendition
698
- if (dependencies)
699
- {
700
- process.log("marking rendition from wcm, " + (descriptor.fragmentId ? "fragmentId: " + descriptor.fragmentId : "no fragmentId"));
701
- renditions.markRendition(req, descriptor, dependencies, function (err) {
702
-
703
- // if we got an error writing the page, then we have to roll back and invalidate disk cache
704
- if (err)
705
- {
706
- process.log("Caught error on WCM markRendition, invalidating: " + pageBasePath + ", err:" + err);
707
- _handleCachePageInvalidate(contentStore, pageBasePath, function() {
708
- // done
709
- });
710
- }
711
-
712
- });
713
- }
714
-
715
- var pageFilePath = path.join(pageBasePath, "page.html");
716
- var cacheFilePath = cloudcms.toCacheFilePath(pageFilePath);
717
-
718
- // take out a lock so that only one "request" can write to cache at a time for this path
719
- _LOCK(contentStore, _lock_identifier(pageBasePath), function(err, releaseLockFn) {
720
-
721
- // write page file
722
- contentStore.writeFile(pageFilePath, text, function (err) {
723
-
724
- if (err)
725
- {
726
- // failed to write page file, start cleanup
727
- return cleanup(contentStore, pageFilePath, cacheFilePath, function() {
728
- releaseLockFn();
729
- callback(err);
730
- });
731
- }
732
-
733
- var cacheInfo = cloudcms.buildCacheInfo(res);
734
- if (!cacheInfo)
735
- {
736
- cacheInfo = {
737
- "mimetype": "text/html"
738
- };
739
- }
740
- if (!cacheInfo.mimetype)
741
- {
742
- cacheInfo.mimetype = "text/html";
743
- }
744
-
745
- // write cache info file
746
- contentStore.writeFile(cacheFilePath, JSON.stringify(cacheInfo, null, " "), function (err) {
747
-
748
- if (err)
749
- {
750
- // failed to write page file, start cleanup
751
- return cleanup(contentStore, pageFilePath, cacheFilePath, function() {
752
- releaseLockFn();
753
- callback(err);
754
- });
755
- }
756
-
757
- releaseLockFn();
758
- callback(null, pageFilePath, cacheInfo);
759
- });
760
- });
761
- });
762
- };
763
-
764
- var handleCachePageRead = function(req, descriptor, pageBasePath, callback)
765
- {
766
- if (!isPageCacheEnabled(req))
767
- {
768
- return callback();
769
- }
770
-
771
- var contentStore = req.stores.content;
772
- var pageFilePath = path.join(pageBasePath, "page.html");
773
- var cacheFilePath = cloudcms.toCacheFilePath(pageFilePath);
774
-
775
- // take out a lock so that only one "request" can read from cache at a time for this path
776
- _LOCK(contentStore, _lock_identifier(pageBasePath), function(err, releaseLockFn) {
777
-
778
- contentStore.readFile(cacheFilePath, function(err, cacheInfoString) {
779
-
780
- if (err)
781
- {
782
- // failed to write page file, start cleanup
783
- return cleanup(contentStore, pageFilePath, cacheFilePath, function() {
784
- releaseLockFn();
785
- callback(err);
786
- });
787
- }
788
-
789
- util.safeReadStream(contentStore, pageFilePath, function (err, stream) {
790
-
791
- if (err)
792
- {
793
- // failed to write page file, start cleanup
794
- return cleanup(contentStore, pageFilePath, cacheFilePath, function() {
795
- releaseLockFn();
796
- callback(err);
797
- });
798
- }
799
-
800
- // make sure cache info is valid
801
- // we do this in case a non-JSON string comes back, we don't want to bomb out the server
802
- var cacheInfo = null;
803
- try {
804
- cacheInfo = JSON.parse(cacheInfoString)
805
- } catch (e) {
806
- // swallow
807
- }
808
-
809
- if (!cacheInfo)
810
- {
811
- // failed to write page file, start cleanup
812
- return cleanup(contentStore, pageFilePath, cacheFilePath, function() {
813
- releaseLockFn();
814
- callback({
815
- "message": "Failed to parse cache info for path: " + cacheFilePath
816
- });
817
- });
818
- }
819
-
820
- releaseLockFn();
821
- callback(null, stream, cacheInfo);
822
- });
823
- });
824
- });
825
- };
826
-
827
- var handleCachePageInvalidate = function(host, repositoryId, branchId, pageCacheKey, callback)
828
- {
829
- // this is the path to the page folder
830
- // if we blow away this folder, we blow away all page fragments as well
831
- var pageBasePath = path.join("wcm");
832
- if (repositoryId)
833
- {
834
- pageBasePath = path.join(pageBasePath, "repositories", repositoryId);
835
- }
836
- if (branchId)
837
- {
838
- pageBasePath = path.join(pageBasePath, "branches", branchId);
839
- }
840
- if (pageCacheKey)
841
- {
842
- pageBasePath = path.join(pageBasePath, "pages", pageCacheKey);
843
- }
844
-
845
- // list all of the hosts
846
- var stores = require("../stores/stores");
847
- stores.produce(host, function (err, stores) {
848
-
849
- if (err) {
850
- return callback(err);
851
- }
852
-
853
- _handleCachePageInvalidate(stores.content, pageBasePath, function() {
854
- callback();
855
- });
856
- });
857
- };
858
-
859
- var _handleCachePageInvalidate = function(contentStore, pageBasePath, callback)
860
- {
861
- _LOCK(contentStore, _lock_identifier(pageBasePath), function(err, releaseLockFn) {
862
-
863
- contentStore.existsDirectory(pageBasePath, function (exists) {
864
-
865
- if (!exists)
866
- {
867
- releaseLockFn();
868
- return callback();
869
- }
870
-
871
- contentStore.removeDirectory(pageBasePath, function () {
872
- releaseLockFn();
873
- callback();
874
- });
875
- });
876
- });
877
- };
878
-
879
- var _lock_identifier = function()
880
- {
881
- var args = Array.prototype.slice.call(arguments);
882
-
883
- return args.join("_");
884
- };
885
-
886
- var _LOCK = function(store, lockIdentifier, workFunction)
887
- {
888
- var lockKeys = [];
889
- if (store) {
890
- lockKeys.push(store.id);
891
- }
892
- if (lockIdentifier) {
893
- lockKeys.push(lockIdentifier);
894
- }
895
-
896
- process.locks.lock(lockKeys.join("_"), workFunction);
897
- };
898
-
899
- var bindSubscriptions = function()
900
- {
901
- if (process.broadcast)
902
- {
903
- // NOTE: all page rendition invalidation based on changes to nodes happens on the server side within the
904
- // Cloud CMS API itself. Cloud CMS maintains a master record of how page renditions and nodes are related.
905
- // When a node changes in Cloud CMS, the API finds any page renditions that need to invalidate and then
906
- // sends those along as page rendition invalidation events. These are handled here...
907
-
908
- // LISTEN: "invalidate_page_rendition"
909
- process.broadcast.subscribe("invalidate_page_rendition", function (message, channel, invalidationDone) {
910
-
911
- if (!invalidationDone) {
912
- invalidationDone = function() { };
913
- }
914
-
915
- process.log("HEARD: invalidate_page_rendition");
916
-
917
- var clearFragmentCacheFn = function(message)
918
- {
919
- var pageCacheKey = message.pageCacheKey;
920
- var fragmentCacheKey = message.fragmentCacheKey;
921
-
922
- var scope = message.scope;
923
- var host = message.host;
924
-
925
- var repositoryId = message.repositoryId;
926
- var branchId = message.branchId;
927
- // at the moment, caching on disk uses "master" for the master branch instead of the actual branch id
928
- var isMasterBranch = message.isMasterBranch;
929
-
930
- return function(done3)
931
- {
932
- if (scope === "FRAGMENT" || scope === "ALL")
933
- {
934
- var buildFragmentsBasePath = function(branchId) {
935
- if (pageCacheKey) {
936
- return path.join("wcm", "repositories", repositoryId, "branches", branchId, "pages", pageCacheKey, "fragments");
937
- }
938
-
939
- return path.join("duster", "repositories", repositoryId, "branches", branchId, "fragments");
940
- };
941
-
942
- if (support.isFragmentCacheEnabled())
943
- {
944
- // for master branch, we make a silent attempt using "master" as the branch ID
945
- if (isMasterBranch)
946
- {
947
- var fragmentsBasePath = buildFragmentsBasePath("master");
948
- support.handleCacheFragmentInvalidate(host, fragmentsBasePath, fragmentCacheKey, function(err) {
949
- // done
950
- });
951
- }
952
-
953
- var fragmentsBasePath = buildFragmentsBasePath(branchId);
954
- support.handleCacheFragmentInvalidate(host, fragmentsBasePath, fragmentCacheKey, function(err, invalidatedPath) {
955
-
956
- if (!err) {
957
- process.log(" > Invalidated fragment [host: " + host + ", path: " + invalidatedPath + "]");
958
- }
959
-
960
- return done3();
961
- });
962
- }
963
- else
964
- {
965
- return done3();
966
- }
967
- }
968
- else
969
- {
970
- done3();
971
- }
972
- }
973
- }(message);
974
-
975
- var clearPageCacheFn = function(message)
976
- {
977
- var pageCacheKey = message.pageCacheKey;
978
- var scope = message.scope;
979
- var host = message.host;
980
-
981
- var repositoryId = message.repositoryId;
982
- var branchId = message.branchId;
983
- // at the moment, caching on disk uses "master" for the master branch instead of the actual branch id
984
- var isMasterBranch = message.isMasterBranch;
985
-
986
- return function(done2)
987
- {
988
- if (scope === "PAGE" || scope === "ALL")
989
- {
990
- if (isPageCacheEnabled())
991
- {
992
- // for master branch, we make a silent attempt using "master" as the branch ID
993
- if (isMasterBranch)
994
- {
995
- handleCachePageInvalidate(host, repositoryId, "master", pageCacheKey, function() {
996
-
997
- });
998
- }
999
-
1000
- handleCachePageInvalidate(host, repositoryId, branchId, pageCacheKey, function(err) {
1001
-
1002
- if (!err) {
1003
- process.log(" > Invalidated page [host: " + host + ", repository: " + repositoryId + ", branch: " + branchId + ", page: " + pageCacheKey + "]");
1004
- }
1005
-
1006
- return done2();
1007
- });
1008
- }
1009
- else
1010
- {
1011
- return done2();
1012
- }
1013
- }
1014
- else
1015
- {
1016
- done2();
1017
- }
1018
-
1019
- }
1020
- }(message);
1021
-
1022
- async.waterfall([
1023
- clearFragmentCacheFn,
1024
- clearPageCacheFn
1025
- ], function() {
1026
- invalidationDone();
1027
- });
1028
-
1029
- });
1030
-
1031
- // LISTEN: "invalidate_all_page_renditions"
1032
- process.broadcast.subscribe("invalidate_all_page_renditions", function (message, channel, invalidationDone) {
1033
-
1034
- if (!invalidationDone) {
1035
- invalidationDone = function() { };
1036
- }
1037
-
1038
- // process.log("HEARD: invalidate_all_page_renditions");
1039
-
1040
- var clearFragmentCacheFn = function(message)
1041
- {
1042
- //var host = message.host;
1043
- var scope = message.scope;
1044
-
1045
- return function(done2)
1046
- {
1047
- if (scope === "FRAGMENT" || scope === "ALL")
1048
- {
1049
- // TODO: fragment level invalidation
1050
- return done2();
1051
- }
1052
- else
1053
- {
1054
- done2();
1055
- }
1056
- }
1057
- }(message);
1058
-
1059
- var clearPageCacheFn = function(message)
1060
- {
1061
- var host = message.host;
1062
- var scope = message.scope;
1063
-
1064
- return function(done2)
1065
- {
1066
- if (scope === "PAGE" || scope === "ALL")
1067
- {
1068
- if (isPageCacheEnabled())
1069
- {
1070
- handleCachePageInvalidate(host, null, null, null, function(err) {
1071
-
1072
- if (!err) {
1073
- process.log(" > Invalidated all pages [host: " + host + "]");
1074
- }
1075
-
1076
- return done2();
1077
- });
1078
- }
1079
- else
1080
- {
1081
- return done2();
1082
- }
1083
- }
1084
-
1085
- }
1086
- }(message);
1087
-
1088
- async.waterfall([
1089
- clearFragmentCacheFn,
1090
- clearPageCacheFn
1091
- ], function() {
1092
- invalidationDone();
1093
- });
1094
-
1095
- });
1096
- }
1097
- };
1098
-
1099
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
1100
- //
1101
- // RESULTING OBJECT
1102
- //
1103
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////
1104
-
1105
- var r = {};
1106
-
1107
- r.wcmInterceptor = function()
1108
- {
1109
- return util.createInterceptor("wcm", function(req, res, next, stores, cache, configuration) {
1110
-
1111
- if (!isEnabled())
1112
- {
1113
- return next();
1114
- }
1115
-
1116
- if (!req.gitana)
1117
- {
1118
- return next();
1119
- }
1120
-
1121
- if (req.method.toLowerCase() !== "get")
1122
- {
1123
- return next();
1124
- }
1125
-
1126
- // ensures that the WCM PAGES cache is preloaded for the current branch
1127
- // pages must be loaded ahead of time so that matching can be performed
1128
- preloadPages(req, function(err, pages) {
1129
-
1130
- if (err)
1131
- {
1132
- return next(err);
1133
- }
1134
-
1135
- var offsetPath = req.path;
1136
-
1137
- // find a page for this path
1138
- // this looks at wcm:page urls and finds a best fit, extracting tokens
1139
- findMatchingPage(pages, offsetPath, function(err, page, tokens, matchingPath) {
1140
-
1141
- if (err)
1142
- {
1143
- req.log("An error occurred while attempting to match path: " + offsetPath);
1144
-
1145
- return next();
1146
- }
1147
-
1148
- // if we found a page, then store it on the request and adjust the request to reflect things we extract
1149
- if (page)
1150
- {
1151
- req.page = page;
1152
-
1153
- // ensure empty set of page attributes
1154
- if (!req.pageAttributes) {
1155
- req.pageAttributes = {};
1156
- }
1157
-
1158
- req.pageTokens = tokens ? tokens : {};
1159
- req.pageMatchingPath = matchingPath;
1160
-
1161
- // override the param() method so that token values are handed back as well
1162
- var _param = req.param;
1163
- req.param = function(name) {
1164
-
1165
- var v = undefined;
1166
-
1167
- if (this.pageTokens)
1168
- {
1169
- v = this.pageTokens[name];
1170
- }
1171
- if (!v)
1172
- {
1173
- v = _param.call(this, name);
1174
- }
1175
-
1176
- return v;
1177
- };
1178
-
1179
- }
1180
-
1181
- next();
1182
- });
1183
- });
1184
- });
1185
- };
1186
-
1187
- /**
1188
- * Provides WCM page retrieval from Cloud CMS.
1189
- *
1190
- * @param configuration
1191
- * @return {Function}
1192
- */
1193
- r.wcmHandler = function()
1194
- {
1195
- // bind listeners for broadcast events
1196
- bindSubscriptions();
1197
-
1198
- // wcm handler
1199
- return util.createHandler("wcm", function(req, res, next, stores, cache, configuration) {
1200
-
1201
- if (!isEnabled())
1202
- {
1203
- return next();
1204
- }
1205
-
1206
- if (!req.gitana)
1207
- {
1208
- return next();
1209
- }
1210
-
1211
- var page = req.page;
1212
- if (!page)
1213
- {
1214
- return next();
1215
- }
1216
-
1217
- var offsetPath = req.path;
1218
-
1219
- var tokens = req.pageTokens;
1220
- var matchingPath = req.pageMatchingPath;
1221
-
1222
- var webStore = stores.web;
1223
-
1224
- // either serve the page back from cache or run dust over it
1225
- // after dust is run over it, we can stuff it into cache for the next request to benefit from
1226
- var descriptor = {
1227
- "url": req.protocol + "://" + req.domainHost + offsetPath,
1228
- "host": req.domainHost,
1229
- "protocol": req.protocol,
1230
- "path": offsetPath,
1231
- "params": req.query ? req.query : {},
1232
- "pageAttributes": req.pageAttributes ? req.pageAttributes : {},
1233
- "headers": req.headers,
1234
- "matchingTokens": tokens,
1235
- "matchingPath": matchingPath,
1236
- "matchingUrl": req.protocol + "://" + req.domainHost + matchingPath,
1237
- "matchingPageId": page._doc,
1238
- "matchingPageTitle": page.title ? page.title : page._doc,
1239
- "scope": "PAGE"
1240
- };
1241
-
1242
- if (req.repositoryId) {
1243
- descriptor.repositoryId = req.repositoryId;
1244
- }
1245
-
1246
- if (req.branchId) {
1247
- descriptor.branchId = req.branchId;
1248
- }
1249
-
1250
- // support stripping out specific parameters
1251
- var cacheKeyConfig = getPageCacheKeyConfig(req);
1252
- if (cacheKeyConfig.params)
1253
- {
1254
- // exclude all by default?
1255
- if (cacheKeyConfig.params.excludeAll)
1256
- {
1257
- descriptor.params = {};
1258
- }
1259
-
1260
- // exclude specific parameters
1261
- if (cacheKeyConfig.params.excludes && cacheKeyConfig.params.excludes.length > 0)
1262
- {
1263
- for (var i = 0; i < cacheKeyConfig.params.excludes.length; i++)
1264
- {
1265
- delete descriptor.params[cacheKeyConfig.params.excludes[i]];
1266
- }
1267
- }
1268
-
1269
- // include specific parameters
1270
- if (cacheKeyConfig.params.includes && cacheKeyConfig.params.includes.length > 0)
1271
- {
1272
- var keepers = {};
1273
-
1274
- for (var i = 0; i < cacheKeyConfig.params.includes.length; i++)
1275
- {
1276
- var v = descriptor.params[cacheKeyConfig.params.includes[i]];
1277
- if (v)
1278
- {
1279
- keepers[cacheKeyConfig.params.includes[i]] = v;
1280
- }
1281
- }
1282
-
1283
- descriptor.params = keepers;
1284
- }
1285
- }
1286
-
1287
- // generate a page cache key from the descriptor (and store on the descriptor)
1288
- var pageCacheKey = util.generatePageCacheKey(descriptor);
1289
- descriptor.pageCacheKey = pageCacheKey;
1290
-
1291
- // base path for storage
1292
- var pageBasePath = path.join("wcm", "repositories", req.repositoryId, "branches", req.branchId, "pages", pageCacheKey);
1293
-
1294
- // is this already in cache?
1295
- handleCachePageRead(req, descriptor, pageBasePath, function(err, readStream, cacheInfo) {
1296
-
1297
- if (!err && readStream)
1298
- {
1299
- // yes, we found it in cache, so we'll simply pipe it back from disk
1300
-
1301
- // log cache hit
1302
- if (isPageCacheEnabled(req))
1303
- {
1304
- req.log("Page Cache Hit: " + req.url);
1305
- }
1306
-
1307
- // set "cloudcms-cache-hit" header
1308
- util.setHeaderOnce(res, "cloudcms-wcm-cache-hit", "true");
1309
-
1310
- // SPECIAL HANDLING FOR OFFSET PATH "/" TO SUPPORT HTML CONTENT TYPE HEADER
1311
- if (offsetPath === "/") {
1312
- offsetPath += "index.html";
1313
- }
1314
-
1315
- util.status(res, res.statusCode);
1316
- util.applyResponseContentType(res, cacheInfo, offsetPath);
1317
- return readStream.pipe(res);
1318
- }
1319
-
1320
- // otherwise, we need to run dust...
1321
-
1322
- // log cache miss
1323
- if (isPageCacheEnabled(req))
1324
- {
1325
- req.log("Page Cache Miss: " + req.url);
1326
- }
1327
-
1328
- // set "cloudcms-cache-hit" header
1329
- util.setHeaderOnce(res, "cloudcms-wcm-cache-hit", "false");
1330
-
1331
- var runDust = function()
1332
- {
1333
- // TODO: block here in case another thread is trying to dust this page at the same time?
1334
-
1335
- if (!req.helpers) {
1336
- req.helpers = {};
1337
- }
1338
- req.helpers.page = page;
1339
-
1340
- // build the model
1341
- var model = req.model;
1342
- if (!model) {
1343
- model = {};
1344
- }
1345
- model.page = {};
1346
- model.template = {
1347
- "path": page.templatePath
1348
- };
1349
- model.request = {
1350
- "tokens": tokens,
1351
- "matchingPath": matchingPath
1352
- };
1353
-
1354
- // model stores reference to page descriptor
1355
- model._page_descriptor = descriptor;
1356
-
1357
- // model stores a base path that we'll use for storage of fragments
1358
- model._fragments_base_path = path.join(pageBasePath, "fragments");
1359
-
1360
- // page keys to copy
1361
- for (var k in page) {
1362
- if (k.indexOf("_") === 0) {
1363
- } else {
1364
- model.page[k] = page[k];
1365
- }
1366
- }
1367
-
1368
- // set _doc and id (equivalent)
1369
- model.page._doc = model.page.id = page._doc;
1370
-
1371
- // dust it up
1372
- duster.execute(req, webStore, page.templatePath, model, function (err, text, dependencies, stats) {
1373
-
1374
- if (err)
1375
- {
1376
- // something screwed up during the dust execution
1377
- // it might be a bad template
1378
- req.log("Failed to process dust template: " + page.templatePath + " for model: " + JSON.stringify(model, null, " ") + ", err: " + JSON.stringify(err, null, " "));
1379
-
1380
- // assume a 500 error code
1381
- if (!err.status) {
1382
- err.status = 500;
1383
- }
1384
- if (!err.message) {
1385
- err.message = "There was a problem rendering this web page, please contact your administrator";
1386
- }
1387
-
1388
- return next(err);
1389
- }
1390
-
1391
- // set "cloudcms-cache-hit" header
1392
- util.setHeaderOnce(res, "cloudcms-wcm-cache-write", "true");
1393
-
1394
- // we now have the result (text) and the dependencies that this page flagged (dependencies)
1395
- // use these to write to the page cache
1396
- // don't wait for this to complete, assume it completes in background
1397
- handleCachePageWrite(req, res, descriptor, pageBasePath, dependencies, text, function(err, pageFilePath, pageCacheInfo) {
1398
-
1399
- //res.status(200);
1400
- //res.send(text);
1401
-
1402
- req.log("Page Cache Write: " + req.url);
1403
- });
1404
-
1405
- // SPECIAL HANDLING FOR OFFSET PATH "/" TO SUPPORT HTML CONTENT TYPE HEADER
1406
- if (offsetPath === "/") {
1407
- offsetPath += "index.html";
1408
- }
1409
-
1410
- // send back results right away
1411
- util.status(res, res.statusCode);
1412
- util.applyResponseContentType(res, null, offsetPath);
1413
- util.setHeaderOnce(res, "cloudcms-dust-execution-time", stats.dustExecutionTime);
1414
- res.send(text);
1415
- });
1416
- };
1417
- runDust();
1418
- });
1419
- });
1420
- };
1421
-
1422
- /**
1423
- * Manual method for resetting cache.
1424
- *
1425
- * @param host
1426
- * @param repositoryId
1427
- * @param branchId
1428
- * @param pageCacheKey
1429
- * @param callback
1430
- */
1431
- r.resetCache = function(host, repositoryId, branchId, pageCacheKey, callback)
1432
- {
1433
- handleCachePageInvalidate(host, repositoryId, branchId, pageCacheKey, callback);
1434
- };
1435
-
1436
- return r;
1437
- }();