001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.deployment;
023:
024: import java.io.File;
025: import java.io.FileInputStream;
026: import java.io.Serializable;
027: import java.net.URL;
028: import java.net.URLClassLoader;
029: import java.util.ArrayList;
030: import java.util.Collection;
031: import java.util.Date;
032: import java.util.HashMap;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Set;
036: import java.util.jar.JarFile;
037: import java.util.jar.Manifest;
038:
039: import javax.management.MBeanServer;
040: import javax.management.ObjectName;
041:
042: import org.jboss.logging.Logger;
043: import org.jboss.mx.loading.LoaderRepositoryFactory;
044: import org.jboss.mx.loading.RepositoryClassLoader;
045: import org.jboss.mx.loading.LoaderRepositoryFactory.LoaderRepositoryConfig;
046: import org.jboss.util.collection.ListSet;
047: import org.jboss.util.file.Files;
048: import org.w3c.dom.Document;
049:
050: /**
051: * Service Deployment Info .
052: *
053: * Every deployment (even the J2EE ones) should be seen at some point as
054: * Service Deployment info
055: *
056: * @see org.jboss.system.Service
057: *
058: * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
059: * @author <a href="mailto:David.Maplesden@orion.co.nz">David Maplesden</a>
060: * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
061: * @author <a href="mailto:daniel.schulze@telkel.com">Daniel Schulze</a>
062: * @author <a href="mailto:Christoph.Jung@infor.de">Christoph G. Jung</a>
063: * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
064: * @version $Revision: 57205 $ <p>
065: */
066: public class DeploymentInfo implements Serializable {
067: /** @since 4.0.1 */
068: private static final long serialVersionUID = 1131841473723490707L;
069:
070: private static final Logger log = Logger
071: .getLogger(DeploymentInfo.class);
072:
073: // Variables ------------------------------------------------------------
074:
075: /** The initial construction timestamp */
076: public Date date = new Date();
077:
078: /** the URL identifing this SDI **/
079: public URL url;
080:
081: /** An optional URL to a local copy of the deployment */
082: public URL localUrl;
083:
084: /** The URL used to watch for changes when the deployment is unpacked */
085: public URL watch;
086:
087: /** The suffix of the deployment url */
088: public String shortName;
089:
090: /** The last system time the deployment inited by the MainDeployer */
091: public long lastDeployed = 0;
092:
093: /** use for "should we redeploy failed" */
094: public long lastModified = 0;
095:
096: /** A free form status for the "state" can be Deployed/failed etc etc */
097: public String status;
098:
099: /** The current state of the deployment */
100: public DeploymentState state = DeploymentState.CONSTRUCTED;
101:
102: /** The subdeployer that handles the deployment */
103: public transient SubDeployer deployer;
104:
105: /** Unified CL is a global scope class loader **/
106: public transient RepositoryClassLoader ucl;
107:
108: /** local Cl is a CL that is used for metadata loading, if ejb-jar.xml is
109: left in the parent CL through old deployments, this makes sure that we
110: use the local version. You must use the URLClassLoader.findResource method
111: to restrict loading to the deployment URL.
112: */
113: public transient URLClassLoader localCl;
114:
115: /** A CL for preloading annotations, it should be made available
116: * in the deployment create step.
117: */
118: public transient URLClassLoader annotationsCl;
119:
120: /** The classpath declared by this xml descriptor, needs <classpath> entry **/
121: public final Collection classpath = new ArrayList();
122:
123: /** The mbeans deployed */
124: public final List mbeans = new ArrayList();
125:
126: /** Anyone can have subdeployments */
127: public final Set subDeployments = new ListSet();
128:
129: /** And the subDeployments have a parent */
130: public DeploymentInfo parent = null;
131:
132: /** the web root context in case of war file */
133: public String webContext;
134:
135: /** the manifest entry of the deployment (if any)
136: * manifest is not serializable ... is only needed
137: * at deployment time, so we mark it transient
138: */
139: public transient Manifest manifest;
140:
141: /**
142: * Each Deployment is really mapping one to one to a XML document, here in
143: * its parsed form. A xerces document (org.apache.xerces.dom.DocumentImpl)
144: * is serializable.
145: */
146: public Document document;
147:
148: /** An optional URL to the URL of the document loaded */
149: public URL documentUrl;
150:
151: /** We can hold "typed" metadata, really an interpretation of the bare XML document */
152: public transient Object metaData;
153:
154: /** If this deployed as part of an EAR, there may be an alernative DD */
155: public String alternativeDD;
156:
157: /** An arbitrary map of state associated with the deployment */
158: public transient HashMap context = new HashMap();
159:
160: /** Is this a stand-alone service descriptor */
161: public boolean isXML;
162:
163: /** Is this a stand-alone script */
164: public boolean isScript;
165:
166: /** Does the deployment url point to a directory */
167: public boolean isDirectory;
168:
169: /** Are the subdeploymets already sorted */
170: public boolean sortedSubDeployments;
171:
172: /**
173: * The variable <code>deployedObject</code> can contain the MBean that
174: * is created through the deployment. for instance, deploying an ejb-jar
175: * results in an EjbModule mbean, which is stored here.
176: */
177: public ObjectName deployedObject;
178:
179: /** The configuration of the loader repository for this deployment */
180: public LoaderRepositoryConfig repositoryConfig;
181:
182: /** The MBeanServer associated with the deployment */
183: private transient MBeanServer server;
184:
185: /**
186: * CTOR
187: */
188: public DeploymentInfo(final URL url, final DeploymentInfo parent,
189: final MBeanServer server) throws DeploymentException {
190: this .server = server;
191: // The key url the deployment comes from
192: this .url = url;
193:
194: // this may be changed by deployers in case of directory and xml file following
195: this .watch = url;
196:
197: // Whether we are part of a subdeployment or not
198: this .parent = parent;
199:
200: // Is it a directory?
201: if (url.getProtocol().startsWith("file")
202: && new File(url.getFile()).isDirectory())
203: this .isDirectory = true;
204:
205: // Does it even exist?
206: if (!isDirectory) {
207: try {
208: url.openStream().close();
209: } catch (Exception e) {
210: throw new DeploymentException("url " + url
211: + " could not be opened, does it exist?");
212: }
213: }
214:
215: if (parent != null) {
216: parent.subDeployments.add(this );
217: // Use the repository of our topmost parent
218: repositoryConfig = getTopRepositoryConfig();
219: }
220:
221: // The "short name" for the deployment http://myserver/mybean.ear should yield "mybean.ear"
222: shortName = getShortName(url.getPath());
223:
224: // Do we have an XML descriptor only deployment?
225: isXML = shortName.toLowerCase().endsWith(".xml");
226:
227: // Do we have a stand-olone script deployment?
228: isScript = shortName.toLowerCase().endsWith(".bsh");
229: }
230:
231: public MBeanServer getServer() {
232: return server;
233: }
234:
235: public void setServer(MBeanServer server) {
236: this .server = server;
237: }
238:
239: /** Create a UnifiedClassLoader for the deployment that loads from
240: the localUrl and uses its parent deployments url as its orignal
241: url. Previously xml descriptors simply used the TCL but since the UCLs
242: are now registered as mbeans each must be unique.
243: */
244: public void createClassLoaders() throws Exception {
245: // create a local classloader for local files, don't go with the UCL for ejb-jar.xml
246: if (localCl == null)
247: localCl = new URLClassLoader(new URL[] { localUrl });
248:
249: /* Walk the deployment tree to find the parent deployment and obtain its
250: url to use as our URL from which this deployment unit originated. This
251: is used to determine permissions using the original URL namespace.
252: Also pick up the LoaderRepository from the topmost ancestor.
253: */
254: URL origUrl = url;
255: DeploymentInfo current = this ;
256: while (current.parent != null) {
257: current = current.parent;
258: }
259: origUrl = current.url;
260: repositoryConfig = current.repositoryConfig;
261: if (parent == null) {
262: if (repositoryConfig == null)
263: repositoryConfig = new LoaderRepositoryConfig();
264: // Make sure the specified LoaderRepository exists.
265: LoaderRepositoryFactory.createLoaderRepository(server,
266: repositoryConfig);
267: log.debug("createLoaderRepository from config: "
268: + repositoryConfig);
269: // the classes are passed to a UCL that will share the classes with the whole base
270:
271: Object[] args = { isXML ? null : localUrl, origUrl,
272: Boolean.TRUE };
273: String[] sig = { "java.net.URL", "java.net.URL", "boolean" };
274: ucl = (RepositoryClassLoader) server.invoke(
275: repositoryConfig.repositoryName, "newClassLoader",
276: args, sig);
277: } else {
278: // Add a reference to the LoaderRepository
279: LoaderRepositoryFactory.createLoaderRepository(server,
280: repositoryConfig);
281: // Add the deployment URL to the parent UCL
282: ucl = parent.ucl;
283: ucl.addURL(localUrl);
284: }
285: // Add any library jars seen before the UCL was created
286: if (classpath.size() > 0) {
287: Iterator jars = classpath.iterator();
288: while (jars.hasNext()) {
289: URL jar = (URL) jars.next();
290: ucl.addURL(jar);
291: }
292: }
293: }
294:
295: /** Set the UnifiedLoaderRepository info for the deployment. This can only
296: * be called for the parent deployment, and must be done early in the
297: * Subdeployer init(DeploymentInfo) method prior to any class loading.
298: * @throws Exception
299: */
300: public void setRepositoryInfo(LoaderRepositoryConfig config)
301: throws Exception {
302: if (parent != null) {
303: log
304: .warn("Only the root deployment can set the loader repository, "
305: + "ignoring config=" + config);
306: return;
307: }
308:
309: this .repositoryConfig = config;
310: // Recreate the ucl if it exists
311: if (ucl != null) {
312: ucl.unregister();
313: // Make sure the specified LoaderRepository exists.
314: LoaderRepositoryFactory.createLoaderRepository(server,
315: repositoryConfig);
316: log.debug("createLoaderRepository from config: "
317: + repositoryConfig);
318: // the classes are passed to a UCL that will share the classes with the whole base
319:
320: Object[] args = { isXML ? null : localUrl, url,
321: Boolean.TRUE };
322: String[] sig = { "java.net.URL", "java.net.URL", "boolean" };
323: ucl = (RepositoryClassLoader) server.invoke(
324: repositoryConfig.repositoryName, "newClassLoader",
325: args, sig);
326: }
327: }
328:
329: /** All library jars referenced through either the manifest references
330: * or sar classpaths are added to the root DeploymentInfo class loader. This
331: * is neccessary to avoid IllegalAccessErrors due to classes in a pkg
332: * being split across jars
333: */
334: public void addLibraryJar(URL libJar) {
335: DeploymentInfo current = this ;
336: while (current.parent != null) {
337: current = current.parent;
338: }
339: /* If the UCL exists add the jar to it else use the classpath to
340: indicate that the jars need to be added when the ucl is created.
341: */
342: if (current.ucl != null)
343: current.ucl.addURL(libJar);
344: else
345: classpath.add(libJar);
346: }
347:
348: /** The the class loader repository name of the top most DeploymentInfo
349: */
350: public LoaderRepositoryConfig getTopRepositoryConfig() {
351: LoaderRepositoryConfig topConfig = repositoryConfig;
352: DeploymentInfo info = this ;
353: while (info.parent != null) {
354: info = info.parent;
355: topConfig = info.repositoryConfig;
356: }
357: return topConfig;
358: }
359:
360: /**
361: * getManifest returns (if present) the deployment's manifest
362: * it is lazy loaded to work from the localURL
363: */
364: public Manifest getManifest() {
365: try {
366: if (manifest == null) {
367: File file = new File(localUrl.getFile());
368:
369: if (file.isDirectory()) {
370: FileInputStream fis = new FileInputStream(new File(
371: file, "META-INF/MANIFEST.MF"));
372: manifest = new Manifest(fis);
373: fis.close();
374: } else if (isXML == false)// a jar
375: manifest = new JarFile(file).getManifest();
376: }
377:
378: return manifest;
379: }
380: // It is ok to barf at any time in the above, means no manifest
381: catch (Exception ignored) {
382: return null;
383: }
384: }
385:
386: public void cleanup() {
387: // Remove the deployment UCL
388: if (parent == null && ucl != null)
389: ucl.unregister();
390: ucl = null;
391:
392: // Remove any deployment specific repository
393: if (repositoryConfig != null) {
394: LoaderRepositoryFactory.destroyLoaderRepository(server,
395: repositoryConfig.repositoryName);
396: }
397:
398: subDeployments.clear();
399: mbeans.clear();
400: context.clear();
401: if (localUrl == null || localUrl.equals(url)) {
402: log
403: .debug("Not deleting localUrl, it is null or not a copy: "
404: + localUrl);
405: } else if (Files.delete(localUrl.getFile())) {
406: log.debug("Cleaned Deployment: " + localUrl);
407: } else {
408: log.debug("Could not delete " + localUrl
409: + " restart will delete it");
410: }
411:
412: // Clean up references to other objects
413: localCl = null;
414: annotationsCl = null;
415: localUrl = null;
416: repositoryConfig = null;
417: watch = null;
418: parent = null;
419: manifest = null;
420: document = null;
421: metaData = null;
422: server = null;
423: classpath.clear();
424: state = DeploymentState.DESTROYED;
425: // Who is using this after clear?
426: // deployer = null;
427: }
428:
429: /** The sortName concatenated with the canonical names of all parents. */
430: public String getCanonicalName() {
431: String name = shortName;
432: if (parent != null)
433: name = parent.getCanonicalName() + "/" + name;
434: return name;
435: }
436:
437: private String getShortName(String name) {
438: if (name.endsWith("/"))
439: name = name.substring(0, name.length() - 1);
440: name = name.substring(name.lastIndexOf("/") + 1);
441: return name;
442: }
443:
444: public int hashCode() {
445: return url.hashCode();
446: }
447:
448: public boolean equals(Object other) {
449: if (other instanceof DeploymentInfo) {
450: return ((DeploymentInfo) other).url.equals(this .url);
451: }
452: return false;
453: }
454:
455: public String toString() {
456: StringBuffer s = new StringBuffer(super .toString());
457: s.append(" { url=" + url + " }\n");
458: s.append(" deployer: " + deployer + "\n");
459: s.append(" status: " + status + "\n");
460: s.append(" state: " + state + "\n");
461: s.append(" watch: " + watch + "\n");
462: s.append(" altDD: " + alternativeDD + "\n");
463: s.append(" lastDeployed: " + lastDeployed + "\n");
464: s.append(" lastModified: " + lastModified + "\n");
465: s.append(" mbeans:\n");
466: for (Iterator i = mbeans.iterator(); i.hasNext();) {
467: ObjectName o = (ObjectName) i.next();
468: try {
469: String state = (String) server.getAttribute(o,
470: "StateString");
471: s.append(" " + o + " state: " + state + "\n");
472: } catch (Exception e) {
473: s.append(" " + o + " (state not available)\n");
474: } // end of try-catch
475:
476: } // end of for ()
477:
478: return s.toString();
479: }
480: }
|