/* * RHQ Management Platform * Copyright (C) 2005-2012 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.common.jbossas.client.controller; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.as.controller.client.ModelControllerClient; import org.jboss.as.controller.client.OperationMessageHandler; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; /** * A client that can be used to talk to a JBossAS server via the DMR/ModelControllerClient API. * * @author John Mazzitelli */ public class JBossASClient { // protected to allow subclasses to have a logger, too, without explicitly declaring one themselves protected final Log log = LogFactory.getLog(this.getClass()); public static final String BATCH = "composite"; public static final String BATCH_STEPS = "steps"; public static final String OPERATION = "operation"; public static final String ADDRESS = "address"; public static final String RESULT = "result"; public static final String OUTCOME = "outcome"; public static final String OUTCOME_SUCCESS = "success"; public static final String SUBSYSTEM = "subsystem"; public static final String FAILURE_DESCRIPTION = "failure-description"; public static final String NAME = "name"; public static final String VALUE = "value"; public static final String READ_ATTRIBUTE = "read-attribute"; public static final String READ_RESOURCE = "read-resource"; public static final String WRITE_ATTRIBUTE = "write-attribute"; public static final String ADD = "add"; public static final String REMOVE = "remove"; public static final String SYSTEM_PROPERTY = "system-property"; public static final String PERSISTENT = "persistent"; // used by some operations to persist their effects private ModelControllerClient client; /** * Constructs a new JBoss AS Client that talks to the model through the provided client. * <p/> * Note that the caller is responsible to correctly close the client instance!!! * * @param client the client to use */ public JBossASClient(ModelControllerClient client) { this.client = client; } ///////////////////////////////////////////////////////////////// // Some static methods useful for convienence /** * Convienence method that allows you to create request that reads a single attribute * value to a resource. * * @param attributeName the name of the attribute whose value is to be read * @param address identifies the resource * @return the request */ public static ModelNode createReadAttributeRequest(String attributeName, Address address) { return createReadAttributeRequest(false, attributeName, address); } /** * Convienence method that allows you to create request that reads a single attribute * value to a resource. * * @param runtime if <code>true</code>, the attribute is a runtime attribute * @param attributeName the name of the attribute whose value is to be read * @param address identifies the resource * @return the request */ public static ModelNode createReadAttributeRequest(boolean runtime, String attributeName, Address address) { final ModelNode op = createRequest(READ_ATTRIBUTE, address); op.get("include-runtime").set(runtime); op.get(NAME).set(attributeName); return op; } /** * Convienence method that allows you to create request that writes a single attribute's * string value to a resource. * * @param attributeName the name of the attribute whose value is to be written * @param attributeValue the attribute value that is to be written * @param address identifies the resource * @return the request */ public static ModelNode createWriteAttributeRequest(String attributeName, String attributeValue, Address address) { final ModelNode op = createRequest(WRITE_ATTRIBUTE, address); op.get(NAME).set(attributeName); setPossibleExpression(op, VALUE, attributeValue); return op; } /** * Convienence method that builds a partial operation request node. * * @param operation the operation to be requested * @param address identifies the target resource * @return the partial operation request node - caller should fill this in further to complete the node */ public static ModelNode createRequest(String operation, Address address) { return createRequest(operation, address, null); } /** * Convienence method that builds a partial operation request node, with additional * node properties supplied by the given node. * * @param operation the operation to be requested * @param address identifies the target resource * @param extra provides additional properties to add to the returned request node. * @return the partial operation request node - caller can fill this in further to complete the node */ public static ModelNode createRequest(String operation, Address address, ModelNode extra) { final ModelNode request = (extra != null) ? extra.clone() : new ModelNode(); request.get(OPERATION).set(operation); request.get(ADDRESS).set(address.getAddressNode()); return request; } /** * Creates a batch of operations that can be atomically invoked. * * @param steps the different operation steps of the batch * * @return the batch operation node */ public static ModelNode createBatchRequest(ModelNode... steps) { final ModelNode composite = new ModelNode(); composite.get(OPERATION).set(BATCH); composite.get(ADDRESS).setEmptyList(); final ModelNode stepsNode = composite.get(BATCH_STEPS); for (ModelNode step : steps) { if (step != null) { stepsNode.add(step); } } return composite; } /** * If the given node has a result list, that list will be returned * with the values as Strings. Otherwise, an empty list is returned. * * @param operationResult the node to examine * @return the result list as Strings if there is a list, empty otherwise */ public static List<String> getResultListAsStrings(ModelNode operationResult) { if (!operationResult.hasDefined(RESULT)) { return Collections.emptyList(); } final List<ModelNode> nodeList = operationResult.get(RESULT).asList(); if (nodeList.isEmpty()) { return Collections.emptyList(); } final List<String> list = new ArrayList<String>(nodeList.size()); for (ModelNode node : nodeList) { list.add(node.asString()); } return list; } /** * If the given node has results, those results are returned in a ModelNode. * Otherwise, an empty node is returned. * * @param operationResult the node to examine * @return the results as a ModelNode */ public static ModelNode getResults(ModelNode operationResult) { if (!operationResult.hasDefined(RESULT)) { return new ModelNode(); } return operationResult.get(RESULT); } /** * Returns <code>true</code> if the operation was a success; <code>false</code> otherwise. * * @param operationResult the operation result to test * @return the success or failure flag of the result */ public static boolean isSuccess(ModelNode operationResult) { if (operationResult != null) { return operationResult.hasDefined(OUTCOME) && operationResult.get(OUTCOME).asString().equals(OUTCOME_SUCCESS); } return false; } /** * If the operation result was a failure, this returns the failure description if there is one. * A generic failure message will be returned if the operation was a failure but has no failure * description. A <code>null</code> is returned if the operation was a success. * * @param operationResult the operation whose failure description is to be returned * @return the failure description of <code>null</code> if the operation was a success */ public static String getFailureDescription(ModelNode operationResult) { if (isSuccess(operationResult)) { return null; } if (operationResult != null) { final ModelNode descr = operationResult.get(FAILURE_DESCRIPTION); if (descr != null) { return descr.asString(); } } return "Unknown failure"; } /** * This sets the given node's named attribute to the given value. If the value * appears to be an expression (that is, contains "${" somewhere in it), this will * set the value as an expression on the node. * * @param node the node whose attribute is to be set * @param name the name of the attribute whose value is to be set * @param value the value, possibly an expression * * @return returns the node */ public static ModelNode setPossibleExpression(ModelNode node, String name, String value) { if (value != null) { if (value.contains("${")) { return node.get(name).setExpression(value); } else { return node.get(name).set(value); } } else { return node.get(name).clear(); } } ///////////////////////////////////////////////////////////////// // Non-static methods that need the client public ModelControllerClient getModelControllerClient() { return client; } /** * Convienence method that executes the request. * * @param request * @return results * @throws Exception */ public ModelNode execute(ModelNode request) throws Exception { ModelControllerClient mcc = getModelControllerClient(); return mcc.execute(request, OperationMessageHandler.logging); } /** * This returns information on the resource at the given address. * This will not return an exception if the address points to a non-existent resource, rather, * it will just return null. You can use this as a test for resource existence. * * @param addr * @return the found item or null if not found * @throws Exception if some error prevented the lookup from even happening */ public ModelNode readResource(Address addr) throws Exception { return readResource(addr, false); } /** * This returns information on the resource at the given address, recursively * returning child nodes with the result if recursive argument is set to <code>true</code>. * This will not return an exception if the address points to a non-existent resource, rather, * it will just return null. You can use this as a test for resource existence. * * @param addr * @param recursive if true, return all child data within the resource node * @return the found item or null if not found * @throws Exception if some error prevented the lookup from even happening */ public ModelNode readResource(Address addr, boolean recursive) throws Exception { final ModelNode request = createRequest(READ_RESOURCE, addr); request.get("recursive").set(recursive); final ModelNode results = getModelControllerClient().execute(request, OperationMessageHandler.logging); if (isSuccess(results)) { final ModelNode resource = getResults(results); return resource; } else { return null; } } /** * Removes the resource at the given address. * * @param doomedAddr the address of the resource to remove * @throws Exception */ public void remove(Address doomedAddr) throws Exception { final ModelNode request = createRequest(REMOVE, doomedAddr); final ModelNode response = execute(request); if (!isSuccess(response)) { throw new FailureException(response, "Failed to remove resource at address [" + doomedAddr + "]"); } return; } /** * Convienence method that allows you to obtain a single attribute's string value from * a resource. * * @param attributeName the attribute whose value is to be returned * @param address identifies the resource * @return the attribute value * * @throws Exception if failed to obtain the attribute value */ public String getStringAttribute(String attributeName, Address address) throws Exception { return getStringAttribute(false, attributeName, address); } /** * Convienence method that allows you to obtain a single attribute's string value from * a resource. * * @param runtime if <code>true</code>, the attribute to be retrieved is a runtime attribute * @param attributeName the attribute whose value is to be returned * @param address identifies the resource * @return the attribute value * * @throws Exception if failed to obtain the attribute value */ public String getStringAttribute(boolean runtime, String attributeName, Address address) throws Exception { final ModelNode op = createReadAttributeRequest(runtime, attributeName, address); final ModelNode results = execute(op); if (isSuccess(results)) { final ModelNode version = getResults(results); final String attributeValue = version.asString(); return attributeValue; } else { throw new FailureException(results, "Failed to get attribute [" + attributeName + "] from [" + address + "]"); } } /** * This tries to find specific node within a list of nodes. Given an address and a named node * at that address (the "haystack"), it is assumed that haystack is actually a list of other * nodes. This method looks in the haystack and tries to find the named needle. If it finds it, * that list item is returned. If it does not find the needle in the haystack, it returns null. * * For example, if you want to find a specific datasource in the list of datasources, you * can pass in the address for the datasource subsystem, and ask to look in the data-source * node list (the haystack) and return the named datasource (the needle). * * @param addr * @param haystack * @param needle * @return the found item or null if not found * @throws Exception if the lookup fails for some reason */ public ModelNode findNodeInList(Address addr, String haystack, String needle) throws Exception { final ModelNode queryNode = createRequest(READ_RESOURCE, addr); final ModelNode results = execute(queryNode); if (isSuccess(results)) { final ModelNode haystackNode = getResults(results).get(haystack); if (haystackNode.getType() != ModelType.UNDEFINED) { final List<ModelNode> haystackList = haystackNode.asList(); for (ModelNode needleNode : haystackList) { if (needleNode.has(needle)) { return needleNode; } } } return null; } else { throw new FailureException(results, "Failed to get data for [" + addr + "]"); } } }