/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2006-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.deployment.common; import com.sun.enterprise.config.serverbeans.ServerTags; import org.glassfish.deployment.versioning.VersioningUtils; import java.lang.instrument.ClassFileTransformer; import org.glassfish.api.ActionReport; import org.glassfish.api.deployment.InstrumentableClassLoader; import org.glassfish.api.deployment.OpsParams; import org.glassfish.api.deployment.archive.ReadableArchive; import org.glassfish.api.deployment.archive.ArchiveHandler; import org.glassfish.api.admin.ServerEnvironment; import org.glassfish.internal.api.ClassLoaderHierarchy; import org.glassfish.internal.deployment.*; import org.glassfish.loader.util.ASClassLoaderUtil; import java.util.*; import java.util.logging.Logger; import java.util.logging.Level; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.MalformedURLException; import java.net.URLClassLoader; import org.glassfish.hk2.api.PreDestroy; import com.sun.enterprise.util.LocalStringManagerImpl; import com.sun.enterprise.util.io.FileUtils; import org.glassfish.hk2.classmodel.reflect.Parser; import org.glassfish.hk2.classmodel.reflect.Types; import org.glassfish.logging.annotation.LogMessageInfo; import org.glassfish.logging.annotation.LoggerInfo; import org.glassfish.logging.annotation.LogMessagesResourceBundle; /** * * @author dochez */ public class DeploymentContextImpl implements ExtendedDeploymentContext, PreDestroy { @LogMessagesResourceBundle private static final String SHARED_LOGMESSAGE_RESOURCE = "org.glassfish.deployment.LogMessages"; // Reserve this range [NCLS-DEPLOYMENT-00001, NCLS-DEPLOYMENT-02000] // for message ids used in this deployment common module @LoggerInfo(subsystem = "DEPLOYMENT", description="Deployment logger for common module", publish=true) private static final String DEPLOYMENT_LOGGER = "javax.enterprise.system.tools.deployment.common"; public static final Logger deplLogger = Logger.getLogger(DEPLOYMENT_LOGGER, SHARED_LOGMESSAGE_RESOURCE); final private static LocalStringManagerImpl localStrings = new LocalStringManagerImpl(DeploymentContextImpl.class); private static final String INTERNAL_DIR_NAME = "__internal"; private static final String APP_TENANTS_SUBDIR_NAME = "__app-tenants"; ReadableArchive source; ReadableArchive originalSource; final OpsParams parameters; ActionReport actionReport; final ServerEnvironment env; ClassLoader cloader; ArchiveHandler archiveHandler; Properties props; Map<String, Object> modulesMetaData = new HashMap<String, Object>(); List<ClassFileTransformer> transformers = new ArrayList<ClassFileTransformer>(); Phase phase = Phase.UNKNOWN; ClassLoader sharableTemp = null; Map<String, Properties> modulePropsMap = new HashMap<String, Properties>(); Map<String, Object> transientAppMetaData = new HashMap<String, Object>(); Map<String, ArchiveHandler> moduleArchiveHandlers = new HashMap<String, ArchiveHandler>(); Map<String, ExtendedDeploymentContext> moduleDeploymentContexts = new HashMap<String, ExtendedDeploymentContext>(); ExtendedDeploymentContext parentContext = null; String moduleUri = null; private String tenant = null; private String originalAppName = null; private File tenantDir = null; /** Creates a new instance of DeploymentContext */ public DeploymentContextImpl(Deployment.DeploymentContextBuilder builder, ServerEnvironment env) { this(builder.report(), builder.sourceAsArchive(), builder.params(), env); } public DeploymentContextImpl(ActionReport actionReport, Logger logger, ReadableArchive source, OpsParams params, ServerEnvironment env) { this(actionReport, source, params, env); } public DeploymentContextImpl(ActionReport actionReport, ReadableArchive source, OpsParams params, ServerEnvironment env) { this.originalSource = source; this.source = source; this.actionReport = actionReport; this.parameters = params; this.env = env; } public Phase getPhase() { return phase; } public void setPhase(Phase newPhase) { this.phase = newPhase; } public ReadableArchive getSource() { return source; } public void setSource(ReadableArchive source) { this.source = source; } public <U extends OpsParams> U getCommandParameters(Class<U> commandParametersType) { try { return commandParametersType.cast(parameters); } catch (ClassCastException e) { return null; } } public Logger getLogger() { return deplLogger; } public synchronized void preDestroy() { try { PreDestroy.class.cast(sharableTemp).preDestroy(); } catch (Exception e) { // ignore, the classloader does not need to be destroyed } try { PreDestroy.class.cast(cloader).preDestroy(); } catch (Exception e) { // ignore, the classloader does not need to be destroyed } } /** * Returns the class loader associated to this deployment request. * ClassLoader instances are usually obtained by the getClassLoader API on * the associated ArchiveHandler for the archive type being deployed. * <p/> * This can return null and the container should allocate a ClassLoader * while loading the application. * * @return a class loader capable of loading classes and resources from the * source * @link {org.jvnet.glassfish.apu.deployment.archive.ArchiveHandler.getClassLoader()} */ public ClassLoader getFinalClassLoader() { return cloader; } /** * Returns the class loader associated to this deployment request. * ClassLoader instances are usually obtained by the getClassLoader API on * the associated ArchiveHandler for the archive type being deployed. * <p/> * This can return null and the container should allocate a ClassLoader * while loading the application. * * @return a class loader capable of loading classes and resources from the * source * @link {org.jvnet.glassfish.apu.deployment.archive.ArchiveHandler.getClassLoader()} */ public ClassLoader getClassLoader() { /* TODO -- Replace this method with another that does not imply it is * an accessor and conveys that the result may change depending on the * current lifecycle. For instance contemporaryClassLoader() * Problem was reported by findbug */ return getClassLoader(true); } public synchronized void setClassLoader(ClassLoader cloader) { this.cloader = cloader; } // this classloader will be used for sniffer retrieval, metadata parsing // and the prepare public synchronized void createDeploymentClassLoader(ClassLoaderHierarchy clh, ArchiveHandler handler) throws URISyntaxException, MalformedURLException { this.addTransientAppMetaData(ExtendedDeploymentContext.IS_TEMP_CLASSLOADER, Boolean.TRUE); this.sharableTemp = createClassLoader(clh, handler, null); } // this classloader will used to load and start the application public void createApplicationClassLoader(ClassLoaderHierarchy clh, ArchiveHandler handler) throws URISyntaxException, MalformedURLException { this.addTransientAppMetaData(ExtendedDeploymentContext.IS_TEMP_CLASSLOADER, Boolean.FALSE); if (this.cloader == null) { this.cloader = createClassLoader(clh, handler, parameters.name()); } } private ClassLoader createClassLoader(ClassLoaderHierarchy clh, ArchiveHandler handler, String appName) throws URISyntaxException, MalformedURLException { // first we create the appLib class loader, this is non shared libraries class loader ClassLoader applibCL = clh.getAppLibClassLoader(appName, getAppLibs()); ClassLoader parentCL = clh.createApplicationParentCL(applibCL, this); return handler.getClassLoader(parentCL, this); } public synchronized ClassLoader getClassLoader(boolean sharable) { // if we are in prepare phase, we need to return our sharable temporary class loader // otherwise, we return the final one. if (phase==Phase.PREPARE) { if (sharable) { return sharableTemp; } else { InstrumentableClassLoader cl = InstrumentableClassLoader.class.cast(sharableTemp); return cl.copy(); } } else { // we are out of the prepare phase, destroy the shareableTemp and // return the final classloader if (sharableTemp!=null) { try { PreDestroy.class.cast(sharableTemp).preDestroy(); } catch (Exception e) { // ignore, the classloader does not need to be destroyed } sharableTemp=null; } return cloader; } } /** * Returns a scratch directory that can be used to store things in. * The scratch directory will be persisted accross server restart but * not accross redeployment of the same application * * @param subDirName the sub directory name of the scratch dir * @return the scratch directory for this application based on * passed in subDirName. Returns the root scratch dir if the * passed in value is null. */ public File getScratchDir(String subDirName) { File rootScratchDir = env.getApplicationStubPath(); if (tenant != null && originalAppName != null) { // multi-tenant case rootScratchDir = getRootScratchTenantDirForApp(originalAppName); rootScratchDir = new File(rootScratchDir, tenant); if (subDirName != null ) { rootScratchDir = new File(rootScratchDir, subDirName); } return rootScratchDir; } else { // regular case if (subDirName != null ) { rootScratchDir = new File(rootScratchDir, subDirName); } String appDirName = VersioningUtils.getRepositoryName(parameters.name()); return new File(rootScratchDir, appDirName); } } /** * {@inheritDoc} */ public File getSourceDir() { return new File(getSource().getURI()); } public void addModuleMetaData(Object metaData) { if (metaData!=null) { modulesMetaData.put(metaData.getClass().getName(), metaData); } } public <T> T getModuleMetaData(Class<T> metadataType) { Object moduleMetaData = modulesMetaData.get(metadataType.getName()); if (moduleMetaData != null) { return metadataType.cast(moduleMetaData); } else { for (Object metadata : modulesMetaData.values()) { try { return metadataType.cast(metadata); } catch (ClassCastException e) { } } return null; } } public Collection<Object> getModuleMetadata() { List<Object> copy = new ArrayList<Object>(); copy.addAll(modulesMetaData.values()); return copy; } public Map<String, Object> getTransientAppMetadata() { HashMap<String, Object> copy = new HashMap<String, Object>(); copy.putAll(transientAppMetaData); return copy; } public void addTransientAppMetaData(String metaDataKey, Object metaData) { if (metaData!=null) { transientAppMetaData.put(metaDataKey, metaData); } } public <T> T getTransientAppMetaData(String key, Class<T> metadataType) { Object metaData = transientAppMetaData.get(key); if (metaData != null) { return metadataType.cast(metaData); } return null; } /** * Returns the application level properties that will be persisted as a * key value pair at then end of deployment. That allows individual * Deployers implementation to store some information at the * application level that should be available upon server restart. * Application level propertries are shared by all the modules. * * @return the application's properties. */ public Properties getAppProps() { if (props==null) { props = new Properties(); } return props; } /** * Returns the module level properties that will be persisted as a * key value pair at then end of deployment. That allows individual * Deployers implementation to store some information at the module * level that should be available upon server restart. * Module level properties are only visible to the current module. * @return the module's properties. */ public Properties getModuleProps() { // for standalone case, it would return the same as application level // properties // for composite case, the composite deployer will return proper // module level properties if (props==null) { props = new Properties(); } return props; } /** * Add a new ClassFileTransformer to the context * * @param transformer the new class file transformer to register to the new application * class loader * @throws UnsupportedOperationException if the class loader we use does not support the * registration of a ClassFileTransformer. In such case, the deployer should either fail * deployment or revert to a mode without the byteocode enhancement feature. */ public void addTransformer(ClassFileTransformer transformer) { InstrumentableClassLoader icl = InstrumentableClassLoader.class.cast(getFinalClassLoader()); String isComposite = getAppProps().getProperty(ServerTags.IS_COMPOSITE); if (Boolean.valueOf(isComposite) && icl instanceof URLClassLoader) { URLClassLoader urlCl = (URLClassLoader)icl; boolean isAppLevel = (getParentContext() == null); if (isAppLevel) { // for ear lib PUs, let's install the // tranformers with the EarLibClassLoader icl = InstrumentableClassLoader.class.cast(urlCl.getParent().getParent()); } else { // for modules inside the ear, let's install the // transformers with the EarLibClassLoader in // addition to installing them to module classloader ClassLoader libCl = urlCl.getParent().getParent(); if (!(libCl instanceof URLClassLoader)) { // web module libCl = libCl.getParent(); } if (libCl instanceof URLClassLoader) { InstrumentableClassLoader libIcl = InstrumentableClassLoader.class.cast(libCl); libIcl.addTransformer(transformer); } } } icl.addTransformer(transformer); } /** * Returns the list of transformers registered to this context. * * @return the transformers list */ public List<ClassFileTransformer> getTransformers() { return transformers; } public List<URI> getAppLibs() throws URISyntaxException { List<URI> libURIs = new ArrayList<URI>(); if (parameters.libraries() != null) { URL[] urls = ASClassLoaderUtil.getDeployParamLibrariesAsURLs( parameters.libraries(), env); for (URL url : urls) { File file = new File(url.getFile()); deplLogger.log(Level.FINE, "Specified library jar: "+file.getAbsolutePath()); if (file.exists()){ libURIs.add(url.toURI()); } else { throw new IllegalArgumentException(localStrings.getLocalString("enterprise.deployment.nonexist.libraries", "Specified library jar {0} does not exist: {1}", file.getName(), file.getAbsolutePath())); } } } Set<String> extensionList = null; try{ extensionList = InstalledLibrariesResolver.getInstalledLibraries(source); }catch(IOException ioe){ throw new RuntimeException(ioe); } URL[] extensionListLibraries = ASClassLoaderUtil.getLibrariesAsURLs(extensionList, env); for (URL url : extensionListLibraries) { libURIs.add(url.toURI()); if (deplLogger.isLoggable(Level.FINEST)) { deplLogger.log(Level.FINEST, "Detected [EXTENSION_LIST]" + " installed-library [ " + url + " ] for archive [ "+source.getName()+ "]"); } } return libURIs; } public void clean() { if (parameters.origin == OpsParams.Origin.undeploy || parameters.origin == OpsParams.Origin.deploy ) { // for undeploy or deploy failure roll back // need to remove the generated directories... // need to remove generated/xml, generated/ejb, generated/jsp, // remove generated/xml File generatedXmlRoot = getScratchDir("xml"); FileUtils.whack(generatedXmlRoot); // remove generated/ejb File generatedEjbRoot = getScratchDir("ejb"); // recursively delete... FileUtils.whack(generatedEjbRoot); // remove generated/jsp File generatedJspRoot = getScratchDir("jsp"); // recursively delete... FileUtils.whack(generatedJspRoot); // remove the internal archive directory which holds the original // archive (and possibly deployment plan) that cluster sync can use FileUtils.whack(getAppInternalDir()); FileUtils.whack(getAppAltDDDir()); // remove the root tenant dir for this application FileUtils.whack(getRootTenantDirForApp(parameters.name())); // remove the root tenant generated dir root for this application FileUtils.whack(getRootScratchTenantDirForApp(parameters.name())); } else if (parameters.origin == OpsParams.Origin.mt_unprovision) { // for unprovision application, remove the tenant dir FileUtils.whack(tenantDir); // and remove the generated dir File generatedRoot = getScratchDir(null); FileUtils.whack(generatedRoot); } } public ArchiveHandler getArchiveHandler() { return archiveHandler; } public void setArchiveHandler(ArchiveHandler archiveHandler) { this.archiveHandler = archiveHandler; } public ReadableArchive getOriginalSource() { return originalSource; } /** * Gets the module properties for modules * * @return a map containing module properties */ public Map<String, Properties> getModulePropsMap() { return modulePropsMap; } /** * Sets the module properties for modules * * @param modulePropsMap */ public void setModulePropsMap(Map<String, Properties> modulePropsMap) { this.modulePropsMap = modulePropsMap; } /** * Sets the parent context for the module * * @param parentContext */ public void setParentContext(ExtendedDeploymentContext parentContext) { this.parentContext = parentContext; } /** * Gets the parent context of the module * * * @return the parent context */ public ExtendedDeploymentContext getParentContext() { return parentContext; } /** * Gets the module uri for this module context * * @return the module uri */ public String getModuleUri() { return moduleUri; } /** * Sets the module uri for this module context * * @param moduleUri */ public void setModuleUri(String moduleUri) { this.moduleUri = moduleUri; } /** * Gets the archive handlers for modules * * @return a map containing module archive handlers */ public Map<String, ArchiveHandler> getModuleArchiveHandlers() { return moduleArchiveHandlers; } /** * Gets the deployment context for modules * * @return a map containing module deployment contexts */ public Map<String, ExtendedDeploymentContext> getModuleDeploymentContexts() { return moduleDeploymentContexts; } /** * Gets the action report for this context * * @return an action report */ public ActionReport getActionReport() { return actionReport; } public File getAppInternalDir() { final File internalDir = new File(env.getApplicationRepositoryPath(), INTERNAL_DIR_NAME); return new File(internalDir, VersioningUtils.getRepositoryName(parameters.name())); } public File getAppAltDDDir() { final File altDDDir = env.getApplicationAltDDPath(); return new File(altDDDir, VersioningUtils.getRepositoryName(parameters.name())); } public void setTenant(final String tenant, final String appName) { this.tenant = tenant; this.originalAppName = appName; tenantDir = initTenantDir(); } private File initTenantDir() { if (tenant == null || originalAppName == null) { return null; } File f = getRootTenantDirForApp(originalAppName); f = new File(f, tenant); if (!f.exists() && !f.mkdirs()) { if (deplLogger.isLoggable(Level.FINEST)) { deplLogger.log(Level.FINEST, "Unable to create directory " + f.getAbsolutePath()); } } return f; } private File getRootTenantDirForApp(String appName) { File rootTenantDir = new File(env.getApplicationRepositoryPath(), APP_TENANTS_SUBDIR_NAME); File rootTenantDirForApp = new File(rootTenantDir, appName); return rootTenantDirForApp; } private File getRootScratchTenantDirForApp(String appName) { File rootScratchTenantDir = new File(env.getApplicationStubPath(), APP_TENANTS_SUBDIR_NAME); File rootScratchTenantDirForApp = new File(rootScratchTenantDir, appName); return rootScratchTenantDirForApp; } public String getTenant() { return tenant; } public File getTenantDir() { return tenantDir; } @Override public void postDeployClean(boolean isFinalClean) { if (transientAppMetaData != null) { if (isFinalClean) { transientAppMetaData.clear(); } else { final String [] classNamesToClean = {Types.class.getName(), Parser.class.getName()}; for (String className : classNamesToClean) { transientAppMetaData.remove(className); } } } actionReport = null; } /** * Prepare the scratch directories, creating the directories * if they do not exist */ public void prepareScratchDirs() throws IOException { prepareScratchDir(getScratchDir("ejb")); prepareScratchDir(getScratchDir("xml")); prepareScratchDir(getScratchDir("jsp")); } private void prepareScratchDir(File f) throws IOException { if (!f.isDirectory() && !f.mkdirs()) throw new IOException("Cannot create scratch directory : " + f.getAbsolutePath()); } }