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.FileOutputStream;
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.net.URL;
029: import java.net.URLClassLoader;
030: import java.util.ArrayList;
031: import java.util.Enumeration;
032: import java.util.HashMap;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.ListIterator;
036: import java.util.Map;
037: import java.util.jar.JarEntry;
038: import java.util.jar.JarFile;
039:
040: import javax.management.MBeanServer;
041: import javax.management.MalformedObjectNameException;
042: import javax.management.ObjectName;
043: import javax.xml.parsers.DocumentBuilder;
044: import javax.xml.parsers.DocumentBuilderFactory;
045:
046: import org.jboss.mx.loading.LoaderRepositoryFactory;
047: import org.jboss.mx.loading.LoaderRepositoryFactory.LoaderRepositoryConfig;
048: import org.jboss.mx.util.MBeanProxyExt;
049: import org.jboss.net.protocol.URLLister;
050: import org.jboss.net.protocol.URLListerFactory;
051: import org.jboss.system.ServiceControllerMBean;
052: import org.jboss.system.server.ServerConfig;
053: import org.jboss.system.server.ServerConfigLocator;
054: import org.jboss.util.StringPropertyReplacer;
055: import org.jboss.util.Strings;
056: import org.jboss.util.stream.Streams;
057: import org.jboss.util.xml.JBossEntityResolver;
058: import org.w3c.dom.Element;
059: import org.w3c.dom.Node;
060: import org.w3c.dom.NodeList;
061: import org.xml.sax.InputSource;
062:
063: import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
064:
065: /**
066: * This is the main Service Deployer API.
067: *
068: * @see org.jboss.system.Service
069: *
070: * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
071: * @author <a href="mailto:David.Maplesden@orion.co.nz">David Maplesden</a>
072: * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
073: * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
074: * @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>
075: * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
076: * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis<a/>
077: * @version $Revision: 57205 $
078: */
079: public class SARDeployer extends SubDeployerSupport implements
080: SARDeployerMBean {
081: /** The suffixes we accept, along with their relative order */
082: private static final String[] DEFAULT_ENHANCED_SUFFIXES = new String[] {
083: "050:.deployer", "050:-deployer.xml", "150:.sar",
084: "150:-service.xml" };
085:
086: /** The deployment descriptor to look for */
087: private static final String JBOSS_SERVICE = "META-INF/jboss-service.xml";
088:
089: /** A proxy to the ServiceController. */
090: private ServiceControllerMBean serviceController;
091:
092: /** The server data directory. */
093: private File dataDir;
094:
095: /** The server configuration base URL. For example,
096: file:/<jboss_dist_root>/server/default. Relative service
097: descriptor codebase elements are relative to this URL.
098: */
099: private URL serverHomeURL;
100:
101: /** A HashMap<ObjectName, DeploymentInfo> for the deployed services */
102: private HashMap serviceDeploymentMap = new HashMap();
103:
104: /**
105: * A Map<String, List<String>> of the suffix to accepted archive META-INF descriptor name
106: * @todo externalize this
107: */
108: private Map suffixToDescriptorMap = new ConcurrentReaderHashMap();
109:
110: /** A flag indicating if the parser used for the service descriptor should be configured for namespaces */
111: private boolean useNamespaceAwareParser;
112:
113: /**
114: * Default CTOR
115: */
116: public SARDeployer() {
117: setEnhancedSuffixes(DEFAULT_ENHANCED_SUFFIXES);
118: // Add the .har to META-INF/{jboss-service.xml,hibernate-service.xml} mapping
119: ArrayList tmp = new ArrayList();
120: tmp.add(JBOSS_SERVICE);
121: tmp.add("META-INF/hibernate-service.xml");
122: suffixToDescriptorMap.put(".har", tmp);
123: }
124:
125: public boolean isUseNamespaceAwareParser() {
126: return useNamespaceAwareParser;
127: }
128:
129: public void setUseNamespaceAwareParser(
130: boolean useNamespaceAwareParser) {
131: this .useNamespaceAwareParser = useNamespaceAwareParser;
132: }
133:
134: /**
135: * Get the associated service DeploymentInfo if found, null otherwise
136: *
137: * @param serviceName a service object name
138: * @return The associated service DeploymentInfo if found, null otherwise
139: * @jmx.managed-operation
140: */
141: public DeploymentInfo getService(ObjectName serviceName) {
142: DeploymentInfo di = null;
143: synchronized (serviceDeploymentMap) {
144: di = (DeploymentInfo) serviceDeploymentMap.get(serviceName);
145: }
146: return di;
147: }
148:
149: /**
150: * Describe <code>init</code> method here.
151: *
152: * @param di a <code>DeploymentInfo</code> value
153: * @exception DeploymentException if an error occurs
154: * @jmx.managed-operation
155: */
156: public void init(DeploymentInfo di) throws DeploymentException {
157: try {
158: if (di.url.getPath().endsWith("/")) {
159: // the URL is a unpacked collection, watch the deployment descriptor
160: di.watch = new URL(di.url, JBOSS_SERVICE);
161: } else {
162: // just watch the original URL
163: di.watch = di.url;
164: }
165:
166: // Get the document if not already present
167: parseDocument(di);
168:
169: // Check for a custom loader-repository for scoping
170: NodeList loaders = di.document
171: .getElementsByTagName("loader-repository");
172: if (loaders.getLength() > 0) {
173: Element loader = (Element) loaders.item(0);
174: LoaderRepositoryConfig config = LoaderRepositoryFactory
175: .parseRepositoryConfig(loader);
176: di.setRepositoryInfo(config);
177: }
178:
179: // In case there is a dependent classpath defined parse it
180: parseXMLClasspath(di);
181:
182: // Copy local directory if local-directory element is present
183: NodeList lds = di.document
184: .getElementsByTagName("local-directory");
185: log.debug("about to copy " + lds.getLength()
186: + " local directories");
187:
188: for (int i = 0; i < lds.getLength(); i++) {
189: Element ld = (Element) lds.item(i);
190: String path = ld.getAttribute("path");
191: log.debug("about to copy local directory at " + path);
192:
193: // Get the url of the local copy from the classloader.
194: log.debug("copying from " + di.localUrl + path + " -> "
195: + dataDir);
196:
197: inflateJar(di.localUrl, dataDir, path);
198: }
199: } catch (DeploymentException de) {
200: throw de;
201: } catch (Exception e) {
202: throw new DeploymentException(e);
203: }
204:
205: // invoke super-class initialization
206: super .init(di);
207: }
208:
209: /**
210: * Describe <code>create</code> method here.
211: *
212: * @param di a <code>DeploymentInfo</code> value
213: * @exception DeploymentException if an error occurs
214: * @jmx.managed-operation
215: */
216: public void create(DeploymentInfo di) throws DeploymentException {
217: try {
218: // install the MBeans in this descriptor
219: log.debug("Deploying SAR, create step: url " + di.url);
220:
221: // Register the SAR UCL as an mbean so we can use it as the service loader
222: ObjectName uclName = di.ucl.getObjectName();
223: if (getServer().isRegistered(uclName) == false) {
224: log.debug("Registering service UCL=" + uclName);
225: getServer().registerMBean(di.ucl, uclName);
226: }
227:
228: List mbeans = di.mbeans;
229: mbeans.clear();
230: List descriptorMbeans = serviceController.install(
231: di.document.getDocumentElement(), uclName);
232: mbeans.addAll(descriptorMbeans);
233:
234: // create the services
235: for (Iterator iter = di.mbeans.iterator(); iter.hasNext();) {
236: ObjectName service = (ObjectName) iter.next();
237:
238: // The service won't be created until explicitly dependent mbeans are created
239: serviceController.create(service);
240: synchronized (this .serviceDeploymentMap) {
241: serviceDeploymentMap.put(service, di);
242: }
243: }
244:
245: // Generate a JMX notification for the create stage
246: super .create(di);
247: } catch (DeploymentException e) {
248: log.debug("create operation failed for package " + di.url,
249: e);
250: destroy(di);
251: throw e;
252: } catch (Exception e) {
253: log.debug("create operation failed for package " + di.url,
254: e);
255: destroy(di);
256: throw new DeploymentException(
257: "create operation failed for package " + di.url, e);
258: }
259: }
260:
261: /**
262: * The <code>start</code> method starts all the mbeans in this DeploymentInfo..
263: *
264: * @param di a <code>DeploymentInfo</code> value
265: * @exception DeploymentException if an error occurs
266: * @jmx.managed-operation
267: */
268: public void start(DeploymentInfo di) throws DeploymentException {
269: log.debug("Deploying SAR, start step: url " + di.url);
270: try {
271: // start the services
272:
273: for (Iterator iter = di.mbeans.iterator(); iter.hasNext();) {
274: ObjectName service = (ObjectName) iter.next();
275:
276: // The service won't be started until explicitely dependent mbeans are started
277: serviceController.start(service);
278: }
279: // Generate a JMX notification for the start stage
280: super .start(di);
281: } catch (Exception e) {
282: stop(di);
283: destroy(di);
284: throw new DeploymentException(
285: "start operation failed on package " + di.url, e);
286: }
287: }
288:
289: /** The stop method invokes stop on the mbeans associatedw ith the deployment
290: * in reverse order relative to create.
291: *
292: * @param di the <code>DeploymentInfo</code> value to stop.
293: * @jmx.managed-operation
294: */
295: public void stop(DeploymentInfo di) {
296: log.debug("undeploying document " + di.url);
297:
298: List services = di.mbeans;
299: int lastService = services.size();
300:
301: // stop services in reverse order.
302: for (ListIterator i = services.listIterator(lastService); i
303: .hasPrevious();) {
304: ObjectName name = (ObjectName) i.previous();
305: log.debug("stopping mbean " + name);
306: try {
307: serviceController.stop(name);
308: } catch (Exception e) {
309: log.error("Could not stop mbean: " + name, e);
310: } // end of try-catch
311: }
312:
313: // Generate a JMX notification for the stop stage
314: try {
315: super .stop(di);
316: } catch (Exception ignore) {
317: }
318: }
319:
320: /** The destroy method invokes destroy on the mbeans associated with
321: * the deployment in reverse order relative to create.
322: *
323: * @param di a <code>DeploymentInfo</code> value
324: * @jmx.managed-operation
325: */
326: public void destroy(DeploymentInfo di) {
327: List services = di.mbeans;
328: int lastService = services.size();
329:
330: for (ListIterator i = services.listIterator(lastService); i
331: .hasPrevious();) {
332: ObjectName name = (ObjectName) i.previous();
333: log.debug("destroying mbean " + name);
334: synchronized (serviceDeploymentMap) {
335: serviceDeploymentMap.remove(name);
336: }
337:
338: try {
339: serviceController.destroy(name);
340: } catch (Exception e) {
341: log.error("Could not destroy mbean: " + name, e);
342: } // end of try-catch
343: }
344:
345: for (ListIterator i = services.listIterator(lastService); i
346: .hasPrevious();) {
347: ObjectName name = (ObjectName) i.previous();
348: log.debug("removing mbean " + name);
349: try {
350: serviceController.remove(name);
351: } catch (Exception e) {
352: log.error("Could not remove mbean: " + name, e);
353: } // end of try-catch
354: }
355:
356: // Unregister the SAR UCL
357: try {
358: ObjectName uclName = di.ucl.getObjectName();
359: if (getServer().isRegistered(uclName) == true) {
360: log.debug("Unregistering service UCL=" + uclName);
361: getServer().unregisterMBean(uclName);
362: }
363: } catch (Exception ignore) {
364: }
365:
366: // Generate a JMX notification for the destroy stage
367: try {
368: super .destroy(di);
369: } catch (Exception ignore) {
370: }
371: }
372:
373: // ServiceMBeanSupport overrides --------------------------------
374:
375: /**
376: * The startService method gets the mbeanProxies for MainDeployer
377: * and ServiceController, used elsewhere.
378: *
379: * @exception Exception if an error occurs
380: */
381: protected void startService() throws Exception {
382: super .startService();
383:
384: // get the controller proxy
385: serviceController = (ServiceControllerMBean) MBeanProxyExt
386: .create(ServiceControllerMBean.class,
387: ServiceControllerMBean.OBJECT_NAME, server);
388:
389: // Get the data directory, install url & library url
390: ServerConfig config = ServerConfigLocator.locate();
391: dataDir = config.getServerDataDir();
392: serverHomeURL = config.getServerHomeURL();
393: }
394:
395: /**
396: * This method stops all the applications in this server.
397: */
398: protected void stopService() throws Exception {
399: // deregister with MainDeployer
400: super .stopService();
401:
402: // Help GC
403: serviceController = null;
404: serverHomeURL = null;
405: dataDir = null;
406: }
407:
408: protected ObjectName getObjectName(MBeanServer server,
409: ObjectName name) throws MalformedObjectNameException {
410: return name == null ? OBJECT_NAME : name;
411: }
412:
413: // Protected -----------------------------------------------------
414:
415: protected File[] listFiles(final String urlspec) throws Exception {
416: URL url = Strings.toURL(urlspec);
417:
418: // url is already canonical thanks to Strings.toURL
419: File dir = new File(url.getFile());
420:
421: File[] files = dir.listFiles(new java.io.FileFilter() {
422: public boolean accept(File pathname) {
423: String name = pathname.getName().toLowerCase();
424: return (name.endsWith(".jar") || name.endsWith(".zip"));
425: }
426: });
427:
428: return files;
429: }
430:
431: /**
432: * @param di
433: * @throws Exception
434: */
435: protected void parseXMLClasspath(DeploymentInfo di)
436: throws Exception {
437: ArrayList classpath = new ArrayList();
438: URLListerFactory listerFactory = new URLListerFactory();
439:
440: NodeList children = di.document.getDocumentElement()
441: .getChildNodes();
442: for (int i = 0; i < children.getLength(); i++) {
443: if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
444: Element classpathElement = (Element) children.item(i);
445: if (classpathElement.getTagName().equals("classpath")) {
446: log.debug("Found classpath element: "
447: + classpathElement);
448: if (!classpathElement.hasAttribute("codebase")) {
449: throw new DeploymentException(
450: "Invalid classpath element missing codebase: "
451: + classpathElement);
452: }
453: String codebase = classpathElement.getAttribute(
454: "codebase").trim();
455: // Replace any system property references like ${x}
456: codebase = StringPropertyReplacer
457: .replaceProperties(codebase);
458:
459: String archives = null;
460: if (classpathElement.hasAttribute("archives")) {
461: archives = classpathElement.getAttribute(
462: "archives").trim();
463: // Replace any system property references like ${x}
464: archives = StringPropertyReplacer
465: .replaceProperties(archives);
466: if ("".equals(archives)) {
467: archives = null;
468: }
469: }
470:
471: // Convert codebase to a URL
472: // "." is resolved relative to the deployment
473: // other URLs are resolved relative to SERVER_HOME
474: URL codebaseUrl;
475: if (".".equals(codebase)) {
476: codebaseUrl = new URL(di.url, "./");
477: } else {
478: if (archives != null
479: && codebase.endsWith("/") == false) {
480: codebase += "/";
481: }
482: codebaseUrl = new URL(serverHomeURL, codebase);
483: }
484: log.debug("codebase URL is " + codebaseUrl);
485:
486: if (archives == null) {
487: // archives not supplied so add the codebase itself
488: classpath.add(codebaseUrl);
489: log.debug("added codebase to classpath");
490: } else {
491: // obtain a URLLister for the codebase and use it to obtain
492: // the list of URLs to add
493: log
494: .debug("listing codebase for archives matching "
495: + archives);
496: URLLister lister = listerFactory
497: .createURLLister(codebaseUrl);
498: log.debug("URLLister class is "
499: + lister.getClass().getName());
500: classpath.addAll(lister.listMembers(
501: codebaseUrl, archives));
502: }
503: } // end of if ()
504:
505: } // end of if ()
506: } //end of for
507:
508: // Ok, now we've found the list of urls we need... deploy their classes.
509: Iterator jars = classpath.iterator();
510: while (jars.hasNext()) {
511: URL neededURL = (URL) jars.next();
512: di.addLibraryJar(neededURL);
513: log.debug("deployed classes for " + neededURL);
514: }
515: }
516:
517: /** Parse the META-INF/jboss-service.xml descriptor
518: */
519: protected void parseDocument(DeploymentInfo di) throws Exception {
520: InputStream stream = null;
521: try {
522: if (di.document == null) {
523: DocumentBuilderFactory factory = DocumentBuilderFactory
524: .newInstance();
525: factory.setNamespaceAware(useNamespaceAwareParser);
526: DocumentBuilder parser = factory.newDocumentBuilder();
527: URL docURL = di.localUrl;
528: URLClassLoader localCL = di.localCl;
529: // Load jboss-service.xml from the jar or directory
530: if (di.isXML == false) {
531: // Check the suffix to descriptor mapping
532: String[] descriptors = getDescriptorName(di);
533: for (int n = 0; n < descriptors.length; n++) {
534: String descriptor = descriptors[n];
535: docURL = localCL.findResource(descriptor);
536: if (docURL != null) {
537: // If this is a unpacked deployment, update the watch url
538: if (di.url.getPath().endsWith("/")) {
539: di.watch = new URL(di.url, descriptor);
540: log.debug("Updated watch URL to: "
541: + di.watch);
542: }
543: break;
544: }
545: }
546: // No descriptors, use the default META-INF/jboss-service.xml
547: if (docURL == null)
548: docURL = localCL.findResource(JBOSS_SERVICE);
549: }
550: // Validate that the descriptor was found
551: if (docURL == null)
552: throw new DeploymentException(
553: "Failed to find META-INF/jboss-service.xml for archive "
554: + di.shortName);
555:
556: stream = docURL.openStream();
557: InputSource is = new InputSource(stream);
558: is.setSystemId(docURL.toString());
559: parser.setEntityResolver(new JBossEntityResolver());
560: di.document = parser.parse(is);
561: } else {
562: log.debug("Using existing deployment.document");
563: }
564: } finally {
565: // Close the stream to get around "Too many open files"-errors
566: try {
567: stream.close();
568: } catch (Exception ignore) {
569: }
570: }
571: }
572:
573: /**
574: * The <code>inflateJar</code> copies the jar entries
575: * from the jar url jarUrl to the directory destDir.
576: * It can be used on the whole jar, a directory, or
577: * a specific file in the jar.
578: *
579: * @param url the <code>URL</code> if the directory or entry to copy.
580: * @param destDir the <code>File</code> value of the directory in which to
581: * place the inflated copies.
582: *
583: * @exception DeploymentException if an error occurs
584: * @exception IOException if an error occurs
585: */
586: protected void inflateJar(URL url, File destDir, String path)
587: throws DeploymentException, IOException {
588: String filename = url.getFile();
589: JarFile jarFile = new JarFile(filename);
590: try {
591: for (Enumeration e = jarFile.entries(); e.hasMoreElements();) {
592: JarEntry entry = (JarEntry) e.nextElement();
593: String name = entry.getName();
594:
595: if (path == null || name.startsWith(path)) {
596: File outFile = new File(destDir, name);
597: if (!outFile.exists()) {
598: if (entry.isDirectory()) {
599: outFile.mkdirs();
600: } else {
601: Streams.copyb(
602: jarFile.getInputStream(entry),
603: new FileOutputStream(outFile));
604: }
605: } // end of if (outFile.exists())
606: } // end of if (matches path)
607: }
608: } finally {
609: jarFile.close();
610: }
611: }
612:
613: // Private -------------------------------------------------------
614:
615: /**
616: * Parse the deployment url for its suffix and return a list of the accepted service
617: * descriptor names to look for.
618: *
619: * @param sdi - the sar deployment info
620: * @return the array of sar archive/directory relative names of the service descriptor. If
621: * there is no suffix to descriptor mapping, the default of {JBOSS_SERVICE} will be
622: * returned.
623: */
624: private String[] getDescriptorName(DeploymentInfo sdi) {
625: String[] descriptorNames = { JBOSS_SERVICE };
626: String shortName = sdi.shortName;
627: int dot = shortName.lastIndexOf('.');
628: if (dot >= 0) {
629: String suffix = shortName.substring(dot);
630: List descriptors = (List) suffixToDescriptorMap.get(suffix);
631: if (descriptors != null) {
632: descriptorNames = new String[descriptors.size()];
633: descriptors.toArray(descriptorNames);
634: }
635: }
636: return descriptorNames;
637: }
638: }
|