org.glassfish.appclient.client.CLIBootstrap Maven / Gradle / Ivy
Show all versions of payara-micro Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.appclient.client;
import com.sun.enterprise.util.JDK;
import com.sun.enterprise.util.LocalStringManager;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.util.OS;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.glassfish.appclient.client.acc.UserError;
/**
*
* Constructs a java command to launch the ACC with the correct agent and
* command line arguments, based on the current operating environment and
* the user's own command-line arguments.
*
* The user might have specified JVM options as well as ACC options as
* well as arguments to be passed to the client. Further, we need to make
* sure that the GlassFish extension libraries directories and endorsed
* directories are included in java.ext.dirs and java.endorsed.dirs,
* regardless of whether the user specified any explicitly.
*
* This program emits a java command line that will run the ACC so that it
* will launch the client. The emitted command will need to look like this:
*
* {@code
* java \
* (user-specified JVM options except -jar) \
* (settings for java.ext.dirs and java.endorsed.dirs) \
* -javaagent:(path-to-gf-client.jar)=(option string for our agent) \
* (main class setting: "-jar x.jar" or "a.b.Main" or "path-to-file.class")
* (arguments to be passed to the client)
* }
*
* The general design of this class uses several inner classes, CommandLineElement
* and its extensions. These classes have slightly different behavior depending
* on the specific type of command line element each represents. Each has
* a regex pattern which it uses to decide whether it recognizes a particular
* command line element or not. Each also implements (or inherits) the
* processValue method which actually consumes the command line element being
* handled -- and sometimes the next one as well if the element takes a value
* (such as -classpath).
*
* @author Tim Quinn
*/
public class CLIBootstrap {
public final static String FILE_OPTIONS_INTRODUCER = "argsfile=";
private final static String COMMA_IN_ARG_PLACEHOLDER = "+-+-+-+";
private final static boolean isDebug = System.getenv("AS_DEBUG") != null;
private final static String INPUT_ARGS = System.getenv("inputArgs");
final static String ENV_VAR_PROP_PREFIX = "acc.";
/** options to the ACC that take a value */
private final static String ACC_VALUED_OPTIONS_PATTERN =
"-mainclass|-name|-xml|-configxml|-user|-password|-passwordfile|-targetserver";
/** options to the ACC that take no value */
private final static String ACC_UNVALUED_OPTIONS_PATTERN =
"-textauth|-noappinvoke|-usage|-help";
private final static String JVM_VALUED_OPTIONS_PATTERN =
"-classpath|-cp";
private final static String INSTALL_ROOT_PROPERTY_EXPR = "-Dcom.sun.aas.installRoot=";
private final static String SECURITY_POLICY_PROPERTY_EXPR = "-Djava.security.policy=";
private final static String SECURITY_AUTH_LOGIN_CONFIG_PROPERTY_EXPR = "-Djava.security.auth.login.config=";
private final static String SYSTEM_CLASS_LOADER_PROPERTY_EXPR =
"-Djava.system.class.loader=org.glassfish.appclient.client.acc.agent.ACCAgentClassLoader";
private final static String[] ENV_VARS = {
"_AS_INSTALL", "APPCPATH", "VMARGS"};
private final static String EXT_DIRS_INTRODUCER = "-Djava.ext.dirs";
private final static String ENDORSED_DIRS_INTRODUCER = "-Djava.endorsed.dirs";
private static final LocalStringManager localStrings = new LocalStringManagerImpl(CLIBootstrap.class);
private JavaInfo java;
private GlassFishInfo gfInfo;
private UserVMArgs userVMArgs;
/**
* set up during init with various subtypes of command line elements
*/
private CommandLineElement
extDirs, endorsedDirs, accValuedOptions, accUnvaluedOptions,
jvmPropertySettings, jvmValuedOptions, otherJVMOptions, arguments;
/** arguments passed to the ACC Java agent */
private final AgentArgs agentArgs = new AgentArgs();
/** records how the user specifies the main class: -jar xxx.jar, -client xxx.jar, or a.b.MainClass */
private final JVMMainOption jvmMainSetting = new JVMMainOption();
/** command line elements from most specific to least specific matching pattern */
private CommandLineElement[] elementsInScanOrder;
/** command line elements in the order they should appear on the generated
* command line
*/
private CommandLineElement[] elementsInOutputOrder;
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
try {
/*
* Convert env vars to properties. (This makes testing easier.)
*/
envToProps();
final CLIBootstrap boot = new CLIBootstrap();
/*
* Because of how Windows passes arguments, the calling Windows
* script assigned the input arguments to an environment variable.
* Parse that variable's value into the actual arguments.
*/
if (INPUT_ARGS != null) {
args = convertInputArgsVariable(INPUT_ARGS);
}
final String outputCommandLine = boot.run(args);
if (isDebug) {
System.err.println(outputCommandLine);
}
/*
* Write the generated java command to System.out. The calling
* shell script will execute this command.
*
* Using print instead of println seems to work better. Using
* println added a \r to the end of the last command-line argument
* on Windows under cygwin.
*/
System.out.print(outputCommandLine);
} catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
} catch (UserError ue) {
ue.displayAndExit();
}
}
/**
* Replaces commas in an argument value (which can confuse the ACC agent
* argument parsing because shells strip out double-quotes) with a special
* sequence.
*
* @param s string to encode
* @return encoded string
*/
public static String encodeArg(final String s) {
return s.replace(",", COMMA_IN_ARG_PLACEHOLDER);
}
/**
* Replaces occurrences of comma encoding with commas.
*
* @param s possibly encoded string
* @return decoded string
*/
public static String decodeArg(final String s) {
return s.replace(COMMA_IN_ARG_PLACEHOLDER, ",");
}
private static String[] convertInputArgsVariable(final String inputArgs) {
/*
* The pattern matches a quoted string (double quotes around a string
* containing no double quote) or a non-quoted string (a string containing
* no white space or quotes).
*/
final Pattern argPattern = Pattern.compile("\"([^\"]+)\"|([^\"\\s]+)");
final Matcher matcher = argPattern.matcher(inputArgs);
final List argList = new ArrayList();
while (matcher.find()) {
final String arg = (matcher.group(1) != null ? matcher.group(1) : matcher.group(2));
argList.add(arg);
if (isDebug) {
System.err.println("Captured argument " + arg);
}
}
return argList.toArray(new String[argList.size()]);
}
private static void envToProps() {
for (String envVar : ENV_VARS) {
final String value = System.getenv(envVar);
if (value != null) {
System.setProperty(ENV_VAR_PROP_PREFIX + envVar, value);
if (isDebug) {
System.err.println(ENV_VAR_PROP_PREFIX + envVar + " set to " + value);
}
}
}
}
CLIBootstrap() throws UserError {
init();
}
private void init() throws UserError {
java = initJava();
gfInfo = new GlassFishInfo();
userVMArgs = new UserVMArgs(System.getProperty(ENV_VAR_PROP_PREFIX + "VMARGS"));
/*
* Assign the various command line element matchers. See the
* descriptions of each subtype for what each is used for.
*/
extDirs = new OverridableDefaultedPathBasedOption(
EXT_DIRS_INTRODUCER,
userVMArgs.evExtDirs,
java.ext().getAbsolutePath(),
gfInfo.extPaths());
endorsedDirs = new OverridableDefaultedPathBasedOption(
ENDORSED_DIRS_INTRODUCER,
userVMArgs.evEndorsedDirs,
java.endorsed().getAbsolutePath(),
gfInfo.endorsedPaths());
accValuedOptions = new ACCValuedOption(ACC_VALUED_OPTIONS_PATTERN);
accUnvaluedOptions = new ACCUnvaluedOption(ACC_UNVALUED_OPTIONS_PATTERN);
jvmPropertySettings = new JVMOption("-D.*", userVMArgs.evJVMPropertySettings);
jvmValuedOptions = new JVMValuedOption(JVM_VALUED_OPTIONS_PATTERN,
userVMArgs.evJVMValuedOptions);
otherJVMOptions = new JVMOption("-.*", userVMArgs.evOtherJVMOptions);
arguments = new CommandLineElement(".*", Pattern.DOTALL);
initCommandLineElements();
}
/**
* Populates the command line elements collection to contain the elements
* from most specific matching pattern to least specific.
*/
private void initCommandLineElements() {
/*
* Add the elements in this order so the regex patterns will match
* the correct elements. In this arrangement, the patterns are from
* most specific to most general.
*/
elementsInScanOrder = new CommandLineElement[] {
extDirs,
endorsedDirs,
accValuedOptions,
accUnvaluedOptions,
jvmValuedOptions,
jvmPropertySettings,
jvmMainSetting,
otherJVMOptions,
arguments};
/*
* Add the elements in this order so they appear in the generated
* java command in the correct positions.
*/
//In JDK 9 and later ext and endorsed directory removed .
int major = JDK.getMajor();
if(major >= 9) {
elementsInOutputOrder = new CommandLineElement[]{
jvmValuedOptions,
jvmPropertySettings,
otherJVMOptions,
accUnvaluedOptions,
accValuedOptions,
jvmMainSetting,
arguments
};
}
else
{
elementsInOutputOrder = new CommandLineElement[] {
jvmValuedOptions,
jvmPropertySettings,
otherJVMOptions,
extDirs,
endorsedDirs,
accUnvaluedOptions,
accValuedOptions,
jvmMainSetting,
arguments
};
}
}
/**
* Places double quote marks around a string if the string is not already
* so enclosed.
* @param s
* @return the string wrapped in double quotes if not already that way; the original string otherwise
*/
private static String quote(final String s) {
if (s.length() > 2 && s.charAt(0) != '"' && s.charAt(s.length() - 1) != '"' ) {
return '\"' + s + '\"';
} else {
return s;
}
}
/**
* Quotes the string, on non-Windows systems quoting individually any
* $. The shell will have replaced any env. var. placeholders with
* their values before invoking this program. Anything that looks like a
* placeholder now is an odd but legal name that should not be substituted
* again.
*
* @param s
* @return
*/
private static String quoteSuppressTokenSubst(final String s) {
return (OS.isWindows() ? quote(s) : quote(s.replace("$", "\\$")));
}
/**
* Manages the arguments which will be passed to the ACC Java agent.
*/
private static class AgentArgs {
private final StringBuilder args = new StringBuilder("=mode=acscript");
private char sep = ',';
AgentArgs() {
final String appcPath = System.getProperty(ENV_VAR_PROP_PREFIX + "APPCPATH");
if (appcPath != null && appcPath.length() > 0) {
add("appcpath=" + quote(appcPath));
}
}
/**
* Adds an item to the Java agent arguments.
* @param item
*/
final void add(final String item) {
args.append(sep).append(item);
}
/**
* Adds an ACC argument to the Java agent arguments.
* @param accArg
*/
final void addACCArg(final String accArg) {
add("arg=" + encodeArg(accArg));
}
@Override
public String toString() {
return args.toString();
}
}
/**
* A command-line element. Various subtypes have some different behavior
* for some of the methods.
*/
private class CommandLineElement {
private final Pattern pattern;
Matcher matcher;
private final Pattern whiteSpacePattern = Pattern.compile("[\\r\\n]");
/** allows multiple values; not all command line elements support this*/
final List values = new ArrayList();
CommandLineElement(String patternString) {
this(patternString, 0);
}
CommandLineElement(String patternString, int flags) {
pattern = Pattern.compile(patternString, flags);
}
final boolean matchesPattern(final String element) {
matcher = pattern.matcher(element);
return matcher.matches();
}
boolean matches(final String element) {
return matchesPattern(element);
}
/**
* Processes the command line element at args[slot].
*
* Subclass implementations might consume the next element as well.
* @param args
* @param slot
* @return next slot to be processed
* @throws UserError if the user specified an option that requires a
* value but provided no value (either the next command line element is
* another option or there is no next element)
*/
int processValue(String[] args, int slot) throws UserError {
/*
* Ignore an argument that is just unquoted white space.
*/
final Matcher m = whiteSpacePattern.matcher(args[slot]);
if ( ! m.matches()) {
values.add(args[slot++]);
} else {
slot++;
}
return slot;
}
/**
* Adds the command-line element to the Java agent arguments, if
* appropriate.
*
* @param element
*/
void addToAgentArgs(final String element) {
}
/**
* Returns whether there is a next argument.
* @param args
* @param currentSlot
* @return
*/
boolean isNextArg(String[] args, int currentSlot) {
return currentSlot < args.length - 1;
}
/**
* Returns the next argument in the array, without advancing
* the pointer into the array.
*
* @param args
* @param currentSlot
* @return
*/
String nextArg(String[] args, int currentSlot){
return args[currentSlot + 1];
}
/**
* Makes sure that there is a next argument and that its value does
* not start with a "-" which would indicate an option, rather than
* the value for the option we are currently processing.
*
* @param args
* @param currentSlot
* @throws UserError
*/
void ensureNonOptionNextArg(final String[] args, final int currentSlot) throws UserError {
if ((currentSlot >= args.length - 1) || (args[currentSlot + 1].charAt(0) == '-')) {
throw new UserError("Command line element " + args[currentSlot] + " requires non-option value");
}
}
/**
* Adds a representation for this command-line element to the output
* command line.
*
* @param commandLine
* @return true if any values from this command-line element
* was added to the command line, false otherwise
*/
boolean format(final StringBuilder commandLine) {
return format(commandLine, true);
}
/**
* Adds a representation for this command-line element to the output
* command line, quoting the value if requested.
*
* @param commandLine
* @param useQuotes
* @return true if any values from this command-line element
* were added to the command line; false otherwise
*/
boolean format(final StringBuilder commandLine, boolean useQuotes) {
boolean needSep = false;
for (String value : values) {
if (needSep) {
commandLine.append(valueSep());
}
format(commandLine, useQuotes, value);
needSep = true;
}
return ! values.isEmpty();
}
/**
* Returns the separator character to be inserted in the emitted
* command line between values stored in the same instance of this
* command line element.
* @return
*/
char valueSep() {
return ' ';
}
/**
* Adds a representation for the specified value to the output
* command line, quoting the value if required and
* @param commandLine
* @param useQuotes
* @param v
* @return
*/
StringBuilder format(final StringBuilder commandLine,
final boolean useQuotes, final String v) {
if (commandLine.length() > 0) {
commandLine.append(' ');
}
commandLine.append((useQuotes ? quoteSuppressTokenSubst(v) : v));
return commandLine;
}
}
/**
* A command-line option (an element which starts with "-").
*/
private class Option extends CommandLineElement {
Option(String patternString) {
super(patternString);
}
}
/**
* A JVM command-line option. Only JVM options which appear before the
* main class setting are propagated to the output command line as
* JVM options. If they appear after the main class setting then they
* are treated as arguments to the client.
*
* This type of command line element can include values specified using
* the VMARGS environment variable.
*
*/
private class JVMOption extends Option {
JVMOption(final String patternString,
final CommandLineElement vmargsJVMOptionElement) {
super(patternString);
if (vmargsJVMOptionElement != null) {
values.addAll(vmargsJVMOptionElement.values);
}
}
@Override
boolean matches(final String element) {
/*
* Although the element might match the pattern (-.*) we do
* not treat this as JVM option if we have already processed
* the main class determinant.
*/
return ( ! jvmMainSetting.isSet()) && super.matches(element);
}
}
/**
* ACC options match anywhere on the command line unless and until we
* see "-jar xxx" in which case we impose the Java-style restriction that
* anything which follows the specification of the main class is an
* argument to be passed to the application.
*
* We do not impose the same restriction if the user specified -client xxx.jar
* in order to preserve backward compatibility with earlier releases, in
* which ACC options and client arguments could be intermixed anywhere on
* the command line.
*/
private class ACCUnvaluedOption extends Option {
ACCUnvaluedOption(final String patternString) {
super(patternString);
}
@Override
boolean matches(final String element) {
return ( ! jvmMainSetting.isJarSetting()) && super.matches(element);
}
@Override
int processValue(String[] args, int slot) throws UserError {
final int result = super.processValue(args, slot);
agentArgs.addACCArg(values.get(values.size() - 1));
return result;
}
@Override
boolean format(final StringBuilder commandLine) {
/*
* We do not send ACC arguments to the Java command line. They
* are placed into the agent argument string instead.
*/
return false;
}
}
/**
* An option that takes a value as the next command line element.
*/
private class ValuedOption extends Option {
class OptionValue {
private String option;
private String value;
OptionValue(final String option, final String value) {
this.option = option;
this.value = value;
}
}
List optValues = new ArrayList();
ValuedOption(final String patternString) {
super(patternString);
}
@Override
int processValue(String[] args, int slot) throws UserError {
ensureNonOptionNextArg(args, slot);
optValues.add(new OptionValue(args[slot++], args[slot++]));
return slot;
}
@Override
boolean format(final StringBuilder commandLine) {
for (OptionValue ov : optValues) {
format(commandLine, false /* useQuotes */, ov.option);
format(commandLine, true /* useQuotes */, ov.value);
}
return ! optValues.isEmpty();
}
}
private class JVMValuedOption extends ValuedOption {
JVMValuedOption(final String patternString,
final CommandLineElement vmargsJVMValuedOption) {
super(patternString);
if (vmargsJVMValuedOption != null) {
values.addAll(vmargsJVMValuedOption.values);
}
}
@Override
boolean matches(final String element) {
return ( ! jvmMainSetting.isJarSetting()) && super.matches(element);
}
}
/**
* ACC options can appear until "-jar xxx" on the command line.
*/
private class ACCValuedOption extends ValuedOption {
ACCValuedOption(final String patternString) {
super(patternString);
}
@Override
boolean matches(final String element) {
return ( ! jvmMainSetting.isJarSetting()) && super.matches(element);
}
@Override
int processValue(String[] args, int slot) throws UserError {
final int result = super.processValue(args, slot);
final OptionValue newOptionValue = optValues.get(optValues.size() - 1);
agentArgs.addACCArg(newOptionValue.option);
agentArgs.addACCArg(quote(newOptionValue.value));
return result;
}
@Override
boolean format(final StringBuilder commandLine) {
/*
* We do not send ACC arguments to the Java command line. They
* are placed into the agent argument string instead.
*/
return false;
}
}
/**
* Command line element(s) with which the user specified the client
* to be run. Note that once "-jar xxx" is specified then all
* subsequent arguments are passed to the client as arguments.
* Once "-client xxx" is specified then subsequent arguments are treated
* as ACC options (if they match) or arguments to the client.
*/
private class JVMMainOption extends CommandLineElement {
private static final String JVM_MAIN_PATTERN =
"-jar|-client|[^-][^\\s]*";
private String introducer = null;
JVMMainOption() {
super(JVM_MAIN_PATTERN);
}
boolean isJarSetting() {
return "-jar".equals(introducer);
}
boolean isClientSetting() {
return "-client".equals(introducer);
}
boolean isClassSetting() {
return ( ! isJarSetting() && ! isClientSetting() && isSet());
}
boolean isSet() {
return ! values.isEmpty();
}
@Override
boolean matches(String element) {
/*
* For backward compatibility, the -client element can appear
* multiple times with the last appearance overriding earlier ones.
*/
return (( ! isSet()) ||
( (isClientSetting() && element.equals("-client")))
)
&& super.matches(element);
}
@Override
int processValue(String[] args, int slot) throws UserError {
/*
* We only care about the most recent setting.
*/
values.clear();
/*
* If arg[slot] is -jar or -client we expect the
* next value to be the file. Make sure there is
* a next item and that it does not start with -.
*/
if (args[slot].charAt(0) == '-') {
if (nextLooksOK(args, slot)) {
introducer = args[slot++];
final int result = super.processValue(args, slot);
final String path = values.get(values.size() - 1);
final File clientSpec = new File(path);
if (clientSpec.isDirectory()) {
/*
* Record in the agent args that the user is launching
* a directory. Set the main class launch info to
* launch the ACC JAR.
*/
agentArgs.add("client=dir=" + quote(clientSpec.getAbsolutePath()));
introducer = "-jar";
values.set(values.size() - 1, gfInfo.agentJarPath());
} else {
agentArgs.add("client=jar=" + quote(path));
/*
* The client path is not a directory. It should be a
* .jar or a .ear file. If an EAR, then we want Java to
* launch our ACC jar. If a JAR, then we will launch
* that JAR.
*/
if (path.endsWith(".ear")) {
introducer = "-jar";
values.set(values.size() - 1, gfInfo.agentJarPath());
}
}
return result;
} else {
throw new UserError("-jar or -client requires value but missing");
}
} else {
/*
* This must be a main class specified on the command line.
*/
final int result = super.processValue(args, slot);
agentArgs.add("client=class=" + values.get(values.size() - 1));
return result;
}
}
@Override
boolean format(final StringBuilder commandLine) {
if (introducer != null) {
/*
* In the generated command we always use "-jar" to indicate
* the JAR to be launched, even if the user specified "-client"
* on the appclient command line.
*/
super.format(commandLine, false /* useQuotes */, "-jar");
return super.format(commandLine, true /* useQuotes */);
}
return super.format(commandLine, false /* useQuotes */);
}
private boolean nextLooksOK(final String[] args, final int slot) {
return (isNextArg(args, slot) && (args[slot+1].charAt(0) != '-'));
}
}
/**
* A JVM option that uses values from the GlassFish installation plus default
* value(s) from the Java installation. If the user specifies one of these
* options on the command line then we discard the Java installation values
* and append the GlassFish values to the user's values.
*
* This is used for handling java.ext.dirs and java.endorsed.dirs property
* settings. If the user does not specify the property then the user would
* expect the Java-provided directories to be used. We need to
* specify the GlassFish ones, so that means we need combine the GlassFish
* ones and the default JVM ones explicitly.
*
* On the other hand, if the user specifies the property then the JVM
* defaults are out of play. We still need the GlassFish directories to be
* used though.
*/
private class OverridableDefaultedPathBasedOption extends JVMOption {
private final String defaultValue;
private final List gfValues;
private final String introducer;
private boolean hasCommandLineValueAppeared = false;
OverridableDefaultedPathBasedOption(final String introducer,
final CommandLineElement settingsFromEnvVar,
final String defaultValue,
final String... gfValues) {
super(introducer + "=.*", null);
/*
* Preload the values for this option from the ones the user
* provides in the environment variable, if any. These values
* will be overwritten if the user also provides values on the
* command line.
*/
if (settingsFromEnvVar != null) {
values.addAll(settingsFromEnvVar.values);
}
this.introducer = introducer;
this.defaultValue = defaultValue;
this.gfValues = Arrays.asList(gfValues);
}
@Override
int processValue(String[] args, int slot) throws UserError {
/*
* Once we see a user-provided value on the command line itself
* for the first time, clear out any pre-loaded values from the
* environment variable.
*/
if ( ! hasCommandLineValueAppeared) {
values.clear();
hasCommandLineValueAppeared = true;
}
values.addAll(Arrays.asList(args[slot++].substring(introducer.length() + 1).split(java.pathSeparator())));
return slot;
}
@Override
boolean format(final StringBuilder commandLine) {
final List combinedValues = new ArrayList();
/*
*
*/
if (values.isEmpty()) {
/*
* The user did not specify this property, so we use
* the GlassFish value(s) plus the JVM default.
*/
combinedValues.addAll(gfValues);
combinedValues.add(defaultValue);
} else {
/*
* The user did specify this property, so we use
* the user's value plus the GlassFish value(s).
*/
combinedValues.addAll(values);
combinedValues.addAll(gfValues);
}
commandLine.append(introducer).append("=");
boolean needSep = false;
for (String value : combinedValues) {
if (needSep) {
commandLine.append(java.pathSeparator());
}
commandLine.append(quoteSuppressTokenSubst(value));
needSep = true;
}
return true;
}
}
/**
* Adds JVM properties for various ACC settings.
* @param command
*/
private void addProperties(final StringBuilder command) {
command.append(' ').append(INSTALL_ROOT_PROPERTY_EXPR).append(quote(gfInfo.home().getAbsolutePath()));
command.append(' ').append(SECURITY_POLICY_PROPERTY_EXPR).append(quote(gfInfo.securityPolicy().getAbsolutePath()));
command.append(' ').append(SYSTEM_CLASS_LOADER_PROPERTY_EXPR);
command.append(' ').append(SECURITY_AUTH_LOGIN_CONFIG_PROPERTY_EXPR).append(quote(gfInfo.loginConfig().getAbsolutePath()));
}
/**
* Processes the user-provided command-line elements and creates the
* resulting output string.
*
* @param args
* @throws UserError
*/
private String run(String[] args) throws UserError {
java = initJava();
gfInfo = new GlassFishInfo();
final String[] augmentedArgs = new String[args.length + 2];
augmentedArgs[0] = "-configxml";
augmentedArgs[1] = gfInfo.configxml().getAbsolutePath();
System.arraycopy(args, 0, augmentedArgs, 2, args.length);
/*
* Process each command-line argument by the first CommandLineElement
* which matches the argument.
*/
for (int i = 0; i < augmentedArgs.length; ) {
boolean isMatched = false;
for (CommandLineElement cle : elementsInScanOrder) {
if (isMatched = cle.matches(augmentedArgs[i])) {
i = cle.processValue(augmentedArgs, i);
break;
}
}
if ( ! isMatched) {
throw new UserError("arg " + i + " = " + augmentedArgs[i] + " not recognized");
}
}
final StringBuilder command = new StringBuilder(quote(java.javaExe));
addProperties(command);
/*
* The user does not specify the -javaagent option we need, so we
* provide it here. (It is added to the appropriate command-line
* element object so, when formatted, that command-line element
* includes the -javaagent option.)
*/
addAgentOption();
/*
* If the user did not specify a client or usage or help then add the -usage option.
*/
if ( ! jvmMainSetting.isSet() &&
! isHelp() &&
! isUsage()) {
accUnvaluedOptions.processValue(new String[] {"-usage"}, 0);
}
boolean needSep = true;
for (CommandLineElement e : elementsInOutputOrder) {
needSep = processCommandElement(command, e, needSep);
}
return command.toString();
}
private boolean processCommandElement(
final StringBuilder command,
final CommandLineElement e,
final boolean needSep) {
if (needSep) {
command.append(' ');
}
return e.format(command);
}
private boolean isHelp() {
return accUnvaluedOptions.values.contains("-help");
}
private boolean isUsage() {
return accUnvaluedOptions.values.contains("-usage");
}
/**
* Adds the -javaagent option to the command line.
*
*/
private void addAgentOption() throws UserError {
otherJVMOptions.processValue(new String[] {
"-javaagent:" + quote(gfInfo.agentJarPath()) + agentOptionsFromFile()},
0);
}
private String agentOptionsFromFile() {
try {
final File argsFile = fileContainingAgentArgs();
return '=' + FILE_OPTIONS_INTRODUCER + quote(argsFile.getAbsolutePath());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private File fileContainingAgentArgs() throws IOException {
final File argsFile = File.createTempFile("acc", ".dat");
final PrintStream ps = new PrintStream(argsFile);
ps.println(agentArgs.toString());
ps.close();
return argsFile;
}
/**
* Encapsulates information about the GlassFish installation, mostly useful
* directories within the installation.
*
* Note that we use the property acc._AS_INSTALL to find the installation.
*/
static class GlassFishInfo {
private final File home;
private final File modules;
private final File lib;
private final File libAppclient;
private static final String ACC_CONFIG_PREFIX = "domains/domain1/config";
GlassFishInfo() {
final String asInstallPath = System.getProperty(ENV_VAR_PROP_PREFIX + "_AS_INSTALL");
if (asInstallPath == null || asInstallPath.length() == 0) {
throw new IllegalArgumentException("_AS_INSTALL == null");
}
this.home = new File(asInstallPath);
modules = new File(home, "modules");
lib = new File(home, "lib");
libAppclient = new File(lib, "appclient");
}
File home() {
return home;
}
File modules() {
return modules;
}
File lib() {
return lib;
}
File configxml() {
/*
* Try using glassfish-acc.xml. If that does not exist then the user
* might have done an in-place upgrade from an earlier version that
* used sun-acc.xml.
*/
final File configXMLFile = new File(new File(home, ACC_CONFIG_PREFIX), "glassfish-acc.xml");
if (configXMLFile.canRead()) {
return configXMLFile;
}
final File sunACCXMLFile = new File(new File(home, ACC_CONFIG_PREFIX), "sun-acc.xml");
if (sunACCXMLFile.canRead()) {
return sunACCXMLFile;
}
/*
* We found neither, but when an error is reported we want it to
* report the glassfish-acc.xml file is missing.
*/
return configXMLFile;
}
String[] endorsedPaths() {
return new String[] {
new File(lib, "endorsed").getAbsolutePath(),
new File(modules, "endorsed").getAbsolutePath()};
}
String extPaths() {
return new File(lib, "ext").getAbsolutePath();
}
String agentJarPath() {
return new File(lib, "gf-client.jar").getAbsolutePath();
}
File securityPolicy() {
return new File(libAppclient, "client.policy");
}
File loginConfig() {
return new File(libAppclient, "appclientlogin.conf");
}
}
JavaInfo initJava() {
return new JavaInfo();
}
/**
* Collects information about the current Java implementation.
*
* The user might have defined AS_JAVA or JAVA_HOME, or simply relied on
* the current PATH setting to choose which Java to use. Regardless, once
* this code is running SOME Java has been successfully chosen. Use
* the java.home property to find the JRE's home, which we need for the
* library directory (for example).
*/
static class JavaInfo {
private final static String CYGWIN_PROP_NAME = "org.glassfish.isCygwin";
private final static String SHELL_PROP_NAME = "org.glassfish.appclient.shell";
/*
* The appclient and appclient.bat scripts set ACCJava.
* Properties would be nicer instead of env vars, but the Windows
* script handling of command line args in the for statement treats
* the = in -Dprop=value as an argument separator and breaks the
* property assignment apart into two arguments.
*/
private final static String ACCJava_ENV_VAR_NAME = "ACCJava";
private final boolean useWindowsSyntax = File.separatorChar == '\\' &&
(System.getProperty(SHELL_PROP_NAME) == null);
protected String javaExe;
protected File jreHome;
private JavaInfo() {
init();
}
private void init() {
jreHome = new File(System.getProperty("java.home"));
javaExe = javaExe();
}
protected boolean isValid() {
return javaExe != null && new File(javaExe).canExecute();
}
protected File javaBinDir() {
return new File(jreHome, "bin");
}
String javaExe() {
return System.getenv(ACCJava_ENV_VAR_NAME);
}
File endorsed() {
return new File(lib(), "endorsed");
}
File ext() {
return new File(lib(), "ext");
}
File lib() {
return new File(jreHome, "lib");
}
String pathSeparator() {
return useWindowsSyntax ? ";" : ":";
}
}
/**
* Handles user-specified VM arguments passed by the environment variable
* VMARGS.
*
* This is very much like the handling of the arguments on the more
* general command line, except that we expect only valid VM arguments
* here.
*
* Some of the "main" CommandLineElements processed earlier in the class will
* use the inner command line elements here to augment the values they process.
*/
class UserVMArgs {
private CommandLineElement evExtDirs, evEndorsedDirs,
evJVMPropertySettings, evJVMValuedOptions, evOtherJVMOptions;
private final List evElements = new ArrayList();
UserVMArgs(final String vmargs) throws UserError {
if (isDebug) {
System.err.println("VMARGS = " + (vmargs == null ? "null" : vmargs));
}
evExtDirs = new OverridableDefaultedPathBasedOption(
EXT_DIRS_INTRODUCER,
null,
java.ext().getAbsolutePath(),
gfInfo.extPaths());
evEndorsedDirs = new OverridableDefaultedPathBasedOption(
ENDORSED_DIRS_INTRODUCER,
null,
java.endorsed().getAbsolutePath(),
gfInfo.endorsedPaths());
evJVMPropertySettings = new JVMOption("-D.*", null);
evJVMValuedOptions = new JVMValuedOption(JVM_VALUED_OPTIONS_PATTERN, null);
evOtherJVMOptions = new JVMOption("-.*", null);
initEVCommandLineElements();
if (vmargs == null) {
return;
}
processEVCommandLineElements(convertInputArgsVariable(vmargs));
}
private void initEVCommandLineElements() {
evElements.add(evExtDirs);
evElements.add(evEndorsedDirs);
evElements.add(evJVMPropertySettings);
evElements.add(evJVMValuedOptions);
evElements.add(evOtherJVMOptions);
}
private void processEVCommandLineElements(final String[] envVarJVMArgs) throws UserError {
/*
* Process each command-line argument by the first CommandLineElement
* which matches the argument.
*/
for (int i = 0; i < envVarJVMArgs.length; ) {
boolean isMatched = false;
for (CommandLineElement cle : evElements) {
if (isMatched = cle.matches(envVarJVMArgs[i])) {
i = cle.processValue(envVarJVMArgs, i);
break;
}
}
if ( ! isMatched) {
throw new UserError("arg " + i + " = " + envVarJVMArgs[i] + " not recognized");
}
}
}
}
}