/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.openejb.assembler; import org.apache.openejb.ClassLoaderUtil; import org.apache.openejb.NoSuchApplicationException; import org.apache.openejb.OpenEJBException; import org.apache.openejb.OpenEJBRuntimeException; import org.apache.openejb.UndeployException; import org.apache.openejb.assembler.classic.AppInfo; import org.apache.openejb.assembler.classic.Assembler; import org.apache.openejb.assembler.classic.DeploymentExceptionManager; import org.apache.openejb.config.AppModule; import org.apache.openejb.config.ConfigurationFactory; import org.apache.openejb.config.DeploymentLoader; import org.apache.openejb.config.DeploymentModule; import org.apache.openejb.config.WebModule; import org.apache.openejb.config.sys.AdditionalDeployments; import org.apache.openejb.config.sys.Deployments; import org.apache.openejb.config.sys.JaxbOpenejb; import org.apache.openejb.loader.Files; import org.apache.openejb.loader.IO; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.loader.provisining.ProvisioningResolver; import org.apache.openejb.util.JavaSecurityManagers; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import javax.ejb.Lock; import javax.ejb.Remote; import javax.ejb.Singleton; import javax.ejb.TransactionManagement; import javax.enterprise.inject.Alternative; import javax.validation.ValidationException; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.security.SecureRandom; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.TreeMap; import static javax.ejb.LockType.READ; import static javax.ejb.TransactionManagementType.BEAN; import static org.apache.openejb.config.ConfigurationFactory.ADDITIONAL_DEPLOYMENTS; import static org.apache.openejb.loader.ProvisioningUtil.realLocation; @SuppressWarnings("EjbProhibitedPackageUsageInspection") @Singleton(name = "openejb/Deployer") @Lock(READ) @Remote(Deployer.class) @TransactionManagement(BEAN) @Alternative public class DeployerEjb implements Deployer { private static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB, DeployerEjb.class); public static final String OPENEJB_DEPLOYER_FORCED_APP_ID_PROP = "openejb.deployer.forced.appId"; public static final String OPENEJB_DEPLOYER_HOST = "openejb.deployer.host"; public static final String OPENEJB_USE_BINARIES = "openejb.deployer.binaries.use"; public static final String OPENEJB_PATH_BINARIES = "openejb.deployer.binaries.path"; public static final String OPENEJB_VALUE_BINARIES = "openejb.deployer.binaries.value"; public static final String OPENEJB_APP_AUTODEPLOY = "openejb.app.autodeploy"; public static final ThreadLocal<Boolean> AUTO_DEPLOY = new ThreadLocal<>(); private static final File uniqueFile; private static final boolean oldWarDeployer = "old".equalsIgnoreCase(SystemInstance.get().getOptions().get("openejb.deployer.war", "new")); private static final String OPENEJB_DEPLOYER_SAVE_DEPLOYMENTS = "openejb.deployer.save-deployments"; private static final boolean SAVE_DEPLOYMENTS = SystemInstance.get().getOptions().get(OPENEJB_DEPLOYER_SAVE_DEPLOYMENTS, false); static { final String uniqueName = "OpenEJB-" + new BigInteger(128, new SecureRandom()).toString(Character.MAX_RADIX); final String tempDir = JavaSecurityManagers.getSystemProperty("java.io.tmpdir"); File unique; try { unique = new File(tempDir, uniqueName).getCanonicalFile(); if (!unique.createNewFile()) { throw new IOException("Failed to create file in temp: " + unique); } } catch (final IOException e) { // same trying in work directory unique = new File(SystemInstance.get().getBase().getDirectory(), "work"); if (unique.exists()) { try { unique = new File(unique, uniqueName).getCanonicalFile(); if (!unique.createNewFile()) { throw new IOException("Failed to create file in work: " + unique); } } catch (final IOException e1) { throw new OpenEJBRuntimeException(e); } } else { throw new OpenEJBRuntimeException("cannot create unique file, please set java.io.tmpdir to a writable folder or create work folder", e); } } uniqueFile = unique; uniqueFile.deleteOnExit(); } private final DeploymentLoader deploymentLoader; private final ConfigurationFactory configurationFactory; private final Assembler assembler; public DeployerEjb() { deploymentLoader = new DeploymentLoader(); final ConfigurationFactory component = SystemInstance.get().getComponent(ConfigurationFactory.class); configurationFactory = component == null ? new ConfigurationFactory() : component; assembler = (Assembler) SystemInstance.get().getComponent(org.apache.openejb.spi.Assembler.class); } @Override public String getUniqueFile() { return uniqueFile.getAbsolutePath(); } @Override public Collection<AppInfo> getDeployedApps() { return assembler.getDeployedApplications(); } @Override public AppInfo deploy(final String location) throws OpenEJBException { return deploy(location, null); } @Override public AppInfo deploy(final Properties properties) throws OpenEJBException { return deploy(null, properties); } @Override public AppInfo deploy(final String inLocation, Properties properties) throws OpenEJBException { String rawLocation = inLocation; if (rawLocation == null && properties == null) { throw new NullPointerException("location and properties are null"); } if (rawLocation == null) { rawLocation = properties.getProperty(FILENAME); } if (properties == null) { properties = new Properties(); } AppModule appModule = null; final File file; if ("true".equalsIgnoreCase(properties.getProperty(OPENEJB_USE_BINARIES, "false"))) { file = copyBinaries(properties); } else { file = new File(realLocation(rawLocation).iterator().next()); } final boolean autoDeploy = Boolean.parseBoolean(properties.getProperty(OPENEJB_APP_AUTODEPLOY, "false")); final String host = properties.getProperty(OPENEJB_DEPLOYER_HOST, null); if (WebAppDeployer.Helper.isWebApp(file) && !oldWarDeployer) { AUTO_DEPLOY.set(autoDeploy); try { final AppInfo appInfo = SystemInstance.get().getComponent(WebAppDeployer.class) .deploy(host, contextRoot(properties, file.getAbsolutePath()), file); if (appInfo != null) { saveIfNeeded(properties, file, appInfo); return appInfo; } throw new OpenEJBException("can't deploy " + file.getAbsolutePath()); } finally { AUTO_DEPLOY.remove(); } } AppInfo appInfo = null; try { appModule = deploymentLoader.load(file, null); // Add any alternate deployment descriptors to the modules final Map<String, DeploymentModule> modules = new TreeMap<>(); for (final DeploymentModule module : appModule.getEjbModules()) { modules.put(module.getModuleId(), module); } for (final DeploymentModule module : appModule.getClientModules()) { modules.put(module.getModuleId(), module); } for (final WebModule module : appModule.getWebModules()) { final String contextRoot = contextRoot(properties, module.getJarLocation()); if (contextRoot != null) { module.setContextRoot(contextRoot); module.setHost(host); } modules.put(module.getModuleId(), module); } for (final DeploymentModule module : appModule.getConnectorModules()) { modules.put(module.getModuleId(), module); } for (final Map.Entry<Object, Object> entry : properties.entrySet()) { String name = (String) entry.getKey(); if (name.startsWith(ALT_DD + "/")) { name = name.substring(ALT_DD.length() + 1); final DeploymentModule module; final int slash = name.indexOf('/'); if (slash > 0) { final String moduleId = name.substring(0, slash); name = name.substring(slash + 1); module = modules.get(moduleId); } else { module = appModule; } if (module != null) { final String value = (String) entry.getValue(); final File dd = new File(value); if (dd.canRead()) { module.getAltDDs().put(name, dd.toURI().toURL()); } else { module.getAltDDs().put(name, value); } } } } appInfo = configurationFactory.configureApplication(appModule); appInfo.autoDeploy = autoDeploy; if (properties != null && properties.containsKey(OPENEJB_DEPLOYER_FORCED_APP_ID_PROP)) { appInfo.appId = properties.getProperty(OPENEJB_DEPLOYER_FORCED_APP_ID_PROP); } if (!appInfo.webApps.isEmpty()) { appInfo.properties.setProperty("tomcat.unpackWar", "false"); } assembler.createApplication(appInfo); saveIfNeeded(properties, file, appInfo); return appInfo; } catch (final Throwable e) { // destroy the class loader for the failed application if (appModule != null) { ClassLoaderUtil.destroyClassLoader(appModule.getJarLocation()); } if (null != appInfo) { ClassLoaderUtil.destroyClassLoader(appInfo.appId, appInfo.path); } LOGGER.error("Can't deploy " + inLocation, e); if (e instanceof ValidationException) { throw (ValidationException) e; } final Throwable ex; final DeploymentExceptionManager dem = SystemInstance.get().getComponent(DeploymentExceptionManager.class); if (dem != null) { if (dem.hasDeploymentFailed()) { ex = dem.getLastException(); } else { ex = e; } if (appInfo != null) { dem.clearLastException(appInfo); } } else { ex = e; } if (ex instanceof OpenEJBException) { if (ex.getCause() instanceof ValidationException) { throw (ValidationException) ex.getCause(); } throw (OpenEJBException) ex; } throw new OpenEJBException(ex); } } private void saveIfNeeded(final Properties properties, final File file, final AppInfo appInfo) { if ((SAVE_DEPLOYMENTS && null == properties.getProperty(OPENEJB_DEPLOYER_SAVE_DEPLOYMENTS)) || "true".equalsIgnoreCase(properties.getProperty(OPENEJB_DEPLOYER_SAVE_DEPLOYMENTS, "false"))) { appInfo.properties.setProperty("save-deployment","true"); saveDeployment(file, true); } } private synchronized File copyBinaries(final Properties props) throws OpenEJBException { final File dump = ProvisioningResolver.cacheFile(props.getProperty(OPENEJB_PATH_BINARIES, "dump.war")); if (dump.exists()) { Files.delete(dump); final String name = dump.getName(); if (name.endsWith("ar") && name.length() > 4) { final File exploded = new File(dump.getParentFile(), name.substring(0, name.length() - 4)); if (exploded.exists()) { Files.delete(exploded); } } } try { IO.copy(byte[].class.cast(props.get(OPENEJB_VALUE_BINARIES)), dump); } catch (final IOException e) { throw new OpenEJBException(e); } return dump; } private synchronized void saveDeployment(final File file, final boolean add) { final Deployments deps = new Deployments(); if (file.isDirectory()) { deps.setDir(file.getAbsolutePath()); } else { deps.setFile(file.getAbsolutePath()); } File config; try { config = SystemInstance.get().getBase().getFile(ADDITIONAL_DEPLOYMENTS, false); } catch (final IOException e) { config = null; } if (config == null || !config.getParentFile().exists()) { LOGGER.info("Cannot save the added app because the conf folder does not exist, it will not be present on a restart"); return; } // dump it OutputStream os = null; try { final AdditionalDeployments additionalDeployments; if (config.exists() && config.length() > 0) { final InputStream fis = IO.read(config); try { additionalDeployments = JaxbOpenejb.unmarshal(AdditionalDeployments.class, fis); } finally { IO.close(fis); } } else { additionalDeployments = new AdditionalDeployments(); } if (add) { if (!additionalDeployments.getDeployments().contains(deps)) { additionalDeployments.getDeployments().add(deps); } } else { final Iterator<Deployments> it = additionalDeployments.getDeployments().iterator(); while (it.hasNext()) { final Deployments current = it.next(); if (deps.getDir() != null && deps.getDir().equals(current.getDir())) { it.remove(); break; } else if (deps.getFile() != null && deps.getFile().equals(current.getFile())) { it.remove(); break; } else { // exploded dirs final String jar = deps.getFile(); if (jar != null && jar.length() > 3) { final String substring = jar.substring(0, jar.length() - 4); if (substring.equals(current.getDir()) || substring.equals(current.getFile())) { it.remove(); break; } } else { final String jarC = current.getFile(); if (jarC != null && jarC.length() > 3) { final String substring = jarC.substring(0, jarC.length() - 4); if (substring.equals(deps.getDir()) || substring.equals(deps.getFile())) { it.remove(); break; } } } } } } os = IO.write(config); JaxbOpenejb.marshal(AdditionalDeployments.class, additionalDeployments, os); } catch (final Exception e) { LOGGER.error("cannot save the added app, will not be present next time you'll start", e); } finally { IO.close(os); } } @Override public void undeploy(final String moduleId) throws UndeployException, NoSuchApplicationException { AppInfo appInfo = assembler.getAppInfo(moduleId); if (appInfo == null) { appInfo = assembler.getAppInfo(realLocation(moduleId).iterator().next()); if (appInfo == null) { appInfo = assembler.getAppInfo(new File(moduleId).getAbsolutePath()); if (appInfo == null) { appInfo = assembler.getAppInfo(new File(realLocation(moduleId).iterator().next()).getAbsolutePath()); } } } if (appInfo != null) { try { assembler.destroyApplication(appInfo); } finally { if (appInfo.properties.containsKey("save-deployment")) { saveDeployment(new File(moduleId), false); } } } else { throw new NoSuchApplicationException(moduleId); } } private String contextRoot(final Properties properties, final String jarPath) { return properties.getProperty("webapp." + jarPath + ".context-root"); } @Override public void reload(final String moduleId) { for (final AppInfo info : assembler.getDeployedApplications()) { if (info.path.equals(moduleId)) { reload(info); break; } } } private void reload(final AppInfo info) { if (info.webAppAlone) { final WebAppDeployer component = SystemInstance.get().getComponent(WebAppDeployer.class); if (null != component) { component.reload(info.path); return; } } try { assembler.destroyApplication(info); assembler.createApplication(info); } catch (final Exception e) { throw new OpenEJBRuntimeException(e); } } }