All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.enterprise.admin.launcher.GFLauncher Maven / Gradle / Ivy

There is a newer version: 7.2024.1.Alpha1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2008-2019 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.
 */
// Portions Copyright [2016-2023] [Payara Foundation and/or its affiliates]

package com.sun.enterprise.admin.launcher;

import com.sun.enterprise.universal.collections.CollectionUtils;
import com.sun.enterprise.universal.glassfish.ASenvPropertyReader;
import com.sun.enterprise.universal.glassfish.GFLauncherUtils;
import com.sun.enterprise.universal.glassfish.TokenResolver;
import com.sun.enterprise.universal.i18n.LocalStringsImpl;
import com.sun.enterprise.universal.io.SmartFile;
import com.sun.enterprise.universal.process.ProcessStreamDrainer;
import com.sun.enterprise.universal.xml.MiniXmlParser;
import com.sun.enterprise.universal.xml.MiniXmlParserException;
import com.sun.enterprise.util.JDK;
import com.sun.enterprise.util.OS;
import com.sun.enterprise.util.SystemPropertyConstants;
import com.sun.enterprise.util.io.FileUtils;
import fish.payara.admin.launcher.PayaraDefaultJvmOptions;
import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.sun.enterprise.admin.launcher.GFLauncherConstants.*;
import static com.sun.enterprise.util.SystemPropertyConstants.*;

/**
 * This is the main Launcher class designed for external and internal usage.
 * Each of the 3 kinds of server -- domain, node-agent and instance -- need to
 * subclass this class.
 *
 * @author bnevins
 */
public abstract class GFLauncher {

    private static final Pattern JAVA_VERSION_PATTERN = Pattern.compile(".* version \"([^\"\\-]+)(-.*)?\".*");
    private final List commandLine = new ArrayList();
    private final GFLauncherInfo info;
    private Map asenvProps;
    private JavaConfig javaConfig;
    private JvmOptions jvmOptions;
    private Profiler profiler;
    private Map sysPropsFromXml;
    private String javaExe;
    private String classpath;
    private String adminFileRealmKeyFile;
    private boolean secureAdminEnabled;
    private List debugOptions;
    private long startTime;
    private String logFilename;
    private LaunchType mode = LaunchType.normal;
    private static final LocalStringsImpl STRINGS = new LocalStringsImpl(GFLauncher.class);
    private boolean setupCalledByClients = false; //handle with care
    private int exitValue = -1;
    private ProcessWhacker processWhacker;
    private Process process;
    private ProcessStreamDrainer psd;
    private boolean logFilenameWasFixed = false;
    private boolean needsAutoUpgrade = false;
    private boolean needsManualUpgrade = false;
    private int debugPort = -1;
    private boolean debugSuspend = false;
    
    
    ///////////////////////////////////////////////////////////////////////////
    //////     PUBLIC api area starts here             ////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    /**
     *
     * @return The info object that contains startup info
     */
    public final GFLauncherInfo getInfo() {
        return info;
    }

    /**
     * Launches the server. Any fatal error results in a GFLauncherException No
     * unchecked Throwables of any kind will be thrown.
     *
     * @throws com.sun.enterprise.admin.launcher.GFLauncherException
     */
    @SuppressWarnings("UseSpecificCatch")
    public final void launch() throws GFLauncherException {
        try {
            startTime = System.currentTimeMillis();
            if (!setupCalledByClients) {
                setup();
            }
            internalLaunch();
        } catch (GFLauncherException gfe) {
            throw gfe;
        } catch (Throwable t) {
            // hk2 might throw a java.lang.Error
            throw new GFLauncherException(STRINGS.get("unknownError", t.getMessage()), t);
        } finally {
            GFLauncherLogger.removeLogFileHandler();
        }
    }

    /**
     * Launches the server - but forces the setup() to go through again.
     *
     * @throws com.sun.enterprise.admin.launcher.GFLauncherException
     */
    public final void relaunch() throws GFLauncherException {
        setupCalledByClients = false;
        launch();
    }

    @SuppressWarnings("UseSpecificCatch")
    public final void launchJVM(List cmdsIn) throws GFLauncherException {
        try {
            setup();    // we only use one thing -- the java executable
            List commands = new LinkedList();
            commands.add(javaExe);

            for (String cmd : cmdsIn) {
                commands.add(cmd);
            }

            ProcessBuilder pb = new ProcessBuilder(commands);
            Process p = pb.start();
            ProcessStreamDrainer.drain("launchJVM", p); // just to be safe
        } catch (GFLauncherException gfe) {
            throw gfe;
        } catch (Throwable t) {
            // hk2 might throw a java.lang.Error
            throw new GFLauncherException(STRINGS.get("unknownError", t.getMessage()), t);
        } finally {
            GFLauncherLogger.removeLogFileHandler();
        }
    }

