1 // The Antville Project
  2 // http://code.google.com/p/antville
  3 //
  4 // Copyright 2007-2011 by Tobi Schäfer.
  5 //
  6 // Copyright 2001–2007 Robert Gaggl, Hannes Wallnöfer, Tobi Schäfer,
  7 // Matthias & Michael Platzer, Christoph Lincke.
  8 //
  9 // Licensed under the Apache License, Version 2.0 (the ``License'');
 10 // you may not use this file except in compliance with the License.
 11 // You may obtain a copy of the License at
 12 //
 13 //    http://www.apache.org/licenses/LICENSE-2.0
 14 //
 15 // Unless required by applicable law or agreed to in writing, software
 16 // distributed under the License is distributed on an ``AS IS'' BASIS,
 17 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 18 // See the License for the specific language governing permissions and
 19 // limitations under the License.
 20 //
 21 // $Revision$
 22 // $Author$
 23 // $Date$
 24 // $URL$
 25 
 26 /**
 27  * @fileOverview Defines the Root prototype.
 28  */
 29 
 30 /** @constant */
 31 Root.VERSION = (function(versionString, buildDate) {
 32    // A valid version string is e.g. "1.2.3alpha.4711".
 33    // Repositories could add something like "-compatible" to it,
 34    // FIXME: This should be refactored for modular extension.
 35    var re = /^(\d+)\.(\d+)(?:\.(\d+))?(.+)?\.(\d+)(?:-(.*))?$/;
 36    var parts = re.exec(versionString);
 37    if (parts) {
 38       var result = {
 39          parts: parts,
 40          toString: function() {return parts[0]},
 41          major: parseInt(parts[1]),
 42          revision: parseInt(parts[5]),
 43          date: new Date(buildDate)
 44       };
 45       result.minor = result.major + parseInt(parts[2] || 0) / 10;
 46       result.bugfix = result.minor + "." + (parts[3] || 0);
 47       result.development = parts[4] || "";
 48       result["default"] = result[parts[3] ? "bugfix" : "minor"] + result.development +
 49             (parts[6] ? "-" + parts[6] : String.EMPTY);
 50       return result;
 51    }
 52    return versionString;
 53 })("@version@.@revision@", "@buildDate@");
 54 
 55 this.handleMetadata("creationDelay");
 56 this.handleMetadata("creationScope");
 57 this.handleMetadata("notificationScope");
 58 this.handleMetadata("phaseOutGracePeriod");
 59 this.handleMetadata("phaseOutNotificationPeriod");
 60 this.handleMetadata("phaseOutMode");
 61 this.handleMetadata("probationPeriod");
 62 this.handleMetadata("quota");
 63 this.handleMetadata("replyTo");
 64 
 65 /**
 66  * Antville’s Root prototype is an extent of the Site prototype.
 67  * The singleton instance root is the first created site (“www”).
 68  * @name Root
 69  * @constructor
 70  * @extends Site
 71  * @property {Site[]} _children 
 72  * @property {Admin} admin
 73  * @property {User[]} admins
 74  * @property {Api} api
 75  * @property {String} creationDelay
 76  * @property {String} creationScope
 77  * @property {String} notificationScope
 78  * @property {String} phaseOutGracePeriod
 79  * @property {String} phaseOutMode
 80  * @property {String} phaseOutNotificationPeriod
 81  * @property {String} probationPeriod
 82  * @property {String} quote
 83  * @property {String} replyTo
 84  * @property {Site[]} sites
 85  * @property {Site[]} updates
 86  * @property {User[]} users
 87  */
 88 
 89 /**
 90  * The permission framework handler for the Root prototype.
 91  * @param {String} action
 92  * @returns {Boolean}
 93  */
 94 Root.prototype.getPermission = function(action) {
 95    if (action.contains("admin")) {
 96       return User.require(User.PRIVILEGED);
 97    }
 98    switch (action) {
 99       case "debug":
100       case "default.hook":
101       case "health":
102       case "jala.test":
103       case "jala.test.css":
104       case "mrtg":
105       case "sites":
106       case "updates.xml":
107       return true;
108       case "create":
109       return this.getCreationPermission();
110    }
111    return Site.prototype.getPermission.apply(this, arguments);
112 }
113 
114 Root.prototype.main_action = function() {
115    if (this.users.size() < 1) {
116       this.title = "Antville";
117       this.created = new Date;
118       this.replyTo = "root@localhost";
119       this.locale = java.util.Locale.getDefault().getLanguage();
120       this.timeZone = java.util.TimeZone.getDefault().getID();
121       this.layout.reset();
122       res.redirect(this.members.href("register"));
123    } else if (session.user && this.members.owners.size() < 1) {
124       this.creator = this.modifier = this.layout.creator = 
125             this.layout.modifier = session.user;
126       this.created = this.modified = this.layout.created = 
127             this.layout.modified = new Date;
128       session.user.role = User.PRIVILEGED;
129       res.handlers.membership.role = Membership.OWNER;
130    }
131    return Site.prototype.main_action.apply(this);
132 }
133 
134 Root.prototype.error_action = function() {
135    res.message = String.EMPTY;
136    var param = res.error ? res : session.data;
137    res.status = param.status || 500;
138    res.data.title = gettext("{0} {1} Error", root.getTitle(), param.status);
139    res.data.body = root.renderSkinAsString("$Root#error", param);
140    res.handlers.site.renderSkin("Site#page");
141    return;
142 }
143 
144 Root.prototype.notfound_action = function() {
145    res.status = 404;
146    res.data.title = gettext("{0} {1} Error", root.getTitle(), res.status);
147    res.data.body = root.renderSkinAsString("$Root#notfound", req);
148    res.handlers.site.renderSkin("Site#page");
149    return;
150 }
151 
152 Root.prototype.create_action = function() {
153    if (req.postParams.create) {
154       try {
155          var site = Site.add(req.postParams);
156          Membership.add(session.user, Membership.OWNER, site);
157          root.admin.log(root, "Added site " + site.name);
158          res.message = gettext("Successfully created your site.");
159          res.redirect(site.href());
160       } catch (ex) {
161          res.message = ex;
162          app.log(ex);
163       }
164    }
165 
166    // Cannot use res.handlers.site because somehow it is always root 
167    res.handlers.newSite = new Site;
168    res.handlers.example = new Site;
169    res.handlers.example.name = "foo";
170    res.data.action = this.href(req.action);
171    res.data.title = gettext("Add Site");
172    res.data.body = root.renderSkinAsString("$Root#create");
173    root.renderSkin("Site#page");
174    return;
175 }
176 
177 Root.prototype.sites_action = function() {
178    var now = new Date;
179    if (!this.cache.sites || (now - this.cache.sites.modified > Date.ONEHOUR)) {
180       var sites = this.sites.list();
181       sites.sort(new String.Sorter("title"));
182       this.cache.sites = {list: sites, modified: now};
183    }
184    res.data.list = renderList(this.cache.sites.list, 
185          "$Site#listItem", 25, req.queryParams.page);
186    res.data.pager = renderPager(this.cache.sites.list, 
187          this.href(req.action), 25, req.queryParams.page);
188    res.data.title = gettext("Public Sites");
189    res.data.body = this.renderSkinAsString("$Root#sites");
190    root.renderSkin("Site#page");
191    return;
192 }
193 
194 Root.prototype.updates_xml_action = function() {
195    res.contentType = "text/xml"; 
196    var now = new Date;
197    var feed = new rome.SyndFeedImpl();   
198    feed.setFeedType("rss_2.0");
199    feed.setLink(root.href());
200    feed.setTitle("Recently updated sites at " + root.title);
201    feed.setDescription(root.tagline || String.EMPTY);
202    feed.setLanguage(root.locale.replace("_", "-"));
203    feed.setPublishedDate(now);
204    var entries = new java.util.ArrayList();
205    var entry, description;
206    var sites = root.updates.list(0, 25).sort(Number.Sorter("modified", 
207          Number.Sorter.DESC));
208    for each (var site in sites) {
209       entry = new rome.SyndEntryImpl();
210       entry.setTitle(site.title);
211       entry.setLink(site.href());
212       entry.setAuthor(site.creator.name);
213       entry.setPublishedDate(site.modified);
214       description = new rome.SyndContentImpl();
215       description.setType("text/plain");
216       description.setValue(site.tagline);
217       entry.setDescription(description);
218       entries.add(entry);
219    }
220    feed.setEntries(entries);
221    var output = new rome.SyndFeedOutput();
222    //output.output(feed, res.servletResponse.writer); return;
223    var xml = output.outputString(feed);
224    res.write(xml); //injectXslDeclaration(xml));
225    return;
226 }
227 
228 // Sitemap for Google Webmaster Tools
229 // (Unfortunately, utterly useless.)
230 Root.prototype.sitemap_xml_action = function() {
231    res.contentType = "text/xml";
232    res.writeln('<?xml version="1.0" encoding="UTF-8"?>');
233    res.writeln('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
234    this.sites.forEach(function() {
235       res.writeln('<url>');
236       res.writeln('<loc>' + this.href() + '</loc>');
237       if (this.modified) {
238          res.writeln('<lastmod>' + this.modified.format("yyyy-MM-dd") + '</lastmod>');
239       }
240       res.writeln('</url>');
241    });
242    res.writeln('</urlset>');
243    return;
244 }
245 
246 Root.prototype.health_action = function() {
247    var jvm = java.lang.Runtime.getRuntime();
248    var totalMemory = jvm.totalMemory() / 1024 / 1024;
249    var freeMemory = jvm.freeMemory()  / 1024 / 1024;
250 
251    var param = {
252       uptime: formatNumber((new Date - app.upSince.getTime()) / 
253             Date.ONEDAY, "0.##"),
254       freeMemory: formatNumber(freeMemory),
255       totalMemory: formatNumber(totalMemory),
256       usedMemory: formatNumber(totalMemory - freeMemory),
257       sessions: formatNumber(app.countSessions()),
258       cacheSize: formatNumber(getProperty("cacheSize"))
259    };
260 
261    for each (key in ["activeThreads", "freeThreads", "requestCount", 
262          "errorCount", "xmlrpcCount", "cacheusage"]) {
263       param[key] = formatNumber(app[key]);
264    }
265    
266    param.errorRatio = formatNumber(100 * app.errorCount / app.requestCount);
267    param.errorRatioPerUnit = formatNumber(Admin.health.errorsPerUnit / Admin.health.requestsPerUnit);
268 
269    if (Admin.health) {
270       param.requestsPerUnit = formatNumber(Admin.health.requestsPerUnit);
271       param.errorsPerUnit = formatNumber(Admin.health.errorsPerUnit);
272    }
273    
274    param.entries = app.data.entries.length;
275    param.mails = app.data.mails.length;
276    param.requests = 0;
277    for (var i in app.data.requests) {
278       param.requests += 1;
279    }
280    param.callbacks = app.data.callbacks.length;
281 
282    res.data.title = gettext("{0} Health", root.getTitle());
283    res.data.body = this.renderSkinAsString("$Root#health", param);
284    this.renderSkin("Site#page");
285 }
286 
287 Root.prototype.mrtg_action = function() {
288    res.contentType = "text/plain";
289    var target = req.queryParams.target; 
290    if (!target) {
291       return;
292    }
293    switch (target) {
294       case "cache":
295       res.writeln(0);
296       res.writeln(app.cacheusage * 100 / getProperty("cacheSize", 100));
297       break;
298       case "threads":
299       res.writeln(0);
300       res.writeln(app.activeThreads * 100 / app.freeThreads);
301       break;
302       case "requests":
303       res.writeln(app.errorCount);
304       res.writeln(app.requestCount);
305       break;
306       case "users":
307       res.writeln(app.countSessions());
308       res.writeln(root.users.size());
309       break;
310       case "postings":
311       res.writeln(0);
312       var sql = new Sql;
313       sql.retrieve(Sql.COUNT, "content");
314       sql.traverse(function() {
315          res.writeln(this.count);
316       });
317       break;
318       case "uploads":
319       var sql = new Sql;
320       sql.retrieve(Sql.COUNT, "file");
321       sql.traverse(function() {
322          res.writeln(this.count);
323       });
324       sql.retrieve(Sql.COUNT, "image");
325       sql.traverse(function() {
326          res.writeln(this.count);
327       });
328       break;
329    }
330    res.writeln(app.upSince);
331    res.writeln("mrtg." + target + " of Antville version " + Root.VERSION);
332    return;
333 } 
334 
335 /**
336  * Catch some undefined macro handlers, then delegate to the super prototype.
337  * @param {String} name
338  * @returns {HopObject}
339  * @see Site#getMacroHandler
340  */
341 Root.prototype.getMacroHandler = function(name) {
342    switch (name) {
343       case "admin":
344       case "api":
345       case "sites":
346       return this[name];
347    }
348    return Site.prototype.getMacroHandler.apply(this, arguments);
349 }
350 
351 /**
352  * 
353  * @param {String} name
354  * @returns {Object}
355  * @see Site#getFormOptions
356  */
357 Root.prototype.getFormOptions = function(name) {
358    switch (name) {
359       case "creationScope":
360       return Admin.getCreationScopes();
361       case "notificationScope":
362       return Admin.getNotificationScopes();
363       case "phaseOutMode":
364       return Admin.getPhaseOutModes();
365    }
366    return Site.prototype.getFormOptions.apply(root, arguments);
367 }
368 
369 /**
370  * @returns {Boolean}
371  */
372 Root.prototype.getCreationPermission = function() {
373    var user;
374    if (!(user = session.user)) {
375       return false;
376    } if (User.require(User.PRIVILEGED)) {
377       return true;
378    }
379    
380    switch (root.creationScope) {
381       case User.PRIVILEGED:
382       return false;
383       case User.TRUSTED:
384       return User.require(User.TRUSTED);
385       default:
386       case User.REGULAR:
387       var now = new Date, delta;
388       if (root.probationPeriod) {
389          delta = root.probationPeriod - Math.floor((now - 
390                user.created) / Date.ONEDAY);
391          if (delta > 0) {
392             session.data.error = gettext("You need to wait {0} before you are allowed to create a new site.", 
393                   ngettext("{0} day", "{0} days", delta));
394             return false;
395          }
396       } 
397       if (root.creationDelay && user.sites.count() > 0) {
398          delta = root.creationDelay - Math.floor((now - 
399                user.sites.get(0).created) / Date.ONEDAY);
400          if (delta > 0) {
401             session.data.error = gettext("You need to wait {0} before you are allowed to create a new site.", 
402                   ngettext("{0} day", "{0} days", delta));
403             return false;
404          }
405       }
406    }
407    return true;
408 }
409 
410 /**
411  * 
412  * @param {Object} param
413  * @param {String} name
414  */
415 Root.prototype.static_macro = function(param, name) {
416    return this.getStaticUrl(name);
417 }
418