
org.glassfish.appclient.client.acc.AppClientContainer Maven / Gradle / Ivy
/*
* Copyright (c) 2022, 2024 Contributors to the Eclipse Foundation.
* Copyright (c) 1997, 2018 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.acc;
import com.sun.appserv.connectors.internal.api.ConnectorRuntime;
import com.sun.enterprise.container.common.spi.ManagedBeanManager;
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.PersistenceUnitDescriptor;
import com.sun.enterprise.deployment.ServiceReferenceDescriptor;
import com.sun.enterprise.security.webservices.client.ClientPipeCloser;
import com.sun.logging.LogDomains;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManagerFactory;
import jakarta.transaction.Status;
import jakarta.transaction.TransactionManager;
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 javax.naming.NamingException;
import javax.security.auth.callback.CallbackHandler;
import javax.swing.SwingUtilities;
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.hk2.api.PerLookup;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.persistence.jpa.PersistenceUnitLoader;
import org.jvnet.hk2.annotations.Service;
import org.xml.sax.SAXException;
/**
* 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());
@Inject
private AppClientContainerSecurityHelper appClientContainerSecurityHelper;
@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;
private State state = State.INSTANTIATED; // HK2 will create the instance
private ClientMainClassSetting clientMainClassSetting;
private URLClassLoader classLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
private Launchable client;
private CallbackHandler callerSuppliedCallbackHandler;
/** returned from binding the app client to naming; used in preparing component invocation */
private String componentId;
/**
* 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);
}
/*
* ********************* 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 ReflectiveOperationException, InjectionException, IOException, SAXException {
appClientContainerSecurityHelper.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, SAXException, NoSuchMethodException, UserError {
completePreparation(inst);
}
void setClient(final Launchable client) throws ClassNotFoundException {
this.client = client;
clientMainClassSetting = ClientMainClassSetting.set(client.getMainClass());
}
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, SAXException, 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, SAXException, 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 = Thread.getAllStackTraces();
logger.fine("Checking for EDT thread...");
for (Map.Entry entry : threads.entrySet()) {
logger.log(Level.FINE, " {0}", entry.getKey().toString());
StackTraceElement[] frames = entry.getValue();
if (frames.length > 0) {
StackTraceElement last = frames[frames.length - 1];
if (last.getClassName().equals("java.awt.EventDispatchThread") && last.getMethodName().equals("run")) {
logger.log(Level.FINE, "Thread {0} seems to be the EDT", entry.getKey().toString());
return true;
}
}
logger.fine("Did not recognize any thread as the EDT");
}
return false;
}
private void cleanupWhenSafe() {
if (isEDTRunning()) {
final AtomicReference edt = new AtomicReference<>();
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
edt.set(Thread.currentThread());
}
});
edt.get().join();
} catch (Exception e) {
}
}
stop();
}
private void dumpLoaderURLs() {
final String sep = System.lineSeparator();
final ClassLoader ldr = Thread.currentThread().getContextClassLoader();
if (ldr instanceof ACCClassLoader) {
final ACCClassLoader loader = (ACCClassLoader) ldr;
final URL[] urls = loader.getURLs();
final StringBuilder sb = new StringBuilder("Class loader URLs:");
for (URL url : urls) {
sb.append(" ").append(url.toExternalForm()).append(sep);
}
sb.append(sep);
logger.fine(sb.toString());
}
}
private Method getMainMethod()
throws NoSuchMethodException, ClassNotFoundException, IOException, SAXException, InjectionException, UserError {
// determine the main method using reflection
// verify that it is public static void and takes
// String[] as the only argument
Method result = null;
result = ClientMainClassSetting
.getClientMainClass(classLoader, injectionManager, invocationManager, componentId, this, client.getDescriptor(classLoader))
.getMethod("main", new Class[] { String[].class });
// check modifiers: public static
int modifiers = result.getModifiers();
if (!Modifier.isPublic(modifiers) || !Modifier.isStatic(modifiers)) {
final String err = MessageFormat.format(logger.getResourceBundle().getString("appclient.notPublicOrNotStatic"),
(Object[]) null);
throw new NoSuchMethodException(err);
}
// check return type and exceptions
if (!result.getReturnType().equals(Void.TYPE)) {
final String err = MessageFormat.format(logger.getResourceBundle().getString("appclient.notVoid"), (Object[]) null);
throw new NoSuchMethodException(err);
}
return result;
}
/**
* Stops the app client container.
*
* Note that the calling program should not stop the ACC if there might be other threads running, such as the Swing
* event dispatcher thread. Stopping the ACC can shut down various services that those continuing threads might try to
* use.
*
* Also note that stopping the ACC will have no effect on any thread that the app client itself might have created. If
* the calling program needs to control such threads it and the client code running in the threads should agree on how
* they will communicate with each other. The ACC cannot help with this.
*/
public void stop() {
/*
* Because stop can be invoked automatically at the end of launch, allow the developer's driver program to invoke stop
* again without penalty.
*/
if (state == State.STOPPED) {
return;
}
if (state != State.STARTED) {
throw new IllegalStateException();
}
cleanup.start();
state = State.STOPPED;
}
/**
* Records how the main class has been set - by name or by class - and encapsulates the retrieval of the main class.
*/
enum ClientMainClassSetting {
BY_NAME, BY_CLASS;
static String clientMainClassName;
static volatile Class clientMainClass;
static boolean isInjected = false;
static ClientMainClassSetting set(final String name) {
clientMainClassName = name;
clientMainClass = null;
return BY_NAME;
}
static ClientMainClassSetting set(final Class cl) {
clientMainClass = cl;
clientMainClassName = null;
return BY_CLASS;
}
static Class getClientMainClass(final ClassLoader loader, InjectionManager injectionManager, InvocationManager invocationManager,
String componentId, AppClientContainer container, ApplicationClientDescriptor acDesc)
throws ClassNotFoundException, InjectionException, UserError {
if (clientMainClass == null) {
if (clientMainClassName == null) {
throw new IllegalStateException("neither client main class nor its class name has been set");
}
clientMainClass = Class.forName(clientMainClassName, true, loader);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Loaded client main class {0}", clientMainClassName);
}
}
ComponentInvocation ci = new ComponentInvocation(componentId, ComponentInvocation.ComponentInvocationType.APP_CLIENT_INVOCATION,
container, acDesc.getApplication().getAppName(), acDesc.getModuleName());
invocationManager.preInvoke(ci);
InjectionException injExc = null;
if (!isInjected) {
int retriesLeft = Integer.getInteger("org.glassfish.appclient.acc.maxLoginRetries", 3);
while (retriesLeft > 0 && !isInjected) {
injExc = null;
try {
injectionManager.injectClass(clientMainClass, acDesc);
isInjected = true;
} catch (InjectionException ie) {
Throwable t = ie;
boolean isAuthError = false;
if (container.appClientContainerSecurityHelper.isLoginCancelled()) {
throw new UserError(logger.getResourceBundle().getString("appclient.userCanceledAuth"));
}
while (t != null && !isAuthError) {
isAuthError = t instanceof org.omg.CORBA.NO_PERMISSION;
t = t.getCause();
}
if (isAuthError) {
injExc = ie;
container.appClientContainerSecurityHelper.clearClientSecurityContext();
retriesLeft--;
} else {
throw ie;
}
}
}
if (injExc != null) {
/*
* Despite retries, the credentials were not accepted. Throw a user error which the ACC will display nicely.
*/
Object obj = injExc.getCause();
if (obj != null && obj instanceof NamingException) {
final NamingException ne = (NamingException) obj;
final String expl = ne.getExplanation();
final String msg = MessageFormat.format(logger.getResourceBundle().getString("appclient.RemoteAuthError"), expl);
throw new UserError(msg);
}
}
}
return clientMainClass;
}
}
/**
* Records the current state of the ACC.
*/
enum State {
/**
* HK2 has created the ACC instance
*/
INSTANTIATED,
/**
* ACC is ready for the client to run
*/
PREPARED,
/**
* the ACC has started the client.
*
* Note that if the user launches the client JAR directly (using java -jar theClient.jar) the ACC will not be aware of
* this and so the state remains PREPARED.
*/
STARTED,
/**
* the ACC has stopped in response to a request from the calling program
*/
STOPPED;
}
/**
* Sets the name of the main class to be executed.
*
* Normally the ACC reads the app client JAR's manifest to get the Main-Class attribute. The calling program can
* override that value by invoking this method. The main class name is also useful if the calling program provides an
* EAR that contains multiple app clients as submodules within it; the ACC needs the calling program to specify which of
* the possibly several app client modules is the one to execute.
*
* @param mainClassName
* @return
*/
public void setClientMainClassName(final String clientMainClassName) throws ClassNotFoundException {
clientMainClassSetting = ClientMainClassSetting.set(clientMainClassName);
}
void setClientMainClass(final Class clientMainClass) {
clientMainClassSetting = ClientMainClassSetting.set(clientMainClass);
}
/**
* Assigns the URL stream handler factory.
*
* Needed for web services support.
*/
private static void prepareURLStreamHandling() {
// Set the HTTPS URL stream handler.
URL.setURLStreamHandlerFactory(new DirContextURLStreamHandlerFactory());
}
void setClassLoader(ACCClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Prescribes the exposed behavior of ACC configuration that can be set up further, and can be used to newContainer an
* ACC.
*/
public interface Builder {
AppClientContainer newContainer(URI archiveURI) throws Exception, UserError;
AppClientContainer newContainer(URI archiveURI, CallbackHandler callbackHandler, String mainClassName, String appName)
throws Exception, UserError;
AppClientContainer newContainer(URI archiveURI, CallbackHandler callbackHandler, String mainClassName, String appName,
boolean isTextAuth) throws Exception, UserError;
AppClientContainer newContainer(Class mainClass) throws Exception, UserError;
TargetServer[] getTargetServers();
/**
* Adds an optional {@link MessageSecurityConfig} setting.
*
* @param msConfig the new MessageSecurityConfig
* @return the Builder
instance
*/
Builder addMessageSecurityConfig(final MessageSecurityConfig msConfig);
List getMessageSecurityConfig();
/**
* Sets the optional authentication realm for the ACC.
*
* Each specific realm will determine which properties should be set in the Properties argument.
*
* @param className name of the class which implements the realm
* @return the Builder
instance
*/
Builder authRealm(final String className);
AuthRealm getAuthRealm();
/**
* Sets the optional client credentials to be used during authentication to the back-end.
*
* If the client does not invoke clientCredentials
then the ACC will use a {@link CallbackHandler} when it
* discovers that authentication is required. See {@link #callerSuppliedCallbackHandler}.
*
* @param username username valid in the default realm on the server
* @param password password valid in the default realm on the server for the username
* @return the Builder
instance
*/
Builder clientCredentials(final String user, final char[] password);
ClientCredential getClientCredential();
/**
* Sets the optional client credentials and server-side realm to be used during authentication to the back-end.
*
* If the client does not invoke clientCredentials
then the ACC will use a {@link CallbackHandler} when it
* discovers that authentication is required. See {@link #callerSuppliedCallbackHandler}.
*
* @param username username valid in the specified realm on the server
* @param password password valid in the specified realm on the server for the username
* @param realmName name of the realm on the server within which the credentials are valid
* @return the Builder
instance
*/
Builder clientCredentials(final String user, final char[] password, final String realm);
/**
* Sets the container-level Properties.
*
* @param containerProperties
* @return
*/
Builder containerProperties(final Properties containerProperties);
/**
* Sets the container-level properties.
*
* Typically used when setting the properties from the parsed XML config file.
*
* @param containerProperties Property objects to use in setting the properties
* @return
*/
Builder containerProperties(final List containerProperties);
/**
* Returns the container-level Properties.
*
* @return container-level properties
*/
Properties getContainerProperties();
/**
* Sets the logger which the ACC should use as it runs.
*
* @param logger
* @return
*/
Builder logger(final Logger logger);
Logger getLogger();
/**
* Sets whether the ACC should send the password to the server during authentication.
*
* @param sendPassword
* @return
*/
Builder sendPassword(final boolean sendPassword);
boolean getSendPassword();
}
/**
* Encapsulates all clean-up activity.
*
* The calling program can invoke clean-up by invoking the stop
method or by letting the JVM exit, in which
* case clean-up will occur as part of VM shutdown.
*/
private static class Cleanup implements Runnable {
private AppClientInfo appClientInfo = null;
private boolean cleanedUp = false;
private InjectionManager injectionMgr = null;
private ApplicationClientDescriptor appClient = null;
private Class cls = null;
private final Logger logger;
private Thread cleanupThread = null;
private Collection emfs = null;
private final ServiceLocator habitat;
private ConnectorRuntime connectorRuntime;
private ManagedBeanManager managedBeanMgr;
static Cleanup arrangeForShutdownCleanup(final Logger logger, final ServiceLocator habitat,
final ApplicationClientDescriptor appDesc) {
final Cleanup cu = new Cleanup(logger, habitat, appDesc);
cu.enable();
return cu;
}
private Cleanup(final Logger logger, final ServiceLocator habitat, final ApplicationClientDescriptor appDesc) {
this.logger = logger;
this.habitat = habitat;
this.appClient = appDesc;
}
void setAppClientInfo(AppClientInfo info) {
appClientInfo = info;
}
void setInjectionManager(InjectionManager injMgr, Class cls) {
injectionMgr = injMgr;
this.cls = cls;
}
void setManagedBeanManager(ManagedBeanManager mgr) {
managedBeanMgr = mgr;
}
void setEMFs(Collection emfs) {
this.emfs = emfs;
}
void setConnectorRuntime(ConnectorRuntime connectorRuntime) {
this.connectorRuntime = connectorRuntime;
}
void enable() {
Runtime.getRuntime().addShutdownHook(cleanupThread = new Thread(this, "Cleanup"));
}
void disable() {
Runtime.getRuntime().removeShutdownHook(cleanupThread);
}
/**
* Requests cleanup without relying on the VM's shutdown handling.
*/
void start() {
disable();
run();
}
/**
* Performs clean-up of the ACC.
*
* This method should be invoked directly only by the VM's shutdown handling (or by the CleanUp newContainer method). To
* trigger clean-up without relying on the VM's shutdown handling invoke Cleanup.newContainer() not Cleanup.run().
*/
@Override
public void run() {
logger.fine("Clean-up starting");
_logger.fine("Clean-up starting");
/*
* Do not invoke disable from here. The run method might execute while the VM shutdown is in progress, and attempting to
* remove the shutdown hook at that time would trigger an exception.
*/
cleanUp();
logger.fine("Clean-up complete");
_logger.fine("Clean-up complete");
}
void cleanUp() {
if (!cleanedUp) {
// Do managed bean cleanup early since it can result in
// application code (@PreDestroy) invocations
cleanupManagedBeans();
cleanupEMFs();
cleanupInfo();
cleanupInjection();
cleanupServiceReferences();
cleanupTransactions();
cleanupConnectorRuntime();
cleanedUp = true;
} // End if -- cleanup required
}
private void cleanupEMFs() {
try {
if (emfs != null) {
for (EntityManagerFactory emf : emfs) {
emf.close();
}
emfs.clear();
emfs = null;
}
} catch (Throwable t) {
logger.log(Level.SEVERE, "cleanupEMFs", t);
}
}
private void cleanupInfo() {
try {
if (appClientInfo != null) {
appClientInfo.close();
}
} catch (Throwable t) {
logger.log(Level.SEVERE, "cleanupInfo", t);
}
}
private void cleanupInjection() {
try {
if (injectionMgr != null) {
// inject the pre-destroy methods before shutting down
injectionMgr.invokeClassPreDestroy(cls, appClient);
injectionMgr = null;
}
} catch (Throwable t) {
logger.log(Level.SEVERE, "cleanupInjection", t);
}
}
private void cleanupManagedBeans() {
try {
if (managedBeanMgr != null) {
managedBeanMgr.unloadManagedBeans(appClient.getApplication());
}
} catch (Throwable t) {
logger.log(Level.SEVERE, "cleanupManagedBeans", t);
}
}
private void cleanupServiceReferences() {
try {
if (appClient != null && appClient.getServiceReferenceDescriptors() != null) {
// Cleanup client pipe line, if there were service references
for (Object desc : appClient.getServiceReferenceDescriptors()) {
ClientPipeCloser.getInstance().cleanupClientPipe((ServiceReferenceDescriptor) desc);
}
}
} catch (Throwable t) {
logger.log(Level.SEVERE, "cleanupServiceReferences", t);
}
}
private void cleanupTransactions() {
try {
ServiceHandle inhabitant = habitat.getServiceHandle(TransactionManager.class);
if (inhabitant != null && inhabitant.isActive()) {
TransactionManager txmgr = inhabitant.getService();
if (txmgr.getStatus() == Status.STATUS_ACTIVE || txmgr.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
txmgr.rollback();
}
}
} catch (Throwable t) {
logger.log(Level.SEVERE, "cleanupTransactions", t);
}
}
private void cleanupConnectorRuntime() {
try {
if (connectorRuntime != null) {
connectorRuntime.cleanUpResourcesAndShutdownAllActiveRAs();
connectorRuntime = null;
}
} catch (Throwable t) {
logger.log(Level.SEVERE, "cleanupConnectorRuntime", t);
}
}
}
}