    public void setup() throws GFLauncherException, MiniXmlParserException {
        ASenvPropertyReader pr;
        if (isFakeLaunch()) {
            pr = new ASenvPropertyReader(info.getInstallDir());
        } else {
            pr = new ASenvPropertyReader();
        }

        asenvProps = pr.getProps();
        info.setup();
        setupLogLevels();
        MiniXmlParser parser = new MiniXmlParser(getInfo().getConfigFile(), getInfo().getInstanceName());
        String domainName = parser.getDomainName();
        if (GFLauncherUtils.ok(domainName)) {
            info.setDomainName(domainName);
        }
        info.setAdminAddresses(parser.getAdminAddresses());
        javaConfig = new JavaConfig(parser.getJavaConfig());
        // Set the config java-home value as the Java home for the environment,
        // unless it is empty or it is already refering to a substitution of
        // the environment variable.
        String jhome = javaConfig.getJavaHome();
        if (GFLauncherUtils.ok(jhome) && !jhome.trim().equals("${" + JAVA_ROOT_PROPERTY + "}")) {
            asenvProps.put(JAVA_ROOT_PROPERTY, jhome);
        }
        setJavaExecutable();
        setupProfilerAndJvmOptions(parser);
        setupUpgradeSecurity();

        Map realmprops = parser.getAdminRealmProperties();
        if (realmprops != null) {
            String classname = realmprops.get("classname");
            String keyfile = realmprops.get("file");
            if ("com.sun.enterprise.security.auth.realm.file.FileRealm".equals(classname) && keyfile != null) {
                adminFileRealmKeyFile = keyfile;
            }
        }

        secureAdminEnabled = parser.getSecureAdminEnabled();

        renameOsgiCache();
        setupMonitoring(parser);
        sysPropsFromXml = parser.getSystemProperties();
        asenvProps.put(INSTANCE_ROOT_PROPERTY, getInfo().getInstanceRootDir().getPath());

        debugOptions = getDebug();
        parseDebug();
        parser.setupConfigDir(getInfo().getConfigDir(), getInfo().getInstallDir());
        setLogFilename(parser);
        resolveAllTokens();
        fixLogFilename();
        GFLauncherLogger.addLogFileHandler(logFilename);
        setClasspath();
        setCommandLine();
        logCommandLine();
        checkJDKVersion();
        // if no  element, we need to upgrade this domain
        needsAutoUpgrade = !parser.hasNetworkConfig();
        needsManualUpgrade = !parser.hasDefaultConfig();
        setupCalledByClients = true;
    }
    
    public Map getSysPropsFromXml() {
        return sysPropsFromXml;
    }

    /**
     * Returns the admin realm key file for the server, if the admin realm is a
     * FileRealm. Otherwise return null. This value can be used to create a
     * FileRealm for the server.
     * @return 
     */
    public String getAdminRealmKeyFile() {
        return adminFileRealmKeyFile;
    }

    /**
     * Returns true if secure admin is enabled
     * @return 
     */
    public boolean isSecureAdminEnabled() {
        return secureAdminEnabled;
    }

    /**
     * Returns the exit value of the process. This only makes sense when we ran
     * in verbose mode and waited for the process to exit in the wait() method.
     * Caveat Emptor!
     *
     * @return the process' exit value if it completed and we waited. Otherwise
     * it returns -1
     */
    public final int getExitValue() {
        return exitValue;
    }

    /**
     * You don't want to call this before calling launch because it would not
     * make sense.
     *
     * @return The Process object of the launched Server process. you will
     * either get a valid Process object or an Exception will be thrown. You are
     * guaranteed not to get a null.
     * @throws GFLauncherException if the Process has not been created yet -
     * call launch() before calling this method.
     */
    public final Process getProcess() throws GFLauncherException {
        if (process == null)
            throw new GFLauncherException("invalid_process");

        return process;
    }

    /**
     * A ProcessStreamDrainer is always attached to every Process created here.
     * It is handy for getting the stdin and stdout as a nice String.
     *
     * @return A valid ProcessStreamDrainer. You are guaranteed to never get a
     * null.
     * @throws GFLauncherException if the process has not launched yet
     * @see com.sun.enterprise.universal.process.ProcessStreamDrainer
     */
    public final ProcessStreamDrainer getProcessStreamDrainer() throws GFLauncherException {
        if (psd == null)
            throw new GFLauncherException("invalid_psd");

        return psd;
    }

