View Javadoc
1   /*
2    * Copyright (c) 2005-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.extend.server.deployment;
14  
15  import java.net.URI;
16  import java.util.Collection;
17  import java.util.HashSet;
18  import java.util.Iterator;
19  import java.util.LinkedHashMap;
20  import java.util.LinkedHashSet;
21  import java.util.Map;
22  import java.util.Set;
23  
24  import org.abstracthorizon.extend.server.deployment.support.ProvisionalModule;
25  import org.abstracthorizon.extend.server.support.EnhancedMap;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  
29  /**
30   * Implementation of {@link DeploymentManager}.
31   *
32   * @author Daniel Sendula
33   */
34  public class DeploymentManagerImpl implements DeploymentManager {
35  
36      /** Logger */
37      private final Logger logger = LoggerFactory.getLogger(DeploymentManagerImpl.class);
38  
39      /** List of module loaders */
40      protected ModuleLoaders<ModuleLoader> moduleLoaders = new ModuleLoaders<ModuleLoader>();
41  
42      /** Map of deployed modules */
43      protected DeployedModules<ModuleId, Module> deployedModules = new DeployedModules<ModuleId, Module>();
44  
45      /** Empty constructor */
46      public DeploymentManagerImpl() {
47      }
48  
49      /**
50       * Checks if module is deployed.
51       * @param module module
52       * @return <code>true</code> if given module is deployed
53       */
54      public boolean isDeployed(Module module) {
55  //        Module m = deployedModules.get(module.getName());
56  //        return m == module;
57          return deployedModules.containsValue(module);
58      }
59  
60      /**
61       * Deploys given module. It calls {@link Module#create()} and {@link Module#start()} methods.
62       *
63       * @param module module
64       * @throws RuntimeException if module with the same name is already deployed (and is not {@link ProvisionalModule}
65       */
66      public void deploy(ModuleId moduleId, Module module) {
67          Module old = deployedModules.get(moduleId);
68          if (old != module) {
69              long startedTime = 0;
70              if (logger.isDebugEnabled()) {
71                  startedTime = System.currentTimeMillis();
72                  logger.debug("Deploying module '" + moduleId + "' of class=" + module.getClass());
73              } else if (logger.isInfoEnabled()) {
74                  logger.info("Deploying module '" + moduleId + "'");
75              }
76              if (old != null) {
77                  if (!(old instanceof ProvisionalModule)) {
78                      String errorMsg = "Module with id '" + moduleId + "' already exists; class=" + old.getClass() + ". Cannot deploy it.";
79                      logger.error(errorMsg);
80                      throw new RuntimeException(errorMsg);
81                  }
82                  deployedModules.remove(old);
83              }
84              deployedModules.put(moduleId, module);
85  
86              // Old module is just ProvisionalModule. Update references
87              if (old != null) {
88                  if (logger.isDebugEnabled()) {
89                      logger.debug("Updating references to this module from provisional module; id=" + moduleId + "'");
90                  }
91                  for (Module m : old.getDependsOn()) {
92                      m.getDependOnThis().remove(old);
93                      m.getDependOnThis().add(module);
94                  }
95                  for (Module m : old.getDependOnThis()) {
96                      m.getDependsOn().remove(old);
97                      m.getDependsOn().add(module);
98                  }
99  
100                 module.getDependOnThis().addAll(old.getDependOnThis());
101             }
102 
103             if (logger.isDebugEnabled()) {
104                 logger.debug("Creating module '" + moduleId + "'.");
105             }
106             create(module);
107             if ((module.getState() == Module.CREATED) || (module.getState() == Module.WAITING_ON_CREATE)) {
108                 start(module);
109             }
110             if (logger.isDebugEnabled()) {
111                 logger.debug("Deployed module '" + moduleId + "' (" + Long.toString(System.currentTimeMillis() - startedTime) + "ms)");
112             }
113         }
114     }
115 
116     /**
117      * Undeploys given module. It calls {@link Module#stop()} and
118      * {@link Module#destroy()} methods.
119      * @param module module
120      */
121     public void undeploy(Module module) {
122         ModuleId moduleId = module.getModuleId();
123         if (logger.isInfoEnabled()) {
124             logger.info("Undeploying module '" + moduleId + "'.");
125         }
126         int state = module.getState();
127         if ((state == Module.STARTED) || (state == Module.WAITING_ON_CREATE_TO_START)) {
128             stop(module);
129         }
130         destroy(module);
131 
132         deployedModules.remove(module);
133         if (logger.isInfoEnabled()) {
134             logger.info("Module '" + moduleId + "' is now undeployed.");
135         }
136     }
137 
138     /**
139      * Re-deploys given module. Effectively it calls {@link #undeploy(Module)} and
140      * {@link #deploy(Module)} methods.
141      * @param module module
142      */
143     public void redeploy(Module module) {
144         ModuleId moduleId = module.getModuleId();
145         if (logger.isInfoEnabled()) {
146             logger.info("Redeploying module '" + moduleId + "'.");
147         }
148         if (!deployedModules.containsValue(module)) {
149             throw new RuntimeException("Module is not deployed");
150         }
151 
152         Set<ModuleId> aliases = findAliases(module);
153         Iterator<ModuleId> it = aliases.iterator();
154         undeploy(module);
155         deploy(it.next(), module);
156         while (it.hasNext()) {
157             deployedModules.put(it.next(), module);
158         }
159     }
160 
161     /**
162      * Returns all aliases module is known under
163      * @param module module
164      * @return set of aliases
165      */
166     public Set<ModuleId> findAliases(Module module) {
167         Set<ModuleId> result = new HashSet<ModuleId>();
168         for (Map.Entry<ModuleId, Module> entry : deployedModules.entrySet()) {
169             if (entry.getValue().equals(module)) {
170                 result.add(entry.getKey());
171             }
172         }
173         return result;
174     }
175 
176     /**
177      * Calls {@link Module#create} method.
178      * @param module module whose create method is to be called
179      */
180     public void create(Module module) {
181         if (logger.isDebugEnabled()) {
182             logger.debug("Creating module '" + module.getModuleId() + "'.");
183         }
184         ClassLoader oldcl = Thread.currentThread().getContextClassLoader();
185         ClassLoader newcl = module.getClassLoader();
186         if (newcl != null) {
187             Thread.currentThread().setContextClassLoader(newcl);
188         }
189         try {
190             module.create();
191         } finally {
192             if (newcl != null) {
193                 Thread.currentThread().setContextClassLoader(oldcl);
194             }
195         }
196         if (module.getState() == Module.CREATED) {
197             if (logger.isInfoEnabled()) {
198                 logger.info("Created module '" + module.getModuleId() + "'.");
199             }
200             if (!module.getDependsOn().isEmpty()) {
201                 if (logger.isDebugEnabled()) {
202                     logger.debug("Creating dependent modules of module '" + module.getModuleId() + "'.");
203                 }
204                 for (Module m : module.getDependOnThis()) {
205                     if (m.getState() == Module.WAITING_ON_CREATE) {
206                         create(m);
207                     }
208                     if (m.getState() == Module.WAITING_ON_CREATE_TO_START) {
209                         create(m);
210                         if (m.getState() == Module.CREATED) {
211                             m.setState(Module.WAITING_ON_START);
212                         }
213                     }
214                 }
215             }
216         } else if ((module.getState() == Module.WAITING_ON_CREATE) || (module.getState() == Module.WAITING_ON_CREATE_TO_START)) {
217             if (logger.isDebugEnabled()) {
218                 logger.debug("Creating module '" + module.getModuleId() + "' postponed until dependencies available: " + getUnsatisfiedDependenciesAsAList(module));
219             }
220         } else {
221             if (logger.isWarnEnabled()) {
222                 logger.warn("Creating module '" + module.getModuleId() + "' failed.");
223             }
224         }
225     }
226 
227     /**
228      * Calls {@link Module#start} method.
229      * @param module module whose start method is to be called
230      */
231     public void start(Module module) {
232         if (logger.isDebugEnabled()) {
233             logger.debug("Starting module '" + module.getModuleId() + "'.");
234         }
235         ClassLoader oldcl = Thread.currentThread().getContextClassLoader();
236         ClassLoader newcl = module.getClassLoader();
237         if (newcl != null) {
238             Thread.currentThread().setContextClassLoader(newcl);
239         }
240         try {
241             module.start();
242         } finally {
243             if (newcl != null) {
244                 Thread.currentThread().setContextClassLoader(oldcl);
245             }
246         }
247         if (module.getState() == Module.STARTED) {
248             if (logger.isInfoEnabled()) {
249                 logger.info("Started module '" + module.getModuleId() + "'.");
250             }
251             if (!module.getDependOnThis().isEmpty()) {
252                 if (logger.isDebugEnabled()) {
253                     logger.debug("Starting dependent modules of module '" + module.getModuleId() + "'.");
254                 }
255                 for (Module m : module.getDependOnThis()) {
256                     if (m.getState() == Module.WAITING_ON_CREATE) {
257                         create(m);
258                     }
259                     if (m.getState() == Module.WAITING_ON_CREATE_TO_START) {
260                         create(m);
261                         if (m.getState() == Module.CREATED) {
262                             m.setState(Module.WAITING_ON_START);
263                         }
264                     }
265                     if (m.getState() == Module.WAITING_ON_START) {
266                         start(m);
267                     }
268                 }
269             }
270         } else if ((module.getState() == Module.WAITING_ON_START) || (module.getState() == Module.WAITING_ON_CREATE_TO_START)) {
271             if (logger.isDebugEnabled()) {
272                 logger.debug("Starting module '" + module.getModuleId() + "' postponed until dependencies available: " + getUnsatisfiedDependenciesAsAList(module));
273             }
274         } else {
275             if (logger.isWarnEnabled()) {
276                 logger.warn("Starting module '" + module.getModuleId() + "' failed.");
277             }
278         }
279     }
280 
281     /**
282      * Calls {@link Module#stop} method.
283      * @param module module whose stop method is to be called
284      */
285     public void stop(Module module) {
286         if ((module.getState() == Module.STARTED) && !module.getDependOnThis().isEmpty()) {
287             if (logger.isDebugEnabled()) {
288                 logger.debug("Stopping dependent modules of module '" + module.getModuleId() + "'.");
289             }
290             for (Module m : module.getDependOnThis()) {
291                 if (m.getState() == Module.STARTED) {
292                     stop(m);
293                     if (m.getState() == Module.CREATED) {
294                         m.setState(Module.WAITING_ON_START);
295                     } else {
296                         if (logger.isWarnEnabled()) {
297                             logger.warn("Stopping module '" + module.getModuleId() + "' failed because of stopping of dependent module " + m.getModuleId() + " failed.");
298                         }
299                     }
300                 }
301                 if (m.getState() == Module.STARTED) {
302                     throw new DeploymentException("Cannot stop module " + module.getModuleId() + " since dependent module " + m.getModuleId() + " in in illegal state " + ModuleUtil.stateAsString(m.getState()) + ".");
303                 }
304             }
305         }
306         if (logger.isDebugEnabled()) {
307             logger.debug("Stopping module '" + module.getModuleId() + "'.");
308         }
309         ClassLoader oldcl = Thread.currentThread().getContextClassLoader();
310         ClassLoader newcl = module.getClassLoader();
311         if (newcl != null) {
312             Thread.currentThread().setContextClassLoader(newcl);
313         }
314         try {
315             module.stop();
316         } finally {
317             if (newcl != null) {
318                 Thread.currentThread().setContextClassLoader(oldcl);
319             }
320         }
321         if (module.getState() != Module.STARTED) {
322             if (logger.isInfoEnabled()) {
323                 logger.info("Stopped module '" + module.getModuleId() + "'.");
324             }
325         } else {
326             if (logger.isInfoEnabled()) {
327                 logger.info("Stoping module '" + module.getModuleId() + "' failed.");
328             }
329         }
330     }
331 
332     /**
333      * Calls {@link Module#destroy} method.
334      * @param module module whose destroy method is to be called
335      */
336     public void destroy(Module module) {
337         if ((module.getState() == Module.CREATED) && !module.getDependOnThis().isEmpty()) {
338             if (logger.isDebugEnabled()) {
339                 logger.debug("Destroying dependent modules of module '" + module.getModuleId() + "'.");
340             }
341             for (Module m : module.getDependOnThis()) {
342                 if (m.getState() == Module.CREATED) {
343                     destroy(m);
344                     if (m.getState() == Module.DEFINED) {
345                         m.setState(Module.WAITING_ON_CREATE);
346                     } else {
347                         if (logger.isWarnEnabled()) {
348                             logger.warn("Stopping module '" + module.getModuleId() + "' failed because of stopping of dependent module " + m.getModuleId() + " failed.");
349                         }
350                         return;
351                     }
352                 } else if (m.getState() == Module.WAITING_ON_START) {
353                     destroy(m);
354                     if (m.getState() == Module.DEFINED) {
355                         m.setState(Module.WAITING_ON_CREATE_TO_START);
356                     } else {
357                         if (logger.isWarnEnabled()) {
358                             logger.warn("Stopping module '" + module.getModuleId() + "' failed because of stopping of dependent module " + m.getModuleId() + " failed.");
359                         }
360                         return;
361                     }
362                 }
363                 if ((m.getState() != Module.DEFINED) && (m.getState() != Module.WAITING_ON_CREATE_TO_START) && (m.getState() == Module.WAITING_ON_CREATE)) {
364                     throw new DeploymentException("Cannot destroy module " + module.getModuleId() + " since dependent module " + m.getModuleId() + " in in illegal state " + ModuleUtil.stateAsString(m.getState()) + ".");
365                 }
366             }
367         }
368         if (logger.isDebugEnabled()) {
369             logger.debug("Destroying module '" + module.getModuleId() + "'.");
370         }
371         ClassLoader oldcl = Thread.currentThread().getContextClassLoader();
372         ClassLoader newcl = module.getClassLoader();
373         if (newcl != null) {
374             Thread.currentThread().setContextClassLoader(newcl);
375         }
376         try {
377             module.destroy();
378         } finally {
379             if (newcl != null) {
380                 Thread.currentThread().setContextClassLoader(oldcl);
381             }
382         }
383         if (module.getState() == Module.DEFINED) {
384             if (logger.isInfoEnabled()) {
385                 logger.info("Destroyed module '" + module.getModuleId() + "'.");
386             }
387         } else {
388             if (logger.isInfoEnabled()) {
389                 logger.info("Destroying module '" + module.getModuleId() + "' failed.");
390             }
391         }
392     }
393 //
394 //    /**
395 //     * Finds module by module's uri. This method can create {@link ProvisionalModule} if module
396 //     * with given name doesn't exist and createProvisinal parameter is set to <code>true</code>.
397 //     * {@link ProvisionalModule}'s semantics is of a placeholder for a module that is supposed
398 //     * to be deployed in near future. It usually comes from dependency decleration between modules.
399 //     *
400 //     * @param name name of module
401 //     * @param createProvisional should provisional module be created for this name
402 //     * @return module or <code>null</code>
403 //     */
404 //    public Module findModule(ModuleId moduleId, boolean createProvisional) {
405 //        Module module = (Module)deployedModules.get(moduleId);
406 //        if ((module == null) && createProvisional) {
407 //            module = new ProvisionalModule(moduleId);
408 //            deployedModules.put(moduleId, module);
409 //        }
410 //        return module;
411 //    }
412 
413     /**
414      * Returns map of deployed modules.
415      * @return map of deployed modules
416      */
417     public EnhancedMap<ModuleId, Module> getDeployedModules() {
418         return deployedModules;
419     }
420 
421     /**
422      * Returns <code>true</code> if any of registered module loaders knows how to load (create) module from given url.
423      * @param url url
424      * @return <code>true</code> if module knows how to load (create) module from given url
425      */
426     public boolean canLoad(URI url) {
427         for (ModuleLoader loader : moduleLoaders) {
428             if (loader.canLoad(url)) {
429                 return true;
430             }
431         }
432         return false;
433     }
434     
435     /**
436      * Translates URI to moduleId
437      * @param uri uri
438      * @return module id or <code>null</code>
439      */
440     public ModuleId toModuleId(URI uri) {
441         for (ModuleLoader loader : moduleLoaders) {
442             if (loader.canLoad(uri)) {
443                 ModuleId moduleId = loader.toModuleId(uri);
444                 if (moduleId != null) {
445                     return moduleId;
446                 }
447             }
448         }
449         return null;
450     }
451     
452     
453     /**
454      * Loads module from given URI. If none of registered module loaders can load the module
455      * new {@link ProvisionalModule} is created with the name of last part of the URI.
456      *
457      * will return <code>null</code>.
458      * @param uri URI for module to be loaded from
459      * @return module from given URI
460      */
461     public Module load(URI uri) {
462         Module module = null;
463         for (ModuleLoader loader : moduleLoaders) {
464             if (loader.canLoad(uri)) {
465                 module = loader.load(uri);
466                 return module;
467             }
468         }
469 //        if (module == null) {
470 //            module = new UnknownLoaderModule(uri);
471 //        }
472         
473         return module;
474     }
475 
476     /**
477      * Loads module from given URI. If none of registered module loaders can load the module
478      * new {@link ProvisionalModule} is created with the name of last part of the URI.
479      *
480      * will return <code>null</code>.
481      * @param uri URI for module to be loaded from
482      * @param moduleId module id to be used in final module
483      * @return module from given URI
484      */
485     public Module loadAs(URI uri, ModuleId moduleId) {
486         Module module = null;
487         for (ModuleLoader loader : moduleLoaders) {
488             if (loader.canLoad(uri)) {
489                 module = loader.loadAs(uri, moduleId);
490                 return module;
491             }
492         }
493 //        if (module == null) {
494 //            module = new UnknownLoaderModule(uri);
495 //        }
496         
497         return module;
498     }
499 
500 
501     /**
502      * Utility method that loads and deploys module in one go.
503      * @param uri module's URI
504      */
505     public Module loadAndDeploy(URI uri) {
506         ModuleId moduleId = toModuleId(uri);
507         Module module = null;
508         if (moduleId != null) {
509             module = getDeployedModules().get(moduleId);
510         }
511         if (module == null) {
512             module = load(uri);
513             if (module != null) {
514                 if (!(module instanceof ProvisionalModule)) {
515                     if (!getDeployedModules().containsValue(module)) {
516                         deploy(module.getModuleId(), module);
517                     } else if (!getDeployedModules().containsKey(module.getModuleId())) {
518                         getDeployedModules().put(module.getModuleId(), module);
519                     } else {
520                         logger.debug("Module " + module.getModuleId() + " at " + uri + " already exists");
521                     }
522                 }
523             } else {
524                 // TODO What here? Throw an exception? Return null?
525             }
526         }
527         return module;
528     }
529 //
530 //    /**
531 //     * Utility method that finds module by URI and undeploys it
532 //     * @param uri module's URI
533 //     */
534 //    public void findAndUndeploy(URI uri) {
535 //        Module module = getDeployedModules().get(uri);
536 //        if (module != null) {
537 //            undeploy(module);
538 //        }
539 //    }
540 
541     /**
542      * Utility method
543      * @param uri URI
544      */
545     public void redeploy(ModuleId moduleId) {
546         Module module = getDeployedModules().get(moduleId);
547         undeploy(module);
548         deploy(moduleId, module);
549     }
550 
551     /**
552      * Returns set of module loaders
553      * @return module loaders
554      */
555     public Set<ModuleLoader> getModuleLoaders() {
556         return moduleLoaders;
557     }
558 
559     /**
560      * Sets module loaders. Given set is copied into internal representation that is previously cleared down.
561      * @param moduleLoaders module loaders
562      */
563     public void setModuleLoaders(Set<ModuleLoader> moduleLoaders) {
564         this.moduleLoaders.clear();
565         this.moduleLoaders.addAll(moduleLoaders);
566     }
567 
568     /**
569      * This method goes through all modules with unknown loaders and
570      * tries to load them again (using all registered module loaders)
571      *
572      */
573     protected void tryToDeployUnknown() {
574         Map<ModuleId, Module> toBeDeployed = new LinkedHashMap<ModuleId, Module>();
575         Iterator<Module> it = deployedModules.values().iterator();
576         while (it.hasNext()) {
577             Module module = it.next();
578             if (module instanceof ProvisionalModule) {
579                 URI uri = ((ProvisionalModule)module).getURI();
580                 if ((uri != null) && canLoad(uri)) {
581                     it.remove();
582                     Module m = loadAs(uri, module.getModuleId());
583                     toBeDeployed.put(module.getModuleId(), m);
584                 }
585             }
586         }
587         for (Map.Entry<ModuleId, Module> entry: toBeDeployed.entrySet()) {
588             deploy(entry.getKey(), entry.getValue());
589         }
590     }
591 
592     /**
593      * This class is extension of {@link LinkedHashSet} that
594      * on adding new element calls {@link DeploymentManagerImpl#tryToDeployUnknown()} method.
595      *
596      */
597     protected class ModuleLoaders<E> extends LinkedHashSet<E> {
598 
599         /**
600          * When new element is added to the list
601          * {@link DeploymentManagerImpl#tryToDeployUnknown()} method is called.
602          * @param o new module loader
603          * @return result of {@link Collection#add(Object)} method
604          */
605         public boolean add(E o) {
606             boolean res = super.add(o);
607             if (deployedModules.getUnknownLoaderModulesCount() > 0) {
608                 tryToDeployUnknown();
609             }
610             return res;
611         }
612 
613     }
614 
615 
616     protected static String getUnsatisfiedDependenciesAsAList(Module module) {
617         StringBuffer res = new StringBuffer();
618         boolean first = true;
619 
620 
621         if (!module.getDependsOn().isEmpty()) {
622             for (Module m: module.getDependsOn()) {
623                 int st = m.getState();
624                 if ((st != Module.CREATED) && (st != Module.STARTED)) {
625                     if (first) {
626                         first = false;
627                         res.append(m.getModuleId());
628                     } else {
629                         res.append(',').append(m.getModuleId());
630                     }
631                 }
632             }
633         }
634 
635         return res.toString();
636     }
637 
638     /**
639      * This class keeps deployed modules. Also it keeps track of
640      * {@link ProvisionalModule}s count.
641      */
642     protected class DeployedModules<KeyType, ValueType> extends EnhancedMap<KeyType, ValueType> {
643 
644         /** Number of {@link ProvisionalModule}s */
645         protected int unknownLoaderModules = 0;
646 
647         /** Empty constractor */
648         public DeployedModules() {
649         }
650 
651         /**
652          * Returns get value
653          * @param key key
654          * @return value
655          */
656         public ValueType get(Object key) {
657             if (key instanceof String) {
658                 String s = key.toString();
659                 Iterator<Map.Entry<KeyType, ValueType>> it = entrySet().iterator();
660                 while (it.hasNext()) {
661                     Map.Entry<KeyType, ValueType> entry = it.next();
662                     if ((entry.getKey() != null) && s.equals(entry.getKey().toString())) {
663                         return entry.getValue();
664                     }
665                 }
666                 return null;
667             } else {
668                 return super.get(key);
669             }
670         }
671 
672         /**
673          * Adds new element to the map
674          * @param key key
675          * @param value value
676          * @return previous value under that key
677          */
678         public ValueType put(KeyType key, ValueType value) {
679             ValueType res = super.put(key, value);
680             if ((value instanceof ProvisionalModule) && !(res instanceof ProvisionalModule)) {
681                 unknownLoaderModules = unknownLoaderModules + 1;
682             }
683             return res;
684         }
685 
686         /**
687          * Removes key from the map
688          * @param key key
689          * @return value under that key
690          */
691         public ValueType remove(Object key) {
692             ValueType res = super.remove(key);
693             if (res instanceof ProvisionalModule) {
694                 unknownLoaderModules = unknownLoaderModules - 1;
695             }
696             return res;
697         }
698 
699         /**
700          * Clears the map.
701          */
702         public void clear() {
703             super.clear();
704             unknownLoaderModules = 0;
705         }
706 
707         /**
708          * Returns the number of {@link ProvisionalModule}s
709          * @return the number of {@link ProvisionalModule}s
710          */
711         public int getUnknownLoaderModulesCount() {
712             return unknownLoaderModules;
713         }
714     }
715 }