/**
*
* Copyright 2003-2004 The Apache Software Foundation
*
* Licensed 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.geronimo.deployment;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.geronimo.common.DeploymentException;
import org.apache.geronimo.deployment.util.DeploymentUtil;
import org.apache.geronimo.gbean.GBeanData;
import org.apache.geronimo.kernel.GBeanNotFoundException;
import org.apache.geronimo.kernel.Kernel;
import org.apache.geronimo.kernel.config.Configuration;
import org.apache.geronimo.kernel.config.ConfigurationData;
import org.apache.geronimo.kernel.config.ConfigurationManager;
import org.apache.geronimo.kernel.config.ConfigurationModuleType;
import org.apache.geronimo.kernel.config.ConfigurationUtil;
import org.apache.geronimo.kernel.config.InvalidConfigException;
import org.apache.geronimo.kernel.config.MultiParentClassLoader;
import org.apache.geronimo.kernel.config.NoSuchConfigException;
import org.apache.geronimo.kernel.management.State;
import org.apache.geronimo.kernel.repository.Repository;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* @version $Rev$ $Date$
*/
public class DeploymentContext {
private static final ClassLoader[] DEFAULT_PARENT_CLASSLOADERS = new ClassLoader[]{DeploymentContext.class.getClassLoader()};
private final Kernel kernel;
private final ConfigurationData configurationData;
private final GBeanDataRegistry gbeans = new GBeanDataRegistry();
private final File baseDir;
private final URI baseUri;
private final byte[] buffer = new byte[4096];
private final List loadedAncestors = new ArrayList();
private final List startedAncestors = new ArrayList();
// private final ClassLoader[] parentCL;
public DeploymentContext(File baseDir, URI configId, ConfigurationModuleType type, List parentID, Kernel kernel) throws DeploymentException {
this(baseDir, configId, type, parentID, null, null, kernel);
}
public DeploymentContext(File baseDir, URI configId, ConfigurationModuleType type, List parentId, String domain, String server, Kernel kernel) throws DeploymentException {
assert baseDir != null: "baseDir is null";
assert configId != null: "configID is null";
assert type != null: "type is null";
this.kernel = kernel;
if (!baseDir.exists()) {
baseDir.mkdirs();
}
if (!baseDir.isDirectory()) {
throw new DeploymentException("Base directory is not a directory: " + baseDir.getAbsolutePath());
}
this.baseDir = baseDir;
this.baseUri = baseDir.toURI();
configurationData = new ConfigurationData();
configurationData.setId(configId);
configurationData.setModuleType(type);
configurationData.setParentId(parentId);
configurationData.setDomain(domain);
configurationData.setServer(server);
determineNaming();
determineInherited();
}
private void determineNaming() throws DeploymentException {
if (configurationData.getDomain() != null && configurationData.getServer() != null) {
return;
}
List parentId = configurationData.getParentId();
if (kernel == null || parentId == null || parentId.isEmpty()) {
throw new DeploymentException("neither domain and server nor any way to determine them was provided for configuration " + configurationData.getId());
}
URI parent = (URI) parentId.get(0);
ConfigurationManager configurationManager = ConfigurationUtil.getConfigurationManager(kernel);
try {
boolean loaded = false;
if (!configurationManager.isLoaded(parent)) {
configurationManager.load(parent);
loaded = true;
}
try {
ObjectName parentName = Configuration.getConfigurationObjectName(parent);
configurationData.setDomain((String) kernel.getAttribute(parentName, "domain"));
configurationData.setServer((String) kernel.getAttribute(parentName, "server"));
} catch (Exception e) {
throw new DeploymentException("Unable to copy domain and server from parent configuration", e);
} finally {
if (loaded) {
// we need to unload again so the loadedAncestors list will be in the correct order to start configs.
configurationManager.unload(parent);
}
}
} catch (Exception e) {
throw new DeploymentException("Unable to load first parent of configuration " + configurationData.getId(), e);
} finally {
ConfigurationUtil.releaseConfigurationManager(kernel, configurationManager);
}
//check that domain and server are now known
if (configurationData.getDomain() == null || configurationData.getServer() == null) {
throw new IllegalStateException("Domain or server could not be determined from explicit args or parent configuration. ParentID: " + parentId
+ ", domain: " + configurationData.getDomain()
+ ", server: " + configurationData.getServer());
}
}
private void determineInherited() throws DeploymentException {
if (null == kernel) {
return;
}
List parentId = configurationData.getParentId();
if (parentId != null && parentId.size() > 0) {
ConfigurationManager configurationManager = ConfigurationUtil.getConfigurationManager(kernel);
List inherited = new ArrayList();
try {
for (Iterator iterator = parentId.iterator(); iterator.hasNext();) {
URI uri = (URI) iterator.next();
ObjectName parentName = Configuration.getConfigurationObjectName(uri);
boolean loaded = false;
try {
if (!configurationManager.isLoaded(uri)) {
parentName = configurationManager.load(uri);
loaded = true;
}
String[] nonOverridableClasses = (String[]) kernel.getAttribute(parentName, "nonOverridableClasses");
if (null != nonOverridableClasses) {
for (int i = 0; i < nonOverridableClasses.length; i++) {
inherited.add(nonOverridableClasses[i]);
}
}
} finally {
if (loaded) {
configurationManager.unload(uri);
}
}
}
} catch (DeploymentException e) {
throw e;
} catch (Exception e) {
throw new DeploymentException(e);
}
configurationData.getNonOverridableClasses().addAll(inherited);
}
}
public URI getConfigID() {
return configurationData.getId();
}
public void setInverseClassloading(boolean inverseClassloading) {
configurationData.setInverseClassloading(inverseClassloading);
}
public void addHiddenClasses(Set hiddenClasses) {
configurationData.getHiddenClasses().addAll(hiddenClasses);
}
public void addNonOverridableClasses(Set nonOverridableClasses) {
configurationData.getNonOverridableClasses().addAll(nonOverridableClasses);
}
public void addParentId(List parentId) {
configurationData.getParentId().addAll(parentId);
}
public ConfigurationModuleType getType() {
return configurationData.getModuleType();
}
public File getBaseDir() {
return baseDir;
}
public String getDomain() {
return configurationData.getDomain();
}
public String getServer() {
return configurationData.getServer();
}
public void addGBean(GBeanData gbean) {
assert gbean.getName() != null: "GBean name is null";
gbeans.register(gbean);
}
public Set getGBeanNames() {
return gbeans.getGBeanNames();
}
public Set listGBeans(ObjectName pattern) {
return gbeans.listGBeans(pattern);
}
public GBeanData getGBeanInstance(ObjectName name) throws GBeanNotFoundException {
return gbeans.getGBeanInstance(name);
}
public void addDependency(URI uri) {
configurationData.addDependency(uri);
}
/**
* Copy a packed jar file into the deployment context and place it into the
* path specified in the target path. The newly added packed jar is added
* to the classpath of the configuration.
* <p/>
* NOTE: The class loader that is obtained from this deployment context
* may get out of sync with the newly augmented classpath; obtain a freshly
* minted class loader by calling <code>getConfigurationClassLoader</code> method.
*
* @param targetPath where the packed jar file should be placed
* @param jarFile the jar file to copy
* @throws IOException if there's a problem copying the jar file
*/
public void addIncludeAsPackedJar(URI targetPath, JarFile jarFile) throws IOException {
File targetFile = getTargetFile(targetPath);
DeploymentUtil.copyToPackedJar(jarFile, targetFile);
configurationData.addClassPathLocation(targetPath);
}
/**
* Copy a ZIP file entry into the deployment context and place it into the
* path specified in the target path. The newly added entry is added
* to the classpath of the configuration.
* <p/>
* NOTE: The class loader that is obtained from this deployment context
* may get out of sync with the newly augmented classpath; obtain a freshly
* minted class loader by calling <code>getConfigurationClassLoader</code> method.
*
* @param targetPath where the ZIP file entry should be placed
* @param zipFile the ZIP file
* @param zipEntry the ZIP file entry
* @throws IOException if there's a problem copying the ZIP entry
*/
public void addInclude(URI targetPath, ZipFile zipFile, ZipEntry zipEntry) throws IOException {
File targetFile = getTargetFile(targetPath);
addFile(targetFile, zipFile, zipEntry);
configurationData.addClassPathLocation(targetPath);
}
/**
* Copy a file into the deployment context and place it into the
* path specified in the target path. The newly added file is added
* to the classpath of the configuration.
* <p/>
* NOTE: The class loader that is obtained from this deployment context
* may get out of sync with the newly augmented classpath; obtain a freshly
* minted class loader by calling <code>getConfigurationClassLoader</code> method.
*
* @param targetPath where the file should be placed
* @param source the URL of file to be copied
* @throws IOException if there's a problem copying the ZIP entry
*/
public void addInclude(URI targetPath, URL source) throws IOException {
File targetFile = getTargetFile(targetPath);
addFile(targetFile, source);
configurationData.addClassPathLocation(targetPath);
}
/**
* Copy a file into the deployment context and place it into the
* path specified in the target path. The newly added file is added
* to the classpath of the configuration.
* <p/>
* NOTE: The class loader that is obtained from this deployment context
* may get out of sync with the newly augmented classpath; obtain a freshly
* minted class loader by calling <code>getConfigurationClassLoader</code> method.
*
* @param targetPath where the file should be placed
* @param source the file to be copied
* @throws IOException if there's a problem copying the ZIP entry
*/
public void addInclude(URI targetPath, File source) throws IOException {
File targetFile = getTargetFile(targetPath);
addFile(targetFile, source);
configurationData.addClassPathLocation(targetPath);
}
/**
* Import the classpath from a jar file's manifest. The imported classpath
* is crafted relative to <code>moduleBaseUri</code>.
* <p/>
* NOTE: The class loader that is obtained from this deployment context
* may get out of sync with the newly augmented classpath; obtain a freshly
* minted class loader by calling <code>getConfigurationClassLoader</code> method.
*
* @param moduleFile the jar file from which the manifest is obtained.
* @param moduleBaseUri the base for the imported classpath
* @throws DeploymentException if there is a problem with the classpath in
* the manifest
*/
public void addManifestClassPath(JarFile moduleFile, URI moduleBaseUri) throws DeploymentException {
Manifest manifest = null;
try {
manifest = moduleFile.getManifest();
} catch (IOException e) {
throw new DeploymentException("Could not read manifest: " + moduleBaseUri);
}
if (manifest == null) {
return;
}
String manifestClassPath = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
if (manifestClassPath == null) {
return;
}
for (StringTokenizer tokenizer = new StringTokenizer(manifestClassPath, " "); tokenizer.hasMoreTokens();) {
String path = tokenizer.nextToken();
URI pathUri;
try {
pathUri = new URI(path);
} catch (URISyntaxException e) {
throw new DeploymentException("Invalid manifest classpath entry: module=" + moduleBaseUri + ", path=" + path);
}
if (!pathUri.getPath().endsWith(".jar")) {
throw new DeploymentException("Manifest class path entries must end with the .jar extension (J2EE 1.4 Section 8.2): module=" + moduleBaseUri);
}
if (pathUri.isAbsolute()) {
throw new DeploymentException("Manifest class path entries must be relative (J2EE 1.4 Section 8.2): moduel=" + moduleBaseUri);
}
URI targetUri = moduleBaseUri.resolve(pathUri);
configurationData.addClassPathLocation(targetUri);
}
}
public void addFile(URI targetPath, ZipFile zipFile, ZipEntry zipEntry) throws IOException {
addFile(getTargetFile(targetPath), zipFile, zipEntry);
}
public void addFile(URI targetPath, URL source) throws IOException {
addFile(getTargetFile(targetPath), source);
}
public void addFile(URI targetPath, File source) throws IOException {
addFile(getTargetFile(targetPath), source);
}
public void addFile(URI targetPath, String source) throws IOException {
addFile(getTargetFile(targetPath), new ByteArrayInputStream(source.getBytes()));
}
public void addClass(URI location, String fqcn, byte[] bytes, boolean addToClasspath) throws IOException, URISyntaxException {
assert location.toString().endsWith("/");
if (addToClasspath) {
configurationData.addClassPathLocation(location);
}
String classFileName = fqcn.replace('.', '/') + ".class";
addFile(getTargetFile(new URI(location.toString() + classFileName)), new ByteArrayInputStream(bytes));
}
private void addFile(File targetFile, ZipFile zipFile, ZipEntry zipEntry) throws IOException {
if (zipEntry.isDirectory()) {
targetFile.mkdirs();
} else {
InputStream is = zipFile.getInputStream(zipEntry);
try {
addFile(targetFile, is);
} finally {
DeploymentUtil.close(is);
}
}
}
private void addFile(File targetFile, URL source) throws IOException {
InputStream in = null;
try {
in = source.openStream();
addFile(targetFile, in);
} finally {
DeploymentUtil.close(in);
}
}
private void addFile(File targetFile, File source) throws IOException {
InputStream in = null;
try {
in = new FileInputStream(source);
addFile(targetFile, in);
} finally {
DeploymentUtil.close(in);
}
}
private void addFile(File targetFile, InputStream source) throws IOException {
targetFile.getParentFile().mkdirs();
OutputStream out = null;
try {
out = new FileOutputStream(targetFile);
int count;
while ((count = source.read(buffer)) > 0) {
out.write(buffer, 0, count);
}
} finally {
DeploymentUtil.close(out);
}
}
public File getTargetFile(URI targetPath) {
assert !targetPath.isAbsolute() : "targetPath is absolute";
assert !targetPath.isOpaque() : "targetPath is opaque";
return new File(baseUri.resolve(targetPath));
}
static interface ParentSource {
Collection getParents(URI point) throws DeploymentException;
}
List getExtremalSet(Collection points, ParentSource parentSource) throws DeploymentException {
LinkedHashMap pointToEnvelopeMap = new LinkedHashMap();
for (Iterator iterator = points.iterator(); iterator.hasNext();) {
URI newPoint = (URI) iterator.next();
Set newEnvelope = new HashSet();
getEnvelope(newPoint, parentSource, newEnvelope);
boolean useMe = true;
for (Iterator iterator1 = pointToEnvelopeMap.entrySet().iterator(); iterator1.hasNext();) {
Map.Entry entry = (Map.Entry) iterator1.next();
Set existingEnvelope = (Set) entry.getValue();
if (existingEnvelope.contains(newPoint)) {
useMe = false;
} else if (newEnvelope.contains(entry.getKey())) {
iterator1.remove();
}
}
if (useMe) {
pointToEnvelopeMap.put(newPoint, newEnvelope);
}
}
return new ArrayList(pointToEnvelopeMap.keySet());
}
private void getEnvelope(URI point, ParentSource parentSource, Set envelope) throws DeploymentException {
Collection newPoints = parentSource.getParents(point);
envelope.addAll(newPoints);
for (Iterator iterator = newPoints.iterator(); iterator.hasNext();) {
URI newPoint = (URI) iterator.next();
getEnvelope(newPoint, parentSource, envelope);
}
}
static class ConfigurationParentSource implements ParentSource {
private final Kernel kernel;
public ConfigurationParentSource(Kernel kernel) {
this.kernel = kernel;
}
public Collection getParents(URI configID) throws DeploymentException {
ObjectName configName;
try {
configName = Configuration.getConfigurationObjectName(configID);
} catch (MalformedObjectNameException e) {
throw new DeploymentException("Cannot convert ID to ObjectName: ", e);
}
try {
URI[] parents = (URI[]) kernel.getAttribute(configName, "parentId");
if (parents == null) {
return Collections.EMPTY_LIST;
} else {
return Arrays.asList(parents);
}
} catch (Exception e) {
throw new DeploymentException("Cannot find parents of alleged config: ", e);
}
}
}
private static final Log log = LogFactory.getLog(DeploymentContext.class);
private ClassLoader[] determineParents() throws DeploymentException {
ClassLoader[] parentCL;
List parentId = configurationData.getParentId();
if (kernel != null && parentId != null && parentId.size() > 0) {
ConfigurationManager configurationManager = ConfigurationUtil.getConfigurationManager(kernel);
try {
loadAncestors(kernel, parentId, loadedAncestors, configurationManager);
ParentSource parentSource = new ConfigurationParentSource(kernel);
parentId = getExtremalSet(parentId, parentSource);
configurationData.setParentId(parentId);
try {
for (Iterator iterator = parentId.iterator(); iterator.hasNext();) {
URI uri = (URI) iterator.next();
List started = new ArrayList();
startAncestors(uri, kernel, started, configurationManager);
startedAncestors.addAll(started);
}
} catch (DeploymentException e) {
throw e;
} catch (Exception e) {
throw new DeploymentException(e);
}
} finally {
ConfigurationUtil.releaseConfigurationManager(kernel, configurationManager);
}
try {
parentCL = new ClassLoader[parentId.size()];
for (int i = 0; i < parentId.size(); i++) {
URI uri = (URI) parentId.get(i);
ObjectName parentName = Configuration.getConfigurationObjectName(uri);
parentCL[i] = (ClassLoader) kernel.getAttribute(parentName, "configurationClassLoader");
}
} catch (Exception e) {
throw new DeploymentException(e);
}
} else {
// no explicit parent set, so use the class loader of this class as
// the parent... this class should be in the root geronimo classloader,
// which is normally the system class loader but not always, so be safe
parentCL = DEFAULT_PARENT_CLASSLOADERS;
}
return parentCL;
}
private void loadAncestors(Kernel kernel, List parentId, List loadedAncestors, ConfigurationManager configurationManager) throws DeploymentException {
if (kernel != null && parentId != null) {
try {
for (Iterator iterator = parentId.iterator(); iterator.hasNext();) {
URI uri = (URI) iterator.next();
List newAncestors = configurationManager.loadRecursive(uri);
loadedAncestors.addAll(newAncestors);
}
} catch (Exception e) {
throw new DeploymentException("Unable to load parents", e);
}
}
}
private void startAncestors(URI configID, Kernel kernel, List started, ConfigurationManager configurationManager) throws Exception {
if (configID != null) {
ObjectName configName = Configuration.getConfigurationObjectName(configID);
if (!isRunning(kernel, configName)) {
URI[] patterns = (URI[]) kernel.getGBeanData(configName).getAttribute("parentId");
if (patterns != null) {
for (int i = 0; i < patterns.length; i++) {
URI pattern = patterns[i];
startAncestors(pattern, kernel, started, configurationManager);
}
}
configurationManager.loadGBeans(configID);
started.add(configID);
}
}
}
private static boolean isRunning(Kernel kernel, ObjectName name) throws Exception {
return State.RUNNING_INDEX == kernel.getGBeanState(name);
}
public ClassLoader getClassLoader(Repository repository) throws DeploymentException {
// shouldn't user classpath come before dependencies?
List dependencies = configurationData.getDependencies();
List classPath = configurationData.getClassPath();
URL[] urls = new URL[dependencies.size() + classPath.size()];
try {
int index = 0;
for (Iterator iterator = dependencies.iterator(); iterator.hasNext();) {
URI uri = (URI) iterator.next();
urls[index++] = repository.getURL(uri);
}
for (Iterator i = classPath.iterator(); i.hasNext();) {
URI path = (URI) i.next();
urls[index++] = getTargetFile(path).toURL();
}
} catch (MalformedURLException e) {
throw new DeploymentException(e);
}
ClassLoader[] parentCL = determineParents();
boolean inverseClassloading = configurationData.isInverseClassloading();
Set filter = configurationData.getHiddenClasses();
String[] hiddenFilter = (String[]) filter.toArray(new String[0]);
filter = configurationData.getNonOverridableClasses();
String[] nonOverridableFilter = (String[]) filter.toArray(new String[0]);
return new MultiParentClassLoader(configurationData.getId(), urls, parentCL, inverseClassloading, hiddenFilter, nonOverridableFilter);
}
public void close() throws IOException, DeploymentException {
if (kernel != null) {
ConfigurationManager configurationManager = ConfigurationUtil.getConfigurationManager(kernel);
try {
startedAncestors.clear();
Collections.reverse(loadedAncestors);
for (Iterator iterator = loadedAncestors.iterator(); iterator.hasNext();) {
URI configID = (URI) iterator.next();
if(configurationManager.isLoaded(configID)) {
try {
configurationManager.unload(configID);
} catch (NoSuchConfigException e) {
throw new DeploymentException("Could not find a configuration we previously loaded! " + configID, e);
}
}
}
loadedAncestors.clear();
} finally {
ConfigurationUtil.releaseConfigurationManager(kernel, configurationManager);
}
}
}
public void addChildConfiguration(ConfigurationData configurationData) {
this.configurationData.addChildConfiguration(configurationData);
}
public ConfigurationData getConfigurationData() {
ConfigurationData configurationData = new ConfigurationData(this.configurationData);
configurationData.setGBeans(Arrays.asList(gbeans.getGBeans()));
return configurationData;
}
/**
* @return a copy of the configurations GBeanData
* @deprecated Currently used only in some tests, and may not be appropriate as a public method.
*/
public GBeanData getConfigurationGBeanData() throws MalformedObjectNameException, InvalidConfigException {
URI id = configurationData.getId();
GBeanData config = new GBeanData(Configuration.getConfigurationObjectName(id), Configuration.GBEAN_INFO);
config.setAttribute("id", id);
config.setAttribute("type", configurationData.getModuleType());
config.setAttribute("domain", configurationData.getDomain());
config.setAttribute("server", configurationData.getServer());
List parentId = configurationData.getParentId();
if (parentId != null) {
config.setAttribute("parentId", parentId.toArray(new URI[parentId.size()]));
}
config.setAttribute("gBeanState", Configuration.storeGBeans(gbeans.getGBeans()));
config.setReferencePatterns("Repositories", Collections.singleton(new ObjectName("*:name=Repository,*")));
config.setAttribute("dependencies", configurationData.getDependencies());
config.setAttribute("classPath", configurationData.getClassPath());
return config;
}
public Object getAttribute(ObjectName name, String property) throws Exception {
return kernel.getAttribute(name, property);
}
}