    /**
     * Get the location of the server logfile
     *
     * @return The full path of the logfile
     * @throws GFLauncherException if you call this method too early
     */
    public String getLogFilename() throws GFLauncherException {
        if (!logFilenameWasFixed)
            throw new GFLauncherException(STRINGS.get("internalError") + " call to getLogFilename() before it has been initialized");

        return logFilename;
    }

    /**
     * Return the port number of the debug port, or -1 if debugging is not
     * enabled.
     *
     * @return the debug port, or -1 if not debugging
     */
    public final int getDebugPort() {
        return debugPort;
    }

    /**
     * Return true if suspend=y AND debugging is on. otherwise return false.
     *
     * @return the debug port, or -1 if not debugging
     */
    public final boolean isDebugSuspend() {
        return debugPort >= 0 && debugSuspend;
    }

    /**
     * Does this domain need to be automatically upgraded before it can be
     * started?
     *
     * @return true if the domain needs to be upgraded first
     */
    public final boolean needsAutoUpgrade() {
        return needsAutoUpgrade;
    }

    /**
     * Does this domain need to be manually upgraded before it can be started?
     *
     * @return true if the domain needs to be upgraded first
     */
    public final boolean needsManualUpgrade() {
        return needsManualUpgrade;
    }
    
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    //////     ALL private and package-private below   ////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    
    /**
     * Internal launcher which in turn calls {@link #launchInstance()}
     * @throws GFLauncherException 
     */
    abstract void internalLaunch() throws GFLauncherException;

    private void parseDebug() {
        // look for an option of this form:
        // -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=9009
        // or
        // -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9009
        // and extract the suspend and port values
        for (String opt : debugOptions) {
            if (isJdwpOption(opt)) {
              debugPort = extractDebugPort(opt);
              debugSuspend = extractDebugSuspend(opt);
            }
        }
    }

    static boolean isJdwpOption(String option) {
        return option.startsWith("-Xrunjdwp:") || option.startsWith("-agentlib:jdwp");
    }

    static int extractDebugPort(String option) {
        Pattern portRegex = Pattern.compile(".*address=(?\\d*).*");
        Matcher m = portRegex.matcher(option);
        if (!m.matches()) {
            return -1;
        }
        try {
            String addressGroup = m.group("port");
            return Integer.parseInt(addressGroup);
        } catch (NumberFormatException nfex) {
            return -1;
        }
    }

    static boolean extractDebugSuspend(String option) {
        Pattern suspendRegex = Pattern.compile(".*suspend=[yY](?:,.*|$)");
        Matcher m = suspendRegex.matcher(option);
        return m.matches();
    }

    private void setLogFilename(MiniXmlParser parser) {
        
        // Check if launching an instance to get appropriate logFilename
        if (info.isInstance()) {
            logFilename = parser.getInstanceLogFilename();

        } else /*  Assume DAS if not an instance */{
            logFilename = parser.getLogFilename();
        }

        if (logFilename == null) {
            logFilename = DEFAULT_LOGFILE;
        }
    }

    private void fixLogFilename() throws GFLauncherException {
        if (!GFLauncherUtils.ok(logFilename))
            logFilename = DEFAULT_LOGFILE;

        File f = new File(logFilename);

        if (!f.isAbsolute()) {
            // this is quite normal.  Logging Service will by default return
            // a relative path!
            f = new File(info.getInstanceRootDir(), logFilename);
        }

        // Get rid of garbage like "c:/gf/./././../gf"
        f = SmartFile.sanitize(f);

        // if the file doesn't exist -- make sure the parent dir exists
        // this is common in unit tests AND the first time the instance is
        // started....

        if (!f.exists()) {
            File parent = f.getParentFile();
            if (!parent.isDirectory()) {
                boolean wasCreated = parent.mkdirs();
                if (!wasCreated) {
                    f = null; // give up!!
                }
            }
        }

        if (f == null)
            logFilename = null;
        else
            logFilename = f.getPath();

        logFilenameWasFixed = true;
    }

    // unit tests will want 'fake' so that the process is not really started.
    enum LaunchType {
        normal, debug, trace, fake
    }

    void setMode(LaunchType mode) {
        this.mode = mode;
    }

    LaunchType getMode() {
        return mode;
    }

    boolean isFakeLaunch() {
        return mode == LaunchType.fake;
    }

