1 package com
.google
.apphosting
.utils
.config
;
3 import org
.mortbay
.xml
.XmlParser
.Node
;
6 import java
.io
.FileInputStream
;
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 * 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()}.
22 * This class performs some validation on the XML, but it does rely on
23 * the fact that (by the time our readIndexesXml() method is called)
24 * the XML has already been partially validated by the XML Schema:
26 * java/com/google/appengine/tools/development/datastore-indexes.xsd
29 public class IndexesXmlReader
extends AbstractConfigXmlReader
<IndexesXml
> {
32 * Relative-to-{@code GenerationDirectory.GENERATED_DIR_PROPERTY} file for
35 public static final String GENERATED_INDEX_FILENAME
= "datastore-indexes-auto.xml";
36 public static final String INDEX_FILENAME
= "datastore-indexes.xml";
37 public static final String INDEX_YAML_FILENAME
= "WEB-INF/index.yaml";
39 /** Name of the XML tag in {@code datastore-indexes.xml} for autoindexing */
40 public static final String AUTOINDEX_TAG
= "autoGenerate";
42 private static final String FILENAME
= "WEB-INF/datastore-indexes.xml";
44 private static final String INDEXES_TAG
= "datastore-indexes";
45 private static final String INDEX_TAG
= "datastore-index";
46 private static final String KIND_PROP
= "kind";
47 private static final String ANCESTORS_PROP
= "ancestor";
48 private static final String PROPERTY_TAG
= "property";
49 private static final String NAME_PROP
= "name";
50 private static final String DIRECTION_PROP
= "direction";
51 private static final String DIRECTION_VALUE_ASC
= "asc";
52 private static final String DIRECTION_VALUE_DESC
= "desc";
53 private static final String MODE_PROP
= "mode";
55 private IndexesXml indexesXml
;
58 * Constructs a reader for the {@code indexes.xml} configuration of a given app.
59 * @param appDir root directory of the application
61 public IndexesXmlReader(String appDir
) {
66 * Reads the configuration file.
67 * @return an {@link IndexesXml} representing the parsed configuration.
69 public IndexesXml
readIndexesXml() {
70 return readConfigXml();
74 * Reads the index configuration. If neither the user-written nor the
75 * auto-generated config file exists, returns a {@code null}. Otherwise,
76 * reads both files (if available) and returns the union of both sets of
79 * @throws AppEngineConfigException If the file cannot be parsed properly
82 protected IndexesXml
readConfigXml() {
83 InputStream is
= null;
84 String filename
= null;
86 indexesXml
= new IndexesXml();
89 filename
= getFilename();
90 is
= getInputStream();
92 logger
.info("Successfully processed " + filename
);
94 if (yamlFileExists()) {
95 filename
= getYamlFilename();
96 IndexYamlReader
.parse(getYamlReader(), indexesXml
);
97 logger
.info("Successfully processed " + filename
);
99 if (generatedFileExists()) {
100 filename
= getAutoFilename();
101 is
= getGeneratedStream();
103 logger
.info("Successfully processed " + filename
);
105 } catch (Exception e
) {
106 String msg
= "Received exception processing " + filename
;
107 logger
.log(Level
.SEVERE
, msg
, e
);
108 if (e
instanceof AppEngineConfigException
) {
109 throw (AppEngineConfigException
) e
;
111 throw new AppEngineConfigException(msg
, e
);
119 protected IndexesXml
processXml(InputStream is
) {
120 parse(new ParserCallback() {
121 IndexesXml
.Index index
;
122 IndexesXml
.Type indexType
= null;
125 * Assembles index definitions during XML parsing, and
126 * performs additional validation that was not already done by
129 * There are two types of index definition: "ordered" and
130 * "geo-spatial". For an ordered index, an "ancestor"
131 * specification is optional, and all "property" elements may
132 * optionally specify a "direction", but may not specify a
133 * "mode". In a geo-spatial index, "ancestor" is irrelevant
134 * (and therefore disallowed), and property elements may
135 * specify a mode, but may not specify a direction.
138 public void newNode(Node node
, Stack
<Node
> unusedContainingElements
) {
139 if (INDEX_TAG
.equalsIgnoreCase(node
.getTag())) {
140 String kind
= node
.getAttribute(KIND_PROP
);
141 Boolean ancestorProp
= null;
142 String anc
= node
.getAttribute(ANCESTORS_PROP
);
146 indexType
= IndexesXml
.Type
.ORDERED
;
147 ancestorProp
= anc
.equals("true") || anc
.equals("1");
149 index
= indexesXml
.addNewIndex(kind
, ancestorProp
);
150 } else if (PROPERTY_TAG
.equalsIgnoreCase(node
.getTag())) {
151 assert(index
!= null);
152 String name
= node
.getAttribute(NAME_PROP
);
153 String direction
= node
.getAttribute(DIRECTION_PROP
);
154 String mode
= node
.getAttribute(MODE_PROP
);
156 if (direction
!= null) {
157 if (mode
!= null || indexType
== IndexesXml
.Type
.GEO_SPATIAL
) {
158 throw new AppEngineConfigException(
159 "The 'direction' attribute may not be specified in a 'geospatial' index.");
161 indexType
= IndexesXml
.Type
.ORDERED
;
162 } else if (mode
!= null) {
163 if (indexType
== IndexesXml
.Type
.ORDERED
) {
164 throw new AppEngineConfigException(
165 "The 'mode' attribute may not be specified with 'direction' or 'ancestor'.");
167 indexType
= IndexesXml
.Type
.GEO_SPATIAL
;
170 index
.addNewProperty(name
, direction
, mode
);
178 protected String
getRelativeFilename() {
182 protected File
getGeneratedFile() {
183 File genFile
= new File(GenerationDirectory
.getGenerationDirectory(new File(appDir
)),
184 GENERATED_INDEX_FILENAME
);
188 public String
getAutoFilename() {
189 return getGeneratedFile().getPath();
192 protected boolean generatedFileExists() {
193 return getGeneratedFile().exists();
196 protected InputStream
getGeneratedStream() throws Exception
{
197 return new FileInputStream(getGeneratedFile());
200 protected String
getYamlFilename() {
201 return appDir
+ INDEX_YAML_FILENAME
;
204 protected boolean yamlFileExists() {
205 return new File(getYamlFilename()).exists();
208 protected Reader
getYamlReader() {
210 return new FileReader(getYamlFilename());
211 } catch (FileNotFoundException ex
) {
212 throw new AppEngineConfigException("Cannot find file" + getYamlFilename());