001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs.optional.ejb;
020:
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.FileNotFoundException;
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.net.URL;
027: import java.util.Hashtable;
028: import org.apache.tools.ant.Project;
029: import org.apache.tools.ant.Task;
030: import org.xml.sax.AttributeList;
031: import org.xml.sax.InputSource;
032: import org.xml.sax.SAXException;
033:
034: /**
035: * Inner class used by EjbJar to facilitate the parsing of deployment
036: * descriptors and the capture of appropriate information. Extends
037: * HandlerBase so it only implements the methods needed. During parsing
038: * creates a hashtable consisting of entries mapping the name it should be
039: * inserted into an EJB jar as to a File representing the file on disk. This
040: * list can then be accessed through the getFiles() method.
041: */
042: public class DescriptorHandler extends org.xml.sax.HandlerBase {
043: private static final int DEFAULT_HASH_TABLE_SIZE = 10;
044: private static final int STATE_LOOKING_EJBJAR = 1;
045: private static final int STATE_IN_EJBJAR = 2;
046: private static final int STATE_IN_BEANS = 3;
047: private static final int STATE_IN_SESSION = 4;
048: private static final int STATE_IN_ENTITY = 5;
049: private static final int STATE_IN_MESSAGE = 6;
050:
051: private Task owningTask;
052:
053: private String publicId = null;
054:
055: /**
056: * Bunch of constants used for storing entries in a hashtable, and for
057: * constructing the filenames of various parts of the ejb jar.
058: */
059: private static final String EJB_REF = "ejb-ref";
060: private static final String EJB_LOCAL_REF = "ejb-local-ref";
061: private static final String HOME_INTERFACE = "home";
062: private static final String REMOTE_INTERFACE = "remote";
063: private static final String LOCAL_HOME_INTERFACE = "local-home";
064: private static final String LOCAL_INTERFACE = "local";
065: private static final String BEAN_CLASS = "ejb-class";
066: private static final String PK_CLASS = "prim-key-class";
067: private static final String EJB_NAME = "ejb-name";
068: private static final String EJB_JAR = "ejb-jar";
069: private static final String ENTERPRISE_BEANS = "enterprise-beans";
070: private static final String ENTITY_BEAN = "entity";
071: private static final String SESSION_BEAN = "session";
072: private static final String MESSAGE_BEAN = "message-driven";
073:
074: /**
075: * The state of the parsing
076: */
077: private int parseState = STATE_LOOKING_EJBJAR;
078:
079: // CheckStyle:VisibilityModifier OFF - bc
080: /**
081: * Instance variable used to store the name of the current element being
082: * processed by the SAX parser. Accessed by the SAX parser call-back methods
083: * startElement() and endElement().
084: */
085: protected String currentElement = null;
086:
087: /**
088: * The text of the current element
089: */
090: protected String currentText = null;
091:
092: /**
093: * Instance variable that stores the names of the files as they will be
094: * put into the jar file, mapped to File objects Accessed by the SAX
095: * parser call-back method characters().
096: */
097: protected Hashtable ejbFiles = null;
098:
099: /**
100: * Instance variable that stores the value found in the <ejb-name> element
101: */
102: protected String ejbName = null;
103:
104: private Hashtable fileDTDs = new Hashtable();
105:
106: private Hashtable resourceDTDs = new Hashtable();
107:
108: private boolean inEJBRef = false;
109:
110: private Hashtable urlDTDs = new Hashtable();
111: // CheckStyle:VisibilityModifier OFF - bc
112:
113: /**
114: * The directory containing the bean classes and interfaces. This is
115: * used for performing dependency file lookups.
116: */
117: private File srcDir;
118:
119: /**
120: * Constructor for DescriptorHandler.
121: * @param task the task that owns this desciptor
122: * @param srcDir the source directory
123: */
124: public DescriptorHandler(Task task, File srcDir) {
125: this .owningTask = task;
126: this .srcDir = srcDir;
127: }
128:
129: /**
130: * Register a dtd with a location.
131: * The location is one of a filename, a resource name in the classpath, or
132: * a URL.
133: * @param publicId the public identity of the dtd
134: * @param location the location of the dtd
135: */
136: public void registerDTD(String publicId, String location) {
137: if (location == null) {
138: return;
139: }
140:
141: File fileDTD = new File(location);
142: if (!fileDTD.exists()) {
143: // resolve relative to project basedir
144: fileDTD = owningTask.getProject().resolveFile(location);
145: }
146:
147: if (fileDTD.exists()) {
148: if (publicId != null) {
149: fileDTDs.put(publicId, fileDTD);
150: owningTask.log("Mapped publicId " + publicId
151: + " to file " + fileDTD, Project.MSG_VERBOSE);
152: }
153: return;
154: }
155:
156: if (getClass().getResource(location) != null) {
157: if (publicId != null) {
158: resourceDTDs.put(publicId, location);
159: owningTask.log("Mapped publicId " + publicId
160: + " to resource " + location,
161: Project.MSG_VERBOSE);
162: }
163: }
164:
165: try {
166: if (publicId != null) {
167: URL urldtd = new URL(location);
168: urlDTDs.put(publicId, urldtd);
169: }
170: } catch (java.net.MalformedURLException e) {
171: //ignored
172: }
173:
174: }
175:
176: /**
177: * Resolve the entity.
178: * @see org.xml.sax.EntityResolver#resolveEntity(String, String).
179: * @param publicId The public identifier, or <code>null</code>
180: * if none is available.
181: * @param systemId The system identifier provided in the XML
182: * document. Will not be <code>null</code>.
183: * @return an inputsource for this identifier
184: * @throws SAXException if there is a problem.
185: */
186: public InputSource resolveEntity(String publicId, String systemId)
187: throws SAXException {
188: this .publicId = publicId;
189:
190: File dtdFile = (File) fileDTDs.get(publicId);
191: if (dtdFile != null) {
192: try {
193: owningTask.log("Resolved " + publicId
194: + " to local file " + dtdFile,
195: Project.MSG_VERBOSE);
196: return new InputSource(new FileInputStream(dtdFile));
197: } catch (FileNotFoundException ex) {
198: // ignore
199: }
200: }
201:
202: String dtdResourceName = (String) resourceDTDs.get(publicId);
203: if (dtdResourceName != null) {
204: InputStream is = this .getClass().getResourceAsStream(
205: dtdResourceName);
206: if (is != null) {
207: owningTask.log("Resolved " + publicId
208: + " to local resource " + dtdResourceName,
209: Project.MSG_VERBOSE);
210: return new InputSource(is);
211: }
212: }
213:
214: URL dtdUrl = (URL) urlDTDs.get(publicId);
215: if (dtdUrl != null) {
216: try {
217: InputStream is = dtdUrl.openStream();
218: owningTask.log("Resolved " + publicId + " to url "
219: + dtdUrl, Project.MSG_VERBOSE);
220: return new InputSource(is);
221: } catch (IOException ioe) {
222: //ignore
223: }
224: }
225:
226: owningTask.log("Could not resolve ( publicId: " + publicId
227: + ", systemId: " + systemId + ") to a local entity",
228: Project.MSG_INFO);
229:
230: return null;
231: }
232:
233: /**
234: * Getter method that returns the set of files to include in the EJB jar.
235: * @return the map of files
236: */
237: public Hashtable getFiles() {
238: return (ejbFiles == null) ? new Hashtable() : ejbFiles;
239: }
240:
241: /**
242: * Get the publicId of the DTD
243: * @return the public id
244: */
245: public String getPublicId() {
246: return publicId;
247: }
248:
249: /**
250: * Getter method that returns the value of the <ejb-name> element.
251: * @return the ejb name
252: */
253: public String getEjbName() {
254: return ejbName;
255: }
256:
257: /**
258: * SAX parser call-back method that is used to initialize the values of some
259: * instance variables to ensure safe operation.
260: * @throws SAXException on error
261: */
262: public void startDocument() throws SAXException {
263: this .ejbFiles = new Hashtable(DEFAULT_HASH_TABLE_SIZE, 1);
264: this .currentElement = null;
265: inEJBRef = false;
266: }
267:
268: /**
269: * SAX parser call-back method that is invoked when a new element is entered
270: * into. Used to store the context (attribute name) in the currentAttribute
271: * instance variable.
272: * @param name The name of the element being entered.
273: * @param attrs Attributes associated to the element.
274: * @throws SAXException on error
275: */
276: public void startElement(String name, AttributeList attrs)
277: throws SAXException {
278: this .currentElement = name;
279: currentText = "";
280: if (name.equals(EJB_REF) || name.equals(EJB_LOCAL_REF)) {
281: inEJBRef = true;
282: } else if (parseState == STATE_LOOKING_EJBJAR
283: && name.equals(EJB_JAR)) {
284: parseState = STATE_IN_EJBJAR;
285: } else if (parseState == STATE_IN_EJBJAR
286: && name.equals(ENTERPRISE_BEANS)) {
287: parseState = STATE_IN_BEANS;
288: } else if (parseState == STATE_IN_BEANS
289: && name.equals(SESSION_BEAN)) {
290: parseState = STATE_IN_SESSION;
291: } else if (parseState == STATE_IN_BEANS
292: && name.equals(ENTITY_BEAN)) {
293: parseState = STATE_IN_ENTITY;
294: } else if (parseState == STATE_IN_BEANS
295: && name.equals(MESSAGE_BEAN)) {
296: parseState = STATE_IN_MESSAGE;
297: }
298: }
299:
300: /**
301: * SAX parser call-back method that is invoked when an element is exited.
302: * Used to blank out (set to the empty string, not nullify) the name of
303: * the currentAttribute. A better method would be to use a stack as an
304: * instance variable, however since we are only interested in leaf-node
305: * data this is a simpler and workable solution.
306: * @param name The name of the attribute being exited. Ignored
307: * in this implementation.
308: * @throws SAXException on error
309: */
310: public void endElement(String name) throws SAXException {
311: processElement();
312: currentText = "";
313: this .currentElement = "";
314: if (name.equals(EJB_REF) || name.equals(EJB_LOCAL_REF)) {
315: inEJBRef = false;
316: } else if (parseState == STATE_IN_ENTITY
317: && name.equals(ENTITY_BEAN)) {
318: parseState = STATE_IN_BEANS;
319: } else if (parseState == STATE_IN_SESSION
320: && name.equals(SESSION_BEAN)) {
321: parseState = STATE_IN_BEANS;
322: } else if (parseState == STATE_IN_MESSAGE
323: && name.equals(MESSAGE_BEAN)) {
324: parseState = STATE_IN_BEANS;
325: } else if (parseState == STATE_IN_BEANS
326: && name.equals(ENTERPRISE_BEANS)) {
327: parseState = STATE_IN_EJBJAR;
328: } else if (parseState == STATE_IN_EJBJAR
329: && name.equals(EJB_JAR)) {
330: parseState = STATE_LOOKING_EJBJAR;
331: }
332: }
333:
334: /**
335: * SAX parser call-back method invoked whenever characters are located within
336: * an element. currentAttribute (modified by startElement and endElement)
337: * tells us whether we are in an interesting element (one of the up to four
338: * classes of an EJB). If so then converts the classname from the format
339: * org.apache.tools.ant.Parser to the convention for storing such a class,
340: * org/apache/tools/ant/Parser.class. This is then resolved into a file
341: * object under the srcdir which is stored in a Hashtable.
342: * @param ch A character array containing all the characters in
343: * the element, and maybe others that should be ignored.
344: * @param start An integer marking the position in the char
345: * array to start reading from.
346: * @param length An integer representing an offset into the
347: * char array where the current data terminates.
348: * @throws SAXException on error
349: */
350: public void characters(char[] ch, int start, int length)
351: throws SAXException {
352:
353: currentText += new String(ch, start, length);
354: }
355:
356: /**
357: * Called when an endelement is seen.
358: * This may be overridden in derived classes.
359: * This updates the ejbfiles if the element is an interface or a bean class.
360: * This updates the ejbname if the element is an ejb name.
361: */
362: protected void processElement() {
363: if (inEJBRef
364: || (parseState != STATE_IN_ENTITY
365: && parseState != STATE_IN_SESSION && parseState != STATE_IN_MESSAGE)) {
366: return;
367: }
368:
369: if (currentElement.equals(HOME_INTERFACE)
370: || currentElement.equals(REMOTE_INTERFACE)
371: || currentElement.equals(LOCAL_INTERFACE)
372: || currentElement.equals(LOCAL_HOME_INTERFACE)
373: || currentElement.equals(BEAN_CLASS)
374: || currentElement.equals(PK_CLASS)) {
375:
376: // Get the filename into a String object
377: File classFile = null;
378: String className = currentText.trim();
379:
380: // If it's a primitive wrapper then we shouldn't try and put
381: // it into the jar, so ignore it.
382: if (!className.startsWith("java.")
383: && !className.startsWith("javax.")) {
384: // Translate periods into path separators, add .class to the
385: // name, create the File object and add it to the Hashtable.
386: className = className.replace('.', File.separatorChar);
387: className += ".class";
388: classFile = new File(srcDir, className);
389: ejbFiles.put(className, classFile);
390: }
391: }
392:
393: // Get the value of the <ejb-name> tag. Only the first occurrence.
394: if (currentElement.equals(EJB_NAME)) {
395: if (ejbName == null) {
396: ejbName = currentText.trim();
397: }
398: }
399: }
400: }
|