1 // Copyright 2009 Google Inc. All Rights Reserved.
3 package com
.google
.apphosting
.utils
.config
;
5 import com
.google
.apphosting
.utils
.config
.WebXml
.SecurityConstraint
;
7 import org
.mortbay
.xml
.XmlParser
;
8 import org
.mortbay
.xml
.XmlParser
.Node
;
10 import java
.io
.InputStream
;
12 import java
.util
.Stack
;
15 * This reads {@code web.xml}.
19 public class WebXmlReader
extends AbstractConfigXmlReader
<WebXml
> {
21 private static final String
[] DEFAULT_WELCOME_FILES
= new String
[] {
26 public static final String DEFAULT_RELATIVE_FILENAME
= "WEB-INF/web.xml";
28 private static final String URLPATTERN_TAG
= "url-pattern";
29 private static final String SERVLETMAP_TAG
= "servlet-mapping";
30 private static final String FILTERMAP_TAG
= "filter-mapping";
31 private static final String SECURITYCONST_TAG
= "security-constraint";
32 private static final String AUTHCONST_TAG
= "auth-constraint";
33 private static final String ROLENAME_TAG
= "role-name";
34 private static final String USERDATACONST_TAG
= "user-data-constraint";
35 private static final String TRANSGUARANTEE_TAG
= "transport-guarantee";
36 private static final String WELCOME_FILE_LIST_TAG
= "welcome-file-list";
37 private static final String WELCOME_FILE_TAG
= "welcome-file";
38 private static final String EXTENSION_TAG
= "extension";
39 private static final String MIME_TYPE_TAG
= "mime-type";
40 private static final String ERROR_CODE_TAG
= "error-code";
42 private final String relativeFilename
;
45 * Creates a reader for web.xml.
47 * @param appDir The directory in which the config file resides.
48 * @param relativeFilename The path to the config file, relative to
51 public WebXmlReader(String appDir
, String relativeFilename
) {
53 this.relativeFilename
= relativeFilename
;
57 * Creates a reader for web.xml.
59 * @param appDir The directory in which the web.xml config file resides. The
60 * path to the config file relative to the directory is assumed to be
61 * {@link #DEFAULT_RELATIVE_FILENAME}.
63 public WebXmlReader(String appDir
) {
64 this(appDir
, DEFAULT_RELATIVE_FILENAME
);
68 protected String
getRelativeFilename() {
69 return relativeFilename
;
73 * Parses the config file.
74 * @return A {@link WebXml} object representing the parsed configuration.
76 public WebXml
readWebXml() {
77 return readConfigXml();
81 * Instead of creating a default {@link XmlParser}, use the same
82 * logic as Jetty's {@code WebXmlConfiguration} to create one that
83 * is aware of web.xml files. Specifically, this method registers
84 * static versions of all known web.xml DTD's and schemas to avoid
85 * URL retrieval at parse time.
88 protected XmlParser
createXmlParser() {
90 XmlParser xmlParser
= new XmlParser();
91 URL dtd22
= getClass().getResource("/javax/servlet/resources/web-app_2_2.dtd");
92 URL dtd23
= getClass().getResource("/javax/servlet/resources/web-app_2_3.dtd");
93 URL jsp20xsd
= getClass().getResource("/javax/servlet/resources/jsp_2_0.xsd");
94 URL jsp21xsd
= getClass().getResource("/javax/servlet/resources/jsp_2_1.xsd");
95 URL j2ee14xsd
= getClass().getResource("/javax/servlet/resources/j2ee_1_4.xsd");
96 URL webapp24xsd
= getClass().getResource("/javax/servlet/resources/web-app_2_4.xsd");
97 URL webapp25xsd
= getClass().getResource("/javax/servlet/resources/web-app_2_5.xsd");
98 URL schemadtd
= getClass().getResource("/javax/servlet/resources/XMLSchema.dtd");
99 URL xmlxsd
= getClass().getResource("/javax/servlet/resources/xml.xsd");
100 URL webservice11xsd
=
101 getClass().getResource("/javax/servlet/resources/j2ee_web_services_client_1_1.xsd");
102 URL webservice12xsd
=
103 getClass().getResource("/javax/servlet/resources/javaee_web_services_client_1_2.xsd");
104 URL datatypesdtd
= getClass().getResource("/javax/servlet/resources/datatypes.dtd");
105 xmlParser
.redirectEntity("web-app_2_2.dtd",dtd22
);
106 xmlParser
.redirectEntity("-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN",dtd22
);
107 xmlParser
.redirectEntity("web.dtd",dtd23
);
108 xmlParser
.redirectEntity("web-app_2_3.dtd",dtd23
);
109 xmlParser
.redirectEntity("-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",dtd23
);
110 xmlParser
.redirectEntity("XMLSchema.dtd",schemadtd
);
111 xmlParser
.redirectEntity("http://www.w3.org/2001/XMLSchema.dtd",schemadtd
);
112 xmlParser
.redirectEntity("-//W3C//DTD XMLSCHEMA 200102//EN",schemadtd
);
113 xmlParser
.redirectEntity("jsp_2_0.xsd",jsp20xsd
);
114 xmlParser
.redirectEntity("http://java.sun.com/xml/ns/j2ee/jsp_2_0.xsd",jsp20xsd
);
115 xmlParser
.redirectEntity("jsp_2_1.xsd",jsp21xsd
);
116 xmlParser
.redirectEntity("http://java.sun.com/xml/ns/javaee/jsp_2_1.xsd",jsp21xsd
);
117 xmlParser
.redirectEntity("j2ee_1_4.xsd",j2ee14xsd
);
118 xmlParser
.redirectEntity("http://java.sun.com/xml/ns/j2ee/j2ee_1_4.xsd",j2ee14xsd
);
119 xmlParser
.redirectEntity("web-app_2_4.xsd",webapp24xsd
);
120 xmlParser
.redirectEntity("http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd",webapp24xsd
);
121 xmlParser
.redirectEntity("web-app_2_5.xsd",webapp25xsd
);
122 xmlParser
.redirectEntity("http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd",webapp25xsd
);
123 xmlParser
.redirectEntity("xml.xsd",xmlxsd
);
124 xmlParser
.redirectEntity("http://www.w3.org/2001/xml.xsd",xmlxsd
);
125 xmlParser
.redirectEntity("datatypes.dtd",datatypesdtd
);
126 xmlParser
.redirectEntity("http://www.w3.org/2001/datatypes.dtd",datatypesdtd
);
127 xmlParser
.redirectEntity("j2ee_web_services_client_1_1.xsd",webservice11xsd
);
128 xmlParser
.redirectEntity("http://www.ibm.com/webservices/xsd/j2ee_web_services_client_1_1.xsd",
130 xmlParser
.redirectEntity("javaee_web_services_client_1_2.xsd",webservice12xsd
);
131 xmlParser
.redirectEntity("http://www.ibm.com/webservices/xsd/javaee_web_services_client_1_2.xsd",
138 protected WebXml
processXml(InputStream is
) {
139 final WebXml webXml
= new WebXml();
140 parse(new ParserCallback() {
142 private WebXml
.SecurityConstraint security
;
143 private String extension
;
146 public void newNode(Node node
, Stack
<Node
> ancestors
) {
147 String thisTag
= node
.getTag().toLowerCase();
148 String parentTag
= null;
149 if (ancestors
.size() > 0) {
150 parentTag
= ancestors
.get(ancestors
.size() - 1).getTag().toLowerCase();
153 if (URLPATTERN_TAG
.equals(thisTag
)) {
154 String pattern
= getString(node
);
155 if (SERVLETMAP_TAG
.equals(parentTag
) || FILTERMAP_TAG
.equals(parentTag
)) {
156 String id
= node
.getAttribute("id");
157 webXml
.addServletPattern(pattern
, id
);
158 } else if (security
!= null) {
159 security
.addUrlPattern(pattern
);
161 } else if (ROLENAME_TAG
.equals(thisTag
) && AUTHCONST_TAG
.equals(parentTag
)) {
162 if (security
== null) {
163 throw new AppEngineConfigException(getFilename() + ": <" + ROLENAME_TAG
+
164 "> in <" + AUTHCONST_TAG
+ "> in unrecognized context");
166 security
.setRequiredRole(parseRequiredRole(getString(node
)));
167 } else if (TRANSGUARANTEE_TAG
.equals(thisTag
) && USERDATACONST_TAG
.equals(parentTag
)) {
168 if (security
== null) {
169 throw new AppEngineConfigException(getFilename() + ": <" + TRANSGUARANTEE_TAG
+
170 "> in <" + USERDATACONST_TAG
+ "> in unrecognized context");
172 security
.setTransportGuarantee(parseTransportGuarantee(getString(node
)));
173 } else if (SECURITYCONST_TAG
.equals(thisTag
)) {
174 security
= webXml
.addSecurityConstraint();
175 } else if (WELCOME_FILE_TAG
.equals(thisTag
)) {
176 if (!WELCOME_FILE_LIST_TAG
.equals(parentTag
)) {
177 throw new AppEngineConfigException(getFilename() + ": <" + WELCOME_FILE_TAG
+
178 "> in unrecognized context");
180 webXml
.addWelcomeFile(getString(node
));
181 } else if (EXTENSION_TAG
.equals(thisTag
)) {
182 extension
= getString(node
);
183 } else if (MIME_TYPE_TAG
.equals(thisTag
)) {
184 if (extension
== null) {
185 throw new AppEngineConfigException(getFilename() + ": <" + MIME_TYPE_TAG
+
186 "> without <extension>.");
188 String mimeType
= getString(node
);
189 webXml
.addMimeMapping(extension
, mimeType
);
191 } else if (ERROR_CODE_TAG
.equals(thisTag
)) {
192 String code
= getString(node
);
193 if ("404".equals(code
)) {
194 webXml
.setFallThroughToRuntime(true);
200 if (webXml
.getWelcomeFiles().isEmpty()) {
201 for (String welcomeFile
: DEFAULT_WELCOME_FILES
) {
202 webXml
.addWelcomeFile(welcomeFile
);
209 private SecurityConstraint
.RequiredRole
parseRequiredRole(String role
) {
210 if ("*".equals(role
)) {
211 return SecurityConstraint
.RequiredRole
.ANY_USER
;
212 } else if ("admin".equals(role
)) {
213 return SecurityConstraint
.RequiredRole
.ADMIN
;
215 throw new AppEngineConfigException(getFilename() + ": " +
216 "Unknown role-name: must be '*' or 'admin'");
220 private SecurityConstraint
.TransportGuarantee
parseTransportGuarantee(String transportGuarantee
) {
221 if ("NONE".equalsIgnoreCase(transportGuarantee
)) {
222 return SecurityConstraint
.TransportGuarantee
.NONE
;
223 } else if ("INTEGRAL".equalsIgnoreCase(transportGuarantee
)) {
224 return SecurityConstraint
.TransportGuarantee
.INTEGRAL
;
225 } else if ("CONFIDENTIAL".equalsIgnoreCase(transportGuarantee
)) {
226 return SecurityConstraint
.TransportGuarantee
.CONFIDENTIAL
;
228 throw new AppEngineConfigException(getFilename() + ": " +
229 "Unknown transport-guarantee: must be " +
230 "NONE, INTEGRAL, or CONFIDENTIAL.");