/* * 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. */ 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 javax.inject.Inject; import javax.naming.NamingException; import javax.persistence.EntityManagerFactory; import javax.security.auth.callback.CallbackHandler; import javax.swing.SwingUtilities; import javax.transaction.Status; import javax.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). * <p> * Allows Java programs to: * <ul> * <li>create a new builder for an ACC (see {@link #newBuilder} and {@link AppClientContainerBuilder}), * <li>optionally modify the configuration by invoking various builder methods, * <li>create an embedded instance of the ACC from the builder using {@link AppClientContainerBuilder#newContainer() }, * <li>startClient the client using {@link #startClient(String[])}, and * <li>stop the container using {@link #stop()}. * </ul> * <p> * Each instance of the {@link TargetServer} class passed to the <code>newBuilder</code> * 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 <code>TargetServer</code> * object. Note that the caller prepares the <code>TargetServer</code> * array completely before passing it to one of the <code>newConfig</code> * factory methods. * The <code>Builder</code> 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 <code>TargetServer</code> objects accordingly. * <p> * 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. * <p> * After the calling program has created a new <code>AppClientContainer.Builder</code> instance * it can set optional * information to control the ACC's behavior, such as * <ul> * <li>setting the authentication realm * <li>setting client credentials * (and optionally setting an authentication realm in which the username and password * are valid) * <li>setting the callback handler class * <li>adding one or more {@link MessageSecurityConfig} objects * </ul> * <p> * Once the calling program has used the builder to configure the ACC to its liking it invokes the * builder's <code>newContainer()</code> method. * The return type is an <code>AppClientContainer</code>, and by the time * <code>newContainer</code> returns the <code>AppClientContainer</code> * 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 <code>newContainer</code>. * <p> * If needed, the calling program can invoke the <code>stop</code> method on * the <code>AppClientContainer</code> to shut down the ACC-provided services. * Invoking <code>stop</code> 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 <code>AppClientContainer</code> * API. If the calling program does not invoke <code>stop</code> the ACC will * clean up automatically as the JVM exits. * <p> * A simple case in which the calling program provides an app client JAR file and * a single TargetServer might look like this: * <p> * <code> * * import org.glassfish.appclient.client.acc.AppClientContainer;<br> * import org.glassfish.appclient.client.acc.config.TargetServer;<br> * <br> * AppClientContainerBuilder builder = AppClientContainer.newBuilder(<br> *    new TargetServer("localhost", 3700));<br> * <br> * AppClientContainer acc = builder.newContainer(new File("myAC.jar").toURI());<br> * <br> * </code>(or, alternatively)<code><br> * <br> * AppClientContainer acc = builder.newContainer(MyClient.class);<br> * <br> * <br</code>Then, <code><br> * <br> * acc.startClient(clientArgs);<br> * // The newContainer method returns as soon as the client's main method returns,<br> * // even if the client has started another thread or is using the AWT event<br> * // dispatcher thread * <br> * // At some later point, the program can synchronize with the app client in<br> * // a user-specified way at which point it could invoke<br> * <br> * acc.stop();<br> * <br> * </code> * <p> * 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 * <p> * <code> * AppClientContainerBuilder builder = AppClientContainer.newBuilder(...);<br> * builder.clientCredentials(myUser, myPass).logger(myLogger);<br> * </code> * * @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 <code>AppClientContainer.Builder</code> object */ public static AppClientContainer.Builder newBuilder( final TargetServer[] targetServers) { return new AppClientContainerBuilder(targetServers); } // /** // * Creates a new ACC builder object. // * <p> // * 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 <code>AppClientContainer.Builder</code> 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<EntityManagerFactory> 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 * <code>prepare</code> 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<MessageSecurityConfig> 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 <code>java -jar theClient.jar</code> 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("javax.xml.ws.spi.Provider", "com.sun.enterprise.webservice.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<Thread,StackTraceElement[]> threads = java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Map<Thread,StackTraceElement[]>>() { @Override public Map<Thread, StackTraceElement[]> run() { return Thread.getAllStackTraces(); } }); logger.fine("Checking for EDT thread..."); for (Map.Entry<Thread,StackTraceElement[]> 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<Thread> edt = new AtomicReference<Thread>(); try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { edt.set(Thread.currentThread()); } }); edt.get().join(); } catch (Exception e) { } } stop(); } private void dumpLoaderURLs() { final String sep = System.getProperty("line.separator"); 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, SAXParseException, 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. * <p> * 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. * <p> * 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.secHelper.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.secHelper.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. */ if (injExc.getCause() != null && injExc.getCause() instanceof NamingException) { final NamingException ne = (NamingException) injExc.getCause(); 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. * <p> * 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. * <p> * 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. * <p> * Needed for web services support. */ private static void prepareURLStreamHandling() { // Set the HTTPS URL stream handler. java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() { @Override public Object run() { URL.setURLStreamHandlerFactory(new DirContextURLStreamHandlerFactory()); return null; } }); } 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 { public AppClientContainer newContainer(URI archiveURI) throws Exception, UserError; public AppClientContainer newContainer(URI archiveURI, CallbackHandler callbackHandler, String mainClassName, String appName) throws Exception, UserError; public AppClientContainer newContainer(URI archiveURI, CallbackHandler callbackHandler, String mainClassName, String appName, boolean isTextAuth) throws Exception, UserError; public AppClientContainer newContainer(Class mainClass) throws Exception, UserError; public TargetServer[] getTargetServers(); /** * Adds an optional {@link MessageSecurityConfig} setting. * * @param msConfig the new MessageSecurityConfig * @return the <code>Builder</code> instance */ public Builder addMessageSecurityConfig(final MessageSecurityConfig msConfig); public List<MessageSecurityConfig> getMessageSecurityConfig(); /** * Sets the optional authentication realm for the ACC. * <p> * 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 <code>Builder</code> instance */ public Builder authRealm(final String className); public AuthRealm getAuthRealm(); // /** // * Sets the callback handler the ACC will use when authentication is // * required. If the program does not invoke this method the ACC will use // * the callback handler specified in the client's deployment descriptor, // * if any. Failing that, the ACC will use its own default callback handler // * to prompt for and collect information required during authentication. // * <p> // * A callback handler class set using this method overrides the // * callback handler setting from the client's descriptor, if any, or from // * any previous invocations of <code>callbackHandler</code>. // * // * @param callbackHandlerClassName fully-qualified name of the developer's // * callback handler class // * @return the <code>Builder</code> instance // */ // public Builder callbackHandler(final Class<? extends CallbackHandler> callbackHandlerClass); // // public Class<? extends CallbackHandler> getCallbackHandler(); /** * Sets the optional client credentials to be used during authentication to the * back-end. * <p> * If the client does not invoke <code>clientCredentials</code> 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 <code>Builder</code> instance */ public Builder clientCredentials(final String user, final char[] password); public ClientCredential getClientCredential(); /** * Sets the optional client credentials and server-side realm to be used during * authentication to the back-end. * <p> * If the client does not invoke <code>clientCredentials</code> 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 <code>Builder</code> instance */ public Builder clientCredentials(final String user, final char[] password, final String realm); /** * Sets the container-level Properties. * * @param containerProperties * @return */ public Builder containerProperties(final Properties containerProperties); /** * Sets the container-level properties. * <p> * Typically used when setting the properties from the parsed XML config * file. * * @param containerProperties Property objects to use in setting the properties * @return */ public Builder containerProperties(final List<Property> containerProperties); /** * Returns the container-level Properties. * @return container-level properties */ public Properties getContainerProperties(); /** * Sets the logger which the ACC should use as it runs. * * @param logger * @return */ public Builder logger(final Logger logger); public Logger getLogger(); /** * Sets whether the ACC should send the password to the server during * authentication. * * @param sendPassword * @return */ public Builder sendPassword(final boolean sendPassword); public boolean getSendPassword(); } /** * Encapsulates all clean-up activity. * <p> * The calling program can invoke clean-up by invoking the <code>stop</code> * 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<EntityManagerFactory> 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<EntityManagerFactory> emfs) { this.emfs = emfs; } void setConnectorRuntime(ConnectorRuntime connectorRuntime) { this.connectorRuntime = connectorRuntime; } void enable() { Runtime.getRuntime().addShutdownHook(cleanupThread = new Thread(this, "Cleanup")); } void disable() { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { @Override public Object run() { Runtime.getRuntime().removeShutdownHook(cleanupThread); return null; } } ); } /** * Requests cleanup without relying on the VM's shutdown handling. */ void start() { disable(); run(); } /** * Performs clean-up of the ACC. * <p> * 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<TransactionManager> 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); } } } }