/** * 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