org.jboss.loom.utils.as7.AS7CliUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.loom.utils.as7.AS7CliUtils.java

Source

/**
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 .
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */
package org.jboss.loom.utils.as7;

import org.jboss.loom.ex.CliBatchException;
import org.jboss.loom.conf.AS7Config;
import org.jboss.loom.ex.MigrationException;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.client.OperationBuilder;
import org.jboss.as.controller.client.helpers.ClientConstants;
import org.jboss.dmr.ModelNode;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Set;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.jboss.dmr.ModelType;
import org.jboss.loom.spi.ann.Property;

/**
 *
 * @author Ondrej Zizka, ozizka at redhat.com
 */
public class AS7CliUtils {

    private final static String OP_KEY_PREFIX = "Operation step-";

    public static void removeResourceIfExists(ModelNode loggerCmd, ModelControllerClient aS7Client)
            throws IOException, CliBatchException {

        // Check if exists.
        if (!exists(loggerCmd, aS7Client))
            return;

        // Remove.
        ModelNode res = aS7Client.execute(createRemoveCommandForResource(loggerCmd));
        throwIfFailure(res);
    }

    /**
     *  Queries the AS 7 if given resource exists.
     */
    public static boolean exists(final ModelNode resource, ModelControllerClient client) throws IOException {
        ModelNode query = new ModelNode();
        // Read operation.
        query.get(ClientConstants.OP).set(ClientConstants.READ_RESOURCE_OPERATION);
        // Copy the address.
        query.get(ClientConstants.OP_ADDR).set(resource.get(ClientConstants.OP_ADDR));
        //try{
        ModelNode res = client.execute(query);
        return wasSuccess(res);
        //}
        //// This handling should rather be done in whatever provides the client.
        //catch( IOException ex ){ throw ex; }
        //finally { client.close(); }
    }

    /** Parses the command string into ModalNode and calls the sister method. */
    public static boolean exists(String command, ModelControllerClient client) throws IOException {
        return exists(parseCommand(command, false), client);
    }

    public static ModelNode createRemoveCommandForResource(ModelNode resource) {
        // Copy the address.
        ModelNode query = new ModelNode();
        query.get(ClientConstants.OP_ADDR).set(resource.get(ClientConstants.OP_ADDR));
        // Remove operation.
        query.get(ClientConstants.OP).set(ClientConstants.REMOVE_OPERATION);

        return query;
    }

    /**
     *  Executes CLI request.
     */
    public static void executeRequest(ModelNode request, AS7Config as7config)
            throws IOException, CliBatchException {
        ModelControllerClient client = null;
        try {
            client = ModelControllerClient.Factory.create(as7config.getHost(), as7config.getManagementPort());
            final ModelNode response = client.execute(new OperationBuilder(request).build());
            throwIfFailure(response);
        } catch (IOException ex) {
            // Specific problem on Roman's PC. Need to connect two times.
            //final ModelNode response = client.execute(new OperationBuilder(request).build());
            //throwIfFailure( response );
            throw ex;
        } finally {
            safeClose(client);
        }
    }

    /**
     *  Executes CLI request.
     */
    public static ModelNode executeRequest(String cmd, ModelControllerClient mcc) throws IOException {
        ModelNode node = parseCommand(cmd, true);
        return mcc.execute(node);
    }

    /**
     *  Safely closes closeable resource (a CLI connection in our case).
     */
    public static void safeClose(final Closeable closeable) {
        if (closeable != null)
            try {
                closeable.close();
            } catch (IOException e) {
                //throw new MigrationException("Closing failed: " + e.getMessage(), e);
            }
    }

    /**
     *  If the result is an error, throw an exception.
     */
    private static void throwIfFailure(final ModelNode node) throws CliBatchException {
        if (wasSuccess(node))
            return;

        final String msg;
        if (node.hasDefined(ClientConstants.FAILURE_DESCRIPTION)) {
            if (node.hasDefined(ClientConstants.OP)) {
                msg = String.format("Operation '%s' at address '%s' failed: %s", node.get(ClientConstants.OP),
                        node.get(ClientConstants.OP_ADDR), node.get(ClientConstants.FAILURE_DESCRIPTION));
            } else {
                msg = String.format("Operation failed: %s", node.get(ClientConstants.FAILURE_DESCRIPTION));
            }
        } else {
            msg = String.format("Operation failed: %s", node);
        }
        throw new CliBatchException(msg, node);
    }

    public static boolean wasSuccess(ModelNode node) {
        return ClientConstants.SUCCESS.equals(node.get(ClientConstants.OUTCOME).asString());
    }

