1 package com
.google
.apphosting
.utils
.config
;
3 import com
.google
.common
.base
.Preconditions
;
4 import com
.google
.common
.collect
.ImmutableList
;
6 import org
.xml
.sax
.SAXException
;
9 import java
.io
.FileInputStream
;
10 import java
.io
.FileNotFoundException
;
11 import java
.io
.IOException
;
12 import java
.io
.InputStream
;
13 import java
.util
.logging
.Logger
;
15 import javax
.annotation
.Nullable
;
16 import javax
.xml
.XMLConstants
;
17 import javax
.xml
.transform
.stream
.StreamSource
;
18 import javax
.xml
.validation
.SchemaFactory
;
21 * Utility for server discovery within an EAR directory.
24 public class EarHelper
{
25 private static final Logger LOGGER
= Logger
.getLogger(EarHelper
.class.getName());
26 private static final AppEngineApplicationXmlReader APP_ENGINE_APPLICATION_XML_READER
=
27 new AppEngineApplicationXmlReader();
28 private static final ApplicationXmlReader APPLICATION_XML_READER
= new ApplicationXmlReader();
30 private static final String META_INF
= "META-INF";
31 private static final String APPENGINE_APPLICATION_XML_NAME
= "appengine-application.xml";
32 private static final String APPLICATION_XML_NAME
= "application.xml";
35 * Returns true if passed a path for an EAR directory.
37 * This is a convenience fuction that simply calls
38 * {@link #isEar(String, boolean)} for {@code directory} with {@code logWhyNot}
41 public static boolean isEar(String dir
) {
42 return isEar(dir
, true);
46 * Returns true if {@code directory} contains a {@link #META_INF} sub-directory
47 * with the required {@link #APPENGINE_APPLICATION_XML_NAME} and
48 * {@link #APPLICATION_XML_NAME} files.
50 * Note this test is a spot check to distinguish an EAR directory as
51 * opposed to a WAR directory. Passing this test does not guarantee that a
52 * directory is a fully valid EAR directory.
54 * @param directory The path for the directory to check or null
55 * @param logWhyNot true means to log the reason this returns false if
58 public static boolean isEar(String directory
, boolean logWhyNot
) {
59 if (directory
== null) {
61 LOGGER
.fine("Directory 'null' is not an EAR directory. ");
66 File metaInf
= new File(directory
, META_INF
);
67 if (!hasFile(metaInf
, APPENGINE_APPLICATION_XML_NAME
)) {
68 logNotAnEar(directory
.toString(), APPENGINE_APPLICATION_XML_NAME
, logWhyNot
);
72 if (!hasFile(metaInf
, APPLICATION_XML_NAME
)) {
73 logNotAnEar(directory
.toString(), APPLICATION_XML_NAME
, logWhyNot
);
80 private static void logNotAnEar(String dir
, String missingFile
,
81 boolean withLogging
) {
83 LOGGER
.fine("Directory '" + dir
+ "' is not an EAR directory. File "
84 + new File(dir
, missingFile
) + " not detected.");
88 private static void reportConfigException(String message
) {
90 throw new AppEngineConfigException(message
);
94 * Reads an {@link EarInfo from} the provided EAR directory.
95 * Validation is done via the xsd schemaFile.
96 * @throws AppEngineConfigException if the provided EAR directory is invalid.
98 public static EarInfo
readEarInfo(String earDirectoryPath
, File schemaFile
)
99 throws AppEngineConfigException
{
100 if (!isEar(earDirectoryPath
)) {
101 throw new IllegalArgumentException("earDir '" + earDirectoryPath
102 + "' is not a valid EAR directory.");
104 File earDirectory
= new File(earDirectoryPath
).getAbsoluteFile();
105 File metaInf
= new File(earDirectory
, META_INF
);
106 validateXml(new File(metaInf
, APPENGINE_APPLICATION_XML_NAME
), schemaFile
);
107 AppEngineApplicationXml appEngineApplicationXml
= APP_ENGINE_APPLICATION_XML_READER
.processXml(
108 getInputStream(metaInf
, APPENGINE_APPLICATION_XML_NAME
));
109 ApplicationXml applicationXml
= APPLICATION_XML_READER
.processXml(
110 getInputStream(metaInf
, APPLICATION_XML_NAME
));
111 String applicationId
= appEngineApplicationXml
.getApplicationId();
112 ImmutableList
.Builder
<WebModule
> moduleListBuilder
= ImmutableList
.builder();
113 for (ApplicationXml
.Modules
.Web web
: applicationXml
.getModules().getWeb()) {
114 File applicationDirectory
= getApplicationDirectory(earDirectory
, web
.getWebUri());
115 WebModule webModule
= readWebModule(web
.getContextRoot(), applicationDirectory
,
117 if (!applicationId
.equals(webModule
.getAppEngineWebXml().getAppId())) {
118 LOGGER
.info("Application id '" + appEngineApplicationXml
.getApplicationId() + "' from '"
119 + new File(metaInf
, APPENGINE_APPLICATION_XML_NAME
) + "' is overriding "
120 + " application id '" + webModule
.getAppEngineWebXml().getAppId() + "' from '"
121 + new File(applicationDirectory
, AppEngineWebXmlReader
.DEFAULT_RELATIVE_FILENAME
)
123 webModule
.getAppEngineWebXml().setAppId(applicationId
);
125 moduleListBuilder
.add(webModule
);
127 ImmutableList
<WebModule
> webModules
= moduleListBuilder
.build();
128 if (webModules
.size() == 0) {
129 reportConfigException("At least one web module is required in '"
130 + new File(metaInf
, APPLICATION_XML_NAME
) + "'");
132 return new EarInfo(earDirectory
, appEngineApplicationXml
, applicationXml
, webModules
);
136 * Reads a {@link WebModule} from the provided application directory.
138 * If the application directory contains a WEB-INF/app.yaml file this will call
139 * {@link AppYamlProcessor#convert} to generate WEB-INF/appengine-web.xml and
140 * WEB-INF/web.xml files.
142 * @param contextRoot if this web module is part of an EAR supply the
143 * context-root element from the web module's specification in
144 * ear/META-INF/application.xml otherwise supply null to indicate the web
145 * module has no context root.
146 * @param applicationDirectory the application directory.
147 * @param appengineWebXmlFile the appengine-web.xml to read or null to use the default.
148 * @param webXmlFile the web.xml to read or null to use the default.
149 * @param appIdPrefix a string to prepend to the app id read from appengine-web.xml. May be
150 * empty but not null.
151 * @throws AppEngineConfigException if applicationDirectory is not correct.
153 public static WebModule
readWebModule(
154 @Nullable String contextRoot
, File applicationDirectory
, @Nullable File appengineWebXmlFile
,
155 @Nullable File webXmlFile
, String appIdPrefix
) throws AppEngineConfigException
{
156 Preconditions
.checkNotNull(appIdPrefix
);
157 AppEngineWebXmlReader appEngineWebXmlReader
=
158 newAppEngineWebXmlReader(applicationDirectory
, appengineWebXmlFile
);
159 WebXmlReader webXmlReader
= newWebXmlReader(applicationDirectory
, webXmlFile
);
161 AppYamlProcessor
.convert(new File(applicationDirectory
, "WEB-INF"),
162 appEngineWebXmlReader
.getFilename(), webXmlReader
.getFilename());
164 AppEngineWebXml appEngineWebXml
;
166 appEngineWebXml
= appEngineWebXmlReader
.readAppEngineWebXml();
167 } catch (AppEngineConfigException aece
) {
168 throw new AppEngineConfigException(String
.format("Invalid appengine-web.xml(%s) - %s",
169 appEngineWebXmlReader
.getFilename(), aece
.getMessage()));
171 String appId
= appEngineWebXml
.getAppId();
173 appEngineWebXml
.setAppId(appIdPrefix
+ appEngineWebXml
.getAppId());
176 WebXml webXml
= webXmlReader
.readWebXml();
178 WebModule webModule
=
179 new WebModule(applicationDirectory
, appEngineWebXml
,
180 new File(appEngineWebXmlReader
.getFilename()), webXml
,
181 new File(webXmlReader
.getFilename()), contextRoot
);
185 private static File
getApplicationDirectory(File earDirectory
, String contextRoot
) {
186 File applicationDirectory
= new File(earDirectory
, contextRoot
);
187 if (!applicationDirectory
.exists() || !applicationDirectory
.isDirectory()) {
188 reportConfigException("Application directory '" + applicationDirectory
189 + "' must exist and be a directory.");
191 return applicationDirectory
;
194 private static AppEngineWebXmlReader
newAppEngineWebXmlReader(File applicationDirectory
,
195 File appEngineWebXmlFile
) {
196 return appEngineWebXmlFile
== null ?
197 new AppEngineWebXmlReader(applicationDirectory
.getAbsolutePath()) :
198 new AppEngineWebXmlReader(appEngineWebXmlFile
.getParent(),
199 appEngineWebXmlFile
.getName());
202 private static WebXmlReader
newWebXmlReader(File applicationDirectory
, File webXmlFile
) {
203 return webXmlFile
== null ?
204 new WebXmlReader(applicationDirectory
.getAbsolutePath()) :
205 new WebXmlReader(webXmlFile
.getParent(), webXmlFile
.getName());
209 * Returns an input stream for an existing file.
211 private static InputStream
getInputStream(File parent
, String fileName
) {
212 File file
= new File(parent
, fileName
);
214 return new FileInputStream(file
);
215 } catch (FileNotFoundException fnfe
) {
216 throw new IllegalStateException("File should exist - '" + file
+ "'");
220 private static boolean hasFile(File parent
, String child
) {
221 File file
= new File(parent
, child
);
222 if (!file
.isFile()) {
230 * Validates a given XML document against a given schema.
232 * @param xml file with XML document
233 * @param schema XSD schema to validate with
235 * @throws AppEngineConfigException for malformed XML, or IO errors
237 private static void validateXml(File xml
, File schema
) {
241 StreamSource ss
= null;
243 SchemaFactory factory
= SchemaFactory
.newInstance(XMLConstants
.W3C_XML_SCHEMA_NS_URI
);
245 ss
= new StreamSource(new FileInputStream(xml
));
246 factory
.newSchema(schema
).newValidator().validate(ss
);
247 } catch (SAXException ex
) {
248 throw new AppEngineConfigException("XML error validating " +
249 xml
.getPath() + " against " + schema
.getPath(), ex
);
251 } catch (IOException ex
) {
252 throw new AppEngineConfigException("IO error validating " +
253 xml
.getPath() + " against " + schema
.getPath(), ex
);
257 ss
.getInputStream().close();
258 } catch (IOException e
) {