    /**
     * Returns a list of all the files to be loaded as part of the main classpath.
     * This includes all module jars.
     * @return
     * @throws GFLauncherException if Payara is running in embedded mode
     */
    abstract List getMainClasspath() throws GFLauncherException;

    /**
     * Returns the class of Payara containg the {@code main()} function
     * @return
     * @throws GFLauncherException 
     */
    abstract String getMainClass() throws GFLauncherException;

    GFLauncher(GFLauncherInfo info) {
        this.info = info;
    }

    final Map getEnvProps() {
        return asenvProps;
    }

    public final List getCommandLine() {
        return commandLine;
    }

    final long getStartTime() {
        return startTime;
    }

    /**
     * Launches the instance
     * @throws GFLauncherException if there was an error with the process of starting the instance
     * @throws MiniXmlParserException if there was an error reading the domain.xml
     */
    void launchInstance() throws GFLauncherException, MiniXmlParserException {
        if (isFakeLaunch()) {
            return;
        }

        List cmds;
	// Use launchctl bsexec on MacOS versions before 10.10
	// otherwise use regular startup.
 	// (No longer using StartupItemContext).
	// See GLASSFISH-21343
        if (OS.isDarwin() && useLaunchCtl(System.getProperty("os.version")) && 
	    (!getInfo().isVerboseOrWatchdog())) {
            // On MacOS we need to start long running process with
            // StartupItemContext. See IT 12942
            cmds = new ArrayList();
            // In MacOS 10.10 they removed StartupItemContext
            // so call launchctl directly doing what StartupItemContext did
            // See GLASSFISH-21113
            // remove bsexec as from 10.10.3 bsexec requires sudo
	    cmds.add("nohup");
            cmds.addAll(getCommandLine());            
        } else {
            cmds = getCommandLine();
        }

        ProcessBuilder pb = new ProcessBuilder(cmds);

        // change the directory if there is one specified, o/w stick with the
        // default.
        try {
            File newDir = getInfo().getConfigDir();
            pb.directory(newDir);
        }
        catch (Exception e) {
        }

        //run the process and attach Stream Drainers
        try {
            closeStandardStreamsMaybe();
            process = pb.start();
            final String name = getInfo().getDomainName();

            // verbose trumps watchdog.
            if (getInfo().isVerbose()) {
                psd = ProcessStreamDrainer.redirect(name, process);
            }
            else if (getInfo().isWatchdog()) {
                psd = ProcessStreamDrainer.dispose(name, process);
            }
            else {
                psd = ProcessStreamDrainer.save(name, process);
            }
            writeSecurityTokens(process);
        }
        catch (Exception e) {
            throw new GFLauncherException("jvmfailure", e, e);
        }

        //if verbose, hang around until the domain stops
        if (getInfo().isVerboseOrWatchdog())
            wait(process);
    }

    /**
     * Checks whether to use launchctl for start up by checking if mac os version < 10.10
     *
     * @return  True if osversion < 10.10 
     */
    private static boolean useLaunchCtl(String osversion) {

        int major = 0;
        int minor = 0;

	if (osversion == null || osversion.isEmpty())
	    return false;

        String[] split = osversion.split("[\\._\\-]+");

	try {
            if (split.length > 0  && split[0].length() > 0) {
                major = Integer.parseInt(split[0]);
            }
            if (split.length > 1 && split[1].length() > 0) {
                minor = Integer.parseInt(split[1]);
            }
	    return ((major <= 9) || (major <= 10 && minor < 10));
	}
	catch (NumberFormatException e) {
	    // Assume version is 10.10 or later.
	    return false;
	}
    }

    private void writeSecurityTokens(Process sp) throws GFLauncherException, IOException {
        handleDeadProcess();
        OutputStream os = sp.getOutputStream();
        OutputStreamWriter osw = null;
        BufferedWriter bw = null;
        try {
            osw = new OutputStreamWriter(os);
            bw = new BufferedWriter(osw);
            for (String token : info.securityTokens) {
                bw.write(token);
                bw.newLine();
                bw.flush();      //flushing once is ok too
            }
        }
        catch (IOException e) {
            handleDeadProcess();
            throw e;   //process is not dead, but got some other exception, rethrow it
        }
        finally {
            if (bw != null) {
                bw.close();
            }
            if (osw != null) {
                osw.close();
            }
            if (os != null) {
                try {
                    os.close();
                }
                catch (IOException ioe) {
                    // nothing to do
                }
            }
            if (bw != null) {
                handleDeadProcess();
            }
        }
    }

    private void handleDeadProcess() throws GFLauncherException {
        String trace = getDeadProcessTrace(process);
        if (trace != null)
            throw new GFLauncherException(trace);
    }

