org.glassfish.appclient.client.acc.AppClientContainer Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 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 [2019-2022] [Payara Foundation and/or its affiliates]
package org.glassfish.appclient.client.acc;
import com.sun.enterprise.container.common.spi.util.ComponentEnvManager;
import com.sun.enterprise.container.common.spi.util.InjectionException;
import com.sun.enterprise.container.common.spi.util.InjectionManager;
import com.sun.enterprise.deployment.ApplicationClientDescriptor;
import com.sun.enterprise.deployment.ServiceReferenceDescriptor;
import com.sun.enterprise.security.webservices.ClientPipeCloser;
import com.sun.appserv.connectors.internal.api.ConnectorRuntime;
import com.sun.enterprise.deployment.PersistenceUnitDescriptor;
import com.sun.logging.LogDomains;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import jakarta.inject.Inject;
import javax.naming.NamingException;
import jakarta.persistence.EntityManagerFactory;
import javax.security.auth.callback.CallbackHandler;
import javax.swing.SwingUtilities;
import jakarta.transaction.Status;
import jakarta.transaction.TransactionManager;
import org.apache.naming.resources.DirContextURLStreamHandlerFactory;
import org.glassfish.api.invocation.ComponentInvocation;
import org.glassfish.api.invocation.InvocationManager;
import org.glassfish.appclient.client.acc.config.AuthRealm;
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.Security;
import org.glassfish.appclient.client.acc.config.TargetServer;
import org.glassfish.persistence.jpa.PersistenceUnitLoader;
import com.sun.enterprise.container.common.spi.ManagedBeanManager;
import org.jvnet.hk2.annotations.Service;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.ServiceLocator;
import org.xml.sax.SAXParseException;
/**
* Embeddable Glassfish app client container (ACC).
*
* Allows Java programs to:
*
* - create a new builder for an ACC (see {@link #newBuilder} and {@link AppClientContainerBuilder}),
*
- optionally modify the configuration by invoking various builder methods,
*
- create an embedded instance of the ACC from the builder using {@link AppClientContainerBuilder#newContainer() },
*
- startClient the client using {@link #startClient(String[])}, and
*
- stop the container using {@link #stop()}.
*
*
* Each instance of the {@link TargetServer} class passed to the newBuilder
* method represents one
* server, conveying its host and port number, which the ACC can use to
* "bootstrap" into the server-side ORB(s). The calling
* program can request to use secured communication to a server by also passing
* an instance of the {@link Security} configuration class when it creates the TargetServer
* object. Note that the caller prepares the TargetServer
* array completely before passing it to one of the newConfig
* factory methods.
* The Builder
implementation
* does not override or augment the list of target servers using
* system property values, property settings in the container configuration, etc. If such work
* is necessary to find additional target servers the calling program should do it
* and prepare the array of TargetServer
objects accordingly.
*
* The calling program also passes either a File or URI for the app client
* archive to be run or a Class object for the main class to be run as an app client.
*
* After the calling program has created a new AppClientContainer.Builder
instance
* it can set optional
* information to control the ACC's behavior, such as
*
* - setting the authentication realm
*
- setting client credentials
* (and optionally setting an authentication realm in which the username and password
* are valid)
*
- setting the callback handler class
*
- adding one or more {@link MessageSecurityConfig} objects
*
*
* Once the calling program has used the builder to configure the ACC to its liking it invokes the
* builder's newContainer()
method.
* The return type is an AppClientContainer
, and by the time
* newContainer
returns the AppClientContainer
* has invoked the app client's main method and that method has returned to the ACC.
* Any new thread the client creates or any GUI work it triggers on the AWT
* dispatcher thread continues independently from the thread that called newContainer
.
*
* If needed, the calling program can invoke the stop
method on
* the AppClientContainer
to shut down the ACC-provided services.
* Invoking stop
does not stop any
* threads the client might have started. If the calling program needs to
* control such threads it should do so itself, outside the AppClientContainer
* API. If the calling program does not invoke stop
the ACC will
* clean up automatically as the JVM exits.
*
* A simple case in which the calling program provides an app client JAR file and
* a single TargetServer might look like this:
*
*
*
* import org.glassfish.appclient.client.acc.AppClientContainer;
* import org.glassfish.appclient.client.acc.config.TargetServer;
*
* AppClientContainerBuilder builder = AppClientContainer.newBuilder(
* new TargetServer("localhost", 3700));
*
* AppClientContainer acc = builder.newContainer(new File("myAC.jar").toURI());
*
*
(or, alternatively)
*
* AppClientContainer acc = builder.newContainer(MyClient.class);
*
*
Then,
*
* acc.startClient(clientArgs);
* // The newContainer method returns as soon as the client's main method returns,
* // even if the client has started another thread or is using the AWT event
* // dispatcher thread
*
* // At some later point, the program can synchronize with the app client in
* // a user-specified way at which point it could invoke
*
* acc.stop();
*
*
*
* Public methods on the Builder interfaces which set configuration information return the
* Builder object itself. This allows the calling program to chain together
* several method invocations, such as
*
*
* AppClientContainerBuilder builder = AppClientContainer.newBuilder(...);
* builder.clientCredentials(myUser, myPass).logger(myLogger);
*
*
* @author tjquinn
*/
@Service
@PerLookup
public class AppClientContainer {
// XXX move this
/** Prop name for keeping temporary files */
public static final String APPCLIENT_RETAIN_TEMP_FILES_PROPERTYNAME = "com.sun.aas.jws.retainTempFiles";
private static final Logger logger = LogDomains.getLogger(AppClientContainer.class,
LogDomains.ACC_LOGGER);
private static final Logger _logger = Logger.getLogger(AppClientContainer.class.getName());
/**
* Creates a new ACC builder object, preset with the specified
* target servers.
*
* @param targetServers server(s) to contact during ORB bootstrapping
* @return AppClientContainer.Builder
object
*/
public static AppClientContainer.Builder newBuilder(
final TargetServer[] targetServers) {
return new AppClientContainerBuilder(targetServers);
}
// /**
// * Creates a new ACC builder object.
// *
// * This variant could be invoked, for example, from the main method of
// * our main class in the facade JAR file generated during deployment. If
// * such a generated JAR is launched directly using a java command (and
// * not the appclient script) then that class would have no way to find
// * any configuration information.
// *
// * @return AppClientContainer.Builder
object
// */
// public static AppClientContainer.Builder newBuilder() {
// return new AppClientContainerBuilder();
// }
@Inject
private AppClientContainerSecurityHelper secHelper;
@Inject
private InjectionManager injectionManager;
@Inject
private InvocationManager invocationManager;
@Inject
private ComponentEnvManager componentEnvManager;
@Inject
private ConnectorRuntime connectorRuntime;
@Inject
private ServiceLocator habitat;
private Builder builder;
private Cleanup cleanup = null;
private State state = State.INSTANTIATED; // HK2 will create the instance
private ClientMainClassSetting clientMainClassSetting = null;
private URLClassLoader classLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
private Collection emfs = null;
// private boolean isJWS = false;
private Launchable client = null;
private CallbackHandler callerSuppliedCallbackHandler = null;
/** returned from binding the app client to naming; used in preparing component invocation */
private String componentId = null;
/*
* ********************* ABOUT INITIALIZATION ********************
*
* Note that, internally, the AppClientContainerBuilder's newContainer
* methods use HK2 to instantiate the AppClientContainer object (so we can
* inject references to various other services).
*
* The newContainer method then invokes one of the ACC's
* prepare
methods to initialize the ACC fully. All that is
* left at that point is for the client's main method to be invoked.
*
*/
public void startClient(String[] args) throws Exception, UserError {
prepare(null);
launch(args);
}
void prepareSecurity(final TargetServer[] targetServers,
final List msgSecConfigs,
final Properties containerProperties,
final ClientCredential clientCredential,
final CallbackHandler callerSuppliedCallbackHandler,
final URLClassLoader classLoader,
final boolean isTextAuth) throws InstantiationException,
IllegalAccessException, InjectionException, ClassNotFoundException,
IOException,
SAXParseException {
secHelper.init(targetServers, msgSecConfigs, containerProperties, clientCredential,
callerSuppliedCallbackHandler, classLoader, client.getDescriptor(classLoader),
isTextAuth);
}
void setCallbackHandler(final CallbackHandler callerSuppliedCallbackHandler) {
this.callerSuppliedCallbackHandler = callerSuppliedCallbackHandler;
}
void setBuilder(final Builder builder) {
this.builder = builder;
}
public void prepare(final Instrumentation inst) throws NamingException,
IOException, InstantiationException, IllegalAccessException,
InjectionException, ClassNotFoundException, SAXParseException,
NoSuchMethodException, UserError {
completePreparation(inst);
}
void setClient(final Launchable client) throws ClassNotFoundException {
this.client = client;
clientMainClassSetting = ClientMainClassSetting.set(client.getMainClass());
}
void processPermissions() throws IOException {
//need to process the permissions files
if (classLoader instanceof ACCClassLoader) {
((ACCClassLoader)classLoader).processDeclaredPermissions();
}
}
protected Class loadClass(final String className) throws ClassNotFoundException {
return Class.forName(className, true, classLoader);
}
protected ClassLoader getClassLoader() {
return classLoader;
}
/**
* Gets the ACC ready so the main class can run.
* This can be followed, immediately or after some time, by either an
* invocation of {@link #launch(java.lang.String[]) or
* by the JVM invoking the client's main method (as would happen during
* a java -jar theClient.jar
launch.
*
* @throws java.lang.Exception
*/
private void completePreparation(final Instrumentation inst) throws
NamingException, IOException, InstantiationException,
IllegalAccessException, InjectionException, ClassNotFoundException,
SAXParseException, NoSuchMethodException, UserError {
if (state != State.INSTANTIATED) {
throw new IllegalStateException();
}
/*
* Attach any names defined in the app client. Validate the descriptor
* first, then use it to bind names in the app client. This order is
* important - for example, to set up message destination refs correctly.
*/
client.validateDescriptor();
final ApplicationClientDescriptor desc = client.getDescriptor(classLoader);
componentId = componentEnvManager.bindToComponentNamespace(desc);
/*
* Arrange for cleanup now instead of during launch() because in some use cases
* the JVM will invoke the client's main method itself and launch will
* be skipped.
*/
cleanup = Cleanup.arrangeForShutdownCleanup(logger, habitat, desc);
/*
* Allow pre-destroy handling to work on the main class during clean-up.
*/
cleanup.setInjectionManager(injectionManager,
clientMainClassSetting.clientMainClass);
/*
* If this app client contains persistence unit refs, then initialize
* the PU handling.
*/
Collection extends PersistenceUnitDescriptor> referencedPUs = desc.findReferencedPUs();
if (referencedPUs != null && ! referencedPUs.isEmpty()) {
ProviderContainerContractInfoImpl pcci = new ProviderContainerContractInfoImpl(
(ACCClassLoader) getClassLoader(), inst, client.getAnchorDir(), connectorRuntime);
for (PersistenceUnitDescriptor puDesc : referencedPUs) {
PersistenceUnitLoader pul = new PersistenceUnitLoader(puDesc, pcci);
desc.addEntityManagerFactory(puDesc.getName(), pul.getEMF());
}
cleanup.setEMFs(pcci.emfs());
}
cleanup.setConnectorRuntime(connectorRuntime);
prepareURLStreamHandling();
//This is required for us to enable interrupt jaxws service
//creation calls
System.setProperty("jakarta.xml.ws.spi.Provider", "com.sun.xml.ws.spi.ProviderImpl");
//InjectionManager's injectClass will be called from getMainMethod
// Load any managed beans
ManagedBeanManager managedBeanManager = habitat.getService(ManagedBeanManager.class);
managedBeanManager.loadManagedBeans(desc.getApplication());
cleanup.setManagedBeanManager(managedBeanManager);
/**
* We don't really need the main method here but we do need the side-effects.
*/
getMainMethod();
state = State.PREPARED;
}
public void launch(String[] args) throws
NoSuchMethodException,
ClassNotFoundException,
IllegalAccessException,
IllegalArgumentException,
InvocationTargetException,
IOException,
SAXParseException,
InjectionException,
UserError {
if (state != State.PREPARED) {
throw new IllegalStateException();
}
Method mainMethod = getMainMethod();
// build args to the main and call it
Object params [] = new Object [1];
params[0] = args;
if (logger.isLoggable(Level.FINE)) {
dumpLoaderURLs();
}
mainMethod.invoke(null, params);
state = State.STARTED;
/*
* We need to clean up when the EDT ends or, if there is no EDT, right
* away. In particular, JMS/MQ-related non-daemon threads might still
* be running due to open queueing connections.
*/
cleanupWhenSafe();
}
private boolean isEDTRunning() {
Map threads = java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction