001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.jk.config;
018:
019: import java.io.File;
020: import java.io.IOException;
021: import java.io.StringReader;
022: import java.util.Hashtable;
023: import java.util.Vector;
024:
025: import javax.xml.parsers.DocumentBuilder;
026: import javax.xml.parsers.DocumentBuilderFactory;
027: import javax.xml.parsers.ParserConfigurationException;
028:
029: import org.apache.tomcat.util.IntrospectionUtils;
030: import org.w3c.dom.Document;
031: import org.w3c.dom.Node;
032: import org.xml.sax.EntityResolver;
033: import org.xml.sax.InputSource;
034: import org.xml.sax.SAXException;
035:
036: /* Naming conventions:
037:
038: JK_CONF_DIR == serverRoot/work ( XXX /jkConfig ? )
039:
040: - Each vhost has a sub-dir named after the canonycal name
041:
042: - For each webapp in a vhost, there is a separate WEBAPP_NAME.jkmap
043:
044: - In httpd.conf ( or equivalent servers ), in each virtual host you
045: should "Include JK_CONF_DIR/VHOST/jk_apache.conf". The config
046: file will contain the Alias declarations and other rules required
047: for apache operation. Same for other servers.
048:
049: - WebXml2Jk will be invoked by a config tool or automatically for each
050: webapp - it'll generate the WEBAPP.jkmap files and config fragments.
051:
052: WebXml2Jk will _not_ generate anything else but mappings.
053: It should _not_ try to guess locations or anything else - that's
054: another components' job.
055:
056: */
057:
058: /**
059: * Read a web.xml file and generate the mappings for jk2.
060: * It can be used from the command line or ant.
061: *
062: * In order for the web server to serve static pages, all webapps
063: * must be deployed on the computer that runs Apache, IIS, etc.
064: *
065: * Dynamic pages can be executed on that computer or other servers
066: * in a pool, but even if the main server doesn't run tomcat,
067: * it must have all the static files and WEB-INF/web.xml.
068: * ( you could have a script remove everything else, including jsps - if
069: * security paranoia is present ).
070: *
071: * XXX We could have this in WEB-INF/urimap.properties.
072: *
073: * @author Costin Manolache
074: */
075: public class WebXml2Jk {
076: String vhost = "";
077: String cpath = "";
078: String docBase;
079: String file;
080: String worker = "lb";
081:
082: // -------------------- Settings --------------------
083:
084: // XXX We can also generate location-independent mappings.
085:
086: /** Set the canonycal name of the virtual host.
087: */
088: public void setHost(String vhost) {
089: this .vhost = vhost;
090: }
091:
092: /** Set the canonical name of the virtual host.
093: */
094: public void setContext(String contextPath) {
095: this .cpath = contextPath;
096: }
097:
098: /** Set the base directory where the application is
099: * deployed ( on the web server ).
100: */
101: public void setDocBase(String docBase) {
102: this .docBase = docBase;
103: }
104:
105: // Automatically generated.
106: // /** The file where the jk2 mapping will be generated
107: // */
108: // public void setJk2Conf( String outFile ) {
109: // file=outFile;
110: // type=CONFIG_JK2_URIMAP;
111: // }
112:
113: // /** Backward compat: generate JkMounts for mod_jk1
114: // */
115: // public void setJkmountFile( String outFile ) {
116: // file=outFile;
117: // type=CONFIG_JK_MOUNT;
118: // }
119:
120: /* By default we map to the lb - in jk2 this is automatically
121: * created and includes all tomcat instances.
122: *
123: * This is equivalent to the worker in jk1.
124: */
125: public void setGroup(String route) {
126: worker = route;
127: }
128:
129: // -------------------- Generators --------------------
130: public static interface MappingGenerator {
131: void setWebXmlReader(WebXml2Jk wxml);
132:
133: /** Start section( vhost declarations, etc )
134: */
135: void generateStart() throws IOException;
136:
137: void generateEnd() throws IOException;
138:
139: void generateServletMapping(String servlet, String url)
140: throws IOException;
141:
142: void generateFilterMapping(String servlet, String url)
143: throws IOException;
144:
145: void generateLoginConfig(String loginPage, String errPage,
146: String authM) throws IOException;
147:
148: void generateErrorPage(int err, String location)
149: throws IOException;
150:
151: void generateConstraints(Vector urls, Vector methods,
152: Vector roles, boolean isSSL) throws IOException;
153: }
154:
155: // -------------------- Implementation --------------------
156: Node webN;
157: File jkDir;
158:
159: /** Return the top level node
160: */
161: public Node getWebXmlNode() {
162: return webN;
163: }
164:
165: public File getJkDir() {
166: return jkDir;
167: }
168:
169: /** Extract the wellcome files from the web.xml
170: */
171: public Vector getWellcomeFiles() {
172: Node n0 = getChild(webN, "welcome-file-list");
173: Vector wF = new Vector();
174: if (n0 != null) {
175: for (Node mapN = getChild(webN, "welcome-file"); mapN != null; mapN = getNext(mapN)) {
176: wF.addElement(getContent(mapN));
177: }
178: }
179: // XXX Add index.html, index.jsp
180: return wF;
181: }
182:
183: void generate(MappingGenerator gen) throws IOException {
184: gen.generateStart();
185: log.info("Generating mappings for servlets ");
186: for (Node mapN = getChild(webN, "servlet-mapping"); mapN != null; mapN = getNext(mapN)) {
187:
188: String serv = getChildContent(mapN, "servlet-name");
189: String url = getChildContent(mapN, "url-pattern");
190:
191: gen.generateServletMapping(serv, url);
192: }
193:
194: log.info("Generating mappings for filters ");
195: for (Node mapN = getChild(webN, "filter-mapping"); mapN != null; mapN = getNext(mapN)) {
196:
197: String filter = getChildContent(mapN, "filter-name");
198: String url = getChildContent(mapN, "url-pattern");
199:
200: gen.generateFilterMapping(filter, url);
201: }
202:
203: for (Node mapN = getChild(webN, "error-page"); mapN != null; mapN = getNext(mapN)) {
204: String errorCode = getChildContent(mapN, "error-code");
205: String location = getChildContent(mapN, "location");
206:
207: if (errorCode != null && !"".equals(errorCode)) {
208: try {
209: int err = new Integer(errorCode).intValue();
210: gen.generateErrorPage(err, location);
211: } catch (Exception ex) {
212: log.error("Format error " + location, ex);
213: }
214: }
215: }
216:
217: Node lcN = getChild(webN, "login-config");
218: if (lcN != null) {
219: log.info("Generating mapping for login-config ");
220:
221: String authMeth = getContent(getChild(lcN, "auth-method"));
222: String realm = getContent(getChild(lcN, "realm-name"));
223: if (authMeth == null)
224: authMeth = "BASIC";
225:
226: Node n1 = getChild(lcN, "form-login-config");
227: String loginPage = getChildContent(n1, "form-login-page");
228: String errPage = getChildContent(n1, "form-error-page");
229:
230: if (loginPage != null) {
231: int lpos = loginPage.lastIndexOf("/");
232: String jscurl = loginPage.substring(0, lpos + 1)
233: + "j_security_check";
234: gen.generateLoginConfig(jscurl, errPage, authMeth);
235: }
236: }
237:
238: log.info("Generating mappings for security constraints ");
239: for (Node mapN = getChild(webN, "security-constraint"); mapN != null; mapN = getNext(mapN)) {
240:
241: Vector methods = new Vector();
242: Vector urls = new Vector();
243: Vector roles = new Vector();
244: boolean isSSL = false;
245:
246: Node wrcN = getChild(mapN, "web-resource-collection");
247: for (Node uN = getChild(wrcN, "http-method"); uN != null; uN = getNext(uN)) {
248: methods.addElement(getContent(uN));
249: }
250: for (Node uN = getChild(wrcN, "url-pattern"); uN != null; uN = getNext(uN)) {
251: urls.addElement(getContent(uN));
252: }
253:
254: // Not used at the moment
255: Node acN = getChild(mapN, "auth-constraint");
256: for (Node rN = getChild(acN, "role-name"); rN != null; rN = getNext(rN)) {
257: roles.addElement(getContent(rN));
258: }
259:
260: Node ucN = getChild(mapN, "user-data-constraint");
261: String transp = getContent(getChild(ucN,
262: "transport-guarantee"));
263: if (transp != null) {
264: if ("INTEGRAL".equalsIgnoreCase(transp)
265: || "CONFIDENTIAL".equalsIgnoreCase(transp)) {
266: isSSL = true;
267: }
268: }
269:
270: gen.generateConstraints(urls, methods, roles, isSSL);
271: }
272: gen.generateEnd();
273: }
274:
275: // -------------------- Main and ant wrapper --------------------
276:
277: public void execute() {
278: try {
279: if (docBase == null) {
280: log
281: .error("No docbase - please specify the base directory of you web application ( -docBase PATH )");
282: return;
283: }
284: if (cpath == null) {
285: log
286: .error("No context - please specify the mount ( -context PATH )");
287: return;
288: }
289: File docbF = new File(docBase);
290: File wXmlF = new File(docBase, "WEB-INF/web.xml");
291:
292: Document wXmlN = readXml(wXmlF);
293: if (wXmlN == null)
294: return;
295:
296: webN = wXmlN.getDocumentElement();
297: if (webN == null) {
298: log.error("Can't find web-app");
299: return;
300: }
301:
302: jkDir = new File(docbF, "WEB-INF/jk2");
303: jkDir.mkdirs();
304:
305: MappingGenerator generator = new GeneratorJk2();
306: generator.setWebXmlReader(this );
307: generate(generator);
308:
309: generator = new GeneratorJk1();
310: generator.setWebXmlReader(this );
311: generate(generator);
312:
313: generator = new GeneratorApache2();
314: generator.setWebXmlReader(this );
315: generate(generator);
316:
317: } catch (Exception ex) {
318: ex.printStackTrace();
319: }
320: }
321:
322: public static void main(String args[]) {
323: try {
324: if (args.length == 1
325: && ("-?".equals(args[0]) || "-h".equals(args[0]))) {
326: System.out.println("Usage: ");
327: System.out.println(" WebXml2Jk [OPTIONS]");
328: System.out.println();
329: System.out
330: .println(" -docBase DIR The location of the webapp. Required");
331: System.out
332: .println(" -group GROUP Group, if you have multiple tomcats with diffrent content. ");
333: System.out
334: .println(" The default is 'lb', and should be used in most cases");
335: System.out
336: .println(" -host HOSTNAME Canonical hostname - for virtual hosts");
337: System.out
338: .println(" -context /CPATH Context path where the app will be mounted");
339: return;
340: }
341:
342: WebXml2Jk w2jk = new WebXml2Jk();
343:
344: /* do ant-style property setting */
345: IntrospectionUtils.processArgs(w2jk, args, new String[] {},
346: null, new Hashtable());
347: w2jk.execute();
348: } catch (Exception ex) {
349: ex.printStackTrace();
350: }
351:
352: }
353:
354: private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
355: .getLog(WebXml2Jk.class);
356:
357: // -------------------- DOM utils --------------------
358:
359: /** Get the content of a node
360: */
361: public static String getContent(Node n) {
362: if (n == null)
363: return null;
364: Node n1 = n.getFirstChild();
365: // XXX Check if it's a text node
366:
367: String s1 = n1.getNodeValue();
368: return s1.trim();
369: }
370:
371: /** Get the first child
372: */
373: public static Node getChild(Node parent, String name) {
374: if (parent == null)
375: return null;
376: Node first = parent.getFirstChild();
377: if (first == null)
378: return null;
379: for (Node node = first; node != null; node = node
380: .getNextSibling()) {
381: //System.out.println("getNode: " + name + " " + node.getNodeName());
382: if (name.equals(node.getNodeName())) {
383: return node;
384: }
385: }
386: return null;
387: }
388:
389: /** Get the first child's content ( i.e. it's included TEXT node )
390: */
391: public static String getChildContent(Node parent, String name) {
392: Node first = parent.getFirstChild();
393: if (first == null)
394: return null;
395: for (Node node = first; node != null; node = node
396: .getNextSibling()) {
397: //System.out.println("getNode: " + name + " " + node.getNodeName());
398: if (name.equals(node.getNodeName())) {
399: return getContent(node);
400: }
401: }
402: return null;
403: }
404:
405: /** Get the node in the list of siblings
406: */
407: public static Node getNext(Node current) {
408: Node first = current.getNextSibling();
409: String name = current.getNodeName();
410: if (first == null)
411: return null;
412: for (Node node = first; node != null; node = node
413: .getNextSibling()) {
414: //System.out.println("getNode: " + name + " " + node.getNodeName());
415: if (name.equals(node.getNodeName())) {
416: return node;
417: }
418: }
419: return null;
420: }
421:
422: public static class NullResolver implements EntityResolver {
423: public InputSource resolveEntity(String publicId,
424: String systemId) throws SAXException, IOException {
425: System.out.println("ResolveEntity: " + publicId + " "
426: + systemId);
427: return new InputSource(new StringReader(""));
428: }
429: }
430:
431: public static Document readXml(File xmlF) throws SAXException,
432: IOException, ParserConfigurationException {
433: if (!xmlF.exists()) {
434: log.error("No xml file " + xmlF);
435: return null;
436: }
437: DocumentBuilderFactory dbf = DocumentBuilderFactory
438: .newInstance();
439:
440: dbf.setValidating(false);
441: dbf.setIgnoringComments(false);
442: dbf.setIgnoringElementContentWhitespace(true);
443: //dbf.setCoalescing(true);
444: //dbf.setExpandEntityReferences(true);
445:
446: DocumentBuilder db = null;
447: db = dbf.newDocumentBuilder();
448: db.setEntityResolver(new NullResolver());
449:
450: // db.setErrorHandler( new MyErrorHandler());
451:
452: Document doc = db.parse(xmlF);
453: return doc;
454: }
455:
456: }
|