    private String getDeadProcessTrace(Process sp) throws GFLauncherException {
        //returns null in case the process is NOT dead
        try {
            int ev = sp.exitValue();
            ProcessStreamDrainer psd1 = getProcessStreamDrainer();
            String output = psd1.getOutErrString();
            return STRINGS.get("server_process_died", ev, output);
        }
        catch (IllegalThreadStateException e) {
            //the process is still running and we are ok
            return null;
        }
    }

    void setCommandLine() throws GFLauncherException {
        List cmdLine = getCommandLine();
        cmdLine.clear();
        addIgnoreNull(cmdLine, javaExe);
        addIgnoreNull(cmdLine, "-cp");
        addIgnoreNull(cmdLine, getClasspath());
        addIgnoreNull(cmdLine, debugOptions);

        String CLIStartTime = System.getProperty("WALL_CLOCK_START");

        if (CLIStartTime != null && CLIStartTime.length() > 0) {
            cmdLine.add("-DWALL_CLOCK_START=" + CLIStartTime);
        }

        if (jvmOptions != null)
            addIgnoreNull(cmdLine, jvmOptions.toStringArray());

        GFLauncherNativeHelper nativeHelper = new GFLauncherNativeHelper(info, javaConfig, jvmOptions, profiler);
        addIgnoreNull(cmdLine, nativeHelper.getCommands());
        addIgnoreNull(cmdLine, getMainClass());

        try {
            addIgnoreNull(cmdLine, getInfo().getArgsAsList());
        }
        catch (GFLauncherException gfle) {
            throw gfle;
        }
        catch (Exception e) {
            //harmless
        }
    }

    /**
     * Returns the Java Virtual Machine options (for testing)
     * @return 
     */
    List getJvmOptions() {
        return jvmOptions.toStringArray();
    }

    /**
     * Adds a string to a list if it is not null
     * @param list
     * @param s 
     */
    private void addIgnoreNull(List list, String s) {
        if (GFLauncherUtils.ok(s))
            list.add(s);
    }

    private void addIgnoreNull(List list, Collection ss) {
        if (ss != null && !ss.isEmpty())
            list.addAll(ss);
    }

    private void wait(final Process p) throws GFLauncherException {
        try {
            setShutdownHook(p);
            p.waitFor();
            exitValue = p.exitValue();
        }
        catch (InterruptedException ex) {
            throw new GFLauncherException("verboseInterruption", ex, ex);
        }
    }

    private void setShutdownHook(final Process p) {
        // ON UNIX a ^C on the console will also kill DAS
        // On Windows a ^C on the console will not kill DAS
        // We want UNIX behavior on Windows
        // note that the hook thread will run in both cases:
        // 1. the server died on its own, e.g. with a stop-domain
        // 2. a ^C (or equivalent signal) was received by the console
        // note that exitValue is still set to -1

        // if we are restarting we may get many many processes.
        // Each time this method is called we reset the Process reference inside
        // the processWhacker

        if (processWhacker == null) {
            Runtime runtime = Runtime.getRuntime();
            final String msg = STRINGS.get("serverStopped", info.getType());
            processWhacker = new ProcessWhacker(p, msg);
            runtime.addShutdownHook(new Thread(processWhacker));
        }
        else
            processWhacker.setProcess(p);
    }

    private void resolveAllTokens() {
        // resolve jvm-options against:
        // 1. itself
        // 2. 's from domain.xml
        // 3. system properties -- essential there is, e.g. "${path.separator}" in domain.xml
        // 4. asenvProps
        // 5. env variables
        // i.e. add in reverse order to get the precedence right

        Map all = new HashMap();
        Map envProps = System.getenv();
        Map sysProps =
                CollectionUtils.propertiesToStringMap(System.getProperties());

        // TODO: Uncomment when admin password processing & aliasing is sorted out.

        // Map passwordAliases = new HashMap();
        // try {
        //     String masterPassword = "changeit";
        //     if (IdentityManager.getMasterPassword() != null)
        //         masterPassword = IdentityManager.getMasterPassword();
        //     PasswordAdapter pa = new PasswordAdapter(masterPassword.toCharArray());
        //     Enumeration e = pa.getAliases();
        //     if (e.hasMoreElements()) {
        //         String alias = (String) e.nextElement();
        //         passwordAliases.put(alias, pa.getPasswordForAlias(alias));
        //     }
        // } catch (Exception e) {
        //     // TODO: ignore now. Defaults to not resolving password aliases
        // }
        // all.putAll(passwordAliases);

        all.putAll(envProps);
        all.putAll(asenvProps);
        all.putAll(sysProps);
        all.put(SystemPropertyConstants.SERVER_NAME, getInfo().getInstanceName());
        all.putAll(sysPropsFromXml);
        all.putAll(jvmOptions.getCombinedMap());
        all.putAll(profiler.getConfig());
        TokenResolver resolver = new TokenResolver(all);
        resolver.resolve(jvmOptions.xProps);
        resolver.resolve(jvmOptions.xxProps);
        resolver.resolve(jvmOptions.plainProps);
        resolver.resolve(jvmOptions.sysProps);
        resolver.resolve(javaConfig.getMap());
        resolver.resolve(profiler.getConfig());
        resolver.resolve(debugOptions);
        //resolver.resolve(sysPropsFromXml);
        logFilename = resolver.resolve(logFilename);
        adminFileRealmKeyFile = resolver.resolve(adminFileRealmKeyFile);

        // TODO ?? Resolve sysPropsFromXml ???
    }

