From 2d278e827242fa2cb666280ea8de9550628287af Mon Sep 17 00:00:00 2001 From: p3k Date: Thu, 5 Apr 2007 15:33:58 +0000 Subject: [PATCH] Initial commit of helma.Group to where it belongs IMHO (helma.Group is the JavaScript wrapper for the HelmaGroup extension which currently still resides in helmagroups/apps/groupdemo/Global/GroupExtension.js) Although the global variable "groups" still contains the manager object to handle all group objects, the inner structure was streamlined to conform with Helma's library structure (helma namespace) and to generally prevent namespace cluttering. --- helma/Group.js | 867 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 867 insertions(+) create mode 100644 helma/Group.js diff --git a/helma/Group.js b/helma/Group.js new file mode 100644 index 00000000..d838dcb4 --- /dev/null +++ b/helma/Group.js @@ -0,0 +1,867 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2006 Helma Software. All Rights Reserved. + * + * $RCSfile: Html.js,v $ + * $Author: robert $ + * $Revision: 1.4 $ + * $Date: 2007/01/30 14:49:57 $ + */ + +/** + * @fileoverview A JavaScript library wrapping + * Packages.helma.extensions.helmagroups + */ + +// Define the global namespace if not existing +if (!global.helma) { + global.helma = {}; +} + +/** + * Constructs a new helma.Group Object. + * This is what is retrieved through groups.get(groupName), + * wrapping the root object of each group tree. + * @param {FIXME} javaGroup FIXME + */ +helma.Group = function(javaGroup) { + // private variable containing the wrapper object + var groupRoot = new helma.Group.Wrapper(javaGroup.getRoot()); + + /** + * @returns the wrapped java object Group + */ + this.getJavaObject = function() { + return javaGroup; + }; + + /** + * sets a key/value pair on the group's root, + * wraps the function of the wrapper object + */ + this.set = function(key, val, sendMode) { + groupRoot.set(key, val, sendMode); + return; + }; + + /** + * removes a key from the group's root, + * wraps the function of the root GroupObject + */ + this.remove = function(key, sendMode) { + return groupRoot.remove(key, sendMode); + }; + + /** + * retrieves a key from the group's root, + * wraps the function of the root GroupObject + */ + this.get = function(key) { + return groupRoot.get(key); + }; + + /** + * @see helma.Group.Wrapper.listChildren + */ + this.listChildren = function() { + return groupRoot.listChildren(); + }; + + /** + * @see helma.Group.Wrapper.listProperties + */ + this.listProperties = function() { + return groupRoot.listProperties(); + }; + + /** + * @see helma.Group.Wrapper.countChildren + */ + this.countChildren = function() { + return groupRoot.countChildren(); + }; + + /** + * @see helma.Group.Wrapper.countProperties + */ + this.countProperties = function() { + return groupRoot.countProperties(); + }; + + /** + * calls a function in all connected applications + * (to be specific: in all registered localClients). + * @param method name of the method in xmlrpc-style: test + * is called as root.test(), stories.137.render + * is called as root.stories.get("137").render() etc etc. + * @param argArr array of arguments to the remote method + * @param sendMode as defined for helma.Group.Wrapper + * @returns array of result objects + */ + this.callFunction = function(method, argArr, sendMode) { + groups.checkWriteAccess(javaGroup); + if (sendMode == null) { + sendMode = helma.Group.Wrapper.DEFAULT_GET; + } + var argVec = new java.util.Vector(); + for (var i=0; i + *
  • a String
  • + *
  • a String containing slashes
  • + *
  • an Array containing String keys
  • + * + * @param {Number} The value to set the property to. + * @param {Object} The mode to use when committing the change to + * the helma.Group + */ + this.set = function(key, val, sendMode) { + if (!key) { + throw "helma.Group.Wrapper.set(): key can't be null"; + } + checkWriteAccess(); + // check content type of value: + var ok = false; + if (val == null) + ok = true; + else if (typeof(val) == "string") + ok = true; + else if (typeof(val) == "number") + ok = true; + else if (typeof(val) == "boolean") + ok = true; + else if (val instanceof Date) + ok = true; + else if (val instanceof helma.Group.Wrapper) + ok = true; + if (ok == false) { + throw "only primitive values, Date and helma.Group.Wrapper allowed in helma.Group.Wrapper.set()"; + } + if (sendMode == null) { + sendMode = helma.Group.Wrapper.DEFAULT_GET; + } + + if (keyIsPath(key)) { + var obj = createPath(this, key); + if (obj != null) { + obj.set(getLastKeyElement(key), val, sendMode); + } + } else { + // set a property/child of this object + if (val == null) { + // null values aren't permitted in the group, + // setting a property to null is the same as deleting it + this.remove(key, sendMode); + } else if (val instanceof helma.Group.Wrapper) { + // replicate helma.Group.Wrapper + javaGroupObject.put(key, val.getJavaObject(), sendMode); + } else { + // put the primitive property (or maybe replicate, + // decision's up to helma.Group.Wrapper) + if (val instanceof Date) { + // convert javascript dates to java dates + val = new java.util.Date(val.getTime()); + } + javaGroupObject.put(key, val, sendMode); + } + } + return; + }; + + + + /** + * Removes a property or a child GroupObject from this instance. + * The Key may be a String, an Array or a String with separator characters ("/"). + * In the latter two cases the argument is considered a path and + * the function walks down that path to find the GroupObject and + * deletes it. + * @param {Object} key Either + * + * @param {Number} The mode to use when committing the change to + * the helma.Group + */ + this.remove = function(key, sendMode) { + checkWriteAccess(); + if (sendMode == null) { + sendMode = helma.Group.Wrapper.DEFAULT_GET; + } + if (keyIsPath(key)) { + var obj = walkPath(this, key); + if (obj != null) { + obj.remove(getLastKeyElement(key)); + } + } else { + javaGroupObject.remove(key, sendMode); + } + return; + }; + + + /** + * Returns either a property or a child GroupObject from + * this GroupObject instance. The key passed as argument + * may be a String, an Array containing Strings or a + * String containing separator characters ("/"). In the latter + * two cases the argument is considered a path and + * the function walks down that path to find the requested + * GroupObject. + * @param {Object} key Either + * + * @return Depending on the argument either the appropriate property + * value or a helma.Group.Wrapper + * @type Object + */ + this.get = function(key) { + if (key == null) { + return null; + } + if (keyIsPath(key)) { + var obj = walkPath(this, key); + if (obj != null) { + return obj.get(getLastKeyElement(key)); + } else { + return null; + } + } else if (javaGroupObject.hasProperty(key)) { + // we got a primitive property + var val = javaGroupObject.getProperty(key); + if (val instanceof java.util.Date) { + // convert java dates to javascript dates + val = new Date(val); + } + return val; + } else if (javaGroupObject.hasChild(key)) { + // we got a child object + return new helma.Group.Wrapper(javaGroupObject.getChild(key)); + } + return null; + }; + + + /** + * Gets a property from this GroupObject. The key passed as argument + * is always considered a property even if it contains a slash. + * This is actually a workaround for the fact that other + * instances of the group not using the javascript extension aren't forbidden + * to add properties containing a slash in the property's name. + * So, using this extension we can at least read the property. + * @param {String} key The name of the property to return + * @returns The value of the property + * @type Object + */ + this.getProperty = function(key) { + if (key == null) { + return null; + } else if (javaGroupObject.hasProperty(key)) { + // we got a primitive property + var val = javaGroupObject.getProperty(key); + if (val instanceof java.util.Date) { + // convert java dates to javascript dates + val = new Date(val); + } + return val; + } + return null; + } + + + /** + * Exchanges this GroupObject with the one passed + * as argument. This is done by exchanging the wrapped + * instance of helma.extensions.helmagroups.GroupObject + * @param {GroupObject} The GroupObject to use + * @returns The GroupObject with the exchanged wrapped java object + * @type GroupObject + */ + this.wrap = function(newGroupObject) { + checkWriteAccess(); + if (javaGroupObject.getState() != helmagroups.GroupObject.REPLICATED) { + throw "helma.Group.Wrapper.wrap() may only be called on replicated GroupObjects"; + } + if (newGroupObject == null || !(newGroupObject instanceof helma.Group.Wrapper)) { + throw "helma.Group.Wrapper.wrap() requires a helma.Group.Wrapper as an argument"; + } + javaGroupObject.wrap(newGroupObject.getJavaObject()); + return this; + }; + + /** + * Clones this GroupObject and returns it. + * This method should be considered if many properties + * of a GroupObject must be set or modified since every + * change to an already replicated GroupObject will + * result in immediate network traffic. Using unwrap + * one can modify several properties and then commit + * the GroupObject at once using {@link #wrap). + * @returns A clone of this GroupObject + * @type GroupObject + */ + this.unwrap = function() { + var javaGroupObjectClone = javaGroupObject.clone(); + javaGroupObjectClone.setChildren(new java.util.Hashtable()); + javaGroupObjectClone.setState(helmagroups.GroupObject.LOCAL); + javaGroupObjectClone.setPath(null); + return new helma.Group.Wrapper(javaGroupObjectClone); + }; + + /** + * Converts this GroupObject into a vanilla Object + * @returns An Object containing all properties of this GroupObject + * @type Object + */ + this.toJSObject = function() { + var key; + var obj = {}; + var e = javaGroupObject.properties(); + while(e.hasMoreElements()) { + obj[key = e.nextElement()] = javaGroupObject.getProperty(key); + } + return obj; + }; + + /** + * Returns an Array containing all child GroupObjects + * @returns An Array containing GroupObjects + * @type Array + */ + this.listChildren = function() { + var arr = []; + var e = javaGroupObject.children(); + while(e.hasMoreElements()) { + arr.push(e.nextElement()); + } + return arr; + }; + + /** + * Returns an Array containing all property + * names of this GroupObject instance + * @returns An Array containing property names + * @type Array + */ + this.listProperties = function() { + var arr = []; + var e = javaGroupObject.properties(); + while(e.hasMoreElements()) { + arr.push(e.nextElement()); + } + return arr; + }; + + /** + * Returns the number of child GroupObjects + * @returns The number of child GroupObjects of this + * helma.Group.Wrapper instance + * @type Number + */ + this.countChildren = function() { + var ht = javaGroupObject.getChildren(); + if (ht == null) { + return 0; + } else { + return ht.size(); + } + }; + + /** + * Returns the number of properties of this GroupObject + * @return The number of properties + * @type Number + */ + this.countProperties = function() { + var ht = javaGroupObject.getProperties(); + return (ht == null) ? 0 : ht.size(); + }; + + /** + * Returns true if the GroupObject is not replicated + * @returns True if this GroupObject is still local + * @type Boolean + */ + this.isLocal = function() { + return (javaGroupObject.getState() + == helmagroups.GroupObject.LOCAL); + }; + + /** @ignore */ + this.toString = function() { + return javaGroupObject.toString(); + }; + + return this; +}; + +/** + * Static properties of GroupObject constructor function. + * These values determine if and for how many confirmation of the + * group members this instance waits after a modification. + * These values are passed through to org.jgroups.blocks.GroupRequest, + * for further comments see the sourcecode of that class + */ +// wait just for the first response +helma.Group.Wrapper.GET_FIRST = 1; +// wait until all members have responded +helma.Group.Wrapper.GET_ALL = 2; +// wait for majority (50% + 1) to respond +helma.Group.Wrapper.GET_MAJORITY = 3; +// wait for majority of all members (may block!) +helma.Group.Wrapper.GET_ABS_MAJORITY = 4; +// don't wait for any response (fire & forget) +helma.Group.Wrapper.GET_NONE = 6; +// default: wait for all responses +helma.Group.Wrapper.DEFAULT_GET = helma.Group.Wrapper.GET_ALL; + +/** + * this is mounted as "groups". the root for all groups started in this application + */ +helma.Group.Manager = function() { + var helmagroups = Packages.helma.extensions.helmagroups; + var extension = helmagroups.GroupExtension.self; + + if (extension == null) { + throw("helma.Group.Manager requires the HelmaGroups Extension \ + located in lib/ext or the application's top-level directory \ + [http://adele.helma.org/download/helma/contrib/helmagroups/]"); + } + + + /** + * get a java object Group for a groupname. + * object is fetched regardless of connection status + * @returns null if group is not defined + */ + var getJavaGroup = function(name) { + return extension.checkAppLink(app.name).get(name); + }; + + + /** + * visible to scripting env: get a group, wrapped as a javascript helma.Group object. + * the group must be defined in app.properties: group.nameXX = + * and can then be accessed like this group.get("nameXX") + * @returns null if group is not defined or not connected + */ + this.get = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null || javaGroup.isConnected() == false) { + return null; + } else { + return new helma.Group(javaGroup); + } + }; + + + /** + * checks for write access to a group according to app.properties + * group.nameXX.writable must be true so that this function returns + * @param nameOrJGroup can be the name of a group or a java Group itself + * @throws an error if group is not writable + */ + this.checkWriteAccess = function(nameOrJGroup) { + var extension = helmagroups.GroupExtension.self; + var jAppLink = extension.checkAppLink(app.name); + if (nameOrJGroup instanceof helmagroups.Group) { + // arg was a Group + var javaGroup = nameOrJGroup; + } else { + // arg was the name of the group + var javaGroup = jAppLink.get(nameOrJGroup); + } + if (javaGroup != null && jAppLink.isWritable(javaGroup) == false) { + throw("tried to access write-protected group"); + } + return true; + }; + + + /** + * try to connect a group + * @returns false if group is not found + */ + this.connect = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + javaGroup.connect(); + return true; + }; + + + /** + * try to disconnect from a group + * @returns false if group is not found + */ + this.disconnect = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + javaGroup.disconnect(); + return true; + }; + + + /** + * try to disconnect and connect again to a group + * @returns false if group is not found + */ + this.reconnect = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + javaGroup.reconnect(); + return true; + }; + + + /** + * try to reset a group (if application may write in group). + * all instances of the group empty their cache. + * @returns false if group is not found + */ + this.reset = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + groups.checkWriteAccess(javaGroup); + javaGroup.reset(); + return true; + }; + + + /** + * try to destroy a group (if application may write in group). + * all other instances of the group disconnect + * @returns false if group is not found + */ + this.destroy = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + groups.checkWriteAccess(javaGroup); + javaGroup.destroy(); + return true; + }; + + + /** + * try to restart a group (if application may write in group). + * all other instances of the group disconnect and reconnect - each app after a different pause + * so that they don't all come up at the same moment + * @returns false if group is not found + */ + this.restart = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + groups.checkWriteAccess(javaGroup); + javaGroup.restart(); + return true; + }; + + + /** + * list the members of this group (ie instances of Group, one helma server is one instance) + * @returns array of strings, false if group is not found + */ + this.listMembers = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + var addrArr = javaGroup.info.listMembers(); + var arr = []; + for (var i=0; i