Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / apphosting / utils / config / AbstractConfigXmlReader.java
bloba8d38b015b645668c32b056e9d58b95e3413b97b
1 // Copyright 2009 Google Inc. All Rights Reserved.
3 package com.google.apphosting.utils.config;
5 import org.mortbay.xml.XmlParser;
6 import org.mortbay.xml.XmlParser.Node;
7 import org.xml.sax.SAXException;
9 import java.io.File;
10 import java.io.FileInputStream;
11 import java.io.FileNotFoundException;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.util.Stack;
15 import java.util.logging.Level;
16 import java.util.logging.Logger;
18 /**
19 * Abstract class for reading the XML files to configure an application.
21 * @param <T> the type of the configuration object returned
25 public abstract class AbstractConfigXmlReader<T> {
27 /**
28 * Callback notified as nodes are traversed in the parsed XML.
30 public interface ParserCallback {
31 /**
32 * Node handling callback.
34 * @param node the newly-entered node
35 * @param ancestors a possibly-empty (but not null) stack of parent nodes
36 * of {@code node}.
37 * @throws AppEngineConfigException if something is wrong in the XML
39 public void newNode(XmlParser.Node node, Stack<XmlParser.Node> ancestors);
42 /** The path to the top level directory of the application. */
43 protected final String appDir;
45 /** Whether the config file must exist in a correct application. */
46 protected final boolean required;
48 /** A logger for messages. */
49 protected Logger logger;
51 /**
52 * Initializes the generic attributes of all our configuration XML readers.
54 * @param appDir pathname to the application directory
55 * @param required {@code true} if is an error for the config file not to exist.
57 public AbstractConfigXmlReader(String appDir, boolean required) {
58 if (appDir.length() > 0 && appDir.charAt(appDir.length() - 1) != File.separatorChar) {
59 appDir += File.separatorChar;
61 this.appDir = appDir;
62 this.required = required;
63 logger = Logger.getLogger(this.getClass().getName());
66 /**
67 * Gets the absolute filename for the configuration file.
69 * @return concatenation of {@link #appDir} and {@link #getRelativeFilename()}
71 public String getFilename() {
72 return appDir + getRelativeFilename();
75 /**
76 * Fetches the name of the configuration file processed by this instance,
77 * relative to the application directory.
79 * @return relative pathname for a configuration file
81 protected abstract String getRelativeFilename();
83 /**
84 * Parses the input stream to compute an instance of {@code T}.
86 * @return the parsed config file
87 * @throws AppEngineConfigException if there is an error.
89 protected abstract T processXml(InputStream is);
91 /**
92 * Does the work of reading the XML file, processing it, and either returning
93 * an object representing the result or throwing error information.
95 * @return A {@link AppEngineWebXml} config object derived from the
96 * contents of the config xml, or {@code null} if no such file is defined and
97 * the config file is optional.
99 * @throws AppEngineConfigException If the file cannot be parsed properly
101 protected T readConfigXml() {
102 InputStream is = null;
103 T configXml;
104 if (!required && !fileExists()) {
105 return null;
107 try {
108 is = getInputStream();
109 configXml = processXml(is);
110 logger.info("Successfully processed " + getFilename());
111 } catch (Exception e) {
112 String msg = "Received exception processing " + getFilename();
113 logger.log(Level.SEVERE, msg, e);
114 if (e instanceof AppEngineConfigException) {
115 throw (AppEngineConfigException) e;
117 throw new AppEngineConfigException(msg, e);
118 } finally {
119 close(is);
121 return configXml;
125 * Tests for file existence. Test clases will often override this, to lie
126 * (and thus stay small by avoiding needing the real filesystem).
128 protected boolean fileExists() {
129 return (new File(getFilename())).exists();
133 * Opens an input stream, or fails with an AppEngineConfigException
134 * containing helpful information. Test classes will often override this.
136 * @return an open {@link InputStream}
137 * @throws AppEngineConfigException
139 protected InputStream getInputStream() {
140 try {
141 return new FileInputStream(getFilename());
142 } catch (FileNotFoundException fnfe) {
143 throw new AppEngineConfigException(
144 "Could not locate " + new File(getFilename()).getAbsolutePath(), fnfe);
149 * Creates an {@link XmlParser} to use when parsing this file.
151 protected XmlParser createXmlParser() {
152 return new XmlParser();
156 * Given an InputStream, create a Node corresponding to the top level xml
157 * element.
159 * @throws AppEngineConfigException If the input stream cannot be parsed.
161 protected XmlParser.Node getTopLevelNode(InputStream is) {
162 XmlParser xmlParser = createXmlParser();
163 try {
164 return xmlParser.parse(is);
165 } catch (IOException e) {
166 String msg = "Received IOException parsing the input stream for " + getFilename();
167 logger.log(Level.SEVERE, msg, e);
168 throw new AppEngineConfigException(msg, e);
169 } catch (SAXException e) {
170 String msg = "Received SAXException parsing the input stream for " + getFilename();
171 logger.log(Level.SEVERE, msg, e);
172 throw new AppEngineConfigException(msg, e);
177 * Parses the nodes of an XML file. This is <i>limited</i> XML parsing,
178 * in particular skipping any TEXT element and parsing only the nodes.
180 * @param parseCb the ParseCallback to call for each node
181 * @param is the input stream to read
182 * @throws AppEngineConfigException on any error
184 protected void parse(ParserCallback parseCb, InputStream is) {
185 Stack<XmlParser.Node> stack = new Stack<XmlParser.Node>();
186 XmlParser.Node top = getTopLevelNode(is);
187 parse(top, stack, parseCb);
191 * Recursive descent helper for {@link #parse(ParserCallback, InputStream)}, calling
192 * the callback for this node and recursing for its children.
194 * @param node the node being visited
195 * @param stack the anscestors of {@code node}
196 * @param parseCb the visitor callback
197 * @throws AppEngineConfigException for any configuration errors
199 protected void parse(XmlParser.Node node, Stack<XmlParser.Node> stack, ParserCallback parseCb) {
200 parseCb.newNode(node, stack);
201 stack.push(node);
202 for (Object child : node) {
203 if (child instanceof XmlParser.Node) {
204 parse((XmlParser.Node) child, stack, parseCb);
207 stack.pop();
211 * Closes the given input stream, converting any {@link IOException} thrown
212 * to an {@link AppEngineConfigException} if necessary.
214 * @throws AppEngineConfigException if the input stream cannot close
216 protected void close(InputStream is) {
217 if (is != null) {
218 try {
219 is.close();
220 } catch (IOException e) {
221 throw new AppEngineConfigException(e);
227 * Gets the Node's first (index zero) content value, as a trimmed string.
229 * @param node the node to get the string from.
231 protected String getString(Node node) {
232 String string = (String) node.get(0);
233 if (string == null) {
234 return null;
235 } else {
236 return string.trim();