View Javadoc

1   /*
2    * Copyright (c) 1998-2002 The Jgroup Team.
3    *
4    * This program is free software; you can redistribute it and/or modify
5    * it under the terms of the GNU Lesser General Public License version 2 as
6    * published by the Free Software Foundation.
7    *
8    * This program is distributed in the hope that it will be useful,
9    * but WITHOUT ANY WARRANTY; without even the implied warranty of
10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11   * GNU Lesser General Public License for more details.
12   *
13   * You should have received a copy of the GNU Lesser General Public License
14   * along with this program; if not, write to the Free Software
15   * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16   *
17   */
18  
19  package jgroup.relacs.config;
20  
21  import java.io.Externalizable;
22  import java.io.IOException;
23  import java.io.ObjectInput;
24  import java.io.ObjectOutput;
25  import java.lang.reflect.Constructor;
26  import java.lang.reflect.InvocationTargetException;
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.NoSuchElementException;
32  import java.util.StringTokenizer;
33  
34  import jgroup.core.ConfigurationException;
35  import jgroup.core.MemberId;
36  import jgroup.core.View;
37  import jgroup.core.arm.DistributionScheme;
38  import jgroup.core.arm.RecoveryStrategy;
39  import jgroup.relacs.types.ViewImpl;
40  
41  import org.apache.log4j.Logger;
42  import org.w3c.dom.Element;
43  import org.w3c.dom.Node;
44  import org.w3c.dom.NodeList;
45  
46  
47  /**
48   *  Class for holding application information, including replication
49   *  style and recovery strategy for the dependable applications.
50   *
51   *  @author Hein Meling
52   *  @since Jgroup 1.2
53   */
54  public class AppConfig
55    implements Externalizable, ConfigurationObject
56  {
57  
58    private static final long serialVersionUID = 1335688016655019665L;
59  
60  
61    ////////////////////////////////////////////////////////////////////////////////////////////
62    // Logger
63    ////////////////////////////////////////////////////////////////////////////////////////////
64  
65    /** Obtain logger for this class */
66    private static final Logger log = Logger.getLogger(AppConfig.class);
67  
68  
69    ////////////////////////////////////////////////////////////////////////////////////////////
70    // Tags
71    ////////////////////////////////////////////////////////////////////////////////////////////
72  
73    // Attributes for the App tag
74    private static final String NAME_ATTRIB                = "name";
75    private static final String GROUP_ATTRIB               = "group";
76  
77    private static final String CLASS_TAG_NAME             = "Class";
78    private static final String EXEC_JVM_ATTRIB            = "inExecJVM";
79    private static final String ARGS_ATTRIB                = "args";
80  
81    private static final String PARAM_TAG_NAME             = "Param";
82    private static final String VALUE_ATTRIB               = "value";
83  
84    private static final String LAYERSTACK_TAG_NAME        = "LayerStack";
85    private static final String ORDER_ATTRIB               = "order";
86  
87    private static final String SERVICE_TAG_NAME           = "Service";
88    private static final String AUTO_ATTRIB                = "auto";
89  
90    private static final String RECOVERYSTRATEGY_TAG_NAME  = "RecoveryStrategy";
91    private static final String REDUNDANCY_TAG_NAME        = "Redundancy";
92    private static final String MINIMAL_ATTRIB             = "minimal";
93    private static final String INITIAL_ATTRIB             = "initial";
94  
95  
96    ////////////////////////////////////////////////////////////////////////////////////////////
97    // Constants
98    ////////////////////////////////////////////////////////////////////////////////////////////
99  
100   /**
101    *  Lower and upper bound on the redundancy level allowed.  Used for
102    *  checking if the specified redundancy levels are valid.
103    */
104   private final static int REDUNDANCY_LOWER_BOUND = 1;
105   private final static int REDUNDANCY_UPPER_BOUND = 20;
106 
107   /**
108    *  Initial capacity is 37, since the expected number of application
109    *  entries is less than 28.  If this succeeds we avoid having to
110    *  rehash the HashMap.  @See java.util.HashMap for details.
111    */
112   private static final int APPLICATION_HASH_SIZE = 37;
113 
114 
115   ////////////////////////////////////////////////////////////////////////////////////////////
116   // Static collections (for inter-object or class access)
117   ////////////////////////////////////////////////////////////////////////////////////////////
118 
119   /** Application registry name -> AppConfig object */
120   private static Map<String,AppConfig> appMap =
121     new HashMap<String,AppConfig>(APPLICATION_HASH_SIZE);
122 
123   /** Application class name -> AppConfig object */
124   private static Map<String,AppConfig> appClazzMap =
125     new HashMap<String,AppConfig>(APPLICATION_HASH_SIZE);
126 
127   /** Application group identifier -> AppConfig object */
128   private static Map<Integer,AppConfig> appGroupMap =
129     new HashMap<Integer,AppConfig>(APPLICATION_HASH_SIZE);
130 
131 
132   ////////////////////////////////////////////////////////////////////////////////////////////
133   // Transient and static fields
134   ////////////////////////////////////////////////////////////////////////////////////////////
135 
136   /** Recovery strategy instance */
137   private transient RecoveryStrategy recoveryStrategy = null;
138 
139   /** The distribution scheme of the local replication manager */
140   private static DistributionScheme dScheme = null;
141 
142 
143   ////////////////////////////////////////////////////////////////////////////////////////////
144   // Fields
145   ////////////////////////////////////////////////////////////////////////////////////////////
146 
147   /** The name used in the registry for this application */
148   private String regName;
149 
150   /** Group identifier */
151   private int groupId;
152 
153   /** ClassData (className, classArgs) */
154   private ClassData classData;
155 
156   /** The service set or layer stack order for this application */
157   private List<String> serviceSet;
158 
159   /** Map containing parameters associated with this application */
160   private Map<String,String> paramMap;
161 
162   /**
163    *  Boolean value used to indicate that if the application
164    *  should be started within the same JVM as the execution
165    *  daemon.  If the value is false the application will be
166    *  started in a separate JVM.
167    */
168   private boolean inExecJVM;
169 
170   /** Recovery strategy class */
171   private Class<?> strategyClass;
172 
173   /** Initial redundancy for this application */
174   private int initialRedundancy = -1;
175 
176   /** Minimal redundancy for this application */
177   private int minimalRedundancy = -1;
178 
179   /** The current view of this application */
180   private View view;
181 
182   /** The set of hosts on which this application is known to be located */
183   private HostSet viewHosts = new HostSet();
184 
185 
186   ////////////////////////////////////////////////////////////////////////////////////////////
187   // Constructors
188   ////////////////////////////////////////////////////////////////////////////////////////////
189 
190   /**
191    *  Creates a new <code>AppConfig</code> instance, from the given class
192    *  name, arguments and group identifier.  The <code>AppConfig</code>
193    *  object <i>must</i> also be initialized with the required
194    *  redundancy and replication style parameters through the
195    *  corresponding set methods; otherwise an
196    *  <code>IllegalStateException</code> will be thrown upon using the
197    *  <code>AppConfig</code> object.
198    *
199    *  @param className
200    *     The classname for this application object.
201    *  @param args
202    *     The arguments for the application object.
203    *  @param groupId
204    *     The group identifier for this application object.
205    *  @exception ClassNotFoundException 
206    *     Raised if the given class could not be found.
207    */
208   public AppConfig(String regName, String className, String args, int groupId, boolean inExecJVM)
209 //    throws ClassNotFoundException
210   {
211     this.regName = regName;
212     this.groupId = groupId;
213     this.view = new ViewImpl(groupId);
214     StringTokenizer argTokens = new StringTokenizer(args);
215     String[] classArgs = new String[argTokens.countTokens()];
216     for (int i = 0; argTokens.hasMoreTokens(); i++)
217       classArgs[i] = argTokens.nextToken();
218     classData = new ClassData(className, classArgs);
219     this.inExecJVM = inExecJVM;
220   }
221 
222 
223   /**
224    *  Augment the system with an additional application.
225    *
226    *  @param regname
227    *     the registry name for this application
228    *  @param classname
229    *     the class name
230    *  @param args
231    *     argument string
232    *  @param group
233    *     the group identifier for the application
234    *  @param inExecJVM
235    *     must be true if the application should be started in the execution daemon JVM.
236    *     if set to false, the application will be started in a separate process.
237    *  @return 
238    *    application info object associated with the <classname, args> pair 
239    *  @throws ConfigurationException
240    *    if the specified class name is already registered
241    */
242   public static AppConfig addApplication(String regname, String classname, String args,
243                                   int group, boolean inExecJVM)
244     throws ConfigurationException
245   {
246 //    if (appMap.containsKey(regname))
247 //      throw new ConfigurationException("Registry name already defined: " + regname);
248     /*
249      * We don't allow multiple application definitions for the same class.
250      */
251     if (appClazzMap.containsKey(classname))
252       throw new ConfigurationException("Class name already defined: " + classname);
253     AppConfig app = new AppConfig(regname, classname, args, group, inExecJVM);
254 //    try {
255 //      app = new AppConfig(regname, classname, args, group, inExecJVM);
256 //    } catch (ClassNotFoundException e) {
257 //      throw new ConfigurationException("Could not find the specified application class: "
258 //        + e.getMessage());
259 //    }
260     appMap.put(regname, app);
261     appClazzMap.put(classname, app);
262     appGroupMap.put(new Integer(group), app);
263     return app;
264   }
265 
266 
267   /**
268    * Add the given set of applications to the static maps.
269    *
270    * @param apps
271    * @throws ConfigurationException
272    */
273   public static void addApplications(AppConfig[] apps)
274     throws ConfigurationException
275   {
276     for (int i = 0; i < apps.length; i++) {
277       AppConfig app = apps[i];
278       String classname = app.getClassName();
279       if (appClazzMap.containsKey(classname))
280         throw new ConfigurationException("Class name already defined: " + classname);
281       appMap.put(app.getRegistryName(), app);
282       appClazzMap.put(classname, app);
283       appGroupMap.put(new Integer(app.getGroupId()), app);
284     }
285   }
286 
287 
288   /**
289    *  Returns an array of all applications registered.
290    */
291   public static AppConfig[] getApplications()
292   {
293     return (AppConfig[]) appMap.values().toArray(new AppConfig[appMap.size()]);
294   }
295 
296 
297   /**
298    *  Returns an <CODE>AppConfig</CODE> object associated with the
299    *  specified name.  The name may be either the registry name or the
300    *  class name of the application.
301    *
302    *  @exception NullPointerException
303    *    Raised if the specified name has no corresponding class in the
304    *    applications configuration.
305    */
306   public static AppConfig getApplication(String name)
307   {
308     AppConfig app = (AppConfig) appMap.get(name);
309     if (app == null) {
310       app = (AppConfig) appClazzMap.get(name);
311       if (app == null)
312         throw new NullPointerException("Unknown application: " + name);
313     }
314     return app;
315   }
316 
317 
318   /**
319    *  Returns an <CODE>AppConfig</CODE> object associated with the
320    *  specified object instance.
321    *
322    *  @exception NullPointerException
323    *    Raised if the specified object instance name has no
324    *    corresponding class in the applications configuration.
325    */
326   public static AppConfig getApplication(Object appObj)
327   {
328     if (appObj == null)
329       throw new NullPointerException("No application object specified");
330     AppConfig app = (AppConfig) appClazzMap.get(appObj.getClass().getName());
331     if (app == null)
332       throw new NullPointerException("Unknown application: " + appObj.getClass().getName());
333     return app;
334   }
335 
336 
337   /**
338    *  Returns an <CODE>AppConfig</CODE> object associated with the
339    *  specified class.
340    *
341    *  @exception NullPointerException
342    *    Raised if the specified class has no corresponding class
343    *    in the applications configuration.
344    */
345   public static AppConfig getApplication(Class cl)
346   {
347     AppConfig app = (AppConfig) appClazzMap.get(cl.getName());
348     if (app == null)
349       throw new NullPointerException("Unknown application: " + cl.getName());
350     return app;
351   }
352 
353 
354   /**
355    *  Returns an <CODE>AppConfig</CODE> object associated with the
356    *  specified group identifier.
357    *
358    *  @exception NullPointerException
359    *    Raised if the specified group identifier has no corresponding application
360    *    in the applications configuration.
361    */
362   public static AppConfig getApplication(int gid)
363   {
364     AppConfig app = (AppConfig) appGroupMap.get(new Integer(gid));
365     if (app == null)
366       throw new NullPointerException("Unknown application for group: " + gid);
367     return app;
368   }
369 
370   /**
371    *  Method to store the distribution scheme of the local
372    *  replication manager.  This is required for recovery strategy
373    *  construction and initialization.
374    */
375   public static void setDistributionScheme(DistributionScheme distScheme)
376   {
377     dScheme = distScheme;
378   }
379 
380 
381   ////////////////////////////////////////////////////////////////////////////////////////////
382   // Methods from ConfigurationObject
383   ////////////////////////////////////////////////////////////////////////////////////////////
384 
385   /**
386    *  Parses the specified element.
387    */
388   public void parse(Element elm)
389     throws ConfigurationException
390   {
391     /* 
392      *  Read attributes of the App tag
393      */
394     String regname = ConfigParser.getAttrib(elm, NAME_ATTRIB, true);
395     int group = ConfigParser.getIntAttrib(elm, GROUP_ATTRIB, true);
396     AppConfig app = null;
397 
398     /*
399      * Nodes within the application tag should be either Class,
400      * LayerStack, ReplicationStyle or RecoveryStrategy tags.
401      */
402     NodeList tagList = elm.getChildNodes();
403 
404     /* Parse the list of tags */
405     for (int i = 1; i <= tagList.getLength(); i++) {
406       Node node = tagList.item(i);
407       if (node != null && !node.getNodeName().startsWith("#")) {
408         elm = (Element)node;
409 
410         if (ConfigParser.checkTag(elm, CLASS_TAG_NAME)) {
411           String classname = ConfigParser.getAttrib(elm, NAME_ATTRIB, true);
412           boolean inExecJVM = ConfigParser.getBooleanAttrib(elm, EXEC_JVM_ATTRIB, false);
413           String args = ConfigParser.getAttrib(elm, ARGS_ATTRIB, false);
414           /* Add the application data to the system */
415           app = AppConfig.addApplication(regname, classname, args, group, inExecJVM);
416 
417         } else if (ConfigParser.checkTag(elm, PARAM_TAG_NAME)) {
418           String pName = ConfigParser.getAttrib(elm, NAME_ATTRIB, true);
419           String pValue = ConfigParser.getAttrib(elm, VALUE_ATTRIB, true);
420           app.addParam(pName, pValue);
421 
422         } else if (ConfigParser.checkTag(elm, LAYERSTACK_TAG_NAME)) {
423           if (!parseServiceSet(app, elm)) {
424             /*
425              * The service set was not defined using the Service tag,
426              * check if there is an order atttribute instead.
427              */
428             String stackOrder = ConfigParser.getAttrib(elm, ORDER_ATTRIB, true);
429             app.setServiceSet(stackOrder);
430           }
431 
432         } else if (ConfigParser.checkTag(elm, RECOVERYSTRATEGY_TAG_NAME)) {
433           parseRecoveryStrategy(app, elm);
434         }
435       }
436     }
437   }
438 
439 
440   ////////////////////////////////////////////////////////////////////////////////////////////
441   // Private methods
442   ////////////////////////////////////////////////////////////////////////////////////////////
443 
444   private boolean parseServiceSet(AppConfig app, Element elm)
445     throws ConfigurationException
446   {
447     NodeList serviceList = elm.getChildNodes();
448     if (serviceList.getLength() == 0) {
449       return false;
450     }
451 
452     /* Parse the list of tags */
453     for (int i = 1; i <= serviceList.getLength(); i++) {
454       Node node = serviceList.item(i);
455       if (node != null && !node.getNodeName().startsWith("#")) {
456         elm = (Element)node;
457         if (ConfigParser.checkTag(elm, SERVICE_TAG_NAME)) {
458           String serviceName = ConfigParser.getAttrib(elm, NAME_ATTRIB, true);
459           boolean auto = ConfigParser.getBooleanAttrib(elm, AUTO_ATTRIB, false);
460           if (auto)
461             app.addParam(serviceName + "." + AUTO_ATTRIB, Boolean.toString(auto));
462           app.addService(serviceName);
463           parseServiceParams(serviceName, app, elm);
464         }
465       }
466     }
467     return true;
468   }
469 
470 
471   private void parseServiceParams(String serviceName, AppConfig app, Element elm)
472     throws ConfigurationException
473   {
474     NodeList paramList = elm.getChildNodes();
475 
476     /* Parse the list of tags */
477     for (int i = 1; i <= paramList.getLength(); i++) {
478       Node node = paramList.item(i);
479       if (node != null && !node.getNodeName().startsWith("#")) {
480         elm = (Element)node;
481         if (ConfigParser.checkTag(elm, PARAM_TAG_NAME)) {
482           String pName = serviceName + "." + ConfigParser.getAttrib(elm, NAME_ATTRIB, true);
483           String pValue = ConfigParser.getAttrib(elm, VALUE_ATTRIB, true);
484           app.addParam(pName, pValue);
485         }
486       }
487     }
488   }
489 
490 
491   private void parseRecoveryStrategy(AppConfig app, Element elm)
492     throws ConfigurationException
493   {
494     int initial = 0, minimal = 0;
495 
496     String strategy = ConfigParser.getAttrib(elm, NAME_ATTRIB, true);
497 
498     NodeList redundancyList = elm.getChildNodes();
499 
500     /* Parse the list of tags */
501     for (int i=1; i <= redundancyList.getLength(); i++) {
502       Node node = redundancyList.item(i);
503       if (node != null && !node.getNodeName().startsWith("#")) {
504         elm = (Element)node;
505         if (ConfigParser.checkTag(elm, REDUNDANCY_TAG_NAME)) {
506           initial = ConfigParser.getIntAttrib(elm, INITIAL_ATTRIB, true);
507           minimal = ConfigParser.getIntAttrib(elm, MINIMAL_ATTRIB, true);
508         }
509       }
510     }
511     app.setRecoveryStrategy(strategy);
512     /*
513      * Check if the redundancy parameters should be overridden by the
514      * ones defined as system properties.
515      */
516     String minimalStr = System.getProperty("minimal.redundancy");
517     String initialStr = System.getProperty("initial.redundancy");
518     if (minimalStr != null && minimalStr.length() > 0 && !minimalStr.equals("default")) {
519       minimal = Integer.parseInt(minimalStr);
520     }
521     if (initialStr != null && initialStr.length() > 0 && !initialStr.equals("default")) {
522       initial = Integer.parseInt(initialStr);
523     }
524     app.setRedundancy(minimal, initial);
525   }
526 
527 
528   ////////////////////////////////////////////////////////////////////////////////////////////
529   // Application Info interface methods
530   ////////////////////////////////////////////////////////////////////////////////////////////
531 
532   /**
533    *  Returns the name associated with this application in the
534    *  (dependable) registry.
535    */
536   public String getRegistryName()
537   {
538     return regName;
539   }
540 
541 
542   /**
543    *  Returns a class data object for the application.
544    *
545    *  @return 
546    *    the class data object for the application.
547    */
548   public ClassData getClassData()
549   {
550     return classData;
551   }
552 
553 
554   /**
555    * Set the properties that should apply for this application.
556    * If no properties map is provided, the properties is initialized
557    * according to this applications properties.
558    */
559   public void setProperties(Map<String,String> properties)
560   {
561     if (properties == null) {
562       /*
563        * If the given properties map is null, we initialize a new
564        * map with the properties of this application.
565        */
566       properties = new HashMap<String,String>(3);
567     }
568     if (!properties.containsKey("minimal.redundancy"))
569       properties.put("minimal.redundancy", Integer.toString(getMinimalRedundancy()));
570     if (!properties.containsKey("initial.redundancy"))
571       properties.put("initial.redundancy", Integer.toString(getInitialRedundancy()));
572     classData.setProperties(properties);
573   }
574 
575 
576   public int getGroupId()
577   {
578     return groupId;
579   }
580 
581 
582   /**
583    *  Returns the class name as a string.
584    *
585    *  @return 
586    *    Returns the class name as a string.
587    */
588   public String getClassName()
589   {
590     return classData.getName();
591   }
592 
593 
594   /**
595    *  Returns the class arguments as a string array.
596    *
597    *  @return 
598    *    Returns the class arguments as a string array.
599    */
600   public String[] getClassArgs()
601   {
602     return classData.getArgs();
603   }
604 
605 
606   /**
607    *  Returns true if this application should be started within the
608    *  same JVM as the execution daemon.  False indicates that the
609    *  application should be started external the the execution daemon,
610    *  that is in a separate JVM.
611    */
612   public boolean startInExecJVM()
613   {
614     return inExecJVM;
615   }
616 
617 
618   /**
619    *  Returns the value associated with the parameter name provided
620    *  to this method.
621    *
622    *  @param name
623    *    The name of the parameter to retreive from the parameter map.
624    *  @return
625    *    The parameter value associated with the given parameter name.
626    *  @throws NoSuchElementException
627    *    Raised if the given parameter name was not found in the internal
628    *    parameter map, or if there is no parameters associated with this
629    *    application.
630    */
631   public String getParam(String name)
632     throws NoSuchElementException
633   {
634     if (paramMap == null) {
635       throw new NoSuchElementException("The application has no associated parameters");
636     }
637     String strval = (String) paramMap.get(name);
638     if (strval == null) {
639       throw new NoSuchElementException("No parameter named '" + name
640         + "' found to be associated with application: " + regName);
641     }
642     return strval;
643   }
644 
645 
646   /**
647    *  Returns the value associated with the parameter name provided
648    *  to this method; if this application has no such parameter defined
649    *  the provided default value will be returned.
650    *
651    *  @param name
652    *    The name of the parameter to retreive from the parameter map.
653    *  @param defaultVal
654    *    The default value to return if the given parameter is not
655    *    defined in the parameter map.
656    *  @return
657    *    The parameter value associated with the given parameter name.
658    */
659   public String getParam(String name, String defaultVal)
660   {
661     if (paramMap == null) {
662       return defaultVal;
663     }
664     String strval = (String) paramMap.get(name);
665     if (strval == null) {
666       return defaultVal;
667     }
668     return strval;
669   }
670 
671 
672   /**
673    *  Returns the integer value associated with the parameter name
674    *  provided to this method.
675    *
676    *  @param name
677    *    The name of the parameter to retreive from the parameter map.
678    *  @return
679    *    The <code>int</code> parameter value associated with the given
680    *    parameter name.
681    *  @throws NoSuchElementException
682    *    Raised if the given parameter name was not found in the internal
683    *    parameter map.
684    */
685   public int getIntParam(String name)
686     throws NoSuchElementException
687   {
688     return Integer.valueOf(getParam(name)).intValue();
689   }
690 
691 
692   /**
693    *  Returns the integer value associated with the parameter name
694    *  provided to this method.  If this application has no associated
695    *  value for this parameter, the provided default value is returned.
696    *
697    *  @param name
698    *    The name of the parameter to retreive from the parameter map.
699    *  @param defaultValue
700    *    The default value of the parameter to be return if no value
701    *    is available in the parameter map.
702    *  @return
703    *    The <code>int</code> parameter value associated with the given
704    *    parameter name.  If the parameter have no specified value for
705    *    the associated application, we return the provided default value.
706    */
707   public int getIntParam(String name, int defaultValue)
708   {
709     if (paramMap == null) {
710       return defaultValue;
711     }
712     String strval = (String) paramMap.get(name);
713     if (strval == null) {
714       return defaultValue;
715     }
716     return Integer.valueOf(strval).intValue();
717   }
718 
719 
720   /**
721    *  Returns the boolean value associated with the parameter name
722    *  provided to this method.  If this application has no associated
723    *  value for this parameter, false is returned.
724    *
725    *  @param name
726    *    The name of the parameter to retreive from the parameter map.
727    *  @return
728    *    The <code>boolean</code> parameter value associated with the given
729    *    parameter name.  If the parameter have no specified value for
730    *    the associated application, false is returned.
731    */
732   public boolean getBooleanParam(String name)
733   {
734     if (paramMap == null) {
735       return false;
736     }
737     String strval = (String) paramMap.get(name);
738     if (strval == null) {
739       return false;
740     }
741     return Boolean.valueOf(strval).booleanValue();
742   }
743 
744 
745   /**
746    *  Retrieve the set of services used by this application.
747    *
748    *  @return 
749    *    A <code>String[]</code> of services used by this application,
750    *    ordered from the lowest layer at index position zero.
751    *  @exception IllegalStateException
752    *    Raised if the service set has not yet been initialized.
753    */
754   public String[] getServiceSet()
755   {
756     if (serviceSet == null) {
757       throw new IllegalStateException("Service set not yet initialized.");
758     }
759     return (String[]) serviceSet.toArray(new String[serviceSet.size()]);
760   }
761 
762 
763   /**
764    *  Returns true if this application contains the specified service,
765    *  otherwise false is returned.
766    *
767    *  @return 
768    *    True if this application contains the specified service.
769    *  @exception IllegalStateException
770    *    Raised if the service set has not yet been initialized.
771    */
772   public boolean hasService(String service)
773   {
774     if (serviceSet == null) {
775       throw new IllegalStateException("Service set not yet initialized.");
776     }
777     return serviceSet.contains(service);
778   }
779 
780 
781   public RecoveryStrategy getRecoveryStrategy()
782   {
783     if (strategyClass == null)
784       throw new IllegalStateException("RecoveryStrategy not set");
785     if (recoveryStrategy == null) {
786       try {
787         initRecoveryStrategy();
788       } catch (ConfigurationException e) {
789         throw new IllegalStateException(e.getMessage());
790       }
791     }
792     return recoveryStrategy;
793   }
794 
795 
796   /**
797    *  Set the minimal and initial redundancy for this application.
798    *
799    *  @param initial  
800    *    initial redundancy for this application
801    *  @param minimal
802    *    minimal redundancy for this application
803    *  @exception ConfigurationException
804    *    if the redundancy specifications are out of bound.
805    */
806   public void setRedundancy(int minimal, int initial)
807     throws ConfigurationException
808   {
809     if (initial < minimal)
810       throw new ConfigurationException("Initial redundancy is less than the minimal redundancy.");
811 
812     if (initial >= REDUNDANCY_LOWER_BOUND && initial <= REDUNDANCY_UPPER_BOUND)
813       initialRedundancy = initial;
814     else if (initial > REDUNDANCY_UPPER_BOUND)
815       throw new ConfigurationException("Initial redundancy is too high.");
816     else
817       throw new ConfigurationException("Initial redundancy is too low.");
818 
819     if (minimal >= REDUNDANCY_LOWER_BOUND && minimal <= REDUNDANCY_UPPER_BOUND)
820       minimalRedundancy = minimal;
821     else if (minimal > REDUNDANCY_UPPER_BOUND)
822       throw new ConfigurationException("Minimal redundancy is too high.");
823     else
824       throw new ConfigurationException("Minimal redundancy is too low.");
825   }
826 
827   public int getInitialRedundancy()
828   {
829     if (initialRedundancy == -1) 
830       throw new IllegalStateException("Inital redundancy is not set: " + this);
831     return initialRedundancy;
832   }
833 
834 
835   public int getMinimalRedundancy()
836   {
837     if (minimalRedundancy == -1) 
838       throw new IllegalStateException("Minimal redundancy is not set");
839     return minimalRedundancy;
840   }
841 
842 
843   public int getCurrentGroupSize()
844   {
845     return view.size();
846   }
847 
848   /**
849    *  Returns true if this application needs recovery,
850    *  otherwise false is returned.
851    */
852   public boolean needsRecovery()
853   {
854     RecoveryStrategy rs = getRecoveryStrategy();
855     return rs.needsRecovery();
856   }
857 
858   ////////////////////////////////////////////////////////////////////////////////////////////
859   // MembershipListener interface methods
860   ////////////////////////////////////////////////////////////////////////////////////////////
861 
862   /**
863    * Update this application group's view and set of view hosts.
864    */
865   public synchronized void viewChange(View view)
866   {
867     if (!this.view.equals(view)) {
868       this.view = view;
869       //FIXME before updating the viewHosts set, we should update the status of the replicas on these hosts
870       // to something other than NORMAL/JOINING...
871       viewHosts.removeAllHosts();
872       MemberId[] members = view.getMembers();
873       for (int i = 0; i < members.length; i++) {
874         Host host = DistributedSystemConfig.getHost(members[i]);
875         // Update the replica status on the host to normal
876         host.viewChange(this);
877         viewHosts.addHost(host);
878       }
879     }
880   }
881 
882   /**
883    * Returns the current view of this application, which may be
884    * an empty view in case the application has not installed
885    * any views yet.
886    */
887   public View getView()
888   {
889     return view;
890   }
891 
892   /**
893    * Returns the set of hosts on which replicas of this application
894    * is known to be located.
895    */
896   public HostSet getViewHosts()
897   {
898     return viewHosts;
899   }
900 
901 
902   ////////////////////////////////////////////////////////////////////////////////////////////
903   // Initialization methods
904   ////////////////////////////////////////////////////////////////////////////////////////////
905 
906   /**
907    *  Sets the recovery strategy for this application.
908    *
909    *  @param strategy
910    *    class name denoting the recovery strategy
911    *  @exception ConfigurationException
912    *    if the recovery strategy is not supported.
913    */
914   public void setRecoveryStrategy(String strategy)
915     throws ConfigurationException
916   {
917     /*
918      * Check if the given strategy use full package naming; if not, we
919      * augment it with the default recovery strategy location.
920      */
921     strategy = (strategy.indexOf(".") != -1) ? strategy : "jgroup.arm.recovery." + strategy;
922     try {
923       strategyClass = Class.forName(strategy);
924     } catch (ClassNotFoundException e) {
925       throw new ConfigurationException("Unsupported recovery strategy specified: " + strategy);
926     }
927   }
928 
929 
930   /**
931    *  Set the service set or stack order for this application.  The
932    *  layer stack order defines the layers that the application requires
933    *  to be used, and the group manager will construct the required set
934    *  of layers in the given order.
935    *
936    *  @param stackOrder
937    *    The colon separate string of services that this application
938    *    requires.
939    *  @exception ConfigurationException
940    *    Thrown if the string contains an unspecified service.
941    */
942   public void setServiceSet(String stackOrder)
943     throws ConfigurationException
944   {
945     String[] stack = stackOrder.split(":");
946     for (int i = 0; i < stack.length; i++) {
947       addService(stack[i]);
948     }
949   }
950 
951 
952   /**
953    *  Add the given service to the protocol stack for this application.
954    *  This method must be invoked in the order in which the layers should
955    *  be organized in the stack.
956    *
957    *  @param serviceName
958    *    The service name to add to the application.
959    */
960   public void addService(String serviceName)
961     throws ConfigurationException
962   {
963     if (serviceSet == null) {
964       serviceSet = new ArrayList<String>();
965     }
966     serviceSet.add(serviceName);
967   }
968 
969 
970   /**
971    *  Add the given name-value pair to the internal parameter map.
972    *
973    *  @param name
974    *    The name of the parameter to add to the map.
975    *  @param value
976    *    The value to associate with the parameter.
977    *
978    *  @throws ConfigurationException
979    *    Raised if the given parameter name already exists within the
980    *    internal parameter map.
981    */
982   public void addParam(String name, String value)
983     throws ConfigurationException
984   {
985     if (paramMap == null) {
986       paramMap = new HashMap<String,String>();
987     } else {
988       if (paramMap.containsKey(name)) {
989         throw new ConfigurationException("Multiply defined parameter name: " + name);
990       }
991     }
992     paramMap.put(name, value);
993   }
994 
995 
996   ////////////////////////////////////////////////////////////////////////////////////////////
997   // Private methods
998   ////////////////////////////////////////////////////////////////////////////////////////////
999 
1000   /**
1001    *  Initialize the recovery strategy for this application.  The
1002    *  recovery strategy should not be instantiated on other host than
1003    *  the ones running the replication manager replicas.  That is, since
1004    *  we expect the recovery strategy to make use of the local
1005    *  replication manager in order to perform recovery.  Creating an
1006    *  instance on all hosts parsing the <code>application.xml</code>
1007    *  would thus result in an instance of the replication manager on
1008    *  each host as well.  Therefore, we only init the recovery strategy
1009    *  upon invocations of the getRecoveryStrategy() method, typically
1010    *  only invoked from within the replication manager (Correlator).
1011    *
1012    *  @exception ConfigurationException
1013    *    If the recovery strategy associated with this application
1014    *    could not be initialized.
1015    */
1016   private void initRecoveryStrategy()
1017     throws ConfigurationException
1018   {
1019     if (dScheme == null) {
1020       throw new IllegalStateException("Cannot initialize recovery strategy "
1021           + "without providing a distribution scheme.");
1022     }
1023     try {
1024       /*
1025        * Check if the recovery strategy used by this application has
1026        * already been initialized; if so, we just return now.
1027        */
1028       if (recoveryStrategy != null)
1029         return;
1030 
1031       /*
1032        * A no-argument constructor must be provided by a class
1033        * implementing a recovery strategy.  This pattern cannot be
1034        * enforced by the RecoveryStratgy interface, thus we must check
1035        * this at runtime instead.
1036        */
1037       Constructor<?> c = strategyClass.getConstructor( new Class[] {} );
1038       recoveryStrategy = (RecoveryStrategy) c.newInstance( new Object[] {} );
1039       /* Initialize the recovery strategy. */
1040       recoveryStrategy.initialize(dScheme, this);
1041 
1042     } catch (NoSuchMethodException e) {
1043       throw new ConfigurationException(strategyClass + " does not implement a non-argument constructor.");
1044     } catch (IllegalArgumentException e) {
1045       throw new ConfigurationException(strategyClass + " does not implement a non-argument constructor.");
1046     } catch (IllegalAccessException e) {
1047       throw new ConfigurationException(strategyClass + " does not provide an accessible constructor.");
1048     } catch (InstantiationException e) {
1049       throw new ConfigurationException(strategyClass + " represents an abstract class.");
1050     } catch (InvocationTargetException e) {
1051       throw new ConfigurationException(strategyClass + " has thrown an exception.", e.getTargetException());
1052     }
1053   }
1054 
1055 
1056   ////////////////////////////////////////////////////////////////////////////////////////////
1057   // Methods from Object
1058   ////////////////////////////////////////////////////////////////////////////////////////////
1059 
1060   /**
1061    *  Returns a hash code value for this application info object.
1062    */
1063   public int hashCode()
1064   {
1065     return regName.hashCode() ^ classData.hashCode() ^ groupId;
1066   }
1067 
1068 
1069   /**
1070    *  Returns true if the provided object is equal to this object.
1071    */
1072   public boolean equals(Object obj)
1073   {
1074     if (!(obj instanceof AppConfig)) {
1075       return false;
1076     }
1077     AppConfig app = (AppConfig) obj;
1078     return (regName.equals(app.regName) &&
1079             classData.equals(app.classData) &&
1080             groupId == app.groupId);
1081   }
1082 
1083 
1084   /**
1085    *  Returns a string representation of this object
1086    */
1087   public String toString() 
1088   {
1089     return toString(false);
1090   }
1091 
1092 
1093   /**
1094    * Returns a string representation of this object.  If the 
1095    * <code>full</code> parameter is false, only a short version
1096    * is returned, otherwise a full version is returned.
1097    */
1098   public String toString(boolean full)
1099   {
1100     StringBuilder buf = new StringBuilder();
1101     if (full) {
1102       buf.append("[AppConfig: ");
1103       buf.append(regName);
1104       buf.append(", gid=");
1105       buf.append(groupId);
1106       buf.append(": ");
1107     }
1108     buf.append(classData.getShortName());
1109     buf.append(": ");
1110     if (full) {
1111       String[] srSet = getServiceSet();
1112       for (int i = 0; i < srSet.length; i++) {
1113         buf.append((i != 0) ? ":" : "");
1114         buf.append(srSet[i]);
1115       }
1116       buf.append(";  ");
1117       if (strategyClass != null)
1118         buf.append(strategyClass.getName());
1119       buf.append(", ");
1120     }
1121     buf.append("{");
1122     buf.append(initialRedundancy);
1123     buf.append(", ");
1124     buf.append(minimalRedundancy);
1125     buf.append("}, viewHosts=");
1126     buf.append(viewHosts);
1127     if (full)
1128       buf.append("]");
1129     return buf.toString();
1130   }
1131 
1132   ////////////////////////////////////////////////////////////////////////////////////////////
1133   // Methods from Externalizable
1134   ////////////////////////////////////////////////////////////////////////////////////////////
1135 
1136   /**
1137    *  Default constructor for externalization and configuration parser construction.
1138    */
1139   public AppConfig() { }
1140 
1141 
1142   /**
1143    *  @see java.io.Externalizable#readExternal(ObjectInput)
1144    */
1145   @SuppressWarnings("unchecked")
1146   public void readExternal(ObjectInput in)
1147     throws IOException, ClassNotFoundException
1148   {
1149     regName = in.readUTF();
1150     groupId = in.readInt();
1151     classData = (ClassData) in.readObject();
1152     serviceSet = (List) in.readObject();
1153     paramMap = (Map) in.readObject();
1154     inExecJVM = in.readBoolean();
1155     strategyClass = (Class) in.readObject();
1156     initialRedundancy = in.readInt();
1157     minimalRedundancy = in.readInt();
1158     view = new ViewImpl();
1159     view.readExternal(in);
1160     viewChange(view);
1161     if (log.isDebugEnabled())
1162       log.debug("Received AppConfig object: " + toString());
1163     /*
1164      * Replace the application in the local maps with the application
1165      * being unmarshalled, so as to update the various parameters at
1166      * other hosts.
1167      */
1168     appMap.put(regName, this);
1169     appClazzMap.put(classData.getName(), this);
1170     appGroupMap.put(new Integer(groupId), this);
1171   }
1172 
1173 
1174   /**
1175    *  @see java.io.Externalizable#writeExternal(ObjectOutput)
1176    */
1177   public void writeExternal(ObjectOutput out)
1178     throws IOException
1179   {
1180     out.writeUTF(regName);
1181     out.writeInt(groupId);
1182     out.writeObject(classData);
1183     out.writeObject(serviceSet);
1184     out.writeObject(paramMap);
1185     out.writeBoolean(inExecJVM);
1186     out.writeObject(strategyClass);
1187     out.writeInt(initialRedundancy);
1188     out.writeInt(minimalRedundancy);
1189     view.writeExternal(out);
1190     if (log.isDebugEnabled())
1191       log.debug("Sending AppConfig object: " + toString());
1192   }
1193 
1194 } // END AppConfig