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.util.ArrayList;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import javax.xml.parsers.DocumentBuilder;
29  import javax.xml.parsers.DocumentBuilderFactory;
30  
31  import jgroup.core.ConfigurationException;
32  import jgroup.util.Abort;
33  
34  import org.apache.log4j.Logger;
35  import org.w3c.dom.Document;
36  import org.w3c.dom.Element;
37  import org.w3c.dom.Node;
38  import org.w3c.dom.NodeList;
39  import org.xml.sax.ErrorHandler;
40  import org.xml.sax.SAXException;
41  import org.xml.sax.SAXParseException;
42  
43  /**
44   *  Parser code to read an XML configuration file, using the supplied URL.
45   *
46   *  @author Hein Meling
47   *  @since Jgroup 1.2
48   */
49  public final class ConfigParser
50  {
51  
52    ////////////////////////////////////////////////////////////////////////////////////////////
53    // Logger
54    ////////////////////////////////////////////////////////////////////////////////////////////
55  
56    /** Obtain logger for this class */
57    private static final Logger log = Logger.getLogger(ConfigParser.class);
58  
59  
60    ////////////////////////////////////////////////////////////////////////////////////////////
61    // Constants - XML tags
62    ////////////////////////////////////////////////////////////////////////////////////////////
63  
64    private static final String CONFIG_ROOT_TAG_NAME       	= "Configuration";
65    private static final String SERVICES_ROOT_TAG_NAME     	= "Services";
66    private static final String APPLICATIONS_ROOT_TAG_NAME 	= "Applications";
67    private static final String CLIENT_CONFIG_ROOT_TAG_NAME	= "ClientConfiguration";
68    private static final String EXPERIMENT_ROOT_TAG_NAME 		= "ExperimentConfiguration";
69    private static final String VERSION_ATTRIB                = "version";
70  
71  
72    ////////////////////////////////////////////////////////////////////////////////////////////
73    // Fields
74    ////////////////////////////////////////////////////////////////////////////////////////////
75  
76    /**
77     *  Map containing information about configuration classes.  It is a
78     *  mapping from configuration class literal to an Object implementing
79     *  that class, and containing configuration information.
80     */
81    private static Map<Class,ConfigurationObject> configMap =
82      new HashMap<Class,ConfigurationObject>();
83  
84    /** Used to avoid duplicate parsing. */
85    private static Set<String> parsedFiles = new HashSet<String>();
86  
87  
88    ////////////////////////////////////////////////////////////////////////////////////////////
89    // Public static methods
90    ////////////////////////////////////////////////////////////////////////////////////////////
91  
92    /**
93     * The same as <code>parse(urlConfig, false)</code>.
94     */
95    public static void parse(String urlConfig)
96      throws ConfigurationException
97    {
98      parse(urlConfig, false);
99    }
100 
101 
102   /**
103    *  Parses the file obtained from the specified URL and inserts in an
104    *  internal map, associations from class literals of configuration
105    *  objects to configuration object instances.
106    *    
107    *  @param urlConfig  the URL to the XML configuration file containing
108    *    the description of the distributed system.
109    *  @param parseAgain  If false, all config-files will be parsed only once.
110    */
111   public static void parse(String urlConfig, boolean parseAgain)
112     throws ConfigurationException
113   {
114     /* To avoid duplicate parsing, but only if parseAgain is false */
115     if (parsedFiles.contains(urlConfig) && !parseAgain)
116       return;
117     parsedFiles.add(urlConfig);
118 
119     Document doc = null;
120     try {
121       DocumentBuilderFactory docBF = DocumentBuilderFactory.newInstance();
122       boolean validate = false;
123       String validateStr = System.getProperty("jgroup.config.validate");
124       if (validateStr != null && validateStr.equals("true")) {
125         validate = true;
126       }
127       docBF.setValidating(validate);
128       DocumentBuilder db = docBF.newDocumentBuilder();
129       db.setErrorHandler(new ParsingErrorHandler());
130       doc = db.parse(urlConfig);
131     } catch (Exception exception) {
132       log.warn("Parser exception while parsing configuration file", exception);
133       throw new ConfigurationException("Unable to parse configuration file: " + urlConfig);
134     }
135 
136     /* Get and check the root element */
137     Element elm = doc.getDocumentElement();
138     if (!checkVersion(elm, CONFIG_ROOT_TAG_NAME, "1.1") &&
139         !checkVersion(elm, CLIENT_CONFIG_ROOT_TAG_NAME, "1.0") &&
140         !checkVersion(elm, SERVICES_ROOT_TAG_NAME, "1.0") &&
141         !checkVersion(elm, APPLICATIONS_ROOT_TAG_NAME, "1.1") &&
142         !checkVersion(elm, EXPERIMENT_ROOT_TAG_NAME, "0.1" ))
143       throw new ConfigurationException("Unexpected root tag in " + urlConfig);
144 
145     /* Builds a map with the configuration objects */
146     NodeList nl = elm.getChildNodes();
147     for (int i = 1; i <= nl.getLength(); i++) {
148       Node node = nl.item(i);
149       if (node != null && !node.getNodeName().startsWith("#")) {
150 
151         /* Obtain a configuration object for the node */
152         ConfigurationObject obj = parseTag((Element) node);
153 
154         /* Insert the configuration object in the map */
155         configMap.put(obj.getClass(), obj);
156       }
157     }
158   }
159 
160 
161   /**
162    *  Retrieve an instance of the given configuration class, that is
163    *  containing the configuration information.  For example, the
164    *  configuration data object associated with the XML tag
165    *  <code>Transport</code>, can be retreived using the class literal
166    *  <code>TransportConfig.class</code>.
167    *
168    *  @param cl
169    *    The class literal of the configuration object to retreive.
170    *  @return 
171    *    An instance of the configuration object retreived.
172    *
173    *  @exception NullPointerException
174    *    If there is no such configuration object in the internal
175    *    configuration map.  This may occur for two reasons; (i) there
176    *    is no such configuration tag specified in the configuration file,
177    *    or (ii) the specified class literal is a non-existant class.
178    *  @exception IllegalStateException
179    *    If the parser was not invoked prior to invoking this method.
180    */
181   public static Object getConfig(Class cl)
182   {
183     if (configMap.isEmpty())
184       throw new IllegalStateException("getConfig() invoked before parsing the config files");
185     Object obj = configMap.get(cl);
186     if (obj == null)
187       throw new NullPointerException("No configuration data available for " + cl);
188     return obj;
189   }
190 
191 
192   ////////////////////////////////////////////////////////////////////////////////////////////
193   // Private methods for parsing the various tags in the XML tree
194   ////////////////////////////////////////////////////////////////////////////////////////////
195 
196   /**
197    *  Parses a child of the root element and returns a configuration
198    *  object for it.  Given the tag name, a class in the
199    *  <code>jgroup.relacs.config</code> is used to parse the element and
200    *  to store its configuration data.  The name of this class is
201    *  obtained by adding the suffix Config to the class name.  All these
202    *  classes must extend the ConfigurationObject interface.
203    *  
204    *  @param elm 
205    *    the element to parse
206    *  @exception ConfigurationException 
207    *    if the tag given by the <code>elm</code> object could not be parsed.
208    */
209   private static ConfigurationObject parseTag(Element elm)
210     throws ConfigurationException
211   {
212     try {
213       Class cl = Class.forName("jgroup.relacs.config." + elm.getTagName() + "Config");
214       ConfigurationObject config = (ConfigurationObject) cl.newInstance();
215       config.parse(elm);
216       return config;
217     } catch (ClassCastException e) {
218       e.printStackTrace();
219       throw new ConfigurationException("Unexpected tag " + elm.getTagName());
220     } catch (ConfigurationException e) {
221       throw e;
222     } catch (Exception e) {
223       e.printStackTrace();
224       throw new ConfigurationException("Error parsing configuration file: " + e.getMessage());
225     }
226   }
227 
228 
229   ////////////////////////////////////////////////////////////////////////////////////////////
230   // Package methods for parsing the XML tree
231   ////////////////////////////////////////////////////////////////////////////////////////////
232 
233   /**
234    *  Parses the specified <code>Element</code> for sub-elements.
235    *
236    *  @param elm
237    *    The <code>Element</code> object to parse for sub-elements.
238    *  @return 
239    *    An <code>Element[]</code> containing the sub-elements.
240    */
241   static Element[] parseList(Element elm)
242   {
243     NodeList tagList = elm.getChildNodes();
244     ArrayList<Node> elmList = new ArrayList<Node>(tagList.getLength());
245 
246     /* Parse the list of tags */
247     for (int i = 1; i <= tagList.getLength(); i++) {
248       Node node = tagList.item(i);
249       if (node != null && !node.getNodeName().startsWith("#"))
250         elmList.add(node);
251     }
252     elmList.trimToSize();
253     return (Element[]) elmList.toArray(new Element[elmList.size()]);
254   }
255 
256 
257   /**
258    *  Check that the configuration file has the expected
259    *  version number and root tag.
260    *
261    *  @param elm
262    *    The root element to check.
263    *  @param rootTagName
264    *    The expected root tag name to compare the XML root element with.
265    *  @return 
266    *    True if the root tag and version number was correct, and we
267    *    can continue parsing the rest of the XML document.  False is
268    *    returned if the root tag is incorrect.
269    *  @throws ConfigurationException
270    *    Raised if the version number is incorrect.
271    */
272   static boolean checkVersion(Element elm, String rootTagName, String versionValue)
273     throws ConfigurationException
274   {
275     String cfgRoot = elm.getTagName();
276     String cfgVersion = elm.getAttribute(VERSION_ATTRIB);
277     if (cfgRoot.equalsIgnoreCase(rootTagName))
278       if (cfgVersion.equals(versionValue))
279         return true;
280       else
281         throw new ConfigurationException("Wrong version (" + cfgVersion + ") for "
282           + rootTagName + " configuration file. Expected version: " + versionValue);
283     else
284       return false;
285   }
286 
287 
288   /**
289    *  Returns true if the given element in the XML DOM tree corresponds
290    *  to the given tag string.
291    *
292    *  @param elm  An element in the DOM tree.
293    *  @param tag  A tag string to match against the DOM tree element.
294    *  @return true if the given element in the XML DOM tree corresponds
295    *  to the given tag string.
296    */
297   static boolean checkTag(Element elm, String tag)
298   {
299     return elm.getTagName().equalsIgnoreCase(tag);
300   }
301 
302 
303   /**
304    *  Get the content of a single attribute within a tag.
305    *
306    *  @param elm  an element in the DOM tree.
307    *  @param attrib  attribute within the given tag.
308    *  @param required  true if attribute is required; false otherwise.
309    *  @return  value of the attribute found.
310    *  @exception ConfigurationException if the given attribute is required and its
311    *    undefined.
312    */
313   static String getAttrib(Element elm, String attrib, boolean required)
314     throws ConfigurationException
315   {
316     /* Get the specified attribute's value; note that we are case-sensitive. */
317     String value = elm.getAttribute(attrib);
318 
319     /*
320      * REQUIRED vs IMPLIED check; if attribute is required and the
321      * 'value' is zero length we throw an ConfigurationException.
322      */
323     if (required && value.length() == 0)
324       throw new ConfigurationException("Expected required attribute: " + attrib);
325 
326     return value;
327   }
328 
329 
330   /**
331    *  Returns the value of a single attribute within a tag as an integer.
332    *  Note that by definition, we also allow the string "ALL" to be represented
333    *  in an integer type attribute.  The value "ALL" will return a -1 from this
334    *  method.
335    *
336    *  @param elm  an element in the DOM tree.
337    *  @param attrib  attribute within the given tag.
338    *  @param required  true if attribute is required; false otherwise.
339    *  @return  integer value of the attribute found.
340    *  @exception ConfigurationException if the given attribute is required and its
341    *    undefined.
342    */
343   static int getIntAttrib(Element elm, String attrib, boolean required)
344     throws ConfigurationException
345   {
346     String value = getAttrib(elm, attrib, required);
347     if ("".equals(value)) {
348       throw new ConfigurationException("Attribute " + attrib + " is not defined");
349     } else if ("ALL".equalsIgnoreCase(value)) {
350       return -1;
351     }
352     try {
353       return Integer.parseInt(value);
354     } catch(NumberFormatException e) {
355       throw new ConfigurationException("Illegal value for attribute " + 
356         attrib+ ": "  + elm.getAttribute(attrib));
357     }
358   }
359 
360 
361   public static boolean getBooleanAttrib(Element elm, String attrib, boolean required)
362     throws ConfigurationException
363   {
364     String strval = getAttrib(elm, attrib, required);
365     if (strval == null || strval.length() == 0)
366       return false;
367     return Boolean.valueOf(strval).booleanValue();
368   }
369 
370 
371   ////////////////////////////////////////////////////////////////////////////////////////////
372   // Inner class for handling parser errors
373   ////////////////////////////////////////////////////////////////////////////////////////////
374 
375   /**
376    *  Class <code>ParsingErrorHandler</code> provides methods that will
377    *  be called upon parsing errors. This implementation just prints the
378    *  content of the error messages, and exits for serious errors.
379    *
380    *  @author Hein Meling
381    */
382   static class ParsingErrorHandler
383     implements ErrorHandler
384   {
385 
386     public void warning(SAXParseException ex)
387     {
388       System.err.println("Warning: " + ex.getMessage());
389     }
390 
391     public void error(SAXParseException ex)
392     {
393       dumpContent();
394       Abort.exit("Error: " + ex.getMessage(), 1);
395     }
396 
397     public void fatalError(SAXParseException ex)
398       throws SAXException
399     {
400       dumpContent();
401       Abort.exit("Fatal error: " + ex.getMessage(), 2);
402     }
403 
404     private void dumpContent()
405     {
406       System.err.println("Parsed files:");
407       for (Iterator iter = parsedFiles.iterator(); iter.hasNext(); ) 
408         System.err.println(" - " + iter.next());
409       System.err.println("Content of configuration map:");
410       for (Iterator iter = configMap.values().iterator(); iter.hasNext(); ) 
411         System.err.println(" - " + iter.next());
412     }
413 
414   } // END ParsingErrorHandler
415 
416 } // END ConfigParser