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

Save This Page
Home » jboss-5.0.0.CR1-src » org » jboss » deployment » [javadoc | source]