/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.ant.taskdefs.optional.ejb; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.file.Files; import java.util.Hashtable; import java.util.Map; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.xml.sax.AttributeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * Inner class used by EjbJar to facilitate the parsing of deployment * descriptors and the capture of appropriate information. Extends * HandlerBase so it only implements the methods needed. During parsing * creates a hashtable consisting of entries mapping the name it should be * inserted into an EJB jar as to a File representing the file on disk. This * list can then be accessed through the getFiles() method. */ public class DescriptorHandler extends org.xml.sax.HandlerBase { private static final int DEFAULT_HASH_TABLE_SIZE = 10; private static final int STATE_LOOKING_EJBJAR = 1; private static final int STATE_IN_EJBJAR = 2; private static final int STATE_IN_BEANS = 3; private static final int STATE_IN_SESSION = 4; private static final int STATE_IN_ENTITY = 5; private static final int STATE_IN_MESSAGE = 6; private Task owningTask; private String publicId = null; /** * Bunch of constants used for storing entries in a hashtable, and for * constructing the filenames of various parts of the ejb jar. */ private static final String EJB_REF = "ejb-ref"; private static final String EJB_LOCAL_REF = "ejb-local-ref"; private static final String HOME_INTERFACE = "home"; private static final String REMOTE_INTERFACE = "remote"; private static final String LOCAL_HOME_INTERFACE = "local-home"; private static final String LOCAL_INTERFACE = "local"; private static final String BEAN_CLASS = "ejb-class"; private static final String PK_CLASS = "prim-key-class"; private static final String EJB_NAME = "ejb-name"; private static final String EJB_JAR = "ejb-jar"; private static final String ENTERPRISE_BEANS = "enterprise-beans"; private static final String ENTITY_BEAN = "entity"; private static final String SESSION_BEAN = "session"; private static final String MESSAGE_BEAN = "message-driven"; /** * The state of the parsing */ private int parseState = STATE_LOOKING_EJBJAR; // CheckStyle:VisibilityModifier OFF - bc /** * Instance variable used to store the name of the current element being * processed by the SAX parser. Accessed by the SAX parser call-back methods * startElement() and endElement(). */ protected String currentElement = null; /** * The text of the current element */ protected String currentText = null; /** * Instance variable that stores the names of the files as they will be * put into the jar file, mapped to File objects Accessed by the SAX * parser call-back method characters(). */ protected Hashtable<String, File> ejbFiles = null; /** * Instance variable that stores the value found in the <ejb-name> element */ protected String ejbName = null; private Map<String, File> fileDTDs = new Hashtable<>(); private Map<String, String> resourceDTDs = new Hashtable<>(); private boolean inEJBRef = false; private Map<String, URL> urlDTDs = new Hashtable<>(); // CheckStyle:VisibilityModifier OFF - bc /** * The directory containing the bean classes and interfaces. This is * used for performing dependency file lookups. */ private File srcDir; /** * Constructor for DescriptorHandler. * @param task the task that owns this descriptor * @param srcDir the source directory */ public DescriptorHandler(Task task, File srcDir) { this.owningTask = task; this.srcDir = srcDir; } /** * Register a dtd with a location. * The location is one of a filename, a resource name in the classpath, or * a URL. * @param publicId the public identity of the dtd * @param location the location of the dtd */ public void registerDTD(String publicId, String location) { if (location == null) { return; } File fileDTD = new File(location); if (!fileDTD.exists()) { // resolve relative to project basedir fileDTD = owningTask.getProject().resolveFile(location); } if (fileDTD.exists()) { if (publicId != null) { fileDTDs.put(publicId, fileDTD); owningTask.log("Mapped publicId " + publicId + " to file " + fileDTD, Project.MSG_VERBOSE); } return; } if (getClass().getResource(location) != null) { if (publicId != null) { resourceDTDs.put(publicId, location); owningTask.log("Mapped publicId " + publicId + " to resource " + location, Project.MSG_VERBOSE); } } try { if (publicId != null) { URL urldtd = new URL(location); urlDTDs.put(publicId, urldtd); } } catch (java.net.MalformedURLException e) { //ignored } } /** * Resolve the entity. * @see org.xml.sax.EntityResolver#resolveEntity(String, String) * @param publicId The public identifier, or <code>null</code> * if none is available. * @param systemId The system identifier provided in the XML * document. Will not be <code>null</code>. * @return an inputsource for this identifier * @throws SAXException if there is a problem. */ @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException { this.publicId = publicId; File dtdFile = fileDTDs.get(publicId); if (dtdFile != null) { try { owningTask.log("Resolved " + publicId + " to local file " + dtdFile, Project.MSG_VERBOSE); return new InputSource(Files.newInputStream(dtdFile.toPath())); } catch (IOException ex) { // ignore } } String dtdResourceName = resourceDTDs.get(publicId); if (dtdResourceName != null) { InputStream is = this.getClass().getResourceAsStream(dtdResourceName); if (is != null) { owningTask.log("Resolved " + publicId + " to local resource " + dtdResourceName, Project.MSG_VERBOSE); return new InputSource(is); } } URL dtdUrl = urlDTDs.get(publicId); if (dtdUrl != null) { try { InputStream is = dtdUrl.openStream(); owningTask.log("Resolved " + publicId + " to url " + dtdUrl, Project.MSG_VERBOSE); return new InputSource(is); } catch (IOException ioe) { //ignore } } owningTask.log("Could not resolve ( publicId: " + publicId + ", systemId: " + systemId + ") to a local entity", Project.MSG_INFO); return null; } /** * Getter method that returns the set of files to include in the EJB jar. * @return the map of files */ public Hashtable<String, File> getFiles() { return ejbFiles == null ? new Hashtable<>() : ejbFiles; } /** * Get the publicId of the DTD * @return the public id */ public String getPublicId() { return publicId; } /** * Getter method that returns the value of the <ejb-name> element. * @return the ejb name */ public String getEjbName() { return ejbName; } /** * SAX parser call-back method that is used to initialize the values of some * instance variables to ensure safe operation. * @throws SAXException on error */ @Override public void startDocument() throws SAXException { this.ejbFiles = new Hashtable<>(DEFAULT_HASH_TABLE_SIZE, 1); this.currentElement = null; inEJBRef = false; } /** * SAX parser call-back method that is invoked when a new element is entered * into. Used to store the context (attribute name) in the currentAttribute * instance variable. * @param name The name of the element being entered. * @param attrs Attributes associated to the element. * @throws SAXException on error */ @Override public void startElement(String name, AttributeList attrs) throws SAXException { this.currentElement = name; currentText = ""; if (EJB_REF.equals(name) || EJB_LOCAL_REF.equals(name)) { inEJBRef = true; } else if (parseState == STATE_LOOKING_EJBJAR && EJB_JAR.equals(name)) { parseState = STATE_IN_EJBJAR; } else if (parseState == STATE_IN_EJBJAR && ENTERPRISE_BEANS.equals(name)) { parseState = STATE_IN_BEANS; } else if (parseState == STATE_IN_BEANS && SESSION_BEAN.equals(name)) { parseState = STATE_IN_SESSION; } else if (parseState == STATE_IN_BEANS && ENTITY_BEAN.equals(name)) { parseState = STATE_IN_ENTITY; } else if (parseState == STATE_IN_BEANS && MESSAGE_BEAN.equals(name)) { parseState = STATE_IN_MESSAGE; } } /** * SAX parser call-back method that is invoked when an element is exited. * Used to blank out (set to the empty string, not nullify) the name of * the currentAttribute. A better method would be to use a stack as an * instance variable, however since we are only interested in leaf-node * data this is a simpler and workable solution. * @param name The name of the attribute being exited. Ignored * in this implementation. * @throws SAXException on error */ @Override public void endElement(String name) throws SAXException { processElement(); currentText = ""; this.currentElement = ""; if (name.equals(EJB_REF) || name.equals(EJB_LOCAL_REF)) { inEJBRef = false; } else if (parseState == STATE_IN_ENTITY && name.equals(ENTITY_BEAN)) { parseState = STATE_IN_BEANS; } else if (parseState == STATE_IN_SESSION && name.equals(SESSION_BEAN)) { parseState = STATE_IN_BEANS; } else if (parseState == STATE_IN_MESSAGE && name.equals(MESSAGE_BEAN)) { parseState = STATE_IN_BEANS; } else if (parseState == STATE_IN_BEANS && name.equals(ENTERPRISE_BEANS)) { parseState = STATE_IN_EJBJAR; } else if (parseState == STATE_IN_EJBJAR && name.equals(EJB_JAR)) { parseState = STATE_LOOKING_EJBJAR; } } /** * SAX parser call-back method invoked whenever characters are located within * an element. currentAttribute (modified by startElement and endElement) * tells us whether we are in an interesting element (one of the up to four * classes of an EJB). If so then converts the classname from the format * org.apache.tools.ant.Parser to the convention for storing such a class, * org/apache/tools/ant/Parser.class. This is then resolved into a file * object under the srcdir which is stored in a Hashtable. * @param ch A character array containing all the characters in * the element, and maybe others that should be ignored. * @param start An integer marking the position in the char * array to start reading from. * @param length An integer representing an offset into the * char array where the current data terminates. * @throws SAXException on error */ @Override public void characters(char[] ch, int start, int length) throws SAXException { currentText += new String(ch, start, length); } /** * Called when an endelement is seen. * This may be overridden in derived classes. * This updates the ejbfiles if the element is an interface or a bean class. * This updates the ejbname if the element is an ejb name. */ protected void processElement() { if (inEJBRef || (parseState != STATE_IN_ENTITY && parseState != STATE_IN_SESSION && parseState != STATE_IN_MESSAGE)) { return; } if (HOME_INTERFACE.equals(currentElement) || REMOTE_INTERFACE.equals(currentElement) || LOCAL_INTERFACE.equals(currentElement) || LOCAL_HOME_INTERFACE.equals(currentElement) || BEAN_CLASS.equals(currentElement) || PK_CLASS.equals(currentElement)) { // Get the filename into a String object String className = currentText.trim(); // If it's a primitive wrapper then we shouldn't try and put // it into the jar, so ignore it. if (!className.startsWith("java.") && !className.startsWith("javax.")) { // Translate periods into path separators, add .class to the // name, create the File object and add it to the Hashtable. className = className.replace('.', File.separatorChar); className += ".class"; ejbFiles.put(className, new File(srcDir, className)); } } // Get the value of the <ejb-name> tag. Only the first occurrence. if (currentElement.equals(EJB_NAME)) { if (ejbName == null) { ejbName = currentText.trim(); } } } }