    private void setJavaExecutable() throws GFLauncherException {
        // first choice is from domain.xml
        if (setJavaExecutableIfValid(javaConfig.getJavaHome()))
            return;

        // second choice is from asenv
        if (!setJavaExecutableIfValid(asenvProps.get(JAVA_ROOT_PROPERTY)))
            throw new GFLauncherException("nojvm");

    }

    void setClasspath() throws GFLauncherException {
        List mainCP = getMainClasspath(); // subclass provides this
        List envCP = javaConfig.getEnvClasspath();
        List sysCP = javaConfig.getSystemClasspath();
        List prefixCP = javaConfig.getPrefixClasspath();
        List suffixCP = javaConfig.getSuffixClasspath();
        List profilerCP = profiler.getClasspath();
        List extCP = Collections.singletonList(
                new File(info.getInstanceRootDir(), "lib/ext/*")
        );

        // create a list of all the classpath pieces in the right order
        List all = new ArrayList();
        all.addAll(extCP);
        all.addAll(prefixCP);
        all.addAll(profilerCP);
        all.addAll(mainCP);
        all.addAll(sysCP);
        all.addAll(envCP);
        all.addAll(suffixCP);
        setClasspath(GFLauncherUtils.fileListToPathString(all));
    }

    boolean setJavaExecutableIfValid(String filename) {
        if (!GFLauncherUtils.ok(filename)) {
            return false;
        }

        File f = new File(filename);

        if (!f.isDirectory()) {
            return false;
        }

        if (GFLauncherUtils.isWindows()) {
            f = new File(f, "bin/java.exe");
        }
        else {
            f = new File(f, "bin/java");
        }

        if (f.exists()) {
            javaExe = SmartFile.sanitize(f).getPath();
            return true;
        }
        return false;
    }

    private List getDebug() {
        if (info.isDebug() || javaConfig.isDebugEnabled()) {
            return javaConfig.getDebugOptions();
        }
        return Collections.emptyList();
    }

    private void setupProfilerAndJvmOptions(MiniXmlParser parser) throws MiniXmlParserException, GFLauncherException {
        // add JVM options from Profiler *last* so they override config's
        // JVM options

        profiler = new Profiler(
                parser.getProfilerConfig(),
                parser.getProfilerJvmOptions(),
                parser.getProfilerSystemProperties());

        Optional jdkVersion = getConfiguredJdkVersion(javaExe);
        List rawJvmOptions = parser.getJvmOptions()
                .stream()
                .filter(fullOption -> JDK.isCorrectJDK(jdkVersion, fullOption.vendorOrVM, fullOption.minVersion, fullOption.maxVersion))
                .map(option -> option.option)
                .collect(Collectors.toList());
        rawJvmOptions.addAll(getSpecialSystemProperties());
        if (profiler.isEnabled()) {
            rawJvmOptions.addAll(profiler.getJvmOptions());
        }
        jvmOptions = new JvmOptions(rawJvmOptions);
        if (info.isDropInterruptedCommands()) {
            jvmOptions.sysProps.put(SystemPropertyConstants.DROP_INTERRUPTED_COMMANDS, Boolean.TRUE.toString());
        }
        
        // PAYARA-1681 - Add default Payara JVM options if an override isn't in place
        addDefaultJvmOptions();
    }

    /**
     * Get the Java version from the given path to a Java executable.
     *
     * @param javaExePath The full path to the executable java command.
     * @return The Java version as a JDK.Version object, if successful.
     * @throws GFLauncherException
     */
    private Optional getConfiguredJdkVersion(String javaExePath) throws GFLauncherException {
        try {
            Runtime r = Runtime.getRuntime();
            Process p = r.exec(javaExePath + " -version");
            p.waitFor();
            try (BufferedReader b = new BufferedReader(new InputStreamReader(p.getErrorStream()))) {
                String line;
                while ((line = b.readLine()) != null) {
                    Matcher m = JAVA_VERSION_PATTERN.matcher(line);
                    if (m.matches()) {
                        return Optional.ofNullable(JDK.getVersion(m.group(1)));
                    }
                }
            }
            return Optional.empty();
        } catch (IOException | InterruptedException ex) {
            throw new GFLauncherException("nojvm");
        }
    }

    private void addDefaultJvmOptions() {
        if (!jvmOptions.getCombinedMap().containsKey(PayaraDefaultJvmOptions.GRIZZLY_DEFAULT_MEMORY_MANAGER_PROPERTY)) {
            jvmOptions.sysProps.put(PayaraDefaultJvmOptions.GRIZZLY_DEFAULT_MEMORY_MANAGER_PROPERTY, 
                    PayaraDefaultJvmOptions.GRIZZLY_DEFAULT_MEMORY_MANAGER_VALUE);
            
            // Log that we've made the change
            GFLauncherLogger.fine(GFLauncherLogger.DEFAULT_JVM_OPTION, 
                    PayaraDefaultJvmOptions.GRIZZLY_DEFAULT_MEMORY_MANAGER_PROPERTY,
                    PayaraDefaultJvmOptions.GRIZZLY_DEFAULT_MEMORY_MANAGER_VALUE);
        }
    }

    private void setupUpgradeSecurity() throws GFLauncherException {
        // If this is an upgrade and the security manager is on,
        // copy the current server.policy file to the domain
        // before the upgrade.
        if (info.isUpgrade()
                && jvmOptions.sysProps.containsKey("java.security.manager")) {

            GFLauncherLogger.info(GFLauncherLogger.COPY_SERVER_POLICY);

            File source = new File(new File(new File(info.installDir, "lib"),
                    "templates"), "server.policy");
            File target = new File(info.getConfigDir(), "server.policy");

            try {
                FileUtils.copyFile(source, target);
            }
            catch (IOException ioe) {
                // the actual error is wrapped differently depending on
                // whether the problem was with the source or target
                Throwable cause = ioe.getCause() == null ? ioe : ioe.getCause();
                throw new GFLauncherException(STRINGS.get(
                        "COPY_SERVER_POLICY_error", cause.getMessage()));
            }
        }
    }

    /**
     * Because of some issues in GlassFish OSGi launcher, a server updated from
     * version 3.0.x to 3.1 won't start if a OSGi cache directory populated with
     * 3.0.x modules is used. So, as a work around, we rename the cache
     * directory when upgrade path is used. See GLASSFISH-15772 for more
     * details.
     *
     * @throws GFLauncherException if it fails to rename the cache directory
     */
    private void renameOsgiCache() throws GFLauncherException {
        if (info.isUpgrade()) {
            File osgiCacheDir = new File(info.getDomainRootDir(),
                    "osgi-cache");
            File backupOsgiCacheDir = new File(info.getDomainRootDir(),
                    "osgi-cache-" + System.currentTimeMillis());
            if (osgiCacheDir.exists() && !backupOsgiCacheDir.exists()) {
                if (!FileUtils.renameFile(osgiCacheDir, backupOsgiCacheDir)) {
                    throw new GFLauncherException(STRINGS.get("rename_osgi_cache_failed", osgiCacheDir, backupOsgiCacheDir));
                }
                else {
                    GFLauncherLogger.fine("rename_osgi_cache_succeeded", osgiCacheDir, backupOsgiCacheDir);
                }
            }
        }
    }

    private void setupMonitoring(MiniXmlParser parser) throws GFLauncherException {
        // As usual we have to be very careful.

        // If it is NOT enabled -- we are out of here!!!
        if (!parser.isMonitoringEnabled()) {
            return;
        }

        // if the user has a hard-coded "-javaagent" jvm-option that uses OUR jar
        // then we do NOT want to add our own.
        Set plainKeys = jvmOptions.plainProps.keySet();
        for (String key : plainKeys) {
            if (key.startsWith("javaagent:")) {
                // complications -- of course!!  They may have mix&match forward and back slashes
                key = key.replace('\\', '/');
                if (key.contains(FLASHLIGHT_AGENT_NAME))
                    return; // Done!!!!
            }
        }

        // It is not already specified AND monitoring is enabled.
        try {
            jvmOptions.plainProps.put(getMonitoringAgentJvmOptionString(), null);
        }
        catch (GFLauncherException gfe) {
            // This has been defined as a non-fatal error.
            // Silently ignore it -- but do NOT add it as an option
        }
    }

