summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--modules/enterprise/remoting/cli/src/main/samples/README.txt31
-rw-r--r--modules/enterprise/remoting/cli/src/main/samples/modules/bundles.js150
-rw-r--r--modules/enterprise/remoting/cli/src/main/samples/modules/drift.js240
-rw-r--r--modules/enterprise/remoting/cli/src/main/samples/modules/jbossas.js811
-rw-r--r--modules/enterprise/remoting/cli/src/main/samples/modules/util.js280
5 files changed, 1507 insertions, 5 deletions
diff --git a/modules/enterprise/remoting/cli/src/main/samples/README.txt b/modules/enterprise/remoting/cli/src/main/samples/README.txt
index 913cf62..96f0d46 100644
--- a/modules/enterprise/remoting/cli/src/main/samples/README.txt
+++ b/modules/enterprise/remoting/cli/src/main/samples/README.txt
@@ -6,12 +6,33 @@ have not undergone the same level of testing as other parts of the code base.
As such, the quality may not meet RHQ standards in some cases. Please consider
this when using these scripts for anything other than demonstration or testing.
-There may be inter-dependencies between some of the scripts. There is currently
-no mechanism in place to automatically handle script-level dependencies;
-consequently, you need to make sure to run scripts in the correct order. If a
-script has a dependency on other scripts, it will be documented within the
-script itself so that you know which scripts need to be run and in what order.
+There are inter-dependencies between some of the scripts. But because these
+scripts are not implemented as modules but rather as examples of what
+can be done, those dependencies are not expressed in code but have to be
+expressed manually on the commandline of the interactive CLI as suggested by
+the comments in the respective files (another reason is to keep the scripts
+backwards compatible in case someone used them in previous versions of RHQ
+that didn't offer CommonJS modules).
+In the "modules" directory, there are sample modules that mirror many of
+these sample scripts in a "modularized" form so that they are more easily
+available in your scripts (if you compare the files, you will find that
+the changes are fairly minor).
+
+The scripts in the "modules" directory are also available in the downloads
+section of the RHQ server. From your scripts, they are therefore available
+in at least two ways:
+
+1) var bundles = require("rhq://samples/modules/bundles");
+This will load the module stored locally in the samples/modules directory.
+
+2) var util = require("rhq://downloads/util");
+This will, as long as the CLI session is connected to an RHQ server, download
+the script from the RHQ server.
+
+Note that the latter, i.e. the "rhq://downloads/...", is also usable in the
+alert scripts that run on the RHQ server as alert notifications (unlike,
+of course, the former form).
Feedback
--------
diff --git a/modules/enterprise/remoting/cli/src/main/samples/modules/bundles.js b/modules/enterprise/remoting/cli/src/main/samples/modules/bundles.js
new file mode 100644
index 0000000..f7fbdc2
--- /dev/null
+++ b/modules/enterprise/remoting/cli/src/main/samples/modules/bundles.js
@@ -0,0 +1,150 @@
+/**
+ * file: bundles.js
+ *
+ * description: this file contains a few helper functions to make working
+ * with the bundle subsystem more convenient.
+ *
+ * Note that you must login before you can load the scripts.
+ *
+ * author: lkrejci@redhat.com
+ */
+
+var util = require('./util');
+
+/**
+ * A simple function to create a new bundle version from a zip file containing
+ * the bundle.
+ *
+ * @param pathToBundleZipFile the path to the bundle on the local file system
+ *
+ * @return an instance of BundleVersion class describing what's been created on
+ * the RHQ server.
+ */
+exports.createBundleVersion = function(pathToBundleZipFile) {
+ var bytes = getFileBytes(pathToBundleZipFile)
+ return BundleManager.createBundleVersionViaByteArray(bytes)
+}
+
+/**
+ * This is a helper function that one can use to find out what base directories
+ * given resource type defines.
+ * <p>
+ * These base directories then can be used when specifying bundle destinations.
+ *
+ * @param resourceTypeId
+ * @returns a java.util.Set of ResourceTypeBundleConfiguration objects
+ */
+exports.getAllBaseDirectories = function(resourceTypeId) {
+ var crit = new ResourceTypeCriteria;
+ crit.addFilterId(resourceTypeId);
+ crit.fetchBundleConfiguration(true);
+
+ var types = ResourceTypeManager.findResourceTypesByCriteria(crit);
+
+ if (types.size() == 0) {
+ throw "Could not find a resource type with id " + resourceTypeId;
+ } else if (types.size() > 1) {
+ throw "More than one resource type found with id " + resourceTypeId + "! How did that happen!";
+ }
+
+ var type = types.get(0);
+
+ return type.getResourceTypeBundleConfiguration().getBundleDestinationBaseDirectories();
+}
+
+/**
+ * Creates a new destination for given bundle. Once a destination exists,
+ * actual bundle versions can be deployed to it.
+ * <p>
+ * Note that this only differs from the <code>BundleManager.createBundleDestination</code>
+ * method in the fact that one can provide bundle and resource group names instead of their
+ * ids.
+ *
+ * @param destinationName the name of the destination to be created
+ * @param description the description for the destination
+ * @param bundleName the name of the bundle to create the destination for
+ * @param groupName name of a group of resources that the destination will handle
+ * @param baseDirName the name of the basedir definition that represents where inside the
+ * deployment of the individual resources the bundle will get deployed
+ * @param deployDir the specific sub directory of the base dir where the bundles will get deployed
+ *
+ * @return BundleDestination object
+ */
+exports.createBundleDestination = function(destinationName, description, bundleName, groupName, baseDirName, deployDir) {
+ var groupCrit = new ResourceGroupCriteria;
+ groupCrit.addFilterName(groupName);
+ var groups = ResourceGroupManager.findResourceGroupsByCriteria(groupCrit);
+
+ if (groups.empty) {
+ throw "No group called '" + groupName + "' found.";
+ }
+
+ var group = groups.get(0);
+
+ var bundleCrit = new BundleCriteria;
+ bundleCrit.addFilterName(bundleName);
+ var bundles = BundleManager.findBundlesByCriteria(bundleCrit);
+
+ if (bundles.empty) {
+ throw "No bundle called '" + bundleName + "' found.";
+ }
+
+ var bundle = bundles.get(0);
+
+ return BundleManager.createBundleDestination(bundle.id, destinationName, description, baseDirName, deployDir, group.id);
+}
+
+/**
+ * Tries to deploy given bundle version to provided destination using given configuration.
+ * <p>
+ * This method blocks while waiting for the deployment to complete or fail.
+ *
+ * @param destination the bundle destination (or id thereof)
+ * @param bundleVersion the bundle version to deploy (or id thereof)
+ * @param deploymentConfiguration the deployment configuration. This can be an ordinary
+ * javascript object (hash) or an instance of RHQ's Configuration. If it is the former,
+ * it is converted to a Configuration instance using the <code>asConfiguration</code>
+ * function from <code>util.js</code>. Please consult the documentation of that method
+ * to understand the limitations of that approach.
+ * @param description the deployment description
+ * @param isCleanDeployment if true, perform a wipe of the deploy directory prior to the deployment; if false,
+ * perform as an upgrade to the existing deployment, if any
+ *
+ * @return the BundleDeployment instance describing the deployment
+ */
+exports.deployBundle = function(destination, bundleVersion, deploymentConfiguration, description, isCleanDeployment) {
+ var destinationId = destination;
+ if (typeof(destination) == 'object') {
+ destinationId = destination.id;
+ }
+
+ var bundleVersionId = bundleVersion;
+ if (typeof(bundleVersion) == 'object') {
+ bundleVersionId = bundleVersion.id;
+ }
+
+ var deploymentConfig = deploymentConfiguration;
+ if (!(deploymentConfiguration instanceof Configuration)) {
+ deploymentConfig = util.asConfiguration(deploymentConfiguration);
+ }
+
+ var deployment = BundleManager.createBundleDeployment(bundleVersionId, destinationId, description, deploymentConfig);
+
+ deployment = BundleManager.scheduleBundleDeployment(deployment.id, isCleanDeployment);
+
+ var crit = new BundleDeploymentCriteria;
+ crit.addFilterId(deployment.id);
+
+ while (deployment.status == BundleDeploymentStatus.PENDING || deployment.status == BundleDeploymentStatus.IN_PROGRESS) {
+ java.lang.Thread.currentThread().sleep(1000);
+ var dps = BundleManager.findBundleDeploymentsByCriteria(crit);
+ if (dps.empty) {
+ throw "The deployment disappeared while we were waiting for it to complete.";
+ }
+
+ deployment = dps.get(0);
+ }
+
+ return deployment;
+}
+
diff --git a/modules/enterprise/remoting/cli/src/main/samples/modules/drift.js b/modules/enterprise/remoting/cli/src/main/samples/modules/drift.js
new file mode 100644
index 0000000..b07eaec
--- /dev/null
+++ b/modules/enterprise/remoting/cli/src/main/samples/modules/drift.js
@@ -0,0 +1,240 @@
+/**
+ * file: drift.js
+ *
+ * description: This script contains functions that illustrate and highlight
+ * drift monitoring functionality. Some of the functionality demonstrated in
+ * this script is currently available only via RHQ's remote client (and CLI)
+ * at the time of this writing.
+ *
+ * Note that you must login before you can load the scripts. Also please note
+ * that if a function is not documented, then it is not intended for public
+ * use. It is used only as an internal helper function.
+ *
+ * author: jsanda@redhat.com
+ */
+
+var util = require("./util");
+
+/**
+ * description: Generates a snapshot of changes sets belonging to a particular
+ * drift definition of a specific resource. By default all change sets are
+ * included in the snapshot. An optional third argument can be specified to
+ * limit or control the number of change sets included in the snapshot.
+ *
+ * Note that a snapshot is an accumulation or aggregation of change sets that
+ * provides a view of a resource, or more precisely, all files being monitored
+ * for drift, at a particular version or point in time.
+ *
+ * arguments:
+ * rid: A resource id, expected to be an integer
+ *
+ * defName: The drift definition name as a string
+ *
+ * filter: (optional) A map or JS object that limits the number of
+ * change sets included in the snapshot. It should specify two
+ * properties - startVersion and endVersion.
+ *
+ * return: A org.rhq.core.domamin.drift.DriftSnapshot object
+ *
+ * usage:
+ * // generates a snapshot that includes all change sets belonging to the
+ * // mydrift drift configuration.
+ * createSnapshot(123, 'mydrift')
+ *
+ * // generates a snapshot that includes change sets 3 through 5 inclusive.
+ * createSnapshot(123, 'mydrift', {startVersion: 3, endVersion: 5})
+ */
+exports.createSnapshot = function(rid, defName) {
+ var driftDef = findDriftDefinition(rid, function(d) { return defName.equals(d.name) });
+ var snapshotRequest;
+
+ if (arguments.length > 2) {
+ var filters = arguments[2];
+ snapshotRequest = DriftSnapshotRequest(driftDef.id, filters.startVersion, filters.endVersion);
+ } else {
+ snapshotRequest = DriftSnapshotRequest(driftDef.id);
+ }
+
+ return DriftManager.getSnapshot(snapshotRequest);
+}
+
+/** private helper function to find a drift definition by a filter */
+function findDriftDefinition(rid, filter) {
+ var criteria = ResourceCriteria();
+ criteria.addFilterId(rid);
+ criteria.fetchDriftDefinitions(true);
+
+ var resources = ResourceManager.findResourcesByCriteria(criteria);
+ var resource = resources.get(0);
+
+ return util.find(resource.driftDefinitions, function(config) {
+ return filter(DriftDefinition(config));
+ });
+}
+
+/**
+ * description: Generates a diff report that is printed to the console. By
+ * default this function generates a snapshot diff. The function expects two
+ * arguments which should be DriftSnapshot objects. A diff between those two
+ * snapshots is generated and printed to the console. A path can be specified
+ * as an optional third argument. If a path is a specified as a third argument,
+ * then a file diff will be performed using the file that matches the path in
+ * each change set. The format will be a unified diff.
+ *
+ * Note that the snapshots can be from the same or from different resources;
+ * however, if they are from different resources, it is assumed that the
+ * resources are of the same type.
+ *
+ * arguments:
+ * s1: A DriftSnapshot object
+ *
+ * s2: A DriftSnapshot object
+ *
+ * path: (optional) A string that specifies a path that exists in both s1 and
+ * s2.
+ *
+ * return: This function does not return a useful value. It may be an empty
+ * string or it could be null. Instead of returning its results, the function
+ * prints them to the CLI console. As such, this function is intended for
+ * use from the interactive CLI shell.
+ *
+ * usage:
+ * // Generates a snapshot diff report
+ * diff(s1, s2)
+ *
+ * // Generates a file diff report in the unified format
+ * diff(s1, s2, 'jboss_home/bin/run.conf')
+ */
+exports.diff = function(s1, s2) {
+ var theDiff = s1.diff(s2);
+
+ if (arguments.length > 2) {
+ var path = arguments[2];
+
+ if (theDiff.elementsInConflict.size() == 0) {
+ // If the snapshot diff reports no files in conflict, then there
+ // is no need to call the server to perform the file diff. We can
+ // instead return quickly.
+ println("There are no differences to report");
+ return "";
+ }
+
+ var pathFilter = function(entry) { return entry.path == path; };
+ var e1 = util.find(s1.driftInstances, pathFilter);
+ var e2 = util.find(s2.driftInstances, pathFilter);
+
+ var fileDiff = DriftManager.generateUnifiedDiff(e1, e2);
+ util.foreach(fileDiff.diff, println);
+ return "";
+ }
+
+ function printEntry(entry) {
+ println(entry.newDriftFile.hashId + '\t' + entry.path);
+ }
+
+ function report(header, elements) {
+ println(header + ':');
+ util.foreach(elements, printEntry);
+ println('\n');
+ }
+
+ report('elements in conflict', theDiff.elementsInConflict);
+ report('elements not in left', theDiff.elementsNotInLeft);
+ report('elements not in right', theDiff.elementsNotInRight);
+}
+
+/**
+ * description: Generates and returns the drift history for a file being
+ * monitored for drift. This function takes three arguments. The first two, the
+ * resource id drift configuration name, uniquely identify the drift
+ * configuration. The third argument specifies a path that is set up for
+ * monitoring by the drift configuration.
+ *
+ * arguments:
+ * rid: A resource id
+ *
+ * configName: A drift configuration name
+ *
+ * path: A path that is set up for drift monitoring by the drift
+ * configuration that is identified by the first two arguments. The path
+ * should be specified as relative to the base directory from which
+ * monitoring is done.
+ *
+ * return: A History object, which is a native JS object. This object contains
+ * a few methods for working with the file history:
+ *
+ * list: prints a short summary of all versions of the file
+ * view: returns the contents of a particular version of the file
+ * compare: compares two versions of the file and prints a unified diff
+ *
+ * usage:
+ * $ history = fetchHistory(123, 'mydrift', 'bin/run.conf')
+ * $ history.list() // prints a summary of all versions of 'bin/run.conf'
+ * $ history.view(1) // returns the full contents of version 1 of the file
+ * $ history.compare(1, 2) // generates and prints a unified diff of versions 1 and 2
+ */
+exports.fetchHistory = function(rid, driftDefName, path) {
+ function History() {
+ var entries = [];
+
+ function findDrift(version) {
+ return util.find(entries, function(drift) {
+ return drift.changeSet.version == version
+ });
+ }
+
+
+ var generate = function() {
+ entries = [];
+ var criteria = GenericDriftCriteria();
+ criteria.addFilterResourceIds([rid]);
+ criteria.fetchChangeSet(true);
+ criteria.addFilterPath(path);
+
+ var drifts = DriftManager.findDriftsByCriteria(criteria);
+ util.foreach(drifts, function(drift) {
+ if (drift.changeSet.driftDefinition.name == driftDefName &&
+ drift.path == path) {
+ entries.push(drift);
+ }
+ });
+
+ entries.sort(function(d1, d2) {
+ return d1.changeSet.version <= d2.changeSet.version
+ });
+ }
+
+ this.list = function() {
+ var format = java.text.DateFormat.getDateTimeInstance();
+ println(path + "\n-----------------------------------");
+ util.foreach(entries, function(drift) {
+ println(drift.changeSet.version + "\t" + format.format(drift.ctime));
+ });
+ }
+
+ this.view = function(version) {
+ var drift = findDrift(version);
+ if (drift == null) {
+ return "Could not find version " + version;
+ }
+ return java.lang.String(DriftManager.getDriftFileAsByteArray(drift.newDriftFile.hashId));
+ }
+
+ this.compare = function(v1, v2) {
+ var d1 = findDrift(v1);
+ if (d1 == null) {
+ return "Could not find version " + v1;
+ }
+ var d2 = findDrift(v2);
+ if (d2 == null) {
+ return "Could not find version " + v2;
+ }
+ var fileDiff = DriftManager.generateUnifiedDiff(d1, d2);
+ util.foreach(fileDiff.diff, println);
+ }
+
+ generate();
+ }
+
+ return new History();
+}
diff --git a/modules/enterprise/remoting/cli/src/main/samples/modules/jbossas.js b/modules/enterprise/remoting/cli/src/main/samples/modules/jbossas.js
new file mode 100644
index 0000000..6369cc1
--- /dev/null
+++ b/modules/enterprise/remoting/cli/src/main/samples/modules/jbossas.js
@@ -0,0 +1,811 @@
+/**
+ * This module contains a variety of utility functions for working with JBoss AS in different versions.
+ *
+ * @author Lukas Krejci
+ */
+
+
+var util = require('./util');
+var bundles = require("./bundles");
+
+//init the version-specific objects. We'll be adding the functions to them further down below.
+exports.as7 = {}
+
+/**
+ * This creates a new destination in the JBoss AS (4,5,6,7) servers from the provided
+ * group and deploys the application provided by the bundle to it. Once the app is deployed,
+ * the servers in the group are sequentially stopped and started.
+ *
+ * @param bundleZipFile the path to the zip file with the application bundle
+ * @param deploymentConfiguration the deployment configuration required by the bundle.
+ * See the <code>deployBundle</code> in <code>bundles.js</code> to find out about
+ * supported formats.
+ * @param groupName the name of the compatible group of JBossAS servers to deploy the bundle to
+ * @param destinationName the name of the destination that will be created so that the bundle
+ * can be deployed to it
+ * @param destinationDescription the description of the destination
+ * @param baseDirName the name of the base directory for the deployment. Each resource type
+ * defines a list of deployment base directories which you can review using, for example,
+ * the <code>getAllBaseDirectories(resourceTypeId)</code> function from <code>bundles.js</code>.
+ * @param deployDir the name of the directory under the base dir in which the bundle will be
+ * deployed to.
+ *
+ * @return the BundleDeployment instance
+ */
+exports.createAppAndRestart = function(bundleZipFile, deploymentConfiguration, groupName, destinationName, destinationDescription, baseDirName, deployDir) {
+ var gcrit = new ResourceGroupCriteria;
+ gcrit.addFilterName(groupName);
+ gcrit.fetchResourceType(true);
+
+ var groups = ResourceGroupManager.findResourceGroupsByCriteria(gcrit);
+ if (groups.empty) {
+ throw "Could not find a resource group called " + groupName;
+ } else if (groups.size() > 1) {
+ throw "There are more than 1 groups called " + groupName;
+ }
+
+ var group = groups.get(0);
+ var targetResourceType = group.resourceType;
+
+ var deployFn = function(restartFn) {
+
+ var bundleVersion = bundles.createBundleVersion(bundleZipFile);
+ var destination = BundleManager.createBundleDestination(bundleVersion.bundle.id, destinationName, destinationDescription, baseDirName, deployDir, group.id);
+
+ var deployment = bundles.deployBundle(bundleVersion, destination, deploymentConfiguration, null);
+
+ if (deployment.status != BundleDeploymentStatus.SUCCESS) {
+ throw "Deployment wasn't successful: " + deployment;
+ }
+
+ restartFn(group);
+
+ return deployment;
+ };
+
+ if (targetResourceType.plugin == "JBossAS" && targetResourceType.name == "JBossAS Server") {
+ return deployFn(_restartAS4);
+ } else if (targetResourceType.plugin == "JBossAS5" && targetResourceType.name == "JBossAS Server") {
+ return deployFn(_restartAS5);
+ } else if (targetResourceType.plugin == "jboss-as-7" &&
+ (targetResourceType.name == "JBossAS7-Standalone" ||
+ targetResourceType.name == "JBossAS-Managed")) {
+ return deployFn(_restartAS7);
+ }
+
+ throw "The resource group the destination targets doesn't seem to be a JBoss AS server group.";
+}
+
+/**
+ * Deploys an updated bundle of an enterprise application to a destination that
+ * points to a group of JBoss AS(4,5,6,7) servers and then stops all the servers
+ * serially and then starts them up again sequentially.
+ *
+ * @param bundleZipFile
+ * the path to the bundle zip file with the application
+ * @param jbasDestination
+ * the bundle destination the bundle should be deployed to (or id
+ * thereof)
+ * @param deploymentConfiguration
+ * the deployment configuration. This can be an ordinary javascript
+ * object (hash) or an instance of RHQ's Configuration. If it is the
+ * former, it is converted to the Configuration instance using the
+ * <code>asConfiguration</code> function from <code>util.js</code>.
+ * Please consult the documentation of that method to understand the
+ * limitations of that approach.
+ * @param description
+ * the deployment description
+ *
+ * @return the deployment instance
+ */
+exports.updateAppAndRestart = function(bundleZipFile, jbasDestination, deploymentConfiguration) {
+ // first figure out the jbas version we are deploying to
+ var destinationId = jbasDestination;
+ if (typeof(jbasDestination) == 'object') {
+ destinationId = jbasDestination.id;
+ }
+
+ var destCrit = new BundleDestinationCriteria
+ destCrit.fetchGroup(true)
+
+ var destinations = BundleManager.findBundleDestinationsByCriteria(destCrit);
+
+ if (destinations.empty) {
+ throw "No destinations corresponding to " + jbasDestination + " found on the server.";
+ }
+
+ var destination = destinations.get(0);
+
+ var targetResourceType = destination.group.resourceType;
+
+ if (targetResourceType == null) {
+ throw "This function expects a compatible group of JBoss AS (4,5,6 or 7) resources but the provided destination is connected with " + destination.group;
+ }
+
+ var deployFn = function(restartFn) {
+ var bundleVersion = bundles.createBundleVersion(bundleZipFile);
+ var deployment = bundles.deployBundle(bundleVersion, destination, deploymentConfiguration, null);
+
+ if (deployment.status != BundleDeploymentStatus.SUCCESS) {
+ throw "Deployment wasn't successful: " + deployment;
+ }
+
+ restartFn(destination.group);
+
+ return deployment;
+ };
+
+ if (targetResourceType.plugin == "JBossAS" && targetResourceType.name == "JBossAS Server") {
+ return deployFn(_restartAS4);
+ } else if (targetResourceType.plugin == "JBossAS5" && targetResourceType.name == "JBossAS Server") {
+ return deployFn(_restartAS5);
+ } else if (targetResourceType.plugin == "jboss-as-7" &&
+ (targetResourceType.name == "JBossAS7-Standalone" ||
+ targetResourceType.name == "JBossAS-Managed")) {
+ return deployFn(_restartAS7);
+ }
+
+ throw "The resource group the destination targets doesn't seem to be a JBoss AS server group.";
+}
+
+/**
+ * This function adds an AS7 server to a cluster with another AS7 instance, using
+ * the provided node name as its identifier.
+ * <p>
+ * This method only makes the new member use the same configuration as the existing
+ * member and synchronizes its jgroups, messaging and modcluster socket bindings
+ * with the cluster. Optionally it also copies over all the deployments to the
+ * new member.
+ * <p>
+ * The configurations are therefore assumed to be otherwise compatible - i.e.
+ * the infinispan cache containers should be configured correctly (or left at
+ * default values, which are configured correctly), etc.
+ *
+ * @param newAs7Resource the resource proxy of the AS7 instance to add
+ * @param newNodeName the node name, i.e. unique identification, of the new member
+ * in the cluster
+ * @param existingClusterMemberResource an AS7 instance that belongs to a cluster
+ * the new member should join into.
+ * @param copyDeployments whether or not to copy the deployments from the existing
+ * member to the new member
+ */
+exports.as7.addToCluster = function(newAs7Resource, newNodeName, existingClusterMemberResource, doCopyDeployments) {
+ println("Reading config of the existing cluster member");
+ var existingMember = {
+ 'id' : existingClusterMemberResource.id,
+ 'resourceConfiguration' : _getLiveResourceConfiguration(existingClusterMemberResource),
+ 'pluginConfiguration' : _getPluginConfiguration(existingClusterMemberResource)
+ };
+
+ var clusterConfig = _getClusterSignificantConfig(existingClusterMemberResource.children,
+ existingMember.pluginConfiguration, existingMember.resourceConfiguration);
+
+ println("Reading config of the new member");
+ var newMember = {
+ 'id' : newAs7Resource.id,
+ 'resourceConfiguration' : _getLiveResourceConfiguration(newAs7Resource),
+ 'pluginConfiguration' : _getPluginConfiguration(newAs7Resource)
+ };
+
+ var memberConfig = _getClusterSignificantConfig(newAs7Resource.children, newMember.pluginConfiguration, newMember.resourceConfiguration);
+
+ var memberResourceConfiguration = newMember.resourceConfiguration.deepCopy(false);
+
+ if (memberConfig['config'] != clusterConfig['config']) {
+ println("The configurations of the servers differ.\n" +
+ "The new cluster member's configuration will be changed to match the configuration of the existing member.");
+
+ newAs7Resource.updatePluginConfiguration(_changeConfig(newMember.pluginConfiguration, clusterConfig['config']));
+
+ //we need to restart straight away so that we see the changes to the
+ //rest of the configuration caused by the change of current config.
+ println("Restarting the new cluster member to switch it to the new configuration.");
+ newAs7Resource.restart();
+
+ //refresh the resource
+ newAs7Resource = ProxyFactory.getResource(newMember.id);
+ newMember = {
+ 'id' : newAs7Resource.id,
+ 'resourceConfiguration' : _getLiveResourceConfiguration(newAs7Resource),
+ 'pluginConfiguration' : _getPluginConfiguration(newAs7Resource)
+ };
+
+ //refresh the cluster specific config after the restart with the new
+ //config
+ memberConfig = _getClusterSignificantConfig(newAs7Resource.children, newMember.pluginConfiguration, newMember.resourceConfiguration);
+ memberResourceConfiguration = newMember.resourceConfiguration;
+ }
+
+ //now check what's the node name we see
+ if (memberConfig['node-name'] != newNodeName) {
+ println("Updating the node name of the new cluster member from '" + memberConfig['node-name'] + "' to '" + newNodeName + "'");
+ _updateNodeName(memberResourceConfiguration, newNodeName);
+
+ newAs7Resource.updateResourceConfiguration(memberResourceConfiguration);
+ memberResourceConfiguration = newMember.resourceConfiguration.deepCopy(false);
+ }
+
+ //now apply the socket binding changes for jgroups and other cluster
+ //significant subsystems
+ //first find the socket binding group config in the new member
+ for(i in newAs7Resource.children) {
+ var child = newAs7Resource.children[i];
+ if (child.resourceType.name == 'SocketBindingGroup' &&
+ child.resourceType.plugin == 'JBossAS7') {
+
+ println("Updating socket bindings of jgroups, messaging and modcluster subsystems");
+
+ var resourceConfig = _getLiveResourceConfiguration(child);
+
+ var portOffset = javascriptString(resourceConfig.getSimpleValue('port-offset', '0'));
+ var clusterMemberPortOffset = clusterConfig['port-offset'];
+
+ var newConfig = resourceConfig.deepCopy(false);
+
+ _updateSocketBindings(newConfig, portOffset, clusterMemberPortOffset, clusterConfig['jgroups']);
+ _updateSocketBindings(newConfig, portOffset, clusterMemberPortOffset, clusterConfig['messaging']);
+ _updateSocketBindings(newConfig, portOffset, clusterMemberPortOffset, clusterConfig['modcluster']);
+
+ child.updateResourceConfiguration(newConfig);
+ }
+ }
+
+ println("Restarting the new member for the new socket bindings to take effect.");
+ newAs7Resource.restart();
+
+ if (doCopyDeployments) {
+ println("Copying the deployments to the new cluster member...");
+ copyDeployments(existingClusterMemberResource, newAs7Resource);
+
+ println("Restarting the new cluster member.");
+ newAs7Resource.restart();
+ }
+}
+
+/**
+ * Copies all deployments from the source to the target. Both resources must
+ * be standalone AS7 servers.
+ *
+ * @param sourceAS7 the resource id or object of the source AS7 standalone instance
+ * @param targetAS7 the resource id or object of the target AS7 standalone instance
+ */
+exports.as7.copyDeployments = function(sourceAS7, targetAS7) {
+ if (typeof sourceAS7 == 'object') {
+ sourceAS7 = sourceAS7.id;
+ }
+
+ if (typeof targetAS7 == 'object') {
+ targetAS7 = targetAS7.id;
+ }
+
+ var deploymentResourceType = ResourceTypeManager.getResourceTypeByNameAndPlugin('Deployment', 'JBossAS7');
+
+ var deploymentsCrit = new ResourceCriteria;
+ deploymentsCrit.addFilterParentResourceId(sourceAS7);
+ deploymentsCrit.addFilterResourceTypeId(deploymentResourceType.id);
+
+ var unlimitedPageControl = PageControl.unlimitedInstance;
+
+ var sourceDeployments = ResourceManager.findResourcesByCriteria(deploymentsCrit);
+ var iterator = sourceDeployments.iterator();
+ while (iterator.hasNext()) {
+ var deploymentResource = iterator.next();
+ //get a resource proxy for easy access to configurations, etc.
+ deploymentResource = ProxyFactory.getResource(deploymentResource.id);
+
+ println("Copying deployment " + deploymentResource.name);
+
+ var installedPackage = ContentManager.getBackingPackageForResource(deploymentResource.id);
+ var content = ContentManager.getPackageBytes(deploymentResource.id, installedPackage.id);
+
+ var runtimeName = deploymentResource.resourceConfiguration.getSimpleValue('runtime-name', deploymentResource.name);
+
+ var deploymentConfiguration = new Configuration;
+ deploymentConfiguration.put(new PropertySimple('runtimeName', runtimeName));
+
+ //so now we have both metadata and the data of the deployment, let's
+ //push a copy of it to the target server
+ var history = ResourceFactoryManager.createPackageBackedResource(targetAS7,
+ deploymentResourceType.id, deploymentResource.name,
+ deploymentResource.pluginConfiguration,
+ installedPackage.packageVersion.generalPackage.name,
+ installedPackage.packageVersion.version,
+ installedPackage.packageVersion.architecture.id,
+ deploymentConfiguration, content, null);
+
+ while (history.status.name() == 'IN_PROGRESS') {
+ java.lang.Thread.sleep(1000);
+ //the API for checking the create histories is kinda weird..
+ var histories = ResourceFactoryManager.findCreateChildResourceHistory(targetAS7, null, null, unlimitedPageControl);
+ var hit = histories.iterator();
+ var found = false;
+ while(hit.hasNext()) {
+ var h = hit.next();
+
+ if (h.id == history.id) {
+ history = h;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ throw "The history object for the deployment seems to have disappeared, this is very strange.";
+ }
+ }
+
+ println("Deployment finished with status: " + history.status.toString() +
+ (history.status.name() == 'SUCCESS' ? "." : (", error message: " + history.errorMessage + ".")));
+ }
+}
+
+function _getConfigName(as7PluginConfig) {
+ var argsValue = as7PluginConfig.getSimpleValue('startScriptArgs', '');
+
+ var args = argsValue.split('\n');
+
+ var possibleParams = ['-c ', '-c=', '--server-config='];
+
+ var ret = null;
+ for (i in args) {
+ var arg = args[i];
+
+ var found = false;
+
+ for(j in possibleParams) {
+ var param = possibleParams[j];
+ var pos = arg.indexOf(param);
+
+ if (pos >= 0) {
+ ret = arg.substring(pos + param.length).trim();
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ break;
+ }
+ }
+
+ if (ret != null && ret.startsWith('%') && ret.endsWith('%')) {
+ var pluginConfRef = ret.substring(1, ret.length - 1);
+ ret = as7PluginConfig.getSimpleValue(pluginConfigRef, null);
+ }
+
+ return ret;
+}
+
+function _changeConfig(pluginConfig, configName) {
+ var argsValue = javascriptString(pluginConfig.getSimpleValue('startScriptArgs', ''));
+
+ var args = argsValue.split('\n');
+
+ var possibleParams = ['-c ', '-c=', '--server-config='];
+
+ var updated = false;
+
+ for (i in args) {
+ var arg = args[i];
+
+ for(j in possibleParams) {
+ var param = possibleParams[j];
+
+ var pos = arg.indexOf(param);
+
+ if (pos >= 0) {
+ args[i] = param + configName;
+ updated = true;
+ break;
+ }
+ }
+
+ if (updated) {
+ break;
+ }
+ }
+
+ if (!updated) {
+ args.push("-c " + configName);
+ }
+
+ //join the config back together
+ for(i in args) {
+ if (i == 0) {
+ argsValue = args[i];
+ } else {
+ argsValue += "\n" + args[i];
+ }
+ }
+
+ pluginConfig.put(new PropertySimple('startScriptArgs', argsValue));
+
+ return pluginConfig;
+}
+
+/**
+ * The config properties of AS7 can have a form of ${propertyName:defaultvalue}.
+ * Because we can't figure out the value of the property if it is defined,
+ * (or get at its default value), we return the following hash from this function
+ * {
+ * property : propertyName,
+ * value : defaultvalue
+ * }
+ *
+ * At least one of the keys in the above hash will have a value.
+ */
+function _parseValueFromExpr(expr) {
+ var strExpr = expr + '';
+ if (strExpr.indexOf('${') == 0) {
+ var colonIdx = strExpr.indexOf(':');
+ if (colonIdx == -1) {
+ return { 'property' : expr };
+ } else {
+ var property = strExpr.substring(2, colonIdx);
+ var value = strExpr.substring(colonIdx + 1, strExpr.length - 1);
+
+ return { 'property' : property, 'value' : value };
+ }
+ } else {
+ return { 'value' : expr };
+ }
+}
+
+/**
+ * This converts the result of _parseValueFromExpr() function back into the
+ * ${property:defaultvalue} format.
+ */
+function _composeValueExpr(parsedValue) {
+ if (parsedValue.property == undefined) {
+ return parsedValue.value;
+ } else {
+ return '${' + parsedValue.property + ':' + parsedValue.value + '}';
+ }
+}
+
+function _updateNodeName(serverResourceConfiguration, newNodeName) {
+ //define it as a system prop
+ var systemProps = serverResourceConfiguration.getList('*2');
+ if (systemProps == null) {
+ systemProps = new PropertyList('*2');
+ serverResourceConfiguration.put(systemProps);
+ }
+
+ var it = systemProps.list.iterator();
+ var updated = false;
+ while (it.hasNext()) {
+ var systemProp = it.next();
+ var systemPropName = javascriptString(systemProp.getSimpleValue('name', null));
+ if (systemPropName == 'jboss.node.name') {
+ var systemPropValue = systemProp.getSimple('value');
+ if (systemPropValue == null) {
+ systemPropValue = new PropertySimple('value', newNodeName);
+ systemProp.put(systemPropValue);
+ } else {
+ systemPropValue.setValue(newNodeName);
+ }
+
+ updated = true;
+ break;
+ }
+ }
+
+ if (!updated) {
+ var systemProp = new PropertyMap('*:name');
+ systemProp.put(new PropertySimple('name', 'jboss.node.name'));
+ systemProp.put(new PropertySimple('value', newNodeName));
+ systemProps.add(systemProp);
+ }
+}
+
+function _updateSocketBindings(socketBindingsConfig, portOffset, clusterMemberPortOffset, socketBindingConfig) {
+ portOffset = _parseValueFromExpr(portOffset);
+ clusterMemberPortOffset = _parseValueFromExpr(clusterMemberPortOffset);
+
+ var portOffsetValue = portOffset.value != undefined ? portOffset.value : 0;
+ var clusterMemberPortOffsetValue = clusterMemberPortOffset.value != undefined ?
+ clusterMemberPortOffset.value : 0;
+
+ var portOffsetDiff = clusterMemberPortOffsetValue - portOffsetValue;
+
+ var ports = socketBindingsConfig.get('*');
+ var portIterator = ports.list.iterator();
+
+ var appliedProperties = [];
+
+ while (portIterator.hasNext()) {
+ var port = portIterator.next();
+
+ var name = port.getSimpleValue('name', null);
+
+ if (socketBindingConfig[name] != undefined) {
+ //k, this is a significant config, port it over
+
+ //compute the value of the ports
+ if (socketBindingConfig[name]['fixed-port'] == 'true') {
+ port.put(new PropertySimple('port:expr', socketBindingConfig[name]['port:expr']));
+ port.put(new PropertySimple('multicast-port:expr', socketBindingConfig[name]['multicast-port:expr']));
+ port.put(new PropertySimple('multicast-address', socketBindingConfig[name]['multicast-address']));
+ port.put(new PropertySimple('fixed-port', 'true'));
+ } else {
+ var portExprToUse = _parseValueFromExpr(socketBindingConfig[name]['port:expr']);
+ if (portExprToUse.value != undefined) {
+ portExprToUse.value = portOffsetDiff + parseInt(portExprToUse.value) + '';
+ }
+
+ var multicastPortExprToUse = _parseValueFromExpr(socketBindingConfig[name]['multicast-port:expr']);
+ if (multicastPortExprToUse.value != undefined) {
+ multicastPortExprToUse.value = portOffsetDiff + parseInt(multicastPortExprToUse.value) + '';
+ }
+
+ port.put(new PropertySimple('port:expr', _composeValueExpr(portExprToUse)));
+ port.put(new PropertySimple('multicast-port:expr', _composeValueExpr(multicastPortExprToUse)));
+ port.put(new PropertySimple('multicast-address', socketBindingConfig[name]['multicast-address']));
+ port.put(new PropertySimple('fixed-port', 'false'));
+ }
+ appliedProperties.push(name);
+ }
+ }
+
+ //I need contains() and Rhino's Array object apparently doesn't have indexOf()
+ //which I could otherwise use.
+ appliedProperties = java.util.Arrays.asList(appliedProperties);
+
+ //now add to the list the props that were not there
+ for(name in socketBindingConfig) {
+ if (appliedProperties.contains(name)) {
+ continue;
+ }
+
+ var port = new PropertyMap('binding');
+ ports.add(port);
+
+ port.put(new PropertySimple('name', name));
+ port.put(new PropertySimple('multicast-address', socketBindingConfig[name]['multicast-address']));
+ port.put(new PropertySimple('fixed-port', socketBindingConfig[name]['fixed-port']));
+
+ var portExprToUse = _parseValueFromExpr(socketBindingConfig[name]['port:expr']);
+ if (portExprToUse.value != undefined && !socketBindingConfig[name]['fixed-port']) {
+ portExprToUse.value = portOffsetDiff + parseInt(portExprToUse.value) + '';
+ }
+
+ var multicastPortExprToUse = _parseValueFromExpr(socketBindingConfig[name]['multicast-port:expr']);
+ if (multicastPortExprToUse.value != undefined && !socketBindingConfig[name]['fixed-port']) {
+ multicastPortExprToUse.value = portOffsetDiff + parseInt(multicastPortExprToUse.value) + '';
+ }
+
+ port.put(new PropertySimple('port:expr', _composeValueExpr(portExprToUse)));
+ port.put(new PropertySimple('multicast-port:expr', _composeValueExpr(multicastPortExprToUse)));
+ }
+}
+
+
+/**
+ * Returns a javascript hash of configuration properties significant for the
+ * cluster configuration.
+ *
+ * This method is quite simplistic - it merely reads out the important socket
+ * bindings and Infinispan configuration properties.
+ *
+ * It doesn't try to be smart about specifying which concrete caches and cache
+ * containers are used for individual subsystems like EJB3, JPA or web apps.
+ *
+ * @param resource the resource proxy of the AS7
+ */
+function _getClusterSignificantConfig(children, pluginConfiguration, resourceConfiguration) {
+ var ret = {};
+
+ ret['config'] = javascriptString(_getConfigName(pluginConfiguration));
+
+ ret['node-name'] = javascriptString(resourceConfiguration.getSimpleValue('node-name', null));
+
+ //the standalone server has a single socket binding group
+ for(var i in children) {
+ var child = children[i];
+ if (child.resourceType.plugin != 'JBossAS7') {
+ continue;
+ }
+ if (child.resourceType.name == 'SocketBindingGroup') {
+
+ var resourceConfig = _getLiveResourceConfiguration(child);
+
+ ret['port-offset'] = javascriptString(resourceConfig.getSimpleValue('port-offset', '0'));
+ var ports = resourceConfig.get('*');
+ var portIterator = ports.list.iterator();
+
+ var jgroups = {};
+ var messaging = {};
+ var modcluster = {};
+
+ ret['jgroups'] = jgroups;
+ ret['messaging'] = messaging;
+ ret['modcluster'] = modcluster;
+
+ while (portIterator.hasNext()) {
+ var port = portIterator.next();
+ var name = javascriptString(port.getSimpleValue('name', null));
+
+ if (name.indexOf('jgroups') == 0) {
+ jgroups[name] = _getSocketBinding(port);
+ } else if (name.indexOf('messaging') == 0) {
+ messaging[name] = _getSocketBinding(port);
+ } else if (name.indexOf('modcluster') == 0) {
+ modcluster[name] = _getSocketBinding(port);
+ }
+ }
+ } else if (child.resourceType.name == 'Infinispan') {
+ var ispn = {};
+ ret['infinispan'] = ispn;
+
+ //This has disappeared
+ //ispn['default-cache-container'] = javascriptString(child.defaultCacheContainer.value);
+ var cacheContainers = {};
+ ispn['cache-containers'] = cacheContainers;
+
+ for(cc in child.children) {
+ var cacheContainer = child.children[cc];
+
+ var caches = {};
+
+ var containerConfig = _getLiveResourceConfiguration(cacheContainer);
+
+ cacheContainers[cacheContainer.name] = {
+ 'default-cache' : javascriptString(containerConfig.getSimpleValue('default-cache', null)),
+ 'aliases' : _asArray(containerConfig.get('aliases')),
+ 'caches' : caches
+ };
+
+ for (c in cacheContainer.children) {
+ var cache = cacheContainer.children[c];
+
+ caches[cache.name] = {
+ '_flavor' : javascriptString(containerConfig.getSimpleValue('_flavor', null)),
+ 'batching' : javascriptString(containerConfig.getSimpleValue('batching', null)),
+ 'indexing' : javascriptString(containerConfig.getSimpleValue('indexing', null)),
+ 'mode' : javascriptString(containerConfig.getSimpleValue('mode', null))
+ };
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+function _getSocketBinding(port) {
+ return {
+ 'port:expr' : javascriptString(port.getSimpleValue('port:expr', '0')),
+ 'multicast-port:expr' : javascriptString(port.getSimpleValue('multicast-port:expr', null)),
+ 'multicast-address' : javascriptString(port.getSimpleValue('multicast-address', null)),
+ 'fixed-port' : javascriptString(port.getSimpleValue('fixed-port', null))
+ };
+}
+
+function javascriptString(string) {
+ return string == null || string == undefined ? string : string + '';
+}
+
+function _asArray(propertyList) {
+ var ret = [];
+ if (propertyList == undefined || propertyList == null) {
+ return ret;
+ }
+
+ var it = propertyList.list.iterator();
+ while (it.hasNext()) {
+ var prop = it.next();
+
+ if (prop instanceof PropertySimple) {
+ ret.push(javascriptString(prop.stringValue));
+ }
+ }
+
+ return ret;
+}
+
+function _getPluginConfiguration(resource) {
+ if (typeof(resource) == 'number') {
+ resource = ProxyFactory.getResource(resource)
+ } else {
+ resource = ProxyFactory.getResource(resource.id)
+ }
+ return resource.pluginConfiguration
+}
+
+function _getLiveResourceConfiguration(resource) {
+ var id;
+ if (typeof(resource) == 'number') {
+ id = resource;
+ } else {
+ id = resource.id
+ }
+
+ return ConfigurationManager.getLiveResourceConfiguration(id, false)
+}
+
+function _loadGroupMembers(group) {
+ var crit = new ResourceGroupCriteria;
+ crit.addFilterId(group.id);
+ crit.fetchExplicitResources(true);
+ crit.fetchImplicitResources(true);
+
+ var groups = ResourceGroupManager.findResourceGroupsByCriteria(crit)
+
+ if (groups.empty) {
+ throw "Could not find a previously loaded group on the server: " + group;
+ }
+
+ return groups.get(0);
+}
+
+function _allResourceIds(group) {
+ var list = [];
+
+ if (group.explicitResources != null) {
+ foreach(group.explicitResources, function(resource) {
+ list.push(resource.id);
+ });
+ }
+
+ return list;
+}
+
+function _runOperationSequentially(group, operationName, operationConfig) {
+ group = _loadGroupMembers(group);
+ var resourceIds = _allResourceIds(group);
+
+ var conf = operationConfig;
+ if (!(conf instanceof Configuration)) {
+ conf = asConfiguration(conf);
+ }
+
+ // by specifying the resource ids explicitly, we define the order in which
+ // the operations will be executed on individual resources (in sequence).
+ // if the resourceIds were null, the operations would be invoked all at once
+ // in no particular order.
+ var schedule = OperationManager.scheduleGroupOperation(group.id, resourceIds, false, operationName, conf, 0, 0, 0, 0, null);
+
+ // now we need to wait for all the individual operations on resources to
+ // complete
+ var crit = new GroupOperationHistoryCriteria;
+ crit.addFilterJobId(new JobId(schedule.jobName, schedule.jobGroup));
+
+ while (true) {
+ // XXX should this be configurable?
+ // we're waiting for an AS to start/shutdown which takes time..
+ // Let's check in 5s intervals.
+ // We're waiting at the start of this loop to also ensure that the server
+ // had time to issue the quartz job and persist the history entry.
+ java.lang.Thread.currentThread().sleep(5000);
+
+ var results = OperationManager.findGroupOperationHistoriesByCriteria(crit);
+
+ if (results.empty) {
+ throw "Could not find operation history for schedule " + schedule + ". This should not happen.";
+ }
+
+ var history = results.get(0);
+
+ if (history.status != OperationRequestStatus.INPROGRESS) {
+ return history;
+ }
+ }
+}
+
+function _restartAS4(group) {
+ _runOperationSequentially(group, "shutdown", new Configuration);
+ _runOperationSequentially(group, "start", new Configuration);
+}
+
+function _restartAS5(group) {
+ _runOperationSequentially(group, "shutdown", new Configuration);
+ _runOperationSequentially(group, "start", new Configuration);
+}
+
+function _restartAS7(group) {
+ var shutdownOp = group.resourceType.name == "JBossAS7-Standalone" ? "shutdown" : "server:stop";
+ var startOp = group.resourceType.name == "JBossAS7-Standalone" ? "start" : "server:start";
+
+ _runOperationSequentially(group, shutdownOp, new Configuration);
+ _runOperationSequentially(group, startOp, new Configuration);
+}
diff --git a/modules/enterprise/remoting/cli/src/main/samples/modules/util.js b/modules/enterprise/remoting/cli/src/main/samples/modules/util.js
new file mode 100644
index 0000000..b67eebc
--- /dev/null
+++ b/modules/enterprise/remoting/cli/src/main/samples/modules/util.js
@@ -0,0 +1,280 @@
+/**
+ * file: util.js
+ *
+ * description: This script contains functions that provide core, low-level
+ * functionality such as iterating over a collection or searching a collection.
+ * This has no other script or library dependencies.
+ *
+ * author: jsanda@redhat.com
+ * author: lkrejci@redhat.com
+ *
+ * See also http://johnsanda.blogspot.com/search/label/javascript
+ */
+
+
+/**
+ * If obj is a JS array or a java.util.Collection, each element is passed to
+ * the callback function. If obj is a java.util.Map, each map entry is passed
+ * to the callback function as a key/value pair. If obj is none of the
+ * aforementioned types, it is treated as a generic object and each of its
+ * properties is passed to the callback function as a name/value pair.
+ */
+exports.foreach = function (obj, fn) {
+ if (obj instanceof Array) {
+ for (i in obj) {
+ fn(obj[i]);
+ }
+ }
+ else if (obj instanceof java.util.Collection) {
+ var iterator = obj.iterator();
+ while (iterator.hasNext()) {
+ fn(iterator.next());
+ }
+ }
+ else if (obj instanceof java.util.Map) {
+ var iterator = obj.entrySet().iterator()
+ while (iterator.hasNext()) {
+ var entry = iterator.next();
+ fn(entry.key, entry.value);
+ }
+ }
+ else { // assume we have a generic object
+ for (i in obj) {
+ fn(i, obj[i]);
+ }
+ }
+}
+
+/**
+ * Iterates over obj similar to foreach. fn should be a predicate that evaluates
+ * to true or false. The first match that is found is returned.
+ */
+exports.find = function(obj, fn) {
+ if (obj instanceof Array) {
+ for (i in obj) {
+ if (fn(obj[i])) {
+ return obj[i]
+ }
+ }
+ }
+ else if (obj instanceof java.util.Collection) {
+ var iterator = obj.iterator();
+ while (iterator.hasNext()) {
+ var next = iterator.next();
+ if (fn(next)) {
+ return next;
+ }
+ }
+ }
+ else if (obj instanceof java.util.Map) {
+ var iterator = obj.entrySet().iterator();
+ while (iterator.hasNext()) {
+ var entry = iterator.next();
+ if (fn(entry.key, entry.value)) {
+ return {key: entry.key, value: entry.value};
+ }
+ }
+ }
+ else {
+ for (i in obj) {
+ if (fn(i, obj[i])) {
+ return {key: i, value: obj[i]};
+ }
+ }
+ }
+ return null;
+}
+
+/**
+ * Iterates over obj similar to foreach. fn should be a predicate that evaluates
+ * to true or false. All of the matches are returned in a java.util.List.
+ */
+exports.findAll = function(obj, fn) {
+ var matches = java.util.ArrayList();
+ if ((obj instanceof Array) || (obj instanceof java.util.Collection)) {
+ foreach(obj, function(element) {
+ if (fn(element)) {
+ matches.add(element);
+ }
+ });
+ }
+ else {
+ foreach(obj, function(key, value) {
+ if (fn(theKey, theValue)) {
+ matches.add({key: theKey, value: theValue});
+ }
+ });
+ }
+ return matches;
+}
+
+/**
+ * A convenience function to convert javascript hashes into RHQ's configuration
+ * objects.
+ * <p>
+ * The conversion of individual keys in the hash follows these rules:
+ * <ol>
+ * <li> if a value of a key is a javascript array, it is interpreted as PropertyList
+ * <li> if a value is a hash, it is interpreted as a PropertyMap
+ * <li> otherwise it is interpreted as a PropertySimple
+ * <li> a null or undefined value is ignored
+ * </ol>
+ * <p>
+ * Note that the conversion isn't perfect, because the hash does not contain enough
+ * information to restore the names of the list members.
+ * <p>
+ * Example: <br/>
+ * <pre><code>
+ * {
+ * simple : "value",
+ * list : [ "value1", "value2"],
+ * listOfMaps : [ { k1 : "value", k2 : "value" }, { k1 : "value2", k2 : "value2" } ]
+ * }
+ * </code></pre>
+ * gets converted to a configuration object:
+ * Configuration:
+ * <ul>
+ * <li> PropertySimple(name = "simple", value = "value")
+ * <li> PropertyList(name = "list")
+ * <ol>
+ * <li>PropertySimple(name = "list", value = "value1")
+ * <li>PropertySimple(name = "list", value = "value2")
+ * </ol>
+ * <li> PropertyList(name = "listOfMaps")
+ * <ol>
+ * <li> PropertyMap(name = "listOfMaps")
+ * <ul>
+ * <li>PropertySimple(name = "k1", value = "value")
+ * <li>PropertySimple(name = "k2", value = "value")
+ * </ul>
+ * <li> PropertyMap(name = "listOfMaps")
+ * <ul>
+ * <li>PropertySimple(name = "k1", value = "value2")
+ * <li>PropertySimple(name = "k2", value = "value2")
+ * </ul>
+ * </ol>
+ * </ul>
+ * Notice that the members of the list have the same name as the list itself
+ * which generally is not the case.
+ */
+exports.asConfiguration = function(hash) {
+
+ config = new Configuration;
+
+ for(key in hash) {
+ value = hash[key];
+
+ if (value == null) {
+ continue;
+ }
+
+ (function(parent, key, value) {
+ function isArray(obj) {
+ return typeof(obj) == 'object' && (obj instanceof Array);
+ }
+
+ function isHash(obj) {
+ return typeof(obj) == 'object' && !(obj instanceof Array);
+ }
+
+ function isPrimitive(obj) {
+ return typeof(obj) != 'object';
+ }
+
+ //this is an anonymous function, so the only way it can call itself
+ //is by getting its reference via argument.callee. Let's just assign
+ //a shorter name for it.
+ var me = arguments.callee;
+
+ var prop = null;
+
+ if (isPrimitive(value)) {
+ prop = new PropertySimple(key, new java.lang.String(value));
+ } else if (isArray(value)) {
+ prop = new PropertyList(key);
+ for(var i = 0; i < value.length; ++i) {
+ var v = value[i];
+ if (v != null) {
+ me(prop, key, v);
+ }
+ }
+ } else if (isHash(value)) {
+ prop = new PropertyMap(key);
+ for(var i in value) {
+ var v = value[i];
+ if (value != null) {
+ me(prop, i, v);
+ }
+ }
+ }
+
+ if (parent instanceof PropertyList) {
+ parent.add(prop);
+ } else {
+ parent.put(prop);
+ }
+ })(config, key, value);
+ }
+
+ return config;
+}
+
+/**
+ * Opposite of <code>asConfiguration</code>. Converts an RHQ's configuration object
+ * into a javascript hash.
+ *
+ * @param configuration
+ */
+exports.asHash = function(configuration) {
+ ret = {}
+
+ iterator = configuration.getMap().values().iterator();
+ while(iterator.hasNext()) {
+ prop = iterator.next();
+
+ (function(parent, prop) {
+ function isArray(obj) {
+ return typeof(obj) == 'object' && (obj instanceof Array);
+ }
+
+ function isHash(obj) {
+ return typeof(obj) == 'object' && !(obj instanceof Array);
+ }
+
+ var me = arguments.callee;
+
+ var representation = null;
+
+ if (prop instanceof PropertySimple) {
+ representation = prop.stringValue;
+ } else if (prop instanceof PropertyList) {
+ representation = [];
+
+ for(var i = 0; i < prop.list.size(); ++i) {
+ var child = prop.list.get(i);
+ me(representation, child);
+ }
+ } else if (prop instanceof PropertyMap) {
+ representation = {};
+
+ var childIterator = prop.getMap().values().iterator();
+ while(childIterator.hasNext()) {
+ var child = childIterator.next();
+
+ me(representation, child);
+ }
+ }
+
+ if (isArray(parent)) {
+ parent.push(representation);
+ } else if (isHash(parent)) {
+ parent[prop.name] = representation;
+ }
+ })(ret, prop);
+ }
+ (function(parent) {
+
+ })(configuration);
+
+ return ret;
+} \ No newline at end of file