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

org.glassfish.appclient.client.AppClientFacade Maven / Gradle / Ivy

/*
 * Copyright (c) 2023, 2024 Contributors to the Eclipse Foundation
 * Copyright (c) 1997, 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.appclient.client;

import com.sun.enterprise.container.common.spi.util.InjectionException;
import com.sun.enterprise.deployment.node.SaxParserHandlerBundled;
import com.sun.enterprise.universal.glassfish.TokenResolver;
import com.sun.enterprise.util.LocalStringManager;
import com.sun.enterprise.util.LocalStringManagerImpl;

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.bind.ValidationEvent;
import jakarta.xml.bind.util.ValidationEventCollector;

import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.sax.SAXSource;

import org.glassfish.appclient.client.acc.ACCClassLoader;
import org.glassfish.appclient.client.acc.ACCLogger;
import org.glassfish.appclient.client.acc.AgentArguments;
import org.glassfish.appclient.client.acc.AppClientContainer;
import org.glassfish.appclient.client.acc.AppClientContainer.Builder;
import org.glassfish.appclient.client.acc.AppclientCommandArguments;
import org.glassfish.appclient.client.acc.CommandLaunchInfo;
import org.glassfish.appclient.client.acc.CommandLaunchInfo.ClientLaunchType;
import org.glassfish.appclient.client.acc.TargetServerHelper;
import org.glassfish.appclient.client.acc.UserError;
import org.glassfish.appclient.client.acc.Util;
import org.glassfish.appclient.client.acc.config.AuthRealm;
import org.glassfish.appclient.client.acc.config.ClientContainer;
import org.glassfish.appclient.client.acc.config.ClientCredential;
import org.glassfish.appclient.client.acc.config.MessageSecurityConfig;
import org.glassfish.appclient.client.acc.config.Property;
import org.glassfish.appclient.client.acc.config.TargetServer;
import org.glassfish.common.util.GlassfishUrlClassLoader;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import static org.glassfish.appclient.client.acc.CommandLaunchInfo.ClientLaunchType.UNKNOWN;

/**
 * @author tjquinn
 */
public class AppClientFacade {

    private static final String ACC_CONFIG_CONTENT_PROPERTY_NAME = "glassfish-acc.xml.content";
    private static final String MAN_PAGE_PATH = "/org/glassfish/appclient/client/acc/appclient.1m";
    private static final String LINE_SEP = System.lineSeparator();

    private static final Class stringsAnchor = ACCClassLoader.class;
    private static LocalStringManager localStrings = new LocalStringManagerImpl(stringsAnchor);

    private static CommandLaunchInfo launchInfo;
    private static AppclientCommandArguments appClientCommandArgs;
    private static AppClientContainer appClientContainer;