    private String getMonitoringAgentJvmOptionString() throws GFLauncherException {
        File libMonDir = new File(getInfo().getInstallDir(), LIBMON_NAME);
        File flashlightJarFile = new File(libMonDir, FLASHLIGHT_AGENT_NAME);

        if (flashlightJarFile.isFile())
            return "javaagent:" + getCleanPath(flashlightJarFile);
        // No agent jar...
        else {
            String msg = STRINGS.get("no_flashlight_agent", flashlightJarFile);
            GFLauncherLogger.warning(GFLauncherLogger.NO_FLASHLIGHT_AGENT, flashlightJarFile);
            throw new GFLauncherException(msg);
        }
    }

    private static String getCleanPath(File f) {
        return SmartFile.sanitize(f).getPath().replace('\\', '/');
    }

    private List getSpecialSystemProperties() throws GFLauncherException {
        Map props = new HashMap();
        props.put(INSTALL_ROOT_PROPERTY, getInfo().getInstallDir().getAbsolutePath());
        props.put(INSTANCE_ROOT_PROPERTY, getInfo().getInstanceRootDir().getAbsolutePath());
        return (this.propsToJvmOptions(props));
    }

    void logCommandLine() {
        StringBuilder sb = new StringBuilder();

        if (!isFakeLaunch()) {
            Iterable cmdLine = getCommandLine();

            for (String s : cmdLine) {
                sb.append(NEWLINE);
                sb.append(s);
            }
            GFLauncherLogger.info(GFLauncherLogger.COMMAND_LINE, sb.toString());
        }
    }

    private void checkJDKVersion() {
        if(!JDK.isRunningLTSJDK()) {
            GFLauncherLogger.warning("You are running the product on an unsupported JDK version and might see unexpected results or exceptions.");
        }
    }

    String getClasspath() {
        return classpath;
    }

    void setClasspath(String s) {
        classpath = s;
    }

    private List propsToJvmOptions(Map map) {
        List ss = new ArrayList();
        Set> entries = map.entrySet();

        for (Map.Entry entry : entries) {
            String name = entry.getKey();
            String value = entry.getValue();
            String jvm = "-D" + name;

            if (value != null) {
                jvm += "=" + value;
            }

            ss.add(jvm);
        }

        return ss;
    }

    private void setupLogLevels() {
        if (info.isVerbose())
            GFLauncherLogger.setConsoleLevel(Level.INFO);
        else
            GFLauncherLogger.setConsoleLevel(Level.WARNING);
    }

    @SuppressWarnings("UseSpecificCatch")
    private void closeStandardStreamsMaybe() {
        // see issue 12832
        // Windows bug/feature -->
        // Say process A (ssh) creates Process B (asadmin start-instance )
        // which then fires up Process C (the instance).
        // Process B exits but Process A does NOT.  Process A is waiting for
        // Process C to exit.
        // The solution is to close down the standard streams BEFORE creating
        // Process C.  Then Process A becomes convinced that the process it created
        // has finished.
        // If there is a console that means the user is sitting at the terminal
        // directly and we don't have to worry about it.
        // Note that the issue is inside SSH -- not inside GF code per se.  I.e.
        // Process B absolutely positively does exit whether or not this code runs...
        // don't run this unless we have to because our "..." messages disappear.

        if (System.console() == null && OS.isWindows() && !(info.isVerboseOrWatchdog())) {
            String sname;

            if (info.isDomain()) {
                sname = info.getDomainName();
            } else {
                sname = info.getInstanceName();
            }
            
            System.out.println(STRINGS.get("ssh", sname));
            try {
                System.in.close();
            }
            catch (Exception e) { // ignore
            }
            try {
                System.err.close();
            }
            catch (Exception e) { // ignore
            }
            try {
                System.out.close();
            }
            catch (Exception e) { // ignore
            }
        }
    }
    
    ///////////////////////////////////////////////////////////////////////////
    private static class ProcessWhacker implements Runnable {
        
        private final String message;
        private Process process;
        
        ProcessWhacker(Process p, String msg) {
            message = msg;
            process = p;
        }

        void setProcess(Process p) {
            process = p;
        }

        @Override
        public void run() {
            // we are in a shutdown hook -- most of the JVM is gone.
            // logger won't work anymore...
            System.out.println(message);
            process.destroy();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy