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