1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.deployment;
23
24 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 import java.net.MalformedURLException;
30 import java.net.URL;
31 import java.util.Enumeration;
32 import java.util.jar.JarEntry;
33 import java.util.jar.JarFile;
34
35 import javax.management.Notification;
36
37 import org.jboss.mx.util.MBeanProxyExt;
38 import org.jboss.system.ServiceMBeanSupport;
39 import org.jboss.system.server.ServerConfig;
40 import org.jboss.system.server.ServerConfigLocator;
41 import org.jboss.system.server.ServerConfigUtil;
42 import org.jboss.util.file.JarUtils;
43 import org.jboss.util.stream.Streams;
44
45 /**
46 * An abstract {@link SubDeployer}.
47 *
48 * Provides registration with {@link MainDeployer} as well as
49 * implementations of init, create, start, stop and destroy that
50 * generate JMX notifications on completion of the method.
51 *
52 * @version <tt>$Revision: 57108 $</tt>
53 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
54 * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
55 * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
56 */
57 public abstract class SubDeployerSupport extends ServiceMBeanSupport
58 implements SubDeployerExt, SubDeployerExtMBean
59 {
60 /**
61 * Holds the native library <em>suffix</em> for this system.
62 *
63 * Determined by examining the result of System.mapLibraryName(specialToken).
64 * The special token defaults to "XxX", but can be changed by setting the
65 * system property: <tt>org.jboss.deployment.SubDeployerSupport.nativeLibToken</tt>.
66 */
67 protected static final String nativeSuffix;
68
69 /**
70 * Holds the native library <em>prefix</em> for this system.
71 *
72 * @see #nativeSuffix
73 */
74 protected static final String nativePrefix;
75
76 /** A proxy to the MainDeployer. */
77 protected MainDeployerMBean mainDeployer;
78
79 /** The temporary directory into which deployments are unpacked */
80 protected File tempDeployDir;
81
82 /** The list of enhancedSuffixes for this subdeployer */
83 protected String[] enhancedSuffixes;
84
85 /** The suffixes of interest to this subdeployer */
86 protected String[] suffixes;
87
88 /** The relative order of this subdeployer - not really used */
89 protected int relativeOrder = -1;
90
91 /** The temporary directory where native libs are unpacked. */
92 private File tempNativeDir;
93
94 /** Whether to load native libraries */
95 private boolean loadNative = false;
96
97 /**
98 * The <code>createService</code> method is one of the ServiceMBean lifecyle operations.
99 * (no jmx tag needed from superinterface)
100 *
101 * @exception Exception if an error occurs
102 */
103 protected void createService() throws Exception
104 {
105 // get the temporary directories to use
106 ServerConfig config = ServerConfigLocator.locate();
107 tempNativeDir = config.getServerNativeDir();
108 tempDeployDir = config.getServerTempDeployDir();
109 loadNative = ServerConfigUtil.isLoadNative();
110
111 // Setup the proxy to mainDeployer
112 mainDeployer = (MainDeployerMBean)
113 MBeanProxyExt.create(MainDeployerMBean.class,
114 MainDeployerMBean.OBJECT_NAME,
115 server);
116 }
117
118 /**
119 * Performs SubDeployer registration.
120 */
121 protected void startService() throws Exception
122 {
123 // Register with the main deployer
124 mainDeployer.addDeployer(this);
125 }
126
127 /**
128 * Performs SubDeployer deregistration.
129 */
130 protected void stopService() throws Exception
131 {
132 // Unregister with the main deployer
133 mainDeployer.removeDeployer(this);
134 }
135
136 /**
137 * Clean up.
138 */
139 protected void destroyService() throws Exception
140 {
141 // Help the GC
142 mainDeployer = null;
143 tempNativeDir = null;
144 }
145
146 /**
147 * Set an array of suffixes of interest to this subdeployer.
148 * No need to register twice suffixes that may refer to
149 * unpacked deployments (e.g. .sar, .sar/).
150 *
151 * @param suffixes array of suffix strings
152 */
153 protected void setSuffixes(String[] suffixes)
154 {
155 this.suffixes = suffixes;
156 }
157
158 /**
159 * Set the relative order of the specified suffixes
160 * all to the same value.
161 *
162 * @param relativeOrder the relative order of the specified suffixes
163 */
164 protected void setRelativeOrder(int relativeOrder)
165 {
166 this.relativeOrder = relativeOrder;
167 }
168
169 /**
170 * Set the enhanced suffixes list for this deployer,
171 * causing also the supported suffixes list to be updated.
172 *
173 * Each enhanced suffix entries has the form:
174 *
175 * [order:]suffix
176 *
177 * No need to register twice suffixes that may refer to
178 * unpacked deployments (e.g. .sar, .sar/).
179 *
180 * @param enhancedSuffixes
181 */
182 public void setEnhancedSuffixes(String[] enhancedSuffixes)
183 {
184 if (enhancedSuffixes != null)
185 {
186 int len = enhancedSuffixes.length;
187 suffixes = new String[len];
188
189 for (int i = 0; i < len; i++)
190 {
191 // parse each enhancedSuffix
192 SuffixOrderHelper.EnhancedSuffix e =
193 new SuffixOrderHelper.EnhancedSuffix(enhancedSuffixes[i]);
194
195 suffixes[i] = e.suffix;
196 }
197 }
198 this.enhancedSuffixes = enhancedSuffixes;
199 }
200
201 /**
202 * Get an array of enhancedSuffixes
203 *
204 * @return array of enhanced suffix strings
205 */
206 public String[] getEnhancedSuffixes()
207 {
208 return enhancedSuffixes;
209 }
210
211 /**
212 * Get an array of suffixes of interest to this subdeployer
213 *
214 * @return array of suffix strings
215 */
216 public String[] getSuffixes()
217 {
218 return suffixes;
219 }
220
221 /**
222 * Get the relative order of the specified suffixes
223 *
224 * @return the relative order of the specified suffixes
225 */
226 public int getRelativeOrder()
227 {
228 return relativeOrder;
229 }
230
231 /**
232 * A default implementation that uses the suffixes registered
233 * through either setSuffixes() or setEnhancedSuffixes(), to
234 * decide if a module is deployable by this deployer.
235 *
236 * If (according to DeploymentInfo) the deployment refers to
237 * a directory, but not an xml or script deployment, then
238 * the deployment suffix will be checked also against the
239 * registered suffixes + "/".
240 *
241 * @param sdi the DeploymentInfo to check
242 * @return whether the deployer can handle the deployment
243 */
244 public boolean accepts(DeploymentInfo sdi)
245 {
246 String[] acceptedSuffixes = getSuffixes();
247 if (acceptedSuffixes == null)
248 {
249 return false;
250 }
251 else
252 {
253 String urlPath = sdi.url.getPath();
254 String shortName = sdi.shortName;
255 boolean checkDir = sdi.isDirectory && !(sdi.isXML || sdi.isScript);
256
257 for (int i = 0; i < acceptedSuffixes.length; i++)
258 {
259 // First check the urlPath the might end in "/"
260 // then check the shortName where "/" is removed
261 if (urlPath.endsWith(acceptedSuffixes[i]) ||
262 (checkDir && shortName.endsWith(acceptedSuffixes[i])))
263 {
264 return true;
265 }
266 }
267 return false;
268 }
269 }
270
271 /**
272 * Sub-classes should override this method to provide
273 * custom 'init' logic.
274 *
275 * <p>This method calls the processNestedDeployments(di) method and then
276 * issues a JMX notification of type SubDeployer.INIT_NOTIFICATION.
277 * This behaviour can overridden by concrete sub-classes. If further
278 * initialization needs to be done, and you wish to preserve the
279 * functionality, be sure to call super.init(di) at the end of your
280 * implementation.
281 */
282 public void init(DeploymentInfo di) throws DeploymentException
283 {
284 processNestedDeployments(di);
285
286 emitNotification(SubDeployer.INIT_NOTIFICATION, di);
287 }
288
289 /**
290 * Sub-classes should override this method to provide
291 * custom 'create' logic.
292 *
293 * This method issues a JMX notification of type SubDeployer.CREATE_NOTIFICATION.
294 */
295 public void create(DeploymentInfo di) throws DeploymentException
296 {
297 emitNotification(SubDeployer.CREATE_NOTIFICATION, di);
298 }
299
300 /**
301 * Sub-classes should override this method to provide
302 * custom 'start' logic.
303 *
304 * This method issues a JMX notification of type SubDeployer.START_NOTIFICATION.
305 */
306 public void start(DeploymentInfo di) throws DeploymentException
307 {
308 emitNotification(SubDeployer.START_NOTIFICATION, di);
309 }
310
311 /**
312 * Sub-classes should override this method to provide
313 * custom 'stop' logic.
314 *
315 * This method issues a JMX notification of type SubDeployer.START_NOTIFICATION.
316 */
317 public void stop(DeploymentInfo di) throws DeploymentException
318 {
319 emitNotification(SubDeployer.STOP_NOTIFICATION, di);
320 }
321
322 /**
323 * Sub-classes should override this method to provide
324 * custom 'destroy' logic.
325 *
326 * This method issues a JMX notification of type SubDeployer.DESTROY_NOTIFICATION.
327 */
328 public void destroy(DeploymentInfo di) throws DeploymentException
329 {
330 emitNotification(SubDeployer.DESTROY_NOTIFICATION, di);
331 }
332
333 /**
334 * Simple helper to emit a subdeployer notification containing DeploymentInfo
335 */
336 protected void emitNotification(String type, DeploymentInfo di)
337 {
338 Notification notification = new Notification(type, this, getNextNotificationSequenceNumber());
339 notification.setUserData(di);
340 sendNotification(notification);
341 }
342
343 /**
344 * The <code>processNestedDeployments</code> method searches for any nested and
345 * deployable elements. Only Directories and Zipped archives are processed,
346 * and those are delegated to the addDeployableFiles and addDeployableJar
347 * methods respectively. This method can be overridden for alternate
348 * behaviour.
349 */
350 protected void processNestedDeployments(DeploymentInfo di) throws DeploymentException
351 {
352 log.debug("looking for nested deployments in : " + di.url);
353 if (di.isXML)
354 {
355 // no nested archives in an xml file
356 return;
357 }
358
359 if (di.isDirectory)
360 {
361 File f = new File(di.url.getFile());
362 if (!f.isDirectory())
363 {
364 // something is screwy
365 throw new DeploymentException
366 ("Deploy file incorrectly reported as a directory: " + di.url);
367 }
368
369 addDeployableFiles(di, f);
370 }
371 else
372 {
373 try
374 {
375 // Obtain a jar url for the nested jar
376 URL nestedURL = JarUtils.extractNestedJar(di.localUrl, this.tempDeployDir);
377 JarFile jarFile = new JarFile(nestedURL.getFile());
378 addDeployableJar(di, jarFile);
379 }
380 catch (Exception e)
381 {
382 log.warn("Failed to add deployable jar: " + di.localUrl, e);
383
384 //
385 // jason: should probably throw new DeploymentException
386 // ("Failed to add deployable jar: " + jarURLString, e);
387 // rather than make assumptions to what type of deployable
388 // file this was that failed...
389 //
390
391 return;
392 }
393 }
394 }
395
396 /**
397 * This method returns true if the name is a recognized archive file.
398 *
399 * It will query the MainDeployer that keeps a dynamically updated
400 * list of known archive extensions.
401 *
402 * @param name The "short-name" of the URL. It will have any trailing '/'
403 * characters removed, and any directory structure has been removed.
404 * @param url The full url.
405 *
406 * @return true iff the name ends in a known archive extension: .jar, .sar,
407 * .ear, .rar, .zip, .wsr, .war, or if the name matches the native
408 * library conventions.
409 */
410 protected boolean isDeployable(String name, URL url)
411 {
412 // any file under META-INF is not deployable; this method is called
413 // also for zipped content, e.g. dir1/dir2.sar/META-INF/bla.xml
414 if (url.getPath().indexOf("META-INF") != -1)
415 {
416 return false;
417 }
418 String[] acceptedSuffixes = mainDeployer.getSuffixOrder();
419 for (int i = 0; i < acceptedSuffixes.length; i++)
420 {
421 if (name.endsWith(acceptedSuffixes[i]))
422 {
423 return true;
424 }
425 }
426 // this is probably obsolete
427 return (name.endsWith(nativeSuffix) && name.startsWith(nativePrefix));
428 }
429
430 /**
431 * This method recursively searches the directory structure for any files
432 * that are deployable (@see isDeployable). If a directory is found to
433 * be deployable, then its subfiles and subdirectories are not searched.
434 *
435 * @param di the DeploymentInfo
436 * @param dir The root directory to start searching.
437 */
438 protected void addDeployableFiles(DeploymentInfo di, File dir)
439 throws DeploymentException
440 {
441 File[] files = dir.listFiles();
442 for (int i = 0; i < files.length; i++)
443 {
444 File file = files[i];
445 String name = file.getName();
446 try
447 {
448 URL url = file.toURL();
449 if (isDeployable(name, url))
450 {
451 deployUrl(di, url, name);
452 // we don't want deployable units processed any further
453 continue;
454 }
455 }
456 catch (MalformedURLException e)
457 {
458 log.warn("File name invalid; ignoring: " + file, e);
459 }
460 if (file.isDirectory())
461 {
462 addDeployableFiles(di, file);
463 }
464 }
465 }
466
467 /**
468 * This method searches the entire jar file for any deployable files
469 * (@see isDeployable).
470 *
471 * @param di the DeploymentInfo
472 * @param jarFile the jar file to process.
473 */
474 protected void addDeployableJar(DeploymentInfo di, JarFile jarFile)
475 throws DeploymentException
476 {
477 String urlPrefix = "jar:"+di.localUrl.toString()+"!/";
478 for (Enumeration e = jarFile.entries(); e.hasMoreElements();)
479 {
480 JarEntry entry = (JarEntry)e.nextElement();
481 String name = entry.getName();
482 try
483 {
484 URL url = new URL(urlPrefix+name);
485 if (isDeployable(name, url))
486 {
487 // Obtain a jar url for the nested jar
488 URL nestedURL = JarUtils.extractNestedJar(url, this.tempDeployDir);
489 deployUrl(di, nestedURL, name);
490 }
491 }
492 catch (MalformedURLException mue)
493 {
494 //
495 // jason: why are we eating this exception?
496 //
497 log.warn("Jar entry invalid; ignoring: " + name, mue);
498 }
499 catch (IOException ex)
500 {
501 log.warn("Failed to extract nested jar; ignoring: " + name, ex);
502 }
503 }
504 }
505
506 protected void deployUrl(DeploymentInfo di, URL url, String name)
507 throws DeploymentException
508 {
509 log.debug("nested deployment: " + url);
510 try
511 {
512 //
513 // jason: need better handling for os/arch specific libraries
514 // should be able to have multipule native libs in an archive
515 // one for each supported platform (os/arch), we only want to
516 // load the one for the current platform.
517 //
518 // This probably means explitly listing the libraries in a
519 // deployment descriptor, which could probably also be used
520 // to explicitly map the files, as it might be possible to
521 // share a native lib between more than one version, no need
522 // to duplicate the file, metadata can be used to tell us
523 // what needs to be done.
524 //
525 // Also need this mapping to get around the different values
526 // which are used by vm vendors for os.arch and such...
527 //
528
529 if (name.endsWith(nativeSuffix) && name.startsWith(nativePrefix))
530 {
531 File destFile = new File(tempNativeDir, name);
532 log.info("Loading native library: " + destFile.toString());
533
534 File parent = destFile.getParentFile();
535 if (!parent.exists()) {
536 parent.mkdirs();
537 }
538
539 InputStream in = url.openStream();
540 OutputStream out = new FileOutputStream(destFile);
541 Streams.copyb(in, out);
542
543 out.flush();
544 out.close();
545 in.close();
546
547 if (loadNative)
548 System.load(destFile.toString());
549 }
550 else
551 {
552 new DeploymentInfo(url, di, getServer());
553 }
554 }
555 catch (Exception ex)
556 {
557 throw new DeploymentException
558 ("Could not deploy sub deployment "+name+" of deployment "+di.url, ex);
559 }
560 }
561
562 /////////////////////////////////////////////////////////////////////////
563 // Class Property Configuration //
564 /////////////////////////////////////////////////////////////////////////
565
566 /**
567 * Static configuration properties for this class. Allows easy access
568 * to change defaults with system properties.
569 */
570 protected static class ClassConfiguration
571 extends org.jboss.util.property.PropertyContainer
572 {
573 private String nativeLibToken = "XxX";
574
575 public ClassConfiguration()
576 {
577 // properties will be settable under our enclosing classes group
578 super(SubDeployerSupport.class);
579
580 // bind the properties & the access methods
581 bindMethod("nativeLibToken");
582 }
583
584 public void setNativeLibToken(final String token)
585 {
586 this.nativeLibToken = token;
587 }
588
589 public String getNativeLibToken()
590 {
591 return nativeLibToken;
592 }
593 }
594
595 /** The singleton class configuration object for this class. */
596 protected static final ClassConfiguration CONFIGURATION = new ClassConfiguration();
597
598 //
599 // jason: the following needs to be done after setting up the
600 // class config reference, so it is moved it down here.
601 //
602
603 /**
604 * Determine the native library suffix and prefix.
605 */
606 static
607 {
608 // get the token to use from config, incase the default needs
609 // to be changed to resolve problem with a specific platform
610 String token = CONFIGURATION.getNativeLibToken();
611
612 // then determine what the prefix and suffixes are for this platform
613 String nativex = System.mapLibraryName(token);
614 int xPos = nativex.indexOf(token);
615 nativePrefix = nativex.substring(0, xPos);
616 nativeSuffix = nativex.substring(xPos + 3);
617 }
618 }