    /**
     *  Parses the index of operation which failed.
     * 
     *  "failure-description" => 
     *  {"JBAS014653: Composite operation failed and was rolled back. Steps that failed:" => {
     *      "Operation step-12" => "JBAS014803: Duplicate resource [
            (\"subsystem\" => \"security\"),
            (\"security-domain\" => \"other\") ]"
    }}
    * 
    * @deprecated  Use extractFailedOperationNode().
     */
    public static Integer parseFailedOperationIndex(final ModelNode node) throws MigrationException {

        if (ClientConstants.SUCCESS.equals(node.get(ClientConstants.OUTCOME).asString()))
            return 0;

        if (!node.hasDefined(ClientConstants.FAILURE_DESCRIPTION))
            return null;

        ModelNode failDesc = node.get(ClientConstants.FAILURE_DESCRIPTION);
        String key = failDesc.keys().iterator().next();
        // "JBAS014653: Composite operation failed and was rolled back. Steps that failed:" => ...

        ModelNode compositeFailDesc = failDesc.get(key);
        // { "Operation step-1" => "JBAS014803: Duplicate resource ...

        Set<String> keys = compositeFailDesc.keys();
        String opKey = keys.iterator().next();
        // "Operation step-XX"

        if (!opKey.startsWith(OP_KEY_PREFIX))
            return null;

        String opIndex = StringUtils.substring(opKey, OP_KEY_PREFIX.length());

        return Integer.parseInt(opIndex);
    }

    /**
     * @returns A ModelNode with two properties: "failedOpIndex" and "failureDesc".
     */
    public static BatchFailure extractFailedOperationNode(final ModelNode node) throws MigrationException {

        if (ClientConstants.SUCCESS.equals(node.get(ClientConstants.OUTCOME).asString()))
            return null;

        if (!node.hasDefined(ClientConstants.FAILURE_DESCRIPTION))
            return null;

        ModelNode failDesc = node.get(ClientConstants.FAILURE_DESCRIPTION);
        if (failDesc.getType() != ModelType.OBJECT)
            return null;

        String key = failDesc.keys().iterator().next();
        // "JBAS014653: Composite operation failed and was rolled back. Steps that failed:" => ...

        ModelNode compositeFailDesc = failDesc.get(key);
        // { "Operation step-1" => "JBAS014803: Duplicate resource ...

        Set<String> keys = compositeFailDesc.keys();
        String opKey = keys.iterator().next();
        // "Operation step-XX"

        if (!opKey.startsWith(OP_KEY_PREFIX))
            return null;

        String opIndex = StringUtils.substring(opKey, OP_KEY_PREFIX.length());

        return new BatchFailure(Integer.parseInt(opIndex), compositeFailDesc.get(opKey).toString());
    }

