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.BufferedInputStream;
25 import java.io.BufferedOutputStream;
26 import java.io.File;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.OutputStream;
31 import java.net.MalformedURLException;
32 import java.net.URL;
33 import java.net.URLClassLoader;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.Comparator;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.LinkedList;
41 import java.util.List;
42 import java.util.ListIterator;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.StringTokenizer;
46 import java.util.jar.Attributes;
47 import java.util.jar.Manifest;
48 import javax.management.MBeanServer;
49 import javax.management.MalformedObjectNameException;
50 import javax.management.Notification;
51 import javax.management.ObjectName;
52
53 import org.jboss.deployers.client.spi.DeployerClient;
54 import org.jboss.deployers.client.spi.Deployment;
55 import org.jboss.deployers.structure.spi.DeploymentContext;
56 import org.jboss.deployers.structure.spi.DeploymentUnit;
57 import org.jboss.deployers.structure.spi.main.MainDeployerStructure;
58 import org.jboss.deployers.vfs.spi.client.VFSDeployment;
59 import org.jboss.deployers.vfs.spi.client.VFSDeploymentFactory;
60 import org.jboss.kernel.spi.dependency.KernelController;
61 import org.jboss.system.ServiceMBeanSupport;
62 import org.jboss.system.server.ServerConfig;
63 import org.jboss.system.server.ServerConfigLocator;
64 import org.jboss.util.file.Files;
65 import org.jboss.util.file.JarUtils;
66 import org.jboss.util.stream.Streams;
67 import org.jboss.virtual.VFS;
68 import org.jboss.virtual.VirtualFile;
69
70 /**
71 * The legacy component for deployer management. This now simply delegates to the
72 * Main
73 *
74 * @deprecated see org.jboss.deployers.spi.deployment.MainDeployer
75 *
76 * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
77 * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
78 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
79 * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
80 * @author adrian@jboss.org
81 * @version $Revision: 69696 $
82 */
83 public class MainDeployer extends ServiceMBeanSupport
84 implements Deployer, MainDeployerMBean
85 {
86 /** The controller */
87 private KernelController controller;
88 private DeployerClient delegate;
89 private Map<URL, String> contextMap = Collections.synchronizedMap(new HashMap<URL, String>());
90
91 /** The deployment factory */
92 private VFSDeploymentFactory deploymentFactory = VFSDeploymentFactory.getInstance();
93
94 /**
95 * The variable <code>serviceController</code> is used by the
96 * checkIncompleteDeployments method to ask for lists of mbeans
97 * with deployment problems.
98 */
99 private ObjectName serviceController;
100
101 /** Deployers **/
102 private final LinkedList deployers = new LinkedList();
103
104 /** A Map of URL -> DeploymentInfo */
105 private final Map deploymentMap = Collections.synchronizedMap(new HashMap());
106
107 /** A list of all deployments that have deployers. */
108 private final List deploymentList = new ArrayList();
109
110 /** A list of all deployments that do not have deployers. */
111 private final List waitingDeployments = new ArrayList();
112
113 /** A helper for sorting deployment URLs. */
114 private final DeploymentSorter sorter = new DeploymentSorter();
115
116 /** A helper for sorting deploymentInfos */
117 private final Comparator infoSorter = new DeploymentInfoComparator(sorter);
118
119 /** Helper class handling the SuffixOrder and EnhancedSuffixOrder attributes */
120 private final SuffixOrderHelper suffixOrderHelper = new SuffixOrderHelper(sorter);
121
122 /** Should local copies be made of resources on the local file system */
123 private boolean copyFiles = true;
124
125 /** The temporary directory for deployments. */
126 private File tempDir;
127
128 /** The string naming the tempDir **/
129 private String tempDirString;
130
131 /**
132 * Explict no-args contsructor for JMX.
133 */
134 public MainDeployer()
135 {
136 // Is there a better place to obtain startup information?
137 String localCopy = System.getProperty("jboss.deploy.localcopy");
138 if (localCopy != null && (
139 localCopy.equalsIgnoreCase("false") ||
140 localCopy.equalsIgnoreCase("no") ||
141 localCopy.equalsIgnoreCase("off")))
142 {
143 log.debug("Disabling local copies of file: urls");
144 copyFiles = false;
145 }
146 }
147
148 public DeployerClient getKernelMainDeployer()
149 {
150 return delegate;
151 }
152 public void setKernelMainDeployer(DeployerClient delegate)
153 {
154 this.delegate = delegate;
155 }
156
157 public KernelController getController()
158 {
159 return controller;
160 }
161
162 public void setController(KernelController controller)
163 {
164 this.controller = controller;
165 }
166
167 /** Get the flag indicating whether directory content will be deployed
168 *
169 * @return the file copy flag
170 * @jmx.managed-attribute
171 */
172 public boolean getCopyFiles()
173 {
174 return copyFiles;
175 }
176 /** Set the flag indicating whether directory content will be deployed. The
177 * default value is taken from the jboss.deploy.localcopy system
178 * property.
179 *
180 * @param copyFiles the local copy flag value
181 * @jmx.managed-attribute
182 */
183 public void setCopyFiles(boolean copyFiles)
184 {
185 this.copyFiles = copyFiles;
186 }
187
188 /** Get the temp directory
189 *
190 * @return the path to the local tmp directory
191 * @jmx.managed-attribute
192 */
193 public File getTempDir()
194 {
195 return tempDir;
196 }
197 /** Set the temp directory
198 *
199 * @param tempDir the path to the local tmp directory
200 * @jmx.managed-attribute
201 */
202 public void setTempDir(File tempDir)
203 {
204 this.tempDir = tempDir;
205 }
206
207 /** Get the temp directory
208 *
209 * @return the path to the local tmp directory
210 * @jmx.managed-attribute
211 */
212 public String getTempDirString()
213 {
214 return tempDirString;
215 }
216
217 /** Get the ordering of the deployment suffixes
218 *
219 * @return the ordering of the deployment suffixes
220 * @jmx.managed-attribute
221 */
222 public String[] getSuffixOrder()
223 {
224 return suffixOrderHelper.getSuffixOrder();
225 }
226
227 /** Get the enhanced suffix order
228 *
229 * @return the enhanced suffix order
230 * @jmx.managed-attribute
231 */
232 public String[] getEnhancedSuffixOrder()
233 {
234 return suffixOrderHelper.getEnhancedSuffixes();
235 }
236
237 /** Set the enhanced suffix order
238 *
239 * @param enhancedSuffixOrder the enhanced suffix order
240 * @jmx.managed-attribute
241 */
242 public void setEnhancedSuffixOrder(String[] enhancedSuffixOrder)
243 {
244 suffixOrderHelper.setEnhancedSuffixes(enhancedSuffixOrder);
245 }
246
247 /**
248 * Describe <code>setServiceController</code> method here.
249 *
250 * @param serviceController an <code>ObjectName</code> value
251 * @jmx.managed-attribute
252 */
253 public void setServiceController(final ObjectName serviceController)
254 {
255 this.serviceController = serviceController;
256 }
257
258 /**
259 * The <code>listDeployed</code> method returns a collection of DeploymemtInfo
260 * objects for the currently deployed packages.
261 *
262 * @return a <code>Collection</code> value
263 * @jmx.managed-operation
264 */
265 public Collection listDeployed()
266 {
267 synchronized (deploymentList)
268 {
269 log.debug("deployment list string: " + deploymentList);
270 return new ArrayList(deploymentList);
271 }
272 }
273
274 /**
275 * The <code>listDeployedModules</code> method returns a collection of
276 * SerializableDeploymentInfo objects for the currently deployed packages.
277 *
278 * @return a <code>Collection</code> value
279 * @jmx.managed-operation
280 */
281 public Collection listDeployedModules()
282 {
283 log.debug("listDeployedModules");
284
285 HashMap map = new HashMap();
286 synchronized (deploymentList)
287 {
288 Collection col = new ArrayList(deploymentList);
289
290 // create a map entry for each deployment
291 for (Iterator it = col.iterator(); it.hasNext();)
292 {
293 DeploymentInfo info = (DeploymentInfo) it.next();
294 map.put(info.url, new SerializableDeploymentInfo(info));
295 // assign parent and sub deployments
296 fillParentAndChildrenSDI(info, map);
297 }
298 }
299
300 // map.values is not serializable, so we copy
301 return new ArrayList(map.values());
302 }
303
304 /**
305 * Describe <code>listDeployedAsString</code> method here.
306 *
307 * @return a <code>String</code> value
308 * @jmx.managed-operation
309 */
310 public String listDeployedAsString()
311 {
312 return "<pre>" + listDeployed() + "</pre>";
313 }
314
315 /**
316 * The <code>listIncompletelyDeployed</code> method returns a list of packages that have
317 * not deployed completely. The toString method will include any exception in the status
318 * field.
319 *
320 * @return a <code>Collection</code> value
321 * @jmx.managed-operation
322 */
323 public Collection listIncompletelyDeployed()
324 {
325 List id = new ArrayList();
326 List copy;
327 synchronized (deploymentList)
328 {
329 copy = new ArrayList(deploymentList);
330 }
331 for (Iterator i = copy.iterator(); i.hasNext();)
332 {
333 DeploymentInfo di = (DeploymentInfo)i.next();
334 if (!"Deployed".equals(di.status) && !"Starting".equals(di.status))
335 {
336 id.add(di);
337 } // end of if ()
338
339 } // end of for ()
340 return id;
341 }
342
343 /**
344 * The <code>listWaitingForDeployer</code> method returns a collection
345 * of the packages that currently have no identified deployer.
346 *
347 * @return a <code>Collection</code> value
348 * @jmx.managed-operation
349 */
350 public Collection listWaitingForDeployer()
351 {
352 synchronized (waitingDeployments)
353 {
354 return new ArrayList(waitingDeployments);
355 }
356 }
357
358 /**
359 * The <code>addDeployer</code> method registers a deployer with the main deployer.
360 * Any waiting packages are tested to see if the new deployer will deploy them.
361 *
362 * @param deployer a <code>SubDeployer</code> value
363 * @jmx.managed-operation
364 */
365 public void addDeployer(final SubDeployer deployer)
366 {
367 log.debug("Adding deployer: " + deployer);
368 ObjectName deployerName = deployer.getServiceName();
369
370 synchronized(deployers)
371 {
372 deployers.addFirst(deployer);
373 try
374 {
375 String[] suffixes = (String[]) server.getAttribute(deployerName, "EnhancedSuffixes");
376 suffixOrderHelper.addEnhancedSuffixes(suffixes);
377 }
378 catch(Exception e)
379 {
380 log.debug(deployerName + " does not support EnhancedSuffixes");
381 suffixOrderHelper.addSuffixes(deployer.getSuffixes(), deployer.getRelativeOrder());
382 }
383 }
384
385 // Send a notification about the deployer addition
386 Notification msg = new Notification(ADD_DEPLOYER, this, getNextNotificationSequenceNumber());
387 msg.setUserData(deployerName);
388 sendNotification(msg);
389
390 synchronized (waitingDeployments)
391 {
392 List copy = new ArrayList(waitingDeployments);
393 waitingDeployments.clear();
394 for (Iterator i = copy.iterator(); i.hasNext();)
395 {
396 DeploymentInfo di = (DeploymentInfo)i.next();
397 log.debug("trying to deploy with new deployer: " + di.shortName);
398 try
399 {
400 di.setServer(server);
401 deploy(di);
402 }
403 catch (DeploymentException e)
404 {
405 log.error("DeploymentException while trying to deploy a package with a new deployer", e);
406 } // end of try-catch
407 } // end of for ()
408 }
409 }
410
411 /**
412 * The <code>removeDeployer</code> method unregisters a deployer with the MainDeployer.
413 * Deployed packages deployed with this deployer are undeployed.
414 *
415 * @param deployer a <code>SubDeployer</code> value
416 * @jmx.managed-operation
417 */
418 public void removeDeployer(final SubDeployer deployer)
419 {
420 log.debug("Removing deployer: " + deployer);
421 ObjectName deployerName = deployer.getServiceName();
422 boolean removed = false;
423
424 synchronized(deployers)
425 {
426 removed = deployers.remove(deployer);
427 try
428 {
429 String[] suffixes = (String[]) server.getAttribute(deployerName, "EnhancedSuffixes");
430 suffixOrderHelper.removeEnhancedSuffixes(suffixes);
431 }
432 catch(Exception e)
433 {
434 log.debug(deployerName + " does not support EnhancedSuffixes");
435 suffixOrderHelper.removeSuffixes(deployer.getSuffixes(), deployer.getRelativeOrder());
436 }
437 }
438
439 // Send a notification about the deployer removal
440 if (removed)
441 {
442 Notification msg = new Notification(REMOVE_DEPLOYER, this, getNextNotificationSequenceNumber());
443 msg.setUserData(deployerName);
444 sendNotification(msg);
445 }
446
447 List copy;
448 synchronized (deploymentList)
449 {
450 copy = new ArrayList(deploymentList);
451 }
452 for (Iterator i = copy.iterator(); i.hasNext(); )
453 {
454 DeploymentInfo di = (DeploymentInfo)i.next();
455 if (di.deployer == deployer)
456 {
457 undeploy(di);
458 di.deployer = null;
459 synchronized (waitingDeployments)
460 {
461 waitingDeployments.add(di);
462 }
463 }
464 }
465 }
466
467 /**
468 * The <code>listDeployers</code> method returns a collection of ObjectNames of
469 * deployers registered with the MainDeployer.
470 *
471 * @return a <code>Collection<ObjectName></code> value
472 * @jmx.managed-operation
473 */
474 public Collection listDeployers()
475 {
476 ArrayList deployerNames = new ArrayList();
477 synchronized(deployers)
478 {
479 for(int n = 0; n < deployers.size(); n ++)
480 {
481 SubDeployer deployer = (SubDeployer) deployers.get(n);
482 ObjectName name = deployer.getServiceName();
483 deployerNames.add(name);
484 }
485 }
486 return deployerNames;
487 }
488
489 // ServiceMBeanSupport overrides ---------------------------------
490
491 protected ObjectName getObjectName(MBeanServer server, ObjectName name)
492 throws MalformedObjectNameException
493 {
494 return name == null ? OBJECT_NAME : name;
495 }
496
497 /**
498 * The <code>createService</code> method is one of the ServiceMBean lifecyle operations.
499 * (no jmx tag needed from superinterface)
500 * @exception Exception if an error occurs
501 */
502 protected void createService() throws Exception
503 {
504 ServerConfig config = ServerConfigLocator.locate();
505 // Get the temp directory location
506 File basedir = config.getServerTempDir();
507 // Set the local copy temp dir to tmp/deploy
508 tempDir = new File(basedir, "deploy");
509 // Delete any existing content
510 Files.delete(tempDir);
511 // Make sure the directory exists
512 tempDir.mkdirs();
513
514 // used in inLocalCopyDir
515 tempDirString = tempDir.toURL().toString();
516
517 // handles SuffixOrder & RelativeSuffixOrder attributes
518 suffixOrderHelper.initialize();
519 }
520
521 /**
522 * The <code>shutdown</code> method undeploys all deployed packages in
523 * reverse order of their deployement.
524 *
525 * @jmx.managed-operation
526 */
527 public void shutdown()
528 {
529 // if we shutdown in the middle of a scan, it still might be possible that we try to redeploy
530 // things we are busy killing...
531 int deployCounter = 0;
532
533 // undeploy everything in sight
534 List copy;
535 synchronized (deploymentList)
536 {
537 copy = new ArrayList(deploymentList);
538 }
539 for (ListIterator i = copy.listIterator(copy.size()); i.hasPrevious(); )
540 {
541 try
542 {
543 undeploy((DeploymentInfo)i.previous(), true);
544 deployCounter++;
545 }
546 catch (Exception e)
547 {
548 log.info("exception trying to undeploy during shutdown", e);
549 }
550
551 }
552 // Help GC
553 this.deployers.clear();
554 this.deploymentMap.clear();
555 this.deploymentList.clear();
556 this.waitingDeployments.clear();
557 this.tempDir = null;
558
559 log.debug("Undeployed " + deployCounter + " deployed packages");
560 }
561
562
563 /**
564 * Describe <code>redeploy</code> method here.
565 *
566 * @param urlspec a <code>String</code> value
567 * @exception DeploymentException if an error occurs
568 * @exception MalformedURLException if an error occurs
569 * @jmx.managed-operation
570 */
571 public void redeploy(String urlspec)
572 throws DeploymentException, MalformedURLException
573 {
574 redeploy(new URL(urlspec));
575 }
576
577 /**
578 * Describe <code>redeploy</code> method here.
579 *
580 * @param url an <code>URL</code> value
581 * @exception DeploymentException if an error occurs
582 * @jmx.managed-operation
583 */
584 public void redeploy(URL url) throws DeploymentException
585 {
586 undeploy(url);
587 deploy(url);
588 }
589
590 /**
591 * Describe <code>redeploy</code> method here.
592 *
593 * @param sdi a <code>DeploymentInfo</code> value
594 * @exception DeploymentException if an error occurs
595 * @jmx.managed-operation
596 */
597 public void redeploy(DeploymentInfo sdi) throws DeploymentException
598 {
599 try
600 {
601 undeploy(sdi);
602 }
603 catch (Throwable t)
604 {
605 log.info("Throwable from undeployment attempt: ", t);
606 } // end of try-catch
607 sdi.setServer(server);
608 deploy(sdi);
609 }
610
611 /**
612 * The <code>undeploy</code> method undeploys a package identified by a string
613 * representation of a URL.
614 *
615 * @param urlspec the stringfied url to undeploy
616 * @jmx.managed-operation
617 */
618 public void undeploy(String urlspec)
619 throws DeploymentException, MalformedURLException
620 {
621 undeploy(new URL(urlspec));
622 }
623
624 /**
625 * The <code>undeploy</code> method undeploys a package identified by a URL
626 *
627 * @param url the url to undeploy
628 * @jmx.managed-operation
629 */
630 public void undeploy(URL url) throws DeploymentException
631 {
632 String deploymentName = contextMap.remove(url);
633 if (deploymentName != null)
634 {
635 try
636 {
637 delegate.removeDeployment(deploymentName);
638 delegate.process();
639 }
640 catch(Exception e)
641 {
642 DeploymentException ex = new DeploymentException("Error during undeploy of: "+url, e);
643 throw ex;
644 }
645 }
646 else
647 {
648 log.warn("undeploy '" + url + "' : package not deployed");
649 }
650 }
651
652 /**
653 * The <code>undeploy</code> method undeploys a package represented by a
654 * DeploymentInfo object.
655 *
656 * @param di a <code>DeploymentInfo</code> value
657 * @jmx.managed-operation
658 */
659 public void undeploy(DeploymentInfo di)
660 {
661 undeploy(di, false);
662 }
663 protected void undeploy(DeploymentInfo di, boolean isShutdown)
664 {
665 log.debug("Undeploying "+di.url);
666 stop(di);
667 destroy(di);
668 }
669
670 /**
671 * The <code>stop</code> method is the first internal step of undeployment
672 *
673 * @param di a <code>DeploymentInfo</code> value
674 */
675 private void stop(DeploymentInfo di)
676 {
677 // Stop all sub-deployments
678 ArrayList reverseSortedSubs = new ArrayList(di.subDeployments);
679 Collections.sort(reverseSortedSubs, infoSorter);
680 Collections.reverse(reverseSortedSubs);
681 for (Iterator subs = reverseSortedSubs.iterator(); subs.hasNext();)
682 {
683 DeploymentInfo sub = (DeploymentInfo) subs.next();
684 log.debug("Stopping sub deployment: "+sub.url);
685 stop(sub);
686 }
687 // Lastly stop this deployment itself
688 try
689 {
690 // Tell the respective deployer to undeploy this one
691 if (di.deployer != null)
692 {
693 di.deployer.stop(di);
694 di.status="Stopped";
695 di.state = DeploymentState.STOPPED;
696 }
697 }
698 catch (Throwable t)
699 {
700 log.error("Deployer stop failed for: " + di.url, t);
701 }
702
703 }
704
705 /**
706 * The <code>destroy</code> method is the second and final internal undeployment step.
707 *
708 * @param di a <code>DeploymentInfo</code> value
709 */
710 private void destroy(DeploymentInfo di)
711 {
712 // Destroy all sub-deployments
713 ArrayList reverseSortedSubs = new ArrayList(di.subDeployments);
714 Collections.sort(reverseSortedSubs, infoSorter);
715 Collections.reverse(reverseSortedSubs);
716 for (Iterator subs = reverseSortedSubs.iterator(); subs.hasNext();)
717 {
718 DeploymentInfo sub = (DeploymentInfo) subs.next();
719 log.debug("Destroying sub deployment: "+sub.url);
720 destroy(sub);
721 }
722 // Lastly destroy the deployment itself
723 try
724 {
725 // Tell the respective deployer to undeploy this one
726 if (di.deployer != null)
727 {
728 di.deployer.destroy(di);
729 di.status="Destroyed";
730 di.state = DeploymentState.DESTROYED;
731 }
732 }
733 catch (Throwable t)
734 {
735 log.error("Deployer destroy failed for: " + di.url, t);
736 di.state = DeploymentState.FAILED;
737 }
738
739 try
740 {
741 // remove from local maps
742 synchronized (deploymentList)
743 {
744 deploymentMap.remove(di.url);
745 if (deploymentList.lastIndexOf(di) != -1)
746 {
747 deploymentList.remove(deploymentList.lastIndexOf(di));
748 }
749 }
750 synchronized (waitingDeployments)
751 {
752 waitingDeployments.remove(di);
753 }
754 // Nuke my stuff, this includes the class loader
755 di.cleanup();
756
757 log.debug("Undeployed "+di.url);
758 }
759 catch (Throwable t)
760 {
761 log.error("Undeployment cleanup failed: " + di.url, t);
762 }
763 }
764
765 /**
766 * The <code>deploy</code> method deploys a package identified by a
767 * string representation of a URL.
768 *
769 * @param urlspec a <code>String</code> value
770 * @exception MalformedURLException if an error occurs
771 * @jmx.managed-operation
772 */
773 public void deploy(String urlspec)
774 throws DeploymentException, MalformedURLException
775 {
776 if( server == null )
777 throw new DeploymentException("The MainDeployer has been unregistered");
778
779 URL url;
780 try
781 {
782 url = new URL(urlspec);
783 }
784 catch (MalformedURLException e)
785 {
786 File file = new File(urlspec);
787 url = file.toURL();
788 }
789
790 deploy(url);
791 }
792
793 /**
794 * The <code>deploy</code> method deploys a package identified by a URL
795 *
796 * @param url an <code>URL</code> value
797 * @jmx.managed-operation
798 */
799 public void deploy(URL url) throws DeploymentException
800 {
801 log.info("deploy, url="+url);
802 String deploymentName = contextMap.get(url);
803 // if it does not exist create a new deployment
804 if (deploymentName == null)
805 {
806 try
807 {
808 VirtualFile file = VFS.getRoot(url);
809 VFSDeployment deployment = deploymentFactory.createVFSDeployment(file);
810 delegate.addDeployment(deployment);
811 deploymentName = deployment.getName();
812 delegate.process();
813 // TODO: JBAS-4292
814 contextMap.put(url, deploymentName);
815 delegate.checkComplete(deployment);
816 }
817 catch(Exception e)
818 {
819 log.warn("Failed to deploy: "+url, e);
820 DeploymentException ex = new DeploymentException("Failed to deploy: "+url, e);
821 throw ex;
822 }
823 }
824 }
825
826 /**
827 * The <code>deploy</code> method deploys a package represented by a DeploymentInfo object.
828 *
829 * @param deployment a <code>DeploymentInfo</code> value
830 * @exception DeploymentException if an error occurs
831 * @jmx.managed-operation
832 */
833 public void deploy(DeploymentInfo deployment)
834 throws DeploymentException
835 {
836 // If we are already deployed return
837 if (isDeployed(deployment.url))
838 {
839 log.info("Package: " + deployment.url + " is already deployed");
840 return;
841 }
842 log.debug("Starting deployment of package: " + deployment.url);
843
844 boolean inited = false;
845 try
846 {
847 inited = init(deployment);
848 }
849 catch (Throwable t)
850 {
851 log.error("Could not initialise deployment: " + deployment.url, t);
852 DeploymentException.rethrowAsDeploymentException("Could not initialise deployment: " + deployment.url, t);
853 }
854 if ( inited )
855 {
856 create(deployment);
857 start(deployment);
858 log.debug("Deployed package: " + deployment.url);
859 } // end of if ()
860 else
861 {
862 log.debug("Deployment of package: " + deployment.url + " is waiting for an appropriate deployer.");
863 } // end of else
864 }
865
866
867 /**
868 * The <code>init</code> method is the first internal deployment step.
869 * The tasks are to copy the code if necessary,
870 * set up classloaders, and identify the deployer for the package.
871 *
872 * @param deployment a <code>DeploymentInfo</code> value
873 * @throws DeploymentException if an error occurs
874 */
875 private boolean init(DeploymentInfo deployment) throws DeploymentException
876 {
877 // If we are already deployed return
878 if (isDeployed(deployment.url))
879 {
880 log.info("Package: " + deployment.url + " is already deployed");
881 return false;
882 }
883 log.debug("Starting deployment (init step) of package at: " + deployment.url);
884 try
885 {
886 // Create a local copy of that File, the sdi keeps track of the copy directory
887 if (deployment.localUrl == null)
888 {
889 makeLocalCopy(deployment);
890 URL[] localCl = new URL[]{deployment.localUrl};
891 deployment.localCl = new URLClassLoader(localCl);
892 }
893
894 // What deployer is able to deploy this file
895 findDeployer(deployment);
896
897 if(deployment.deployer == null)
898 {
899 deployment.state = DeploymentState.INIT_WAITING_DEPLOYER;
900 log.debug("deployment waiting for deployer: " + deployment.url);
901 synchronized (waitingDeployments)
902 {
903 if (waitingDeployments.contains(deployment) == false)
904 waitingDeployments.add(deployment);
905 }
906 return false;
907 }
908 deployment.state = DeploymentState.INIT_DEPLOYER;
909 //we have the deployer, continue deployment.
910 deployment.deployer.init(deployment);
911 // initialize the unified classloaders for this deployment
912 deployment.createClassLoaders();
913 deployment.state = DeploymentState.INITIALIZED;
914
915 // Add the deployment to the map so we can detect circular deployments
916 synchronized (deploymentList)
917 {
918 deploymentMap.put(deployment.url, deployment);
919 }
920
921 // create subdeployments as needed
922 parseManifestLibraries(deployment);
923
924 log.debug("found " + deployment.subDeployments.size() + " subpackages of " + deployment.url);
925 // get sorted subDeployments
926 ArrayList sortedSubs = new ArrayList(deployment.subDeployments);
927 Collections.sort(sortedSubs, infoSorter);
928 for (Iterator lt = sortedSubs.listIterator(); lt.hasNext();)
929 {
930 init((DeploymentInfo) lt.next());
931 }
932 }
933 catch (Exception e)
934 {
935 deployment.state = DeploymentState.FAILED;
936 DeploymentException.rethrowAsDeploymentException("exception in init of " + deployment.url, e);
937 }
938 finally
939 {
940 // whether you do it or not, for the autodeployer
941 try
942 {
943 URL url = deployment.localUrl == null ? deployment.url : deployment.localUrl;
944
945 long lastModified = -1;
946
947 if (url.getProtocol().equals("file"))
948 lastModified = new File(url.getFile()).lastModified();
949 else
950 lastModified = url.openConnection().getLastModified();
951
952 deployment.lastModified=lastModified;
953 deployment.lastDeployed=System.currentTimeMillis();
954 }
955 catch (IOException ignore)
956 {
957 deployment.lastModified=System.currentTimeMillis();
958 deployment.lastDeployed=System.currentTimeMillis();
959 }
960
961 synchronized (deploymentList)
962 {
963 // Do we watch it? Watch only urls outside our copy directory.
964 if (!inLocalCopyDir(deployment.url) && deploymentList.contains(deployment) == false)
965 {
966 deploymentList.add(deployment);
967 log.debug("Watching new file: " + deployment.url);
968 }
969 }
970 }
971 return true;
972 }
973
974 /**
975 * The <code>create</code> method is the second internal deployment step.
976 * It should set up all information not
977 * requiring other components. for instance, the ejb Container is created,
978 * and the proxy bound into jndi.
979 *
980 * @param deployment a <code>DeploymentInfo</code> value
981 * @throws DeploymentException if an error occurs
982 */
983 private void create(DeploymentInfo deployment) throws DeploymentException
984 {
985 log.debug("create step for deployment " + deployment.url);
986 try
987 {
988 ArrayList sortedSubs = new ArrayList(deployment.subDeployments);
989 Collections.sort(sortedSubs, infoSorter);
990 for (Iterator lt = sortedSubs.listIterator(); lt.hasNext();)
991 {
992 create((DeploymentInfo) lt.next());
993 }
994 deployment.state = DeploymentState.CREATE_SUBDEPLOYMENTS;
995
996 // Deploy this SDI, if it is a deployable type
997 if (deployment.deployer != null)
998 {
999 try
1000 {
1001 deployment.state = DeploymentState.CREATE_DEPLOYER;
1002 deployment.deployer.create(deployment);
1003 // See if all mbeans are created...
1004 deployment.state = DeploymentState.CREATED;
1005 deployment.status="Created";
1006 log.debug("Done with create step of deploying " + deployment.shortName);
1007 }
1008 catch (Throwable t)
1009 {
1010 log.error("Could not create deployment: " + deployment.url, t);
1011 throw t;
1012 }
1013 }
1014 else
1015 {
1016 log.debug("Still no deployer for package in create step: " + deployment.shortName);
1017 } // end of else
1018 }
1019 catch (Throwable t)
1020 {
1021 log.trace("could not create deployment: " + deployment.url, t);
1022 deployment.status = "Deployment FAILED reason: " + t.getMessage();
1023 deployment.state = DeploymentState.FAILED;
1024 DeploymentException.rethrowAsDeploymentException("Could not create deployment: " + deployment.url, t);
1025 }
1026 }
1027
1028 /**
1029 * The <code>start</code> method is the third and final internal deployment step.
1030 * The purpose is to set up relationships between components.
1031 * for instance, ejb links are set up here.
1032 *
1033 * @param deployment a <code>DeploymentInfo</code> value
1034 * @throws DeploymentException if an error occurs
1035 */
1036 private void start(DeploymentInfo deployment) throws DeploymentException
1037 {
1038 deployment.status = "Starting";
1039 log.debug("Begin deployment start " + deployment.url);
1040 try
1041 {
1042 ArrayList sortedSubs = new ArrayList(deployment.subDeployments);
1043 Collections.sort(sortedSubs, infoSorter);
1044 for (Iterator lt = sortedSubs.listIterator(); lt.hasNext();)
1045 {
1046 start((DeploymentInfo) lt.next());
1047 }
1048 deployment.state = DeploymentState.START_SUBDEPLOYMENTS;
1049
1050 // Deploy this SDI, if it is a deployable type
1051 if (deployment.deployer != null)
1052 {
1053 try
1054 {
1055 deployment.state = DeploymentState.START_DEPLOYER;
1056 deployment.deployer.start(deployment);
1057 // See if all mbeans are started...
1058 Object[] args = {deployment, DeploymentState.STARTED};
1059 String[] sig = {"org.jboss.deployment.DeploymentInfo",
1060 "org.jboss.deployment.DeploymentState"};
1061 server.invoke(serviceController, "validateDeploymentState",args, sig);
1062 deployment.status = "Deployed";
1063 log.debug("End deployment start on package: "+ deployment.shortName);
1064 }
1065 catch (Throwable t)
1066 {
1067 log.error("Could not start deployment: " + deployment.url, t);
1068 throw t;
1069 }
1070 }
1071 else
1072 {
1073 log.debug("Still no deployer for package in start step: " + deployment.shortName);
1074 } // end of else
1075 }
1076 catch (Throwable t)
1077 {
1078 log.trace("could not start deployment: " + deployment.url, t);
1079 deployment.state = DeploymentState.FAILED;
1080 deployment.status = "Deployment FAILED reason: " + t.getMessage();
1081 DeploymentException.rethrowAsDeploymentException("Could not create deployment: " + deployment.url, t);
1082 }
1083 }
1084
1085 /**
1086 * The <code>findDeployer</code> method attempts to find a deployer for the DeploymentInfo
1087 * supplied as a parameter.
1088 *
1089 * @param sdi a <code>DeploymentInfo</code> value
1090 */
1091 private void findDeployer(DeploymentInfo sdi)
1092 {
1093 // If there is already a deployer use it
1094 if( sdi.deployer != null )
1095 {
1096 log.debug("using existing deployer "+sdi.deployer);
1097 return;
1098 }
1099
1100 //
1101 // To deploy directories of beans one should just name the directory
1102 // mybean.ear/bla...bla, so that the directory gets picked up by the right deployer
1103 //
1104 synchronized(deployers)
1105 {
1106 for (Iterator iterator = deployers.iterator(); iterator.hasNext(); )
1107 {
1108 SubDeployer deployer = (SubDeployer) iterator.next();
1109 if (deployer.accepts(sdi))
1110 {
1111 sdi.deployer = deployer;
1112 log.debug("using deployer "+deployer);
1113 return;
1114 }
1115 }
1116 }
1117 log.debug("No deployer found for url: " + sdi.url);
1118 }
1119
1120 /**
1121 * The <code>parseManifestLibraries</code> method looks into the manifest for classpath
1122 * goo, and tries to deploy referenced packages.
1123 *
1124 * @param sdi a <code>DeploymentInfo</code> value
1125 */
1126 private void parseManifestLibraries(DeploymentInfo sdi)
1127 {
1128 String classPath = null;
1129
1130 Manifest mf = sdi.getManifest();
1131
1132 if( mf != null )
1133 {
1134 Attributes mainAttributes = mf.getMainAttributes();
1135 classPath = mainAttributes.getValue(Attributes.Name.CLASS_PATH);
1136 }
1137
1138 if (classPath != null)
1139 {
1140 StringTokenizer st = new StringTokenizer(classPath);
1141 log.debug("resolveLibraries: "+classPath);
1142
1143 while (st.hasMoreTokens())
1144 {
1145 URL lib = null;
1146 String tk = st.nextToken();
1147 log.debug("new manifest entry for sdi at "+sdi.shortName+" entry is "+tk);
1148
1149 try
1150 {
1151 if (sdi.isDirectory)
1152 {
1153 File parentDir = new File(sdi.url.getPath()).getParentFile();
1154 lib = new File(parentDir, tk).toURL();
1155 }
1156 else
1157 {
1158 lib = new URL(sdi.url, tk);
1159 }
1160
1161 // Only deploy this if it is not already being deployed
1162 if ( deploymentMap.containsKey(lib) == false )
1163 {
1164 /* Test that the only deployer for this is the JARDeployer.
1165 Any other type of deployment cannot be initiated through
1166 a manifest reference.
1167 */
1168 DeploymentInfo mfRef = new DeploymentInfo(lib, null, getServer());
1169 makeLocalCopy(mfRef);
1170 URL[] localURL = {mfRef.localUrl};
1171 mfRef.localCl = new java.net.URLClassLoader(localURL);
1172 findDeployer(mfRef);
1173 SubDeployer deployer = mfRef.deployer;
1174 if(deployer != null && (deployer instanceof JARDeployer) == false)
1175 {
1176 // Its a non-jar deployment that must be deployed seperately
1177 log.warn("Found non-jar deployer for " + tk + ": " + deployer);
1178 }
1179
1180 // add the library
1181 sdi.addLibraryJar(lib);
1182 }
1183 }
1184 catch (Exception ignore)
1185 {
1186 log.debug("The manifest entry in "+sdi.url+" references URL "+lib+
1187 " which could not be opened, entry ignored", ignore);
1188 }
1189 }
1190 }
1191 }
1192
1193 /**
1194 * Downloads the jar file or directory the src URL points to.
1195 * In case of directory it becomes packed to a jar file.
1196 */
1197 private void makeLocalCopy(DeploymentInfo sdi)
1198 {
1199 try
1200 {
1201 if (sdi.url.getProtocol().equals("file") && (!copyFiles || sdi.isDirectory))
1202 {
1203 // If local copies have been disabled, do nothing
1204 sdi.localUrl = sdi.url;
1205 return;
1206 }
1207 // Are we already in the localCopyDir?
1208 else if (inLocalCopyDir(sdi.url))
1209 {
1210 sdi.localUrl = sdi.url;
1211 return;
1212 }
1213 else
1214 {
1215 String shortName = sdi.shortName;
1216 File localFile = File.createTempFile("tmp", shortName, tempDir);
1217 sdi.localUrl = localFile.toURL();
1218 copy(sdi.url, localFile);
1219 }
1220 }
1221 catch (Exception e)
1222 {
1223 log.error("Could not make local copy for " + sdi.url, e);
1224 }
1225 }
1226
1227 private boolean inLocalCopyDir(URL url)
1228 {
1229 int i = 0;
1230 String urlTest = url.toString();
1231 if( urlTest.startsWith("jar:") )
1232 i = 4;
1233
1234 return urlTest.startsWith(tempDirString, i);
1235 }
1236
1237 protected void copy(URL src, File dest) throws IOException
1238 {
1239 log.debug("Copying " + src + " -> " + dest);
1240
1241 // Validate that the dest parent directory structure exists
1242 File dir = dest.getParentFile();
1243 if (!dir.exists())
1244 {
1245 boolean created = dir.mkdirs();
1246 if( created == false )
1247 throw new IOException("mkdirs failed for: "+dir.getAbsolutePath());
1248 }
1249
1250 // Remove any existing dest content
1251 if( dest.exists() == true )
1252 {
1253 boolean deleted = Files.delete(dest);
1254 if( deleted == false )
1255 throw new IOException("delete of previous content failed for: "+dest.getAbsolutePath());
1256 }
1257
1258 if (src.getProtocol().equals("file"))
1259 {
1260 File srcFile = new File(src.getFile());
1261 if (srcFile.isDirectory())
1262 {
1263 log.debug("Making zip copy of: " + srcFile);
1264 // make a jar archive of the directory
1265 OutputStream out = new BufferedOutputStream(new FileOutputStream(dest));
1266 JarUtils.jar(out, srcFile.listFiles());
1267 out.close();
1268 return;
1269 }
1270 }
1271
1272 InputStream in = new BufferedInputStream(src.openStream());
1273 OutputStream out = new BufferedOutputStream(new FileOutputStream(dest));
1274 Streams.copy(in, out);
1275 out.flush();
1276 out.close();
1277 in.close();
1278 }
1279
1280 /**
1281 * The <code>start</code> method starts a package identified by a URL
1282 *
1283 * @param urlspec an <code>URL</code> value
1284 * @jmx.managed-operation
1285 */
1286 public void start(String urlspec)
1287 throws DeploymentException, MalformedURLException
1288 {
1289 throw new DeploymentException("Not supported");
1290 }
1291
1292 /**
1293 * The <code>stop</code> method stops a package identified by a URL
1294 *
1295 * @param urlspec an <code>URL</code> value
1296 * @jmx.managed-operation
1297 */
1298 public void stop(String urlspec)
1299 throws DeploymentException, MalformedURLException
1300 {
1301 throw new DeploymentException("Not supported");
1302 }
1303
1304 /**
1305 * The <code>isDeployed</code> method tells you if a package identified by a string
1306 * representation of a URL is currently deployed.
1307 *
1308 * @param url a <code>String</code> value
1309 * @return a <code>boolean</code> value
1310 * @exception MalformedURLException if an error occurs
1311 * @jmx.managed-operation
1312 */
1313 public boolean isDeployed(String url)
1314 throws MalformedURLException
1315 {
1316 return isDeployed(new URL(url));
1317 }
1318
1319 /**
1320 * The <code>isDeployed</code> method tells you if a packaged identified by
1321 * a URL is deployed.
1322 * @param url an <code>URL</code> value
1323 * @return a <code>boolean</code> value
1324 * @jmx.managed-operation
1325 */
1326 public boolean isDeployed(URL url)
1327 {
1328 String name = contextMap.get(url);
1329 if (name == null)
1330 {
1331 if (log.isTraceEnabled())
1332 log.trace("No such context: " + url);
1333 if (url == null)
1334 throw new IllegalArgumentException("Null url");
1335 String urlString = url.toString();
1336 // remove this once the JBoss-test is updated with VFS usage
1337 if (urlString.startsWith("vfs") == false)
1338 return checkDeployed("vfs" + urlString);
1339 else
1340 return checkDeployed(urlString);
1341 }
1342
1343 return checkDeployed(name);
1344 }
1345
1346 /**
1347 * Is deployed.
1348 *
1349 * @param name the name of the deployment
1350 * @return true if deployed, false otherwise
1351 */
1352 protected boolean checkDeployed(String name)
1353 {
1354 org.jboss.deployers.spi.DeploymentState deploymentState = delegate.getDeploymentState(name);
1355 log.debug("isDeployed, url="+name+", state="+deploymentState);
1356 return deploymentState == org.jboss.deployers.spi.DeploymentState.DEPLOYED;
1357 }
1358
1359 /**
1360 * The <code>getDeployment</code> method returns the Deployment
1361 * object for the URL supplied.
1362 *
1363 * @param url an <code>URL</code> value
1364 * @return a <code>Deployment</code> value
1365 * @jmx.managed-operation
1366 */
1367 public Deployment getDeployment(URL url)
1368 {
1369 String name = contextMap.get(url);
1370 if (name == null)
1371 return null;
1372
1373 Deployment dc = delegate.getDeployment(name);
1374 log.debug("getDeployment, url="+url+", dc="+dc);
1375 return dc;
1376 }
1377
1378 /**
1379 * The <code>getDeploymentContext</code> method returns the DeploymentContext
1380 * object for the URL supplied.
1381 *
1382 * @param url an <code>URL</code> value
1383 * @return a <code>DeploymentContext</code> value
1384 * @jmx.managed-operation
1385 */
1386 @Deprecated
1387 public DeploymentContext getDeploymentContext(URL url)
1388 {
1389 String name = contextMap.get(url);
1390 if (name == null)
1391 return null;
1392
1393 MainDeployerStructure structure = (MainDeployerStructure) delegate;
1394 DeploymentContext dc = structure.getDeploymentContext(name);
1395 log.debug("getDeploymentContext, url="+url+", dc="+dc);
1396 return dc;
1397 }
1398
1399 /**
1400 * The <code>getDeploymentUnit</code> method returns the DeploymentUnit
1401 * object for the URL supplied.
1402 *
1403 * @param url an <code>URL</code> value
1404 * @return a <code>DeploymentUnit</code> value
1405 * @jmx.managed-operation
1406 */
1407 public DeploymentUnit getDeploymentUnit(URL url)
1408 {
1409 String name = contextMap.get(url);
1410 if (name == null)
1411 return null;
1412
1413 MainDeployerStructure structure = (MainDeployerStructure) delegate;
1414 DeploymentUnit du = structure.getDeploymentUnit(name);
1415 log.debug("getDeploymentUnit, url="+url+", du="+du);
1416 return du;
1417 }
1418
1419 /**
1420 * The <code>getWatchUrl</code> method returns the URL that, when modified,
1421 * indicates that a redeploy is needed.
1422 *
1423 * @param url an <code>URL</code> value
1424 * @return a <code>URL</code> value
1425 * @jmx.managed-operation
1426 */
1427 public URL getWatchUrl(URL url)
1428 {
1429 return url;
1430 }
1431
1432 /** Check the current deployment states and generate a IncompleteDeploymentException
1433 * if there are mbeans waiting for depedencies.
1434 * @exception IncompleteDeploymentException
1435 * @jmx.managed-operation
1436 */
1437 public void checkIncompleteDeployments() throws DeploymentException
1438 {
1439 try
1440 {
1441 delegate.checkComplete();
1442 }
1443 catch (Exception e)
1444 {
1445 throw new DeploymentException("Deployments are incomplete", e);
1446 }
1447 }
1448
1449 /**
1450 * @param parent
1451 * @param map
1452 */
1453 private void fillParentAndChildrenSDI(DeploymentInfo parent, Map map)
1454 {
1455 Set subDeployments = parent.subDeployments;
1456 Iterator it = subDeployments.iterator();
1457 while (it.hasNext())
1458 {
1459 DeploymentInfo child = (DeploymentInfo) it.next();
1460 SerializableDeploymentInfo sdichild = returnSDI(child, map);
1461 sdichild.parent = returnSDI(parent, map);
1462 sdichild.parent.subDeployments.add(sdichild);
1463 fillParentAndChildrenSDI(child, map);
1464 }
1465 }
1466
1467 private SerializableDeploymentInfo returnSDI(DeploymentInfo di, Map map)
1468 {
1469 SerializableDeploymentInfo sdi = (SerializableDeploymentInfo) map.get(di.url);
1470 if( sdi == null )
1471 {
1472 sdi = new SerializableDeploymentInfo(di);
1473 map.put(di.url, sdi);
1474 }
1475 return sdi;
1476 }
1477 }