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<argArr.length; i++) { + argVec.add(argArr[i]); + } + var resVec = javaGroup.execute(method, argVec, sendMode, + javaGroup.DEFAULT_EXECUTE_TIMEOUT); + var resArr = []; + for (var i=0; i<resVec.size(); i++) { + resArr[i] = resVec.get(i); + } + return resArr; + }; + + this.toString = function() { + return javaGroup.toString(); + }; + + return this; +}; + +/** + * Constructs a new helma.Group.Wrapper. This class wraps the java GroupObject + * and provides several methods for retrieving and manipulating properties. + * @param {Object} Instance of helma.extensions.helmagroups.GroupObject + */ +helma.Group.Wrapper = function(javaGroupObject) { + var helmagroups = Packages.helma.extensions.helmagroups; + + if (!javaGroupObject) { + var javaGroupObject = new helmagroups.GroupObject(); + } + + /** + * private method that returns true if the group + * is writable + * @returns Boolean + */ + var checkWriteAccess = function() { + if (javaGroupObject.getState() == helmagroups.GroupObject.REPLICATED) { + groups.checkWriteAccess(javaGroupObject.getGroup()); + } + return true; + }; + + /** + * Checks if the key passed as argument is a path + * (either an Array or a String that contains separator characters) + * @returns Boolean + */ + var keyIsPath = function(key) { + var separator = helmagroups.GroupObject.SEPARATOR; + if ((key instanceof Array) || key.indexOf(separator) != -1) { + return true; + } + return false; + }; + + /** + * Returns the last element if the key passed as argument is a path. + * @returns Boolean + */ + var getLastKeyElement = function(key) { + var separator = helmagroups.GroupObject.SEPARATOR; + if (!(key instanceof Array) && key.indexOf(separator) != -1) { + if (key.charAt(key.length-1)==separator) { + key = key.substring(0, key.length-1); + } + key = key.split(separator); + } + if (key instanceof Array) { + return key[key.length-1]; + } + return null; + }; + + + /** + * if key is a path, walks through the path and returns the lowest GroupObject. + * if tree ends somewhere in the path, function returns null. + * @returns null or GroupObject + */ + var walkPath = function(obj, key) { + var separator = helmagroups.GroupObject.SEPARATOR; + if (!(key instanceof Array) && key.indexOf(separator) != -1) { + if (key.charAt(key.length-1)==separator) { + key = key.substring(0, key.length-1); + } + key = key.split(separator); + } + if (key instanceof Array) { + // loop down until end of array + for (var i=0; i<key.length-1; i++) { + var nextObj = obj.get(key[i]); + if (nextObj == null || !(nextObj instanceof GroupObject)) { + return null; + } + obj = nextObj; + } + return obj; + } + }; + + + /** + * if key is a path, walks through the path and returns the lowest GroupObject. + * if tree ends somewhere in the path, function creates the missing GroupObjects. + * @returns helma.Group.Wrapper + */ + var createPath = function(obj, key) { + var separator = helmagroups.GroupObject.SEPARATOR; + if (!(key instanceof Array) && key.indexOf(separator) != -1) { + if (key.charAt(key.length-1)==separator) { + key = key.substring(0, key.length-1); + } + key = key.split(separator); + } + if (key instanceof Array) { + // loop down until end of array + for (var i=0; i<key.length-1; i++) { + var nextObj = obj.get(key[i]); + if (nextObj == null || !(nextObj instanceof helma.Group.Wrapper)) { + nextObj = new helma.Group.Wrapper(); + obj.set(key[i], nextObj); + } + obj = nextObj; + } + return obj; + } + }; + + + /** + * Returns the wrapped java GroupObject. + * @return Instance of helma.extensions.helmagroups.GroupObject; + * @type helma.extensions.helmagroups.GroupObject + */ + this.getJavaObject = function() { + return javaGroupObject; + }; + + + /** + * Sets a property or a child GroupObject in 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 + * all GroupObjects along this path are created if necessary. + * @param {Object} key Either + * <ul> + * <li>a String</li> + * <li>a String containing slashes</li> + * <li>an Array containing String keys</li> + * </ul> + * @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 + * <ul> + * <li>a String</li> + * <li>a String containing slashes</li> + * <li>an Array containing String keys</li> + * </ul> + * @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 + * <ul> + * <li>a String</li> + * <li>a String containing slashes</li> + * <li>an Array containing String keys</li> + * </ul> + * @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 <em>not</em> 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 = <configfile> + * 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<addrArr.length; i++) { + arr[arr.length] = helmagroups.Config.addressToString(addrArr[i]); + } + return arr; + }; + + + /** + * lists the members applications of this group (may be more than one per instance but also none) + * @returns array of strings, false if group is not found + */ + this.listMemberApps = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + var appsArr = javaGroup.info.listMemberApps(); + var arr = []; + for (var i=0; i<appsArr.length; i++) { + arr[arr.length] = appsArr[i]; + } + return arr; + }; + + + /** + * dumps the keys of the group to a string + * @returns string, notice if group is not found + */ + this.getContent = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return "[not connected]"; + } + return javaGroup.info.print(); + }; + + + /** + * dumps the keys and the content of the group to a string + * @returns string, notice if group is not found + */ + this.getFullContent = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return "[not connected]"; + } + return javaGroup.info.printFull(); + }; + + + /** + * dumps the config of the jgroups stack to a string + * @returns string, false if group is not found + */ + this.getConfig = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + return javaGroup.info.printStack(false); + }; + + + /** + * dumps the config of the jgroups stack including all properties to a string + * @returns string, false if group is not found + */ + this.getFullConfig = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + return javaGroup.info.printStack(true); + }; + + + /** + * returns the connection identifier of the Group instance (localname + multicast-target) + * @returns string, false if group is not found + */ + this.getConnection = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + return javaGroup.info.getConnection(); + }; + + + /** + * returns true/false if the group is connected + */ + this.isConnected = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + return javaGroup.isConnected(); + }; + + + /** + * returns the total number of groupobjects in this group + */ + this.size = function(name) { + var javaGroup = getJavaGroup(name); + if (javaGroup == null) { + return false; + } + return javaGroup.size(); + }; + + + /** + * returns the total number of groupobjects in this group + */ + this.count = function(name) { + return this.size(name); + }; + + this.toString = function() { + return "[helma.Group.Manager]"; + }; + + return this; +} + +// Instantiate helma.Group.Manager as "groups" variable +var groups = new helma.Group.Manager();