helma/helma/Group.js

875 lines
27 KiB
JavaScript

/*
* 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: Group.js,v $
* $Author$
* $Revision$
* $Date$
*/
/**
* @fileoverview A JavaScript library wrapping
* Packages.helma.extensions.helmagroups
* <br /><br />
* To use this optional module, its repository needs to be added to the
* application, for example by calling app.addRepository('modules/helma/Group.js')
*/
// Define the global namespace if not existing
if (!global.helma) {
global.helma = {};
}
/**
* Constructs a new helma.Group Object.
* @class This is what is retrieved through groups.get(groupName),
* wrapping the root object of each group tree.
* @param {FIXME} javaGroup FIXME
* @constructor
*/
helma.Group = function(javaGroup) {
// private variable containing the wrapper object
var groupRoot = new helma.Group.GroupObject(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.GroupObject.listChildren
*/
this.listChildren = function() {
return groupRoot.listChildren();
};
/**
* @see helma.Group.GroupObject.listProperties
*/
this.listProperties = function() {
return groupRoot.listProperties();
};
/**
* @see helma.Group.GroupObject.countChildren
*/
this.countChildren = function() {
return groupRoot.countChildren();
};
/**
* @see helma.Group.GroupObject.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.GroupObject
* @returns array of result objects
*/
this.callFunction = function(method, argArr, sendMode) {
groups.checkWriteAccess(javaGroup);
if (sendMode == null) {
sendMode = helma.Group.GroupObject.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.GroupObject.
* @class This class wraps the java GroupObject
* and provides several methods for retrieving and manipulating properties.
* @param {Object} Instance of helma.extensions.helmagroups.GroupObject
* @constructor
*/
helma.Group.GroupObject = 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 helma.Group.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.GroupObject
*/
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.GroupObject)) {
nextObj = new helma.Group.GroupObject();
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.GroupObject.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.GroupObject)
ok = true;
if (ok == false) {
throw "only primitive values, Date and helma.Group.GroupObject allowed in helma.Group.GroupObject.set()";
}
if (sendMode == null) {
sendMode = helma.Group.GroupObject.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.GroupObject) {
// replicate helma.Group.GroupObject
javaGroupObject.put(key, val.getJavaObject(), sendMode);
} else {
// put the primitive property (or maybe replicate,
// decision's up to helma.Group.GroupObject)
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.GroupObject.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.GroupObject
* @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.GroupObject(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.GroupObject.wrap() may only be called on replicated GroupObjects";
}
if (newGroupObject == null || !(newGroupObject instanceof helma.Group.GroupObject)) {
throw "helma.Group.GroupObject.wrap() requires a helma.Group.GroupObject 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.GroupObject(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.GroupObject 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.GroupObject.GET_FIRST = 1;
// wait until all members have responded
helma.Group.GroupObject.GET_ALL = 2;
// wait for majority (50% + 1) to respond
helma.Group.GroupObject.GET_MAJORITY = 3;
// wait for majority of all members (may block!)
helma.Group.GroupObject.GET_ABS_MAJORITY = 4;
// don't wait for any response (fire & forget)
helma.Group.GroupObject.GET_NONE = 6;
// default: wait for all responses
helma.Group.GroupObject.DEFAULT_GET = helma.Group.GroupObject.GET_ALL;
/**
* This is mounted as "groups".
* @class The root for all groups started in this application
* @constructor
*/
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();