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