    /**
     *  Copies properties using reflection.
     * @param handler  From this object.
     * @param builder  Append to this CLI builder.
     * @param A list of properties, as a space separated string.
     */
    public static void copyProperties(Object source, CliApiCommandBuilder builder, String props) {
        String[] parts = StringUtils.split(props);
        for (String prop : parts) {
            try {
                Method method = source.getClass().getMethod(Property.Utils.convertPropToMethodName(prop));
                if (String.class != method.getReturnType())
                    continue;
                String val = (String) method.invoke(source);
                builder.addPropertyIfSet(prop, val);
            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException
                    | IllegalArgumentException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    /**
     *  Joins the given list into a string of quoted values joined with ", ".
     * @param col
     * @return 
     */
    public static String joinQuoted(Collection<String> col) {

        if (col.isEmpty())
            return "";

        StringBuilder sb = new StringBuilder();
        for (String item : col)
            sb.append(",\"").append(item).append('"');

        String str = sb.toString();
        str = str.replaceFirst(",", "");
        return str;
    }

    /**
     *  Parse CLI command into a ModelNode - /foo=a/bar=b/:operation(param=value,...) .
     * 
     *  TODO: Support nested params.
     */
    public static ModelNode parseCommand(String command) {
        return parseCommand(command, true);
    }

    public static ModelNode parseCommand(String command, boolean needOp) {
        String[] parts = StringUtils.split(command, ':');
        if (needOp && parts.length < 2)
            throw new IllegalArgumentException("Missing CLI command operation: " + command);
        String addr = parts[0];

        ModelNode query = new ModelNode();

        // Addr
        String[] partsAddr = StringUtils.split(addr, '/');
        for (String segment : partsAddr) {
            String[] partsSegment = StringUtils.split(segment, "=", 2);
            if (partsSegment.length != 2)
                throw new IllegalArgumentException("Wrong addr segment format - need '=': " + command);
            query.get(ClientConstants.OP_ADDR).add(partsSegment[0], partsSegment[1]);
        }

        // No op?
        if (parts.length < 2)
            return query;

        // Op
        String[] partsOp = StringUtils.split(parts[1], '(');
        String opName = partsOp[0];
        query.get(ClientConstants.OP).set(opName);

        // Op args
        if (partsOp.length > 1) {
            String args = StringUtils.removeEnd(partsOp[1], ")");
            for (String arg : args.split(",")) {
                String[] partsArg = arg.split("=", 2);
                query.get(partsArg[0]).set(unquote(partsArg[1]));
            }
        }
        return query;
    }// parseCommand()

    /**
     *  Changes "foo\"bar" to foo"bar.
     *  Is tolerant - doesn't check if the quotes are really present.
     */
    public static String unquote(String string) {
        string = StringUtils.removeStart(string, "\"");
        string = StringUtils.removeEnd(string, "\"");
        return StringEscapeUtils.unescapeJava(string);
    }

    /**
     *   Formats Model node to the form of CLI script command - /foo=a/bar=b/:operation(param=value,...) .
     */
    public static String formatCommand(ModelNode command) {

        if (!command.has(ClientConstants.OP))
            throw new IllegalArgumentException("'" + ClientConstants.OP + "' not defined.");
        if (command.get(ClientConstants.OP).getType() != ModelType.STRING)
            throw new IllegalArgumentException("'" + ClientConstants.OP + "' must be a string.");
        if (!command.has(ClientConstants.OP_ADDR))
            throw new IllegalArgumentException("'" + ClientConstants.OP_ADDR + "' not defined.");
        if (command.get(ClientConstants.OP_ADDR).getType() != ModelType.LIST)
            throw new IllegalArgumentException("'" + ClientConstants.OP_ADDR + "' must be a list.");

        // Operation.
        String op = command.get(ClientConstants.OP).asString();

        // Address
        ModelNode addr = command.get(ClientConstants.OP_ADDR);
        StringBuilder sb = new StringBuilder();
        for (int i = 0;; i++) {
            if (!addr.has(i))
                break;
            ModelNode segment = addr.get(i);
            String key = segment.keys().iterator().next();
            sb.append('/').append(key).append('=').append(segment.get(key).asString());
        }
        sb.append(':').append(op);

        // Params.
        boolean hasParams = false;
        Set<String> keys = command.keys();
        for (String key : keys) {
            switch (key) {
            case ClientConstants.OP:
            case ClientConstants.OP_ADDR:
                continue;
            }
            sb.append(hasParams ? ',' : '(');
            hasParams = true;
            sb.append(key).append('=').append(command.get(key));
        }
        if (hasParams)
            sb.append(')');
        return sb.toString();
    }

    /**
    /path=jboss.server.base.dir/:read-attribute(name=path,include-defaults=true)
    {
        "outcome" => "success",
        "result" => "/home/ondra/work/AS/Migration/AS-7.1.3/standalone"
    }
     */
    public static String queryServerPath(String path, ModelControllerClient client) throws MigrationException {

        ModelNode query = new ModelNode();
        query.get(ClientConstants.OP).set(ClientConstants.READ_ATTRIBUTE_OPERATION);
        query.get(ClientConstants.OP_ADDR).add("path", path);
        query.get("name").set("path");
        ModelNode response;
        try {
            response = client.execute(query);
            throwIfFailure(response);
        } catch (IOException | CliBatchException ex) {
            throw new MigrationException("Failed querying for AS 7 directory.", ex);
        }
        ModelNode result = response.get(ClientConstants.RESULT);
        if (result.getType() == ModelType.UNDEFINED)
            return null;
        return result.asString();
    }

    /**
     *  Actually it returns the base dir, i.e. the dir containing bin/, standalone/ etc.
     */
    public static String queryServerHomeDir(ModelControllerClient client) throws MigrationException {
        return queryServerPath("jboss.home.dir", client);
    }

    /**
     *  E.g. $AS/standalone (full path).
     */
    public static String queryServerBaseDir(ModelControllerClient client) throws MigrationException {
        return queryServerPath("jboss.server.base.dir", client);
    }

    public static String queryServerConfigDir(ModelControllerClient client) throws MigrationException {
        return queryServerPath("jboss.server.config.dir", client);
    }

    /**
     *  Escape CLI address element - the parts between / and = in /foo=bar/baz=moo .
     */
    public static String escapeAddressElement(String element) {
        element = element.replace(":", "\\:");
        element = element.replace("/", "\\/");
        element = element.replace("=", "\\=");
        element = element.replace(" ", "\\ ");
        return element;
    }

    /**
     *  Converts "some-property-name" to "getSomePropertyName()".
     *  
     *  @deprecated  Use @Property.Utils.convertPropToMethodName().
     */
    public static String formatGetterName(String prop) {
        return Property.Utils.convertPropToMethodName(prop);
    }

    /**
     *  Returns the name of JDBC driver which uses given module.
     * 
    /subsystem=datasources/:read-attribute(name=installed-drivers)
    {
        "outcome" => "success",
        "result" => [{
            "driver-name" => "h2",
            "deployment-name" => undefined,
            "driver-module-name" => "com.h2database.h2",
            "module-slot" => "main",
            "driver-datasource-class-name" => "",
            "driver-xa-datasource-class-name" => "org.h2.jdbcx.JdbcDataSource",
            "driver-class-name" => "org.h2.Driver",
            "driver-major-version" => 1,
            "driver-minor-version" => 3,
            "jdbc-compliant" => true
        }]
    }
     */
    public static String findJdbcDriverUsingModule(String driverModuleName, ModelControllerClient as7Client)
            throws IOException {

        ModelNode query = parseCommand("/subsystem=datasources/:read-attribute(name=installed-drivers)");
        ModelNode driversNode = as7Client.execute(query);
        driversNode = driversNode.get("result");
        for (ModelNode modelNode : driversNode.asList()) {
            if (modelNode.get("driver-module-name").asString().equals(driverModuleName))
                return modelNode.get("driver-name").asString();
        }
        return null;
    }

}// class