/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.patching.cli; import static java.lang.System.getProperty; import static java.lang.System.getSecurityManager; import static java.lang.System.getenv; import static java.security.AccessController.doPrivileged; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import javax.xml.stream.XMLStreamException; import org.jboss.as.cli.CommandContext; import org.jboss.as.cli.CommandFormatException; import org.jboss.as.cli.CommandLineException; import org.jboss.as.cli.Util; import org.jboss.as.cli.handlers.CommandHandlerWithHelp; import org.jboss.as.cli.handlers.DefaultFilenameTabCompleter; import org.jboss.as.cli.handlers.FilenameTabCompleter; import org.jboss.as.cli.handlers.SimpleTabCompleter; import org.jboss.as.cli.handlers.WindowsFilenameTabCompleter; import org.jboss.as.cli.impl.ArgumentWithListValue; import org.jboss.as.cli.impl.ArgumentWithValue; import org.jboss.as.cli.impl.ArgumentWithoutValue; import org.jboss.as.cli.impl.DefaultCompleter; import org.jboss.as.cli.impl.FileSystemPathArgument; import org.jboss.as.cli.operation.ParsedCommandLine; import org.jboss.as.cli.util.SimpleTable; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.patching.Constants; import org.jboss.as.patching.IoUtils; import org.jboss.as.patching.PatchingException; import org.jboss.as.patching.logging.PatchLogger; import org.jboss.as.patching.metadata.BundledPatch.BundledPatchEntry; import org.jboss.as.patching.metadata.Identity; import org.jboss.as.patching.metadata.Patch; import org.jboss.as.patching.metadata.PatchBundleXml; import org.jboss.as.patching.metadata.PatchElement; import org.jboss.as.patching.metadata.PatchXml; import org.jboss.as.patching.tool.PatchOperationBuilder; import org.jboss.as.patching.tool.PatchOperationTarget; import org.jboss.dmr.ModelNode; import org.wildfly.security.manager.action.ReadEnvironmentPropertyAction; import org.wildfly.security.manager.action.ReadPropertyAction; /** * @author <a href="http://jmesnil.net/">Jeff Mesnil</a> (c) 2012 Red Hat Inc. */ public class PatchHandler extends CommandHandlerWithHelp { static final String PATCH = "patch"; static final String APPLY = "apply"; static final String ROLLBACK = "rollback"; static final String HISTORY = "history"; static final String INFO = "info"; static final String INSPECT = "inspect"; private final ArgumentWithValue host; private final ArgumentWithValue action; private final ArgumentWithoutValue path; private final ArgumentWithValue patchId; private final ArgumentWithValue patchStream; private final ArgumentWithoutValue rollbackTo; private final ArgumentWithValue resetConfiguration; private final ArgumentWithoutValue overrideModules; private final ArgumentWithoutValue overrideAll; private final ArgumentWithListValue override; private final ArgumentWithListValue preserve; private final ArgumentWithoutValue distribution; private final ArgumentWithoutValue modulePath; private final ArgumentWithoutValue bundlePath; private final ArgumentWithoutValue verbose; private final ArgumentWithoutValue streams; /** whether the output should be displayed in a human friendly form or JSON - tools friendly */ private final ArgumentWithoutValue jsonOutput; private static final String lineSeparator = getSecurityManager() == null ? getProperty("line.separator") : doPrivileged(new ReadPropertyAction("line.separator")); public PatchHandler(final CommandContext context) { super(PATCH, false); action = new ArgumentWithValue(this, new SimpleTabCompleter(new String[]{APPLY, ROLLBACK, HISTORY, INFO, INSPECT}), 0, "--action"); host = new ArgumentWithValue(this, new DefaultCompleter(CandidatesProviders.HOSTS), "--host") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { boolean connected = ctx.getControllerHost() != null; return connected && ctx.isDomainMode() && super.canAppearNext(ctx); } }; // apply & rollback arguments overrideModules = new ArgumentWithoutValue(this, "--override-modules") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (canOnlyAppearAfterActions(ctx, APPLY, ROLLBACK)) { return super.canAppearNext(ctx); } return false; } }; overrideModules.addRequiredPreceding(action); overrideAll = new ArgumentWithoutValue(this, "--override-all") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (canOnlyAppearAfterActions(ctx, APPLY, ROLLBACK)) { return super.canAppearNext(ctx); } return false; } }; overrideAll.addRequiredPreceding(action); override = new ArgumentWithListValue(this, "--override") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (canOnlyAppearAfterActions(ctx, APPLY, ROLLBACK)) { return super.canAppearNext(ctx); } return false; } }; override.addRequiredPreceding(action); preserve = new ArgumentWithListValue(this, "--preserve") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (canOnlyAppearAfterActions(ctx, APPLY, ROLLBACK)) { return super.canAppearNext(ctx); } return false; } }; preserve.addRequiredPreceding(action); // apply arguments final FilenameTabCompleter pathCompleter = Util.isWindows() ? new WindowsFilenameTabCompleter(context) : new DefaultFilenameTabCompleter(context); path = new FileSystemPathArgument(this, pathCompleter, 1, "--path") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (canOnlyAppearAfterActions(ctx, APPLY, INSPECT)) { return super.canAppearNext(ctx); } return false; } }; path.addRequiredPreceding(action); // rollback arguments patchId = new ArgumentWithValue(this, 1, "--patch-id") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (canOnlyAppearAfterActions(ctx, INFO, ROLLBACK)) { return super.canAppearNext(ctx); } return false; } }; patchId.addRequiredPreceding(action); patchStream = new ArgumentWithValue(this, "--patch-stream") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (canOnlyAppearAfterActions(ctx, HISTORY, INFO, ROLLBACK)) { return super.canAppearNext(ctx); } return false; } }; patchStream.addRequiredPreceding(action); rollbackTo = new ArgumentWithoutValue(this, "--rollback-to") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (canOnlyAppearAfterActions(ctx, ROLLBACK)) { return super.canAppearNext(ctx); } return false; } }; rollbackTo.addRequiredPreceding(action); resetConfiguration = new ArgumentWithValue(this, SimpleTabCompleter.BOOLEAN, "--reset-configuration") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (canOnlyAppearAfterActions(ctx, ROLLBACK)) { return super.canAppearNext(ctx); } return false; } }; resetConfiguration.addRequiredPreceding(action); distribution = new FileSystemPathArgument(this, pathCompleter, "--distribution") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (ctx.getModelControllerClient() == null && canOnlyAppearAfterActions(ctx, APPLY, ROLLBACK, HISTORY, INFO)) { return super.canAppearNext(ctx); } return false; } }; modulePath = new FileSystemPathArgument(this, pathCompleter, "--module-path") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (ctx.getModelControllerClient() == null && canOnlyAppearAfterActions(ctx, APPLY, ROLLBACK, HISTORY, INFO)) { return super.canAppearNext(ctx); } return false; } }; bundlePath = new FileSystemPathArgument(this, pathCompleter, "--bundle-path") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (ctx.getModelControllerClient() == null && canOnlyAppearAfterActions(ctx, APPLY, ROLLBACK, HISTORY, INFO)) { return super.canAppearNext(ctx); } return false; } }; verbose = new ArgumentWithoutValue(this, "--verbose", "-v") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (canOnlyAppearAfterActions(ctx, INFO, INSPECT)) { return super.canAppearNext(ctx); } return false; } }; verbose.addRequiredPreceding(action); jsonOutput = new ArgumentWithoutValue(this, "--json-output") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { // if (canOnlyAppearAfterActions(ctx, INFO)) { // return super.canAppearNext(ctx); // } // hide from the tab-completion for now return false; } }; streams = new ArgumentWithoutValue(this, "--streams") { @Override public boolean canAppearNext(CommandContext ctx) throws CommandFormatException { if (canOnlyAppearAfterActions(ctx, INFO)) { return super.canAppearNext(ctx); } return false; } }; streams.addRequiredPreceding(action); streams.addCantAppearAfter(verbose); verbose.addCantAppearAfter(streams); streams.addCantAppearAfter(patchStream); patchStream.addCantAppearAfter(streams); streams.addCantAppearAfter(patchId); patchId.addCantAppearAfter(streams); } private boolean canOnlyAppearAfterActions(CommandContext ctx, String... actions) { final String actionStr = this.action.getValue(ctx.getParsedCommandLine()); if(actionStr == null || actions.length == 0) { return false; } return Arrays.asList(actions).contains(actionStr); } @Override protected void doHandle(CommandContext ctx) throws CommandLineException { final ParsedCommandLine parsedLine = ctx.getParsedCommandLine(); if(host.isPresent(parsedLine) && !ctx.isDomainMode()) { throw new CommandFormatException("The --host option is not available in the current context. Connection to the controller might be unavailable or not running in domain mode."); } final String action = this.action.getValue(parsedLine); if(INSPECT.equals(action)) { doInspect(ctx); return; } final PatchOperationTarget target = createPatchOperationTarget(ctx); final PatchOperationBuilder builder = createPatchOperationBuilder(parsedLine); final ModelNode response; try { response = builder.execute(target); } catch (Exception e) { throw new CommandLineException(action + " failed", e); } if (!Util.isSuccess(response)) { final ModelNode fd = response.get(ModelDescriptionConstants.FAILURE_DESCRIPTION); if(!fd.isDefined()) { throw new CommandLineException("Failed to apply patch: " + response.asString()); } if(fd.has(Constants.CONFLICTS)) { final StringBuilder buf = new StringBuilder(); buf.append(fd.get(Constants.MESSAGE).asString()).append(": "); final ModelNode conflicts = fd.get(Constants.CONFLICTS); String title = ""; if(conflicts.has(Constants.BUNDLES)) { formatConflictsList(buf, conflicts, "", Constants.BUNDLES); title = ", "; } if(conflicts.has(Constants.MODULES)) { formatConflictsList(buf, conflicts, title, Constants.MODULES); title = ", "; } if(conflicts.has(Constants.MISC)) { formatConflictsList(buf, conflicts, title, Constants.MISC); } buf.append(lineSeparator).append("Use the --override-all, --override=[] or --preserve=[] arguments in order to resolve the conflict."); throw new CommandLineException(buf.toString()); } else { throw new CommandLineException(Util.getFailureDescription(response)); } } if(INFO.equals(action)) { if(patchId.getValue(parsedLine) != null) { final ModelNode result = response.get(ModelDescriptionConstants.RESULT); if(!result.isDefined()) { return; } SimpleTable table = new SimpleTable(2); table.addLine(new String[]{"Patch ID:", result.get(Constants.PATCH_ID).asString()}); table.addLine(new String[]{"Type:", result.get(Constants.TYPE).asString()}); table.addLine(new String[]{"Identity name:", result.get(Constants.IDENTITY_NAME).asString()}); table.addLine(new String[]{"Identity version:", result.get(Constants.IDENTITY_VERSION).asString()}); table.addLine(new String[]{"Description:", result.get(Constants.DESCRIPTION).asString()}); if (result.hasDefined(Constants.LINK)) { table.addLine(new String[]{"Link:", result.get(Constants.LINK).asString()}); } ctx.printLine(table.toString(false)); final ModelNode elements = result.get(Constants.ELEMENTS); if(elements.isDefined()) { ctx.printLine(""); ctx.printLine("ELEMENTS"); for(ModelNode e : elements.asList()) { table = new SimpleTable(2); table.addLine(new String[]{"Patch ID:", e.get(Constants.PATCH_ID).asString()}); table.addLine(new String[]{"Name:", e.get(Constants.NAME).asString()}); table.addLine(new String[]{"Type:", e.get(Constants.TYPE).asString()}); table.addLine(new String[]{"Description:", e.get(Constants.DESCRIPTION).asString()}); ctx.printLine(""); ctx.printLine(table.toString(false)); } } } else if(jsonOutput.isPresent(parsedLine)) { ctx.printLine(response.toJSONString(false)); } else if(streams.isPresent(parsedLine)) { final List<ModelNode> list = response.get(ModelDescriptionConstants.RESULT).asList(); if(list.size() == 1) { ctx.printLine(list.get(0).asString()); } else { final List<String> streams = new ArrayList<String>(list.size()); for(ModelNode stream : list) { streams.add(stream.asString()); } ctx.printColumns(streams); } } else { final ModelNode result = response.get(ModelDescriptionConstants.RESULT); if(!result.isDefined()) { return; } SimpleTable table = new SimpleTable(2); table.addLine(new String[]{"Version:", result.get(Constants.VERSION).asString()}); addPatchesInfo(result, table); ctx.printLine(table.toString(false)); if(verbose.isPresent(parsedLine)) { printLayerPatches(ctx, result, Constants.ADD_ON); printLayerPatches(ctx, result, Constants.LAYER); } } } else { ctx.printLine(response.toJSONString(false)); } } protected void printLayerPatches(CommandContext ctx, final ModelNode result, final String type) { ModelNode layer = result.get(type); if(layer.isDefined()) { final String header = Character.toUpperCase(type.charAt(0)) + type.substring(1) + ':'; for(String name : layer.keys()) { final ModelNode node = layer.get(name); final SimpleTable table = new SimpleTable(2); table.addLine(new String[]{header, name}); addPatchesInfo(node, table); ctx.printLine(lineSeparator + table.toString(false)); } } } protected void addPatchesInfo(final ModelNode result, SimpleTable table) { table.addLine(new String[]{"Cumulative patch ID:", result.get(Constants.CUMULATIVE).asString()}); final List<ModelNode> patches = result.get(Constants.PATCHES).asList(); final String patchesStr; if(patches.isEmpty()) { patchesStr = "none"; } else { final StringBuilder buf = new StringBuilder(); buf.append(patches.get(0).asString()); for (int i = 1; i < patches.size(); ++i) { buf.append(',').append(patches.get(i).asString()); } patchesStr = buf.toString(); } table.addLine(new String[]{"One-off patches:", patchesStr}); } protected void doInspect(CommandContext ctx) throws CommandLineException { final ParsedCommandLine parsedLine = ctx.getParsedCommandLine(); final String patchPath = path.getValue(parsedLine, true); final File patchFile = new File(patchPath); if(!patchFile.exists()) { throw new CommandLineException("Failed to locate " + patchFile.getAbsolutePath()); } ZipFile patchZip = null; InputStream is = null; try { patchZip = new ZipFile(patchFile); ZipEntry patchXmlEntry = patchZip.getEntry(PatchBundleXml.MULTI_PATCH_XML); if(patchXmlEntry == null) { patchXmlEntry = patchZip.getEntry(PatchXml.PATCH_XML); if(patchXmlEntry == null) { throw new CommandLineException("Neither " + PatchBundleXml.MULTI_PATCH_XML + " nor " + PatchXml.PATCH_XML+ " were found in " + patchFile.getAbsolutePath()); } is = patchZip.getInputStream(patchXmlEntry); final Patch patch = PatchXml.parse(is).resolvePatch(null, null); displayPatchXml(ctx, patch); } else { is = patchZip.getInputStream(patchXmlEntry); final List<BundledPatchEntry> patches = PatchBundleXml.parse(is).getPatches(); displayPatchBundleXml(ctx, patches, patchZip); } } catch (ZipException e) { throw new CommandLineException("Failed to open " + patchFile.getAbsolutePath(), e); } catch (IOException e) { throw new CommandLineException("Failed to open " + patchFile.getAbsolutePath(), e); } catch (PatchingException e) { throw new CommandLineException("Failed to resolve parsed patch", e); } catch (XMLStreamException e) { throw new CommandLineException("Failed to parse patch.xml", e); } finally { if(is != null) { try { is.close(); } catch (IOException e) { } } if (patchZip != null) { try { patchZip.close(); } catch (IOException e) { } } } } private void displayPatchBundleXml(CommandContext ctx, List<BundledPatchEntry> patches, ZipFile patchZip) throws CommandLineException { if(patches.isEmpty()) { return; } for(BundledPatchEntry bundledPatch : patches) { final ZipEntry bundledZip = patchZip.getEntry(bundledPatch.getPatchPath()); if(bundledZip == null) { throw new CommandLineException("Patch file not found in the bundle: " + bundledPatch.getPatchPath()); } InputStream is = null; ZipInputStream bundledPatchIs = null; try { is = patchZip.getInputStream(bundledZip); bundledPatchIs = new ZipInputStream(is); ZipEntry bundledPatchXml = bundledPatchIs.getNextEntry(); while(bundledPatchXml != null) { if(PatchXml.PATCH_XML.equals(bundledPatchXml.getName())) { break; } bundledPatchXml = bundledPatchIs.getNextEntry(); } if(bundledPatchXml == null) { throw new CommandLineException("Failed to locate " + PatchXml.PATCH_XML + " in bundled patch " + bundledPatch.getPatchPath()); } final Patch patch = PatchXml.parse(bundledPatchIs).resolvePatch(null, null); if(verbose.isPresent(ctx.getParsedCommandLine())) { // to make the separation better in the verbose mode ctx.printLine("CONTENT OF " + bundledPatch.getPatchPath() + ':' + Util.LINE_SEPARATOR); } displayPatchXml(ctx, patch); ctx.printLine(""); } catch (Exception e) { throw new CommandLineException("Failed to inspect " + bundledPatch.getPatchPath(), e); } finally { IoUtils.safeClose(bundledPatchIs); IoUtils.safeClose(is); } } } private void displayPatchXml(CommandContext ctx, Patch patch) throws CommandLineException { final Identity identity = patch.getIdentity(); SimpleTable table = new SimpleTable(2); table.addLine(new String[]{"Patch ID:", patch.getPatchId()}); table.addLine(new String[]{"Type:", identity.getPatchType().getName()}); table.addLine(new String[]{"Identity name:", identity.getName()}); table.addLine(new String[]{"Identity version:", identity.getVersion()}); table.addLine(new String[]{"Description:", patch.getDescription() == null ? "n/a" : patch.getDescription()}); if (patch.getLink() != null) { table.addLine(new String[]{"Link:", patch.getLink()}); } ctx.printLine(table.toString(false)); if(verbose.isPresent(ctx.getParsedCommandLine())) { ctx.printLine(""); ctx.printLine("ELEMENTS"); for(PatchElement e : patch.getElements()) { table = new SimpleTable(2); table.addLine(new String[]{"Patch ID:", e.getId()}); table.addLine(new String[]{"Name:", e.getProvider().getName()}); table.addLine(new String[]{"Type:", e.getProvider().isAddOn() ? Constants.ADD_ON : Constants.LAYER}); table.addLine(new String[]{"Description:", e.getDescription()}); ctx.printLine(""); ctx.printLine(table.toString(false)); } } } protected void formatConflictsList(final StringBuilder buf, final ModelNode conflicts, String title, String contentType) { buf.append(title); final List<ModelNode> list = conflicts.get(contentType).asList(); int i = 0; while(i < list.size()) { final ModelNode item = list.get(i++); buf.append(item.asString()); if(i < list.size()) { buf.append(", "); } } } private PatchOperationBuilder createPatchOperationBuilder(ParsedCommandLine args) throws CommandFormatException { final String action = this.action.getValue(args, true); PatchOperationBuilder builder; if (APPLY.equals(action)) { final String path = this.path.getValue(args, true); final File f = new File(path); if(!f.exists()) { // i18n is never used for CLI exceptions throw new CommandFormatException("Path " + f.getAbsolutePath() + " doesn't exist."); } if(f.isDirectory()) { throw new CommandFormatException(f.getAbsolutePath() + " is a directory."); } builder = PatchOperationBuilder.Factory.patch(f); } else if (ROLLBACK.equals(action)) { String resetConfigValue = resetConfiguration.getValue(args, true); boolean resetConfig; if(Util.TRUE.equalsIgnoreCase(resetConfigValue)) { resetConfig = true; } else if(Util.FALSE.equalsIgnoreCase(resetConfigValue)) { resetConfig = false; } else { throw new CommandFormatException("Unexpected value for --reset-configuration (only true and false are allowed): " + resetConfigValue); } final String patchStream = this.patchStream.getValue(args); if(patchId.isPresent(args)) { final String id = patchId.getValue(args, true); final boolean rollbackTo = this.rollbackTo.isPresent(args); builder = PatchOperationBuilder.Factory.rollback(patchStream, id, rollbackTo, resetConfig); } else { builder = PatchOperationBuilder.Factory.rollbackLast(patchStream, resetConfig); } } else if (INFO.equals(action)) { if(streams.isPresent(args)) { return PatchOperationBuilder.Factory.streams(); } final String patchStream = this.patchStream.getValue(args); final String pId = patchId.getValue(args); if(pId == null) { builder = PatchOperationBuilder.Factory.info(patchStream); } else { builder = PatchOperationBuilder.Factory.info(patchStream, pId, verbose.isPresent(args)); } return builder; } else if (HISTORY.equals(action)) { final String patchStream = this.patchStream.getValue(args); builder = PatchOperationBuilder.Factory.history(patchStream); return builder; } else { throw new CommandFormatException("Unrecognized action '" + action + "'"); } if (overrideModules.isPresent(args)) { builder.ignoreModuleChanges(); } if (overrideAll.isPresent(args)) { builder.overrideAll(); } if (override.isPresent(args)) { final String overrideList = override.getValue(args); if(overrideList == null || overrideList.isEmpty()) { throw new CommandFormatException(override.getFullName() + " is missing value."); } for (String path : overrideList.split(",+")) { builder.overrideItem(path); } } if (preserve.isPresent(args)) { final String preserveList = preserve.getValue(args); if(preserveList == null || preserveList.isEmpty()) { throw new CommandFormatException(preserve.getFullName() + " is missing value."); } for (String path : preserveList.split(",+")) { builder.preserveItem(path); } } return builder; } private PatchOperationTarget createPatchOperationTarget(CommandContext ctx) throws CommandLineException { final PatchOperationTarget target; final ParsedCommandLine args = ctx.getParsedCommandLine(); if (ctx.getModelControllerClient() != null) { if(distribution.isPresent(args)) { throw new CommandFormatException(distribution.getFullName() + " is not allowed when connected to the controller."); } if(modulePath.isPresent(args)) { throw new CommandFormatException(modulePath.getFullName() + " is not allowed when connected to the controller."); } if(bundlePath.isPresent(args)) { throw new CommandFormatException(bundlePath.getFullName() + " is not allowed when connected to the controller."); } if (ctx.isDomainMode()) { String hostName = host.getValue(args, true); target = PatchOperationTarget.createHost(hostName, ctx.getModelControllerClient()); } else { target = PatchOperationTarget.createStandalone(ctx.getModelControllerClient()); } } else { final String jbossHome = getJBossHome(args); final File root = new File(jbossHome); final List<File> modules = getFSArgument(modulePath, args, root, "modules"); final List<File> bundles = getFSArgument(bundlePath, args, root, "bundles"); try { target = PatchOperationTarget.createLocal(root, modules, bundles); } catch (Exception e) { throw new CommandLineException("Unable to apply patch to local JBOSS_HOME=" + jbossHome, e); } } return target; } private static final String HOME = "JBOSS_HOME"; private static final String HOME_DIR = "jboss.home.dir"; private String getJBossHome(final ParsedCommandLine args) { final String targetDistro = distribution.getValue(args); if(targetDistro != null) { return targetDistro; } String resolved = getSecurityManager() == null ? getenv(HOME) : doPrivileged(new ReadEnvironmentPropertyAction(HOME)); if (resolved == null) { resolved = getSecurityManager() == null ? getProperty(HOME_DIR) : doPrivileged(new ReadPropertyAction(HOME_DIR)); } if (resolved == null) { throw PatchLogger.ROOT_LOGGER.cliFailedToResolveDistribution(); } return resolved; } private static List<File> getFSArgument(final ArgumentWithoutValue arg, final ParsedCommandLine args, final File root, final String param) { final String value = arg.getValue(args); if (value != null) { final String[] values = value.split(Pattern.quote(File.pathSeparator)); if (values.length == 1) { return Collections.singletonList(new File(value)); } final List<File> resolved = new ArrayList<File>(values.length); for (final String path : values) { resolved.add(new File(path)); } return resolved; } return Collections.singletonList(new File(root, param)); } }