    /**
     * Prepares the ACC (if not already done by the agent) and then transfers control to the ACC.
     * 

* Eventually, the Java runtime will invoke this method as the main method of the application, whether or not the * command line specified the Java agent. If the agent has already run, then it will have prepared the ACC already. If * the agent has not already run, then this method prepares it. *

* If the user has run the generated app client JAR directly - not using the appclient script - then the Java runtime * will invoke this method directly and the command-line arguments should be intended for the client only; no agent or * ACC settings are possible. If the user has used the appclient script, then the script will have created a Java * command which specifies the agent, constructs an agent argument string, and passes as command line arguments only * those values which should be passed to the client. The net result is that, no matter how the app client was launched, * the args array contains only the arguments that are for the client's consumption, without any agent or ACC arguments. * * @param args the command line arguments */ public static void main(String[] args) { try { if (appClientContainer == null) { /* * The facade JAR has been run directly, not via the appclient script and not via Java Web Start. So we have no agent * arguments and no instrumentation for registering transformations. * * Because the agent has not run, we prepare the ACC here. (The agent would have done so itself had it run.) */ prepareACC(null, null); } /* * In any case, the ACC is now prepared. Launch the app client in the prepared ACC. */ appClientContainer.launch(args); } catch (Exception ex) { ex.printStackTrace(); System.exit(1); } catch (UserError ue) { ue.displayAndExit(); } } public static AppClientContainer acc() { return appClientContainer; } public static void launch(String[] args) throws NoSuchMethodException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException, SAXException, InjectionException, UserError { appClientContainer.launch(args); } public static void prepareACC(String agentArgsText, Instrumentation inst) throws UserError, MalformedURLException, URISyntaxException, JAXBException, FileNotFoundException, ParserConfigurationException, SAXException, IOException, Exception { int version = Runtime.version().feature(); if (version != 17) { throw new UserError(localStrings.getLocalString(stringsAnchor, "main.badVersion", "Current Java version {0} is too low; {1} or later required", new Object[] {System.getProperty("java.version"), "21"})); } /* * Analyze the agent argument string. */ AgentArguments agentArgs = AgentArguments.newInstance(agentArgsText); /* * The agent arguments that correspond to the ones that we want to pass to the ACC are the ones with the "arg=" keyword * prefix. These will include arguments with meaning to the ACC (-textauth for example) as well as arguments to be * passed on to the client's main method. */ appClientCommandArgs = AppclientCommandArguments.newInstance(agentArgs.namedValues("arg")); if (appClientCommandArgs.isUsage()) { usage(0); } else if (appClientCommandArgs.isHelp()) { help(); } /* * Examine the agent arguments for settings about how to launch the client. */ launchInfo = CommandLaunchInfo.newInstance(agentArgs); if (launchInfo.getClientLaunchType() == UNKNOWN) { usage(1); } /* * Handle the legacy env. variable APPCPATH. */ ACCClassLoader loader = initClassLoader((inst == null)); Thread.currentThread().setContextClassLoader(loader); /* * The installRoot property will be set by the ServerEnvironment initialization using the ACC start-up context. That * happens during the ACCModulesManager warm-up. */ /* * Load the ACC configuration XML file. */ ClientContainer clientContainer = readConfig(appClientCommandArgs.getConfigFilePath(), loader); /* * Decide what target servers to use. This combines any specified on the command line with any in the config file's * target-server elements as well as any set in the properties of the config file. */ final TargetServer[] targetServers = TargetServerHelper.targetServers(clientContainer, appClientCommandArgs.getTargetServer()); /* * Get the builder. Doing so correctly involves merging the configuration file data with some of the command line and * agent arguments. */ final AppClientContainer.Builder builder = createBuilder(targetServers, clientContainer, appClientCommandArgs); /* * Create the ACC. Again, precisely how we create it depends on some of the command line arguments and agent arguments. */ final AppClientContainer newACC = createContainer(builder, launchInfo, appClientCommandArgs); /* * Because the JMV might invoke the client's main class, the agent needs to prepare the container. (This is done as part * of the AppClientContainer.start() processing in the public API. */ newACC.prepare(inst); appClientContainer = newACC; } private static void usage(final int exitStatus) { System.err.println(getUsage()); System.exit(exitStatus); } private static void help() throws IOException { final InputStream is = AppClientFacade.class.getResourceAsStream(MAN_PAGE_PATH); if (is == null) { usage(0); } try (BufferedReader helpReader = new BufferedReader(new InputStreamReader(is))) { String line; while ((line = helpReader.readLine()) != null) { System.err.println(line); } } finally { System.exit(0); } } private static String getUsage() { return localStrings.getLocalString(stringsAnchor, "main.usage", "appclient [ | -client ] [-mainclass |-name ] [-xml ] [-textauth] [-user ] [-password |-passwordfile ] [-targetserver host[:port][,host[:port]...] [app-args]") + System.lineSeparator() + localStrings.getLocalString(stringsAnchor, "main.usage.1", " or :\n\tappclient [ ] [ | -jar ] [app args]"); } private static ACCClassLoader initClassLoader(final boolean loaderShouldTransform) throws MalformedURLException { ACCClassLoader loader = ACCClassLoader.instance(); if (loader == null) { loader = ACCClassLoader.newInstance(Thread.currentThread().getContextClassLoader(), loaderShouldTransform); } return loader; } private static Builder createBuilder(TargetServer[] targetServers, ClientContainer clientContainer, AppclientCommandArguments appClientCommandArgs) throws IOException { Builder builder = AppClientContainer.newBuilder(targetServers); /* * Augment the builder with settings from the app client options that can affect the builder itself. (This is distinct * from options that affect what client to launch which are handled in creating the ACC itself. */ updateClientCredentials(builder, appClientCommandArgs); List msc = clientContainer.getMessageSecurityConfig(); if (msc != null) { builder.getMessageSecurityConfig().addAll(clientContainer.getMessageSecurityConfig()); } builder.logger(new ACCLogger(clientContainer.getLogService())); AuthRealm authRealm = clientContainer.getAuthRealm(); if (authRealm != null) { builder.authRealm(authRealm.getClassname()); } List property = clientContainer.getProperty(); if (property != null) { builder.containerProperties(property); } return builder; } private static void updateClientCredentials(final Builder builder, final AppclientCommandArguments appClientCommandArgs) { ClientCredential clientCredential = builder.getClientCredential(); String user = (clientCredential != null ? clientCredential.getUserName() : null); char[] pw = (clientCredential != null && clientCredential.getPassword() != null ? clientCredential.getPassword().get() : null); /* * user on command line? */ String commandLineUser; if ((commandLineUser = appClientCommandArgs.getUser()) != null) { user = commandLineUser; } /* * password or passwordfile on command line? (theAppClientCommandArgs class takes care of reading the password from the * file and/or handling the -password option. */ char[] commandLinePW; if ((commandLinePW = appClientCommandArgs.getPassword()) != null) { pw = commandLinePW; } builder.clientCredentials(user, pw); } private static AppClientContainer createContainer(Builder builder, CommandLaunchInfo launchInfo, AppclientCommandArguments appClientArgs) throws Exception, UserError { /* * The launchInfo already knows something about how to conduct the launch. */ ClientLaunchType launchType = launchInfo.getClientLaunchType(); AppClientContainer container; switch (launchType) { case JAR: case DIR: /* * The client name in the launch info is a file path for the directory or JAR to launch. */ container = createContainerForAppClientArchiveOrDir(builder, launchInfo.getClientName(), appClientArgs.getMainclass(), appClientArgs.getName()); break; case URL: container = createContainerForJWSLaunch(builder, launchInfo.getClientName(), appClientArgs.getMainclass(), appClientArgs.getName()); break; case CLASS: container = createContainerForClassName(builder, launchInfo.getClientName()); break; case CLASSFILE: container = createContainerForClassFile(builder, launchInfo.getClientName()); break; default: container = null; } if (container == null) { throw new IllegalArgumentException("cannot choose app client launch type"); } return container; } private static AppClientContainer createContainerForAppClientArchiveOrDir(Builder builder, String appClientPath, String mainClassName, String clientName) throws Exception, UserError { return builder.newContainer(Util.getURI(new File(appClientPath)), null /* callbackHandler */, mainClassName, clientName, appClientCommandArgs.isTextauth()); } private static AppClientContainer createContainerForJWSLaunch(Builder builder, String appClientPath, String mainClassName, String clientName) throws Exception, UserError { return builder.newContainer(URI.create(appClientPath), null /* callbackHandler */, mainClassName, clientName); } private static AppClientContainer createContainerForClassName(Builder builder, String className) throws Exception, UserError { /* * Place "." on the class path so that when we convert the class file path to a fully-qualified class name and try to * load it, we'll find it. */ ClassLoader loader = prepareLoaderToFindClassFile(Thread.currentThread().getContextClassLoader()); Thread.currentThread().setContextClassLoader(loader); return builder.newContainer(Class.forName(className, true, loader)); } private static ClassLoader prepareLoaderToFindClassFile(final ClassLoader currentLoader) throws MalformedURLException { File currentDirPath = new File(System.getProperty("user.dir")); ClassLoader newLoader = new GlassfishUrlClassLoader(new URL[] { currentDirPath.toURI().toURL() }, currentLoader); return newLoader; } private static AppClientContainer createContainerForClassFile(Builder builder, String classFilePath) throws MalformedURLException, ClassNotFoundException, FileNotFoundException, IOException, Exception, UserError { Util.verifyFilePath(classFilePath); /* * Strip off the trailing .class from the path and convert separator characters to dots to build a fully-qualified class * name. */ String className = classFilePath.substring(0, classFilePath.lastIndexOf(".class")).replace(File.separatorChar, '.'); return createContainerForClassName(builder, className); } private static ClientContainer readConfig(final String configPath, final ClassLoader loader) throws UserError, JAXBException, FileNotFoundException, ParserConfigurationException, SAXException, URISyntaxException, IOException { ClientContainer result = null; Reader configReader = null; String configFileLocationForErrorMessage = ""; try { /* * During a Java Web Start launch, the config is passed as a property value. */ String configInProperty = System.getProperty(ACC_CONFIG_CONTENT_PROPERTY_NAME); if (configInProperty != null) { /* * Awkwardly, the glassfish-acc.xml content refers to a config file. We work around this for Java Web Start launch by * capturing the content of that config file into a property setting in the generated JNLP document. We need to write * that content into a temporary file here on the client and then replace a placeholder in the glassfish-acc.xml content * with the path to that temp file. */ final File securityConfigTempFile = Util.writeTextToTempFile(configInProperty, "wss-client-config", ".xml", false); final Properties p = new Properties(); p.setProperty("security.config.path", securityConfigTempFile.getAbsolutePath()); configInProperty = Util.replaceTokens(configInProperty, p); configReader = new StringReader(configInProperty); } else { /* * This is not a Java Web Start launch, so read the configuration from a disk file. */ File configFile = checkXMLFile(configPath); checkXMLFile(appClientCommandArgs.getConfigFilePath()); configReader = new FileReader(configFile); configFileLocationForErrorMessage = configFile.getAbsolutePath(); } /* * Although JAXB makes it very simple to parse the XML into Java objects, we have to do several things explicitly to use * our local copies of DTDs and XSDs. */ SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setValidating(true); spf.setNamespaceAware(true); SAXParser parser = spf.newSAXParser(); XMLReader reader = parser.getXMLReader(); /* * Get the local entity resolver that knows about the bundled .dtd and .xsd files. */ reader.setEntityResolver(new SaxParserHandlerBundled()); /* * To support installation-directory independence the default glassfish-acc.xml refers to the wss-config file using * ${com.sun.aas.installRoot}... So preprocess the glassfish-acc.xml file to replace any tokens with the corresponding * values, then submit that result to JAXB. */ InputSource inputSource = replaceTokensForParsing(configReader); SAXSource saxSource = new SAXSource(reader, inputSource); JAXBContext jc = JAXBContext.newInstance(ClientContainer.class); final ValidationEventCollector vec = new ValidationEventCollector(); Unmarshaller u = jc.createUnmarshaller(); u.setEventHandler(vec); result = (ClientContainer) u.unmarshal(saxSource); if (vec.hasEvents()) { /* * The parser reported at least one warning or error. If all events were warnings, display them as a message and * continue. Otherwise there was at least one error or fatal, so say so and try to continue but say that such errors * might be fatal in future releases. */ boolean isError = false; final StringBuilder sb = new StringBuilder(); for (ValidationEvent ve : vec.getEvents()) { sb.append(ve.getMessage()).append(LINE_SEP); isError |= (ve.getSeverity() != ValidationEvent.WARNING); } String messageIntroduction = localStrings.getLocalString(AppClientFacade.class, isError ? "appclient.errParsingConfig" : "appclient.warnParsingConfig", isError ? "Error parsing app client container configuration {0}. Attempting to continue. In future releases such parsing errors might become fatal. Please correct your configuration file." : "Warning(s) parsing app client container configuration {0}. Continuing.", new Object[] { configFileLocationForErrorMessage }); System.err.println(messageIntroduction + LINE_SEP + sb.toString()); } return result; } finally { if (configReader != null) { configReader.close(); } } } private static InputSource replaceTokensForParsing(final Reader reader) throws FileNotFoundException, IOException, URISyntaxException { char[] buffer = new char[1024]; CharArrayWriter writer = new CharArrayWriter(); int charsRead; while ((charsRead = reader.read(buffer)) != -1) { writer.write(buffer, 0, charsRead); } writer.close(); reader.close(); Map mapping = new HashMap<>(); Properties props = System.getProperties(); for (Enumeration e = props.propertyNames(); e.hasMoreElements();) { String propName = (String) e.nextElement(); mapping.put(propName, props.getProperty(propName)); } TokenResolver resolver = new TokenResolver(mapping); String configWithTokensReplaced = resolver.resolve(writer.toString()); InputSource inputSource = new InputSource(new StringReader(configWithTokensReplaced)); return inputSource; } private static File checkXMLFile(String xmlFullName) throws UserError { try { File f = new File(xmlFullName); if (f.exists() && f.isFile() && f.canRead()) { return f; } else {// If given file does not exists xmlMessage(xmlFullName); return null; } } catch (Exception ex) { xmlMessage(xmlFullName); return null; } } private static void xmlMessage(String xmlFullName) throws UserError { UserError ue = new UserError(localStrings.getLocalString(stringsAnchor, "main.cannot_read_clientContainer_xml", "Client Container xml: {0} not found or unable to read.\nYou may want to use the -xml option to locate your configuration xml.", new String[] { xmlFullName })); ue.setUsage(getUsage()); throw ue; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy