1 // Copyright 2008 Google Inc. All Rights Reserved.
2 package com
.google
.apphosting
.utils
.config
;
4 import org
.mortbay
.xml
.XmlParser
.Node
;
7 import java
.io
.FileNotFoundException
;
8 import java
.io
.FileReader
;
9 import java
.io
.InputStream
;
10 import java
.io
.Reader
;
11 import java
.util
.Stack
;
12 import java
.util
.logging
.Level
;
15 * Creates an {@link IndexesXml} instance from
16 * <appdir>WEB-INF/datastore-indexes.xml. If you want to read the
17 * configuration from a different file, subclass and override
18 * {@link #getFilename()}. If you want to read the configuration from
19 * something that isn't a file, subclass and override
20 * {@link #getInputStream()}.
23 public class IndexesXmlReader
extends AbstractConfigXmlReader
<IndexesXml
> {
26 * Relative-to-{@code GenerationDirectory.GENERATED_DIR_PROPERTY} file for
29 public static final String GENERATED_INDEX_FILENAME
= "datastore-indexes-auto.xml";
30 public static final String INDEX_FILENAME
= "datastore-indexes.xml";
31 public static final String INDEX_YAML_FILENAME
= "WEB-INF/index.yaml";
33 /** Name of the XML tag in {@code datastore-indexes.xml} for autoindexing */
34 public static final String AUTOINDEX_TAG
= "auto-update";
36 private static final String FILENAME
= "WEB-INF/datastore-indexes.xml";
38 private static final String INDEXES_TAG
= "datastore-indexes";
39 private static final String INDEX_TAG
= "datastore-index";
40 private static final String KIND_PROP
= "kind";
41 private static final String ANCESTORS_PROP
= "ancestor";
42 private static final String ANCESTORS_VALUE_YES
= "true";
43 private static final String ANCESTORS_VALUE_NO
= "false";
44 private static final String PROPERTY_TAG
= "property";
45 private static final String NAME_PROP
= "name";
46 private static final String DIRECTION_PROP
= "direction";
47 private static final String DIRECTION_VALUE_ASC
= "asc";
48 private static final String DIRECTION_VALUE_DESC
= "desc";
50 private IndexesXml indexesXml
;
53 * Constructs a reader for the {@code indexes.xml} configuration of a given app.
54 * @param appDir root directory of the application
56 public IndexesXmlReader(String appDir
) {
61 * Reads the configuration file.
62 * @return an {@link IndexesXml} representing the parsed configuration.
64 public IndexesXml
readIndexesXml() {
65 return readConfigXml();
69 * Reads the index configuration. If neither the user-generated nor the
70 * auto-generated config file exists, returns a {@code null}. Otherwise,
71 * reads both files (if available) and returns the union of both sets of
74 * @throws AppEngineConfigException If the file cannot be parsed properly
77 protected IndexesXml
readConfigXml() {
78 InputStream is
= null;
79 String filename
= null;
81 indexesXml
= new IndexesXml();
84 filename
= getFilename();
85 is
= getInputStream();
87 logger
.info("Successfully processed " + filename
);
89 if (yamlFileExists()) {
90 filename
= getYamlFilename();
91 IndexYamlReader
.parse(getYamlReader(), indexesXml
);
92 logger
.info("Successfully processed " + filename
);
94 if (generatedFileExists()) {
95 filename
= getGeneratedFile().getPath();
96 is
= getGeneratedStream();
98 logger
.info("Successfully processed " + filename
);
100 } catch (Exception e
) {
101 String msg
= "Received exception processing " + filename
;
102 logger
.log(Level
.SEVERE
, msg
, e
);
103 if (e
instanceof AppEngineConfigException
) {
104 throw (AppEngineConfigException
) e
;
106 throw new AppEngineConfigException(msg
, e
);
114 protected IndexesXml
processXml(InputStream is
) {
115 parse(new ParserCallback() {
116 boolean first
= true;
117 IndexesXml
.Index index
;
120 public void newNode(Node node
, Stack
<Node
> ancestors
) {
121 switch (ancestors
.size()) {
123 if (!INDEXES_TAG
.equalsIgnoreCase(node
.getTag())) {
124 throw new AppEngineConfigException(getFilename() + " does not contain <"
125 + INDEXES_TAG
+ ">");
128 throw new AppEngineConfigException(getFilename() + " contains multiple <"
129 + INDEXES_TAG
+ ">");
135 if (INDEX_TAG
.equalsIgnoreCase(node
.getTag())) {
136 String kind
= node
.getAttribute(KIND_PROP
);
138 throw new AppEngineConfigException(getFilename() + " has <" + INDEX_TAG
+
139 "> missing required attribute \"" + KIND_PROP
+ "\"");
141 String anc
= node
.getAttribute(ANCESTORS_PROP
).toLowerCase();
142 boolean ancestorProp
= false;
144 if (anc
.equals(ANCESTORS_VALUE_YES
)) {
146 } else if (!anc
.equals(ANCESTORS_VALUE_NO
)) {
147 throw new AppEngineConfigException(getFilename() + " has <" + INDEX_TAG
+
148 "> with attribute \"" + ANCESTORS_PROP
+ "\" not \"" + ANCESTORS_VALUE_YES
+
149 "\" or \"" + ANCESTORS_VALUE_NO
+ "\"");
152 index
= indexesXml
.addNewIndex(kind
, ancestorProp
);
155 throw new AppEngineConfigException(getFilename() + " contains <"
156 + node
.getTag() + "> instead of <" + INDEX_TAG
+ "/>");
161 assert(index
!= null);
162 if (PROPERTY_TAG
.equalsIgnoreCase(node
.getTag())) {
163 String name
= node
.getAttribute(NAME_PROP
);
165 throw new AppEngineConfigException(getFilename() + " has <" + PROPERTY_TAG
+
166 "> missing required attribute \"" + NAME_PROP
+ "\"");
168 String direction
= node
.getAttribute(DIRECTION_PROP
).toLowerCase();
169 boolean ascending
= true;
170 if (direction
!= null) {
171 if (direction
.equals(DIRECTION_VALUE_DESC
)) {
173 } else if (!direction
.equals(DIRECTION_VALUE_ASC
)) {
174 throw new AppEngineConfigException(getFilename() + " has <" + PROPERTY_TAG
+
175 "> with attribute \"" + DIRECTION_PROP
+ "\" not \"" + DIRECTION_VALUE_ASC
+
176 "\" or \"" + DIRECTION_VALUE_DESC
+ "\"");
179 index
.addNewProperty(name
, ascending
);
181 throw new AppEngineConfigException(getFilename() + " contains <"
182 + node
.getTag() + "> instead of <" + PROPERTY_TAG
+ "/>");
187 throw new AppEngineConfigException(getFilename()
188 + " has a syntax error; node <"
189 + node
.getTag() + "> is too deeply nested to be valid.");
197 protected String
getRelativeFilename() {
202 protected File
getGeneratedFile() {
203 File genFile
= new File(GenerationDirectory
.getGenerationDirectory(new File(appDir
)),
204 GENERATED_INDEX_FILENAME
);
208 protected String
getYamlFilename() {
209 return appDir
+ INDEX_YAML_FILENAME
;
212 protected boolean yamlFileExists() {
213 return new File(getYamlFilename()).exists();
216 protected Reader
getYamlReader() {
218 return new FileReader(getYamlFilename());
219 } catch (FileNotFoundException ex
) {
220 throw new AppEngineConfigException("Cannot find file" + getYamlFilename());