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.io.OutputStream;
029: import java.net.MalformedURLException;
030: import java.net.URL;
031: import java.util.Enumeration;
032: import java.util.jar.JarEntry;
033: import java.util.jar.JarFile;
034:
035: import javax.management.Notification;
036:
037: import org.jboss.mx.util.MBeanProxyExt;
038: import org.jboss.system.ServiceMBeanSupport;
039: import org.jboss.system.server.ServerConfig;
040: import org.jboss.system.server.ServerConfigLocator;
041: import org.jboss.system.server.ServerConfigUtil;
042: import org.jboss.util.file.JarUtils;
043: import org.jboss.util.stream.Streams;
044:
045: /**
046: * An abstract {@link SubDeployer}.
047: *
048: * Provides registration with {@link MainDeployer} as well as
049: * implementations of init, create, start, stop and destroy that
050: * generate JMX notifications on completion of the method.
051: *
052: * @version <tt>$Revision: 57205 $</tt>
053: * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
054: * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
055: * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
056: */
057: public abstract class SubDeployerSupport extends ServiceMBeanSupport
058: implements SubDeployerExt, SubDeployerExtMBean {
059: /**
060: * Holds the native library <em>suffix</em> for this system.
061: *
062: * Determined by examining the result of System.mapLibraryName(specialToken).
063: * The special token defaults to "XxX", but can be changed by setting the
064: * system property: <tt>org.jboss.deployment.SubDeployerSupport.nativeLibToken</tt>.
065: */
066: protected static final String nativeSuffix;
067:
068: /**
069: * Holds the native library <em>prefix</em> for this system.
070: *
071: * @see #nativeSuffix
072: */
073: protected static final String nativePrefix;
074:
075: /** A proxy to the MainDeployer. */
076: protected MainDeployerMBean mainDeployer;
077:
078: /** The temporary directory into which deployments are unpacked */
079: protected File tempDeployDir;
080:
081: /** The list of enhancedSuffixes for this subdeployer */
082: protected String[] enhancedSuffixes;
083:
084: /** The suffixes of interest to this subdeployer */
085: protected String[] suffixes;
086:
087: /** The relative order of this subdeployer - not really used */
088: protected int relativeOrder = -1;
089:
090: /** The temporary directory where native libs are unpacked. */
091: private File tempNativeDir;
092:
093: /** Whether to load native libraries */
094: private boolean loadNative = false;
095:
096: /**
097: * The <code>createService</code> method is one of the ServiceMBean lifecyle operations.
098: * (no jmx tag needed from superinterface)
099: *
100: * @exception Exception if an error occurs
101: */
102: protected void createService() throws Exception {
103: // get the temporary directories to use
104: ServerConfig config = ServerConfigLocator.locate();
105: tempNativeDir = config.getServerNativeDir();
106: tempDeployDir = config.getServerTempDeployDir();
107: loadNative = ServerConfigUtil.isLoadNative();
108:
109: // Setup the proxy to mainDeployer
110: mainDeployer = (MainDeployerMBean) MBeanProxyExt.create(
111: MainDeployerMBean.class, MainDeployerMBean.OBJECT_NAME,
112: server);
113: }
114:
115: /**
116: * Performs SubDeployer registration.
117: */
118: protected void startService() throws Exception {
119: // Register with the main deployer
120: mainDeployer.addDeployer(this );
121: }
122:
123: /**
124: * Performs SubDeployer deregistration.
125: */
126: protected void stopService() throws Exception {
127: // Unregister with the main deployer
128: mainDeployer.removeDeployer(this );
129: }
130:
131: /**
132: * Clean up.
133: */
134: protected void destroyService() throws Exception {
135: // Help the GC
136: mainDeployer = null;
137: tempNativeDir = null;
138: }
139:
140: /**
141: * Set an array of suffixes of interest to this subdeployer.
142: * No need to register twice suffixes that may refer to
143: * unpacked deployments (e.g. .sar, .sar/).
144: *
145: * @param suffixes array of suffix strings
146: */
147: protected void setSuffixes(String[] suffixes) {
148: this .suffixes = suffixes;
149: }
150:
151: /**
152: * Set the relative order of the specified suffixes
153: * all to the same value.
154: *
155: * @param relativeOrder the relative order of the specified suffixes
156: */
157: protected void setRelativeOrder(int relativeOrder) {
158: this .relativeOrder = relativeOrder;
159: }
160:
161: /**
162: * Set the enhanced suffixes list for this deployer,
163: * causing also the supported suffixes list to be updated.
164: *
165: * Each enhanced suffix entries has the form:
166: *
167: * [order:]suffix
168: *
169: * No need to register twice suffixes that may refer to
170: * unpacked deployments (e.g. .sar, .sar/).
171: *
172: * @param enhancedSuffixes
173: */
174: public void setEnhancedSuffixes(String[] enhancedSuffixes) {
175: if (enhancedSuffixes != null) {
176: int len = enhancedSuffixes.length;
177: suffixes = new String[len];
178:
179: for (int i = 0; i < len; i++) {
180: // parse each enhancedSuffix
181: SuffixOrderHelper.EnhancedSuffix e = new SuffixOrderHelper.EnhancedSuffix(
182: enhancedSuffixes[i]);
183:
184: suffixes[i] = e.suffix;
185: }
186: }
187: this .enhancedSuffixes = enhancedSuffixes;
188: }
189:
190: /**
191: * Get an array of enhancedSuffixes
192: *
193: * @return array of enhanced suffix strings
194: */
195: public String[] getEnhancedSuffixes() {
196: return enhancedSuffixes;
197: }
198:
199: /**
200: * Get an array of suffixes of interest to this subdeployer
201: *
202: * @return array of suffix strings
203: */
204: public String[] getSuffixes() {
205: return suffixes;
206: }
207:
208: /**
209: * Get the relative order of the specified suffixes
210: *
211: * @return the relative order of the specified suffixes
212: */
213: public int getRelativeOrder() {
214: return relativeOrder;
215: }
216:
217: /**
218: * A default implementation that uses the suffixes registered
219: * through either setSuffixes() or setEnhancedSuffixes(), to
220: * decide if a module is deployable by this deployer.
221: *
222: * If (according to DeploymentInfo) the deployment refers to
223: * a directory, but not an xml or script deployment, then
224: * the deployment suffix will be checked also against the
225: * registered suffixes + "/".
226: *
227: * @param sdi the DeploymentInfo to check
228: * @return whether the deployer can handle the deployment
229: */
230: public boolean accepts(DeploymentInfo sdi) {
231: String[] acceptedSuffixes = getSuffixes();
232: if (acceptedSuffixes == null) {
233: return false;
234: } else {
235: String urlPath = sdi.url.getPath();
236: String shortName = sdi.shortName;
237: boolean checkDir = sdi.isDirectory
238: && !(sdi.isXML || sdi.isScript);
239:
240: for (int i = 0; i < acceptedSuffixes.length; i++) {
241: // First check the urlPath the might end in "/"
242: // then check the shortName where "/" is removed
243: if (urlPath.endsWith(acceptedSuffixes[i])
244: || (checkDir && shortName
245: .endsWith(acceptedSuffixes[i]))) {
246: return true;
247: }
248: }
249: return false;
250: }
251: }
252:
253: /**
254: * Sub-classes should override this method to provide
255: * custom 'init' logic.
256: *
257: * <p>This method calls the processNestedDeployments(di) method and then
258: * issues a JMX notification of type SubDeployer.INIT_NOTIFICATION.
259: * This behaviour can overridden by concrete sub-classes. If further
260: * initialization needs to be done, and you wish to preserve the
261: * functionality, be sure to call super.init(di) at the end of your
262: * implementation.
263: */
264: public void init(DeploymentInfo di) throws DeploymentException {
265: processNestedDeployments(di);
266:
267: emitNotification(SubDeployer.INIT_NOTIFICATION, di);
268: }
269:
270: /**
271: * Sub-classes should override this method to provide
272: * custom 'create' logic.
273: *
274: * This method issues a JMX notification of type SubDeployer.CREATE_NOTIFICATION.
275: */
276: public void create(DeploymentInfo di) throws DeploymentException {
277: emitNotification(SubDeployer.CREATE_NOTIFICATION, di);
278: }
279:
280: /**
281: * Sub-classes should override this method to provide
282: * custom 'start' logic.
283: *
284: * This method issues a JMX notification of type SubDeployer.START_NOTIFICATION.
285: */
286: public void start(DeploymentInfo di) throws DeploymentException {
287: emitNotification(SubDeployer.START_NOTIFICATION, di);
288: }
289:
290: /**
291: * Sub-classes should override this method to provide
292: * custom 'stop' logic.
293: *
294: * This method issues a JMX notification of type SubDeployer.START_NOTIFICATION.
295: */
296: public void stop(DeploymentInfo di) throws DeploymentException {
297: emitNotification(SubDeployer.STOP_NOTIFICATION, di);
298: }
299:
300: /**
301: * Sub-classes should override this method to provide
302: * custom 'destroy' logic.
303: *
304: * This method issues a JMX notification of type SubDeployer.DESTROY_NOTIFICATION.
305: */
306: public void destroy(DeploymentInfo di) throws DeploymentException {
307: emitNotification(SubDeployer.DESTROY_NOTIFICATION, di);
308: }
309:
310: /**
311: * Simple helper to emit a subdeployer notification containing DeploymentInfo
312: */
313: protected void emitNotification(String type, DeploymentInfo di) {
314: Notification notification = new Notification(type, this ,
315: getNextNotificationSequenceNumber());
316: notification.setUserData(di);
317: sendNotification(notification);
318: }
319:
320: /**
321: * The <code>processNestedDeployments</code> method searches for any nested and
322: * deployable elements. Only Directories and Zipped archives are processed,
323: * and those are delegated to the addDeployableFiles and addDeployableJar
324: * methods respectively. This method can be overridden for alternate
325: * behaviour.
326: */
327: protected void processNestedDeployments(DeploymentInfo di)
328: throws DeploymentException {
329: log.debug("looking for nested deployments in : " + di.url);
330: if (di.isXML) {
331: // no nested archives in an xml file
332: return;
333: }
334:
335: if (di.isDirectory) {
336: File f = new File(di.url.getFile());
337: if (!f.isDirectory()) {
338: // something is screwy
339: throw new DeploymentException(
340: "Deploy file incorrectly reported as a directory: "
341: + di.url);
342: }
343:
344: addDeployableFiles(di, f);
345: } else {
346: try {
347: // Obtain a jar url for the nested jar
348: URL nestedURL = JarUtils.extractNestedJar(di.localUrl,
349: this .tempDeployDir);
350: JarFile jarFile = new JarFile(nestedURL.getFile());
351: addDeployableJar(di, jarFile);
352: } catch (Exception e) {
353: log.warn(
354: "Failed to add deployable jar: " + di.localUrl,
355: e);
356:
357: //
358: // jason: should probably throw new DeploymentException
359: // ("Failed to add deployable jar: " + jarURLString, e);
360: // rather than make assumptions to what type of deployable
361: // file this was that failed...
362: //
363:
364: return;
365: }
366: }
367: }
368:
369: /**
370: * This method returns true if the name is a recognized archive file.
371: *
372: * It will query the MainDeployer that keeps a dynamically updated
373: * list of known archive extensions.
374: *
375: * @param name The "short-name" of the URL. It will have any trailing '/'
376: * characters removed, and any directory structure has been removed.
377: * @param url The full url.
378: *
379: * @return true iff the name ends in a known archive extension: .jar, .sar,
380: * .ear, .rar, .zip, .wsr, .war, or if the name matches the native
381: * library conventions.
382: */
383: protected boolean isDeployable(String name, URL url) {
384: // any file under META-INF is not deployable; this method is called
385: // also for zipped content, e.g. dir1/dir2.sar/META-INF/bla.xml
386: if (url.getPath().indexOf("META-INF") != -1) {
387: return false;
388: }
389: String[] acceptedSuffixes = mainDeployer.getSuffixOrder();
390: for (int i = 0; i < acceptedSuffixes.length; i++) {
391: if (name.endsWith(acceptedSuffixes[i])) {
392: return true;
393: }
394: }
395: // this is probably obsolete
396: return (name.endsWith(nativeSuffix) && name
397: .startsWith(nativePrefix));
398: }
399:
400: /**
401: * This method recursively searches the directory structure for any files
402: * that are deployable (@see isDeployable). If a directory is found to
403: * be deployable, then its subfiles and subdirectories are not searched.
404: *
405: * @param di the DeploymentInfo
406: * @param dir The root directory to start searching.
407: */
408: protected void addDeployableFiles(DeploymentInfo di, File dir)
409: throws DeploymentException {
410: File[] files = dir.listFiles();
411: for (int i = 0; i < files.length; i++) {
412: File file = files[i];
413: String name = file.getName();
414: try {
415: URL url = file.toURL();
416: if (isDeployable(name, url)) {
417: deployUrl(di, url, name);
418: // we don't want deployable units processed any further
419: continue;
420: }
421: } catch (MalformedURLException e) {
422: log.warn("File name invalid; ignoring: " + file, e);
423: }
424: if (file.isDirectory()) {
425: addDeployableFiles(di, file);
426: }
427: }
428: }
429:
430: /**
431: * This method searches the entire jar file for any deployable files
432: * (@see isDeployable).
433: *
434: * @param di the DeploymentInfo
435: * @param jarFile the jar file to process.
436: */
437: protected void addDeployableJar(DeploymentInfo di, JarFile jarFile)
438: throws DeploymentException {
439: String urlPrefix = "jar:" + di.localUrl.toString() + "!/";
440: for (Enumeration e = jarFile.entries(); e.hasMoreElements();) {
441: JarEntry entry = (JarEntry) e.nextElement();
442: String name = entry.getName();
443: try {
444: URL url = new URL(urlPrefix + name);
445: if (isDeployable(name, url)) {
446: // Obtain a jar url for the nested jar
447: URL nestedURL = JarUtils.extractNestedJar(url,
448: this .tempDeployDir);
449: deployUrl(di, nestedURL, name);
450: }
451: } catch (MalformedURLException mue) {
452: //
453: // jason: why are we eating this exception?
454: //
455: log.warn("Jar entry invalid; ignoring: " + name, mue);
456: } catch (IOException ex) {
457: log.warn("Failed to extract nested jar; ignoring: "
458: + name, ex);
459: }
460: }
461: }
462:
463: protected void deployUrl(DeploymentInfo di, URL url, String name)
464: throws DeploymentException {
465: log.debug("nested deployment: " + url);
466: try {
467: //
468: // jason: need better handling for os/arch specific libraries
469: // should be able to have multipule native libs in an archive
470: // one for each supported platform (os/arch), we only want to
471: // load the one for the current platform.
472: //
473: // This probably means explitly listing the libraries in a
474: // deployment descriptor, which could probably also be used
475: // to explicitly map the files, as it might be possible to
476: // share a native lib between more than one version, no need
477: // to duplicate the file, metadata can be used to tell us
478: // what needs to be done.
479: //
480: // Also need this mapping to get around the different values
481: // which are used by vm vendors for os.arch and such...
482: //
483:
484: if (name.endsWith(nativeSuffix)
485: && name.startsWith(nativePrefix)) {
486: File destFile = new File(tempNativeDir, name);
487: log.info("Loading native library: "
488: + destFile.toString());
489:
490: File parent = destFile.getParentFile();
491: if (!parent.exists()) {
492: parent.mkdirs();
493: }
494:
495: InputStream in = url.openStream();
496: OutputStream out = new FileOutputStream(destFile);
497: Streams.copyb(in, out);
498:
499: out.flush();
500: out.close();
501: in.close();
502:
503: if (loadNative)
504: System.load(destFile.toString());
505: } else {
506: new DeploymentInfo(url, di, getServer());
507: }
508: } catch (Exception ex) {
509: throw new DeploymentException(
510: "Could not deploy sub deployment " + name
511: + " of deployment " + di.url, ex);
512: }
513: }
514:
515: /////////////////////////////////////////////////////////////////////////
516: // Class Property Configuration //
517: /////////////////////////////////////////////////////////////////////////
518:
519: /**
520: * Static configuration properties for this class. Allows easy access
521: * to change defaults with system properties.
522: */
523: protected static class ClassConfiguration extends
524: org.jboss.util.property.PropertyContainer {
525: private String nativeLibToken = "XxX";
526:
527: public ClassConfiguration() {
528: // properties will be settable under our enclosing classes group
529: super (SubDeployerSupport.class);
530:
531: // bind the properties & the access methods
532: bindMethod("nativeLibToken");
533: }
534:
535: public void setNativeLibToken(final String token) {
536: this .nativeLibToken = token;
537: }
538:
539: public String getNativeLibToken() {
540: return nativeLibToken;
541: }
542: }
543:
544: /** The singleton class configuration object for this class. */
545: protected static final ClassConfiguration CONFIGURATION = new ClassConfiguration();
546:
547: //
548: // jason: the following needs to be done after setting up the
549: // class config reference, so it is moved it down here.
550: //
551:
552: /**
553: * Determine the native library suffix and prefix.
554: */
555: static {
556: // get the token to use from config, incase the default needs
557: // to be changed to resolve problem with a specific platform
558: String token = CONFIGURATION.getNativeLibToken();
559:
560: // then determine what the prefix and suffixes are for this platform
561: String nativex = System.mapLibraryName(token);
562: int xPos = nativex.indexOf(token);
563: nativePrefix = nativex.substring(0, xPos);
564: nativeSuffix = nativex.substring(xPos + 3);
565: }
566: }
|