/* * 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.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import org.apache.tools.ant.AntClassLoader; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.Java; import org.apache.tools.ant.types.Environment; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.util.FileUtils; /** * Websphere deployment tool that augments the ejbjar task. * Searches for the websphere specific deployment descriptors and * adds them to the final ejb jar file. Websphere has two specific descriptors for session * beans: *
deployment
. This step can be performed by the websphere
* element as part of the jar generation process. If the switch
* ejbdeploy
is on, the ejbdeploy tool from the websphere toolset
* is called for every ejb-jar. Unfortunately, this step only works, if you
* use the ibm jdk. Otherwise, the rmic (called by ejbdeploy) throws a
* ClassFormatError. Be sure to switch ejbdeploy off, if run ant with
* sun jdk.
*
*/
public class WebsphereDeploymentTool extends GenericDeploymentTool {
/** ID for ejb 1.1 */
public static final String PUBLICID_EJB11
= "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN";
/** ID for ejb 2.0 */
public static final String PUBLICID_EJB20
= "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN";
/** Schema directory */
protected static final String SCHEMA_DIR = "Schema/";
protected static final String WAS_EXT = "ibm-ejb-jar-ext.xmi";
protected static final String WAS_BND = "ibm-ejb-jar-bnd.xmi";
protected static final String WAS_CMP_MAP = "Map.mapxmi";
protected static final String WAS_CMP_SCHEMA = "Schema.dbxmi";
private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
/** Instance variable that stores the suffix for the websphere jarfile. */
private String jarSuffix = ".jar";
/** Instance variable that stores the location of the ejb 1.1 DTD file. */
private String ejb11DTD;
/** Instance variable that determines whether generic ejb jars are kept. */
private boolean keepGeneric = false;
private boolean alwaysRebuild = true;
private boolean ejbdeploy = true;
/** Indicates if the old CMP location convention is to be used. */
private boolean newCMP = false;
/** The classpath to the websphere classes. */
private Path wasClasspath = null;
/** The DB Vendor name, the EJB is persisted against */
private String dbVendor;
/** The name of the database to create. (For top-down mapping only) */
private String dbName;
/** The name of the schema to create. (For top-down mappings only) */
private String dbSchema;
/** true - Only generate the deployment code, do not run RMIC or Javac */
private boolean codegen;
/** true - Only output error messages, suppress informational messages */
private boolean quiet = true;
/** true - Disable the validation steps */
private boolean novalidate;
/** true - Disable warning and informational messages */
private boolean nowarn;
/** true - Disable informational messages */
private boolean noinform;
/** true - Enable internal tracing */
private boolean trace;
/** Additional options for RMIC */
private String rmicOptions;
/** true- Use the WebSphere 3.5 compatible mapping rules */
private boolean use35MappingRules;
/** the scratchdir for the ejbdeploy operation */
private String tempdir = "_ejbdeploy_temp";
/** the home directory for websphere */
private File websphereHome;
/**
* Get the classpath to the websphere classpaths.
* @return the websphere classpath.
*/
public Path createWASClasspath() {
if (wasClasspath == null) {
wasClasspath = new Path(getTask().getProject());
}
return wasClasspath.createPath();
}
/**
* Set the websphere classpath.
* @param wasClasspath the websphere classpath.
*/
public void setWASClasspath(Path wasClasspath) {
this.wasClasspath = wasClasspath;
}
/** Sets the DB Vendor for the Entity Bean mapping ; optional.
*
* Valid options can be obtained by running the following command:
*
* <WAS_HOME>/bin/EJBDeploy.[sh/bat] -help
*
*
* This is also used to determine the name of the Map.mapxmi and * Schema.dbxmi files, for example Account-DB2UDB_V81-Map.mapxmi * and Account-DB2UDB_V81-Schema.dbxmi. *
* * @param dbvendor database vendor type */ public void setDbvendor(String dbvendor) { this.dbVendor = dbvendor; } /** * Sets the name of the Database to create; optional. * * @param dbName name of the database */ public void setDbname(String dbName) { this.dbName = dbName; } /** * Sets the name of the schema to create; optional. * * @param dbSchema name of the schema */ public void setDbschema(String dbSchema) { this.dbSchema = dbSchema; } /** * Flag, default false, to only generate the deployment * code, do not run RMIC or Javac * * @param codegen option */ public void setCodegen(boolean codegen) { this.codegen = codegen; } /** * Flag, default true, to only output error messages. * * @param quiet option */ public void setQuiet(boolean quiet) { this.quiet = quiet; } /** * Flag to disable the validation steps; optional, default false. * * @param novalidate option */ public void setNovalidate(boolean novalidate) { this.novalidate = novalidate; } /** * Flag to disable warning and informational messages; optional, default false. * * @param nowarn option */ public void setNowarn(boolean nowarn) { this.nowarn = nowarn; } /** * Flag to disable informational messages; optional, default false. * * @param noinform if true disables informational messages */ public void setNoinform(boolean noinform) { this.noinform = noinform; } /** * Flag to enable internal tracing when set, optional, default false. * * @param trace aboolean
value.
*/
public void setTrace(boolean trace) {
this.trace = trace;
}
/**
* Set the rmic options.
*
* @param options the options to use.
*/
public void setRmicoptions(String options) {
this.rmicOptions = options;
}
/**
* Flag to use the WebSphere 3.5 compatible mapping rules ; optional, default false.
*
* @param attr a boolean
value.
*/
public void setUse35(boolean attr) {
use35MappingRules = attr;
}
/**
* Set the rebuild flag to false to only update changes in the jar rather
* than rerunning ejbdeploy; optional, default true.
* @param rebuild a boolean
value.
*/
public void setRebuild(boolean rebuild) {
this.alwaysRebuild = rebuild;
}
/**
* String value appended to the basename of the deployment
* descriptor to create the filename of the WebLogic EJB
* jar file. Optional, default '.jar'.
* @param inString the string to use as the suffix.
*/
public void setSuffix(String inString) {
this.jarSuffix = inString;
}
/**
* This controls whether the generic file used as input to
* ejbdeploy is retained; optional, default false.
* @param inValue either 'true' or 'false'.
*/
public void setKeepgeneric(boolean inValue) {
this.keepGeneric = inValue;
}
/**
* Decide, whether ejbdeploy should be called or not;
* optional, default true.
*
* @param ejbdeploy a boolean
value.
*/
public void setEjbdeploy(boolean ejbdeploy) {
this.ejbdeploy = ejbdeploy;
}
/**
* Setter used to store the location of the Sun's Generic EJB DTD. This
* can be a file on the system or a resource on the classpath.
*
* @param inString the string to use as the DTD location.
*/
public void setEJBdtd(String inString) {
this.ejb11DTD = inString;
}
/**
* Set the value of the oldCMP scheme. This is an antonym for newCMP
* @ant.attribute ignore="true"
* @param oldCMP a boolean
value.
*/
public void setOldCMP(boolean oldCMP) {
this.newCMP = !oldCMP;
}
/**
* Set the value of the newCMP scheme. The old CMP scheme locates the
* websphere CMP descriptor based on the naming convention where the
* websphere CMP file is expected to be named with the bean name as the
* prefix. Under this scheme the name of the CMP descriptor does not match
* the name actually used in the main websphere EJB descriptor. Also,
* descriptors which contain multiple CMP references could not be used.
* @param newCMP a boolean
value.
*/
public void setNewCMP(boolean newCMP) {
this.newCMP = newCMP;
}
/**
* The directory, where ejbdeploy will write temporary files;
* optional, defaults to '_ejbdeploy_temp'.
* @param tempdir the directory name to use.
*/
public void setTempdir(String tempdir) {
this.tempdir = tempdir;
}
/** {@inheritDoc}. */
protected DescriptorHandler getDescriptorHandler(File srcDir) {
DescriptorHandler handler = new DescriptorHandler(getTask(), srcDir);
// register all the DTDs, both the ones that are known and
// any supplied by the user
handler.registerDTD(PUBLICID_EJB11, ejb11DTD);
for (Iterator i = getConfig().dtdLocations.iterator(); i.hasNext();) {
EjbJar.DTDLocation dtdLocation = (EjbJar.DTDLocation) i.next();
handler.registerDTD(dtdLocation.getPublicId(), dtdLocation.getLocation());
}
return handler;
}
/**
* Get a description handler.
* @param srcDir the source directory.
* @return the handler.
*/
protected DescriptorHandler getWebsphereDescriptorHandler(final File srcDir) {
DescriptorHandler handler =
new DescriptorHandler(getTask(), srcDir) {
protected void processElement() {
}
};
for (Iterator i = getConfig().dtdLocations.iterator(); i.hasNext();) {
EjbJar.DTDLocation dtdLocation = (EjbJar.DTDLocation) i.next();
handler.registerDTD(dtdLocation.getPublicId(), dtdLocation.getLocation());
}
return handler;
}
/**
* Add any vendor specific files which should be included in the EJB Jar.
* @param ejbFiles a hashtable entryname -> file.
* @param baseName a prefix to use.
*/
protected void addVendorFiles(Hashtable ejbFiles, String baseName) {
String ddPrefix = (usingBaseJarName() ? "" : baseName);
String dbPrefix = (dbVendor == null) ? "" : dbVendor + "-";
// Get the Extensions document
File websphereEXT = new File(getConfig().descriptorDir, ddPrefix + WAS_EXT);
if (websphereEXT.exists()) {
ejbFiles.put(META_DIR + WAS_EXT,
websphereEXT);
} else {
log("Unable to locate websphere extensions. "
+ "It was expected to be in "
+ websphereEXT.getPath(), Project.MSG_VERBOSE);
}
File websphereBND = new File(getConfig().descriptorDir, ddPrefix + WAS_BND);
if (websphereBND.exists()) {
ejbFiles.put(META_DIR + WAS_BND,
websphereBND);
} else {
log("Unable to locate websphere bindings. "
+ "It was expected to be in "
+ websphereBND.getPath(), Project.MSG_VERBOSE);
}
if (!newCMP) {
log("The old method for locating CMP files has been DEPRECATED.",
Project.MSG_VERBOSE);
log("Please adjust your websphere descriptor and set "
+ "newCMP=\"true\" to use the new CMP descriptor "
+ "inclusion mechanism. ", Project.MSG_VERBOSE);
} else {
// We attempt to put in the MAP and Schema files of CMP beans
try {
// Add the Map file
File websphereMAP = new File(getConfig().descriptorDir,
ddPrefix + dbPrefix + WAS_CMP_MAP);
if (websphereMAP.exists()) {
ejbFiles.put(META_DIR + WAS_CMP_MAP,
websphereMAP);
} else {
log("Unable to locate the websphere Map: "
+ websphereMAP.getPath(), Project.MSG_VERBOSE);
}
File websphereSchema = new File(getConfig().descriptorDir,
ddPrefix + dbPrefix + WAS_CMP_SCHEMA);
if (websphereSchema.exists()) {
ejbFiles.put(META_DIR + SCHEMA_DIR + WAS_CMP_SCHEMA,
websphereSchema);
} else {
log("Unable to locate the websphere Schema: "
+ websphereSchema.getPath(), Project.MSG_VERBOSE);
}
// Theres nothing else to see here...keep moving sonny
} catch (Exception e) {
String msg = "Exception while adding Vendor specific files: "
+ e.toString();
throw new BuildException(msg, e);
}
}
}
/**
* Get the vendor specific name of the Jar that will be output. The
* modification date of this jar will be checked against the dependent
* bean classes.
*/
File getVendorOutputJarFile(String baseName) {
return new File(getDestDir(), baseName + jarSuffix);
}
/**
* Gets the options for the EJB Deploy operation
*
* @return String
*/
protected String getOptions() {
// Set the options
StringBuffer options = new StringBuffer();
if (dbVendor != null) {
options.append(" -dbvendor ").append(dbVendor);
}
if (dbName != null) {
options.append(" -dbname \"").append(dbName).append("\"");
}
if (dbSchema != null) {
options.append(" -dbschema \"").append(dbSchema).append("\"");
}
if (codegen) {
options.append(" -codegen");
}
if (quiet) {
options.append(" -quiet");
}
if (novalidate) {
options.append(" -novalidate");
}
if (nowarn) {
options.append(" -nowarn");
}
if (noinform) {
options.append(" -noinform");
}
if (trace) {
options.append(" -trace");
}
if (use35MappingRules) {
options.append(" -35");
}
if (rmicOptions != null) {
options.append(" -rmic \"").append(rmicOptions).append("\"");
}
return options.toString();
}
/**
* Helper method invoked by execute() for each websphere jar to be built.
* Encapsulates the logic of constructing a java task for calling
* websphere.ejbdeploy and executing it.
*
* @param sourceJar java.io.File representing the source (EJB1.1) jarfile.
* @param destJar java.io.File representing the destination, websphere
* jarfile.
*/
private void buildWebsphereJar(File sourceJar, File destJar) {
try {
if (ejbdeploy) {
Java javaTask = new Java(getTask());
// Set the JvmArgs
javaTask.createJvmarg().setValue("-Xms64m");
javaTask.createJvmarg().setValue("-Xmx128m");
// Set the Environment variable
Environment.Variable var = new Environment.Variable();
var.setKey("websphere.lib.dir");
File libdir = new File(websphereHome, "lib");
var.setValue(libdir.getAbsolutePath());
javaTask.addSysproperty(var);
// Set the working directory
javaTask.setDir(websphereHome);
// Set the Java class name
javaTask.setTaskName("ejbdeploy");
javaTask.setClassname("com.ibm.etools.ejbdeploy.EJBDeploy");
javaTask.createArg().setValue(sourceJar.getPath());
javaTask.createArg().setValue(tempdir);
javaTask.createArg().setValue(destJar.getPath());
javaTask.createArg().setLine(getOptions());
if (getCombinedClasspath() != null
&& getCombinedClasspath().toString().length() > 0) {
javaTask.createArg().setValue("-cp");
javaTask.createArg().setValue(getCombinedClasspath().toString());
}
Path classpath = wasClasspath;
if (classpath == null) {
classpath = getCombinedClasspath();
}
javaTask.setFork(true);
if (classpath != null) {
javaTask.setClasspath(classpath);
}
log("Calling websphere.ejbdeploy for " + sourceJar.toString(),
Project.MSG_VERBOSE);
javaTask.execute();
}
} catch (Exception e) {
// Have to catch this because of the semantics of calling main()
String msg = "Exception while calling ejbdeploy. Details: " + e.toString();
throw new BuildException(msg, e);
}
}
/** {@inheritDoc}. */
protected void writeJar(String baseName, File jarFile, Hashtable files, String publicId)
throws BuildException {
if (ejbdeploy) {
// create the -generic.jar, if required
File genericJarFile = super.getVendorOutputJarFile(baseName);
super.writeJar(baseName, genericJarFile, files, publicId);
// create the output .jar, if required
if (alwaysRebuild || isRebuildRequired(genericJarFile, jarFile)) {
buildWebsphereJar(genericJarFile, jarFile);
}
if (!keepGeneric) {
log("deleting generic jar " + genericJarFile.toString(),
Project.MSG_VERBOSE);
genericJarFile.delete();
}
} else {
// create the "undeployed" output .jar, if required
super.writeJar(baseName, jarFile, files, publicId);
}
}
/**
* Called to validate that the tool parameters have been configured.
* @throws BuildException if there is an error.
*/
public void validateConfigured() throws BuildException {
super.validateConfigured();
if (ejbdeploy) {
String home = getTask().getProject().getProperty("websphere.home");
if (home == null) {
throw new BuildException("The 'websphere.home' property must "
+ "be set when 'ejbdeploy=true'");
}
websphereHome = getTask().getProject().resolveFile(home);
}
}
/**
* Helper method to check to see if a websphere EBJ1.1 jar needs to be
* rebuilt using ejbdeploy. Called from writeJar it sees if the "Bean"
* classes are the only thing that needs to be updated and either updates
* the Jar with the Bean classfile or returns true, saying that the whole
* websphere jar needs to be regened with ejbdeploy. This allows faster
* build times for working developers. * * The way websphere ejbdeploy works is it creates wrappers for the * publicly defined methods as they are exposed in the remote interface. * If the actual bean changes without changing the the method signatures * then only the bean classfile needs to be updated and the rest of the * websphere jar file can remain the same. If the Interfaces, ie. the * method signatures change or if the xml deployment descriptors changed, * the whole jar needs to be rebuilt with ejbdeploy. This is not strictly * true for the xml files. If the JNDI name changes then the jar doesnt * have to be rebuild, but if the resources references change then it * does. At this point the websphere jar gets rebuilt if the xml files * change at all. * * @param genericJarFile java.io.File The generic jar file. * @param websphereJarFile java.io.File The websphere jar file to check to * see if it needs to be rebuilt. * @return true if a rebuild is required. */ // CheckStyle:MethodLength OFF - this will no be fixed protected boolean isRebuildRequired(File genericJarFile, File websphereJarFile) { boolean rebuild = false; JarFile genericJar = null; JarFile wasJar = null; File newwasJarFile = null; JarOutputStream newJarStream = null; ClassLoader genericLoader = null; try { log("Checking if websphere Jar needs to be rebuilt for jar " + websphereJarFile.getName(), Project.MSG_VERBOSE); // Only go forward if the generic and the websphere file both exist if (genericJarFile.exists() && genericJarFile.isFile() && websphereJarFile.exists() && websphereJarFile.isFile()) { //open jar files genericJar = new JarFile(genericJarFile); wasJar = new JarFile(websphereJarFile); Hashtable genericEntries = new Hashtable(); Hashtable wasEntries = new Hashtable(); Hashtable replaceEntries = new Hashtable(); //get the list of generic jar entries for (Enumeration e = genericJar.entries(); e.hasMoreElements();) { JarEntry je = (JarEntry) e.nextElement(); genericEntries.put(je.getName().replace('\\', '/'), je); } //get the list of websphere jar entries for (Enumeration e = wasJar.entries(); e.hasMoreElements();) { JarEntry je = (JarEntry) e.nextElement(); wasEntries.put(je.getName(), je); } //Cycle Through generic and make sure its in websphere genericLoader = getClassLoaderFromJar(genericJarFile); for (Enumeration e = genericEntries.keys(); e.hasMoreElements();) { String filepath = (String) e.nextElement(); if (wasEntries.containsKey(filepath)) { // File name/path match // Check files see if same JarEntry genericEntry = (JarEntry) genericEntries.get(filepath); JarEntry wasEntry = (JarEntry) wasEntries.get(filepath); if ((genericEntry.getCrc() != wasEntry.getCrc()) || (genericEntry.getSize() != wasEntry.getSize())) { if (genericEntry.getName().endsWith(".class")) { //File are different see if its an object or an interface String classname = genericEntry.getName().replace(File.separatorChar, '.'); classname = classname.substring(0, classname.lastIndexOf(".class")); Class genclass = genericLoader.loadClass(classname); if (genclass.isInterface()) { //Interface changed rebuild jar. log("Interface " + genclass.getName() + " has changed", Project.MSG_VERBOSE); rebuild = true; break; } else { //Object class Changed update it. replaceEntries.put(filepath, genericEntry); } } else { // is it the manifest. If so ignore it if (!genericEntry.getName().equals("META-INF/MANIFEST.MF")) { //File other then class changed rebuild log("Non class file " + genericEntry.getName() + " has changed", Project.MSG_VERBOSE); rebuild = true; } break; } } } else { // a file doesn't exist rebuild log("File " + filepath + " not present in websphere jar", Project.MSG_VERBOSE); rebuild = true; break; } } if (!rebuild) { log("No rebuild needed - updating jar", Project.MSG_VERBOSE); newwasJarFile = new File(websphereJarFile.getAbsolutePath() + ".temp"); if (newwasJarFile.exists()) { newwasJarFile.delete(); } newJarStream = new JarOutputStream(new FileOutputStream(newwasJarFile)); newJarStream.setLevel(0); //Copy files from old websphere jar for (Enumeration e = wasEntries.elements(); e.hasMoreElements();) { byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; int bytesRead; InputStream is; JarEntry je = (JarEntry) e.nextElement(); if (je.getCompressedSize() == -1 || je.getCompressedSize() == je.getSize()) { newJarStream.setLevel(0); } else { newJarStream.setLevel(JAR_COMPRESS_LEVEL); } // Update with changed Bean class if (replaceEntries.containsKey(je.getName())) { log("Updating Bean class from generic Jar " + je.getName(), Project.MSG_VERBOSE); // Use the entry from the generic jar je = (JarEntry) replaceEntries.get(je.getName()); is = genericJar.getInputStream(je); } else { //use fle from original websphere jar is = wasJar.getInputStream(je); } newJarStream.putNextEntry(new JarEntry(je.getName())); while ((bytesRead = is.read(buffer)) != -1) { newJarStream.write(buffer, 0, bytesRead); } is.close(); } } else { log("websphere Jar rebuild needed due to changed " + "interface or XML", Project.MSG_VERBOSE); } } else { rebuild = true; } } catch (ClassNotFoundException cnfe) { String cnfmsg = "ClassNotFoundException while processing ejb-jar file" + ". Details: " + cnfe.getMessage(); throw new BuildException(cnfmsg, cnfe); } catch (IOException ioe) { String msg = "IOException while processing ejb-jar file " + ". Details: " + ioe.getMessage(); throw new BuildException(msg, ioe); } finally { // need to close files and perhaps rename output if (genericJar != null) { try { genericJar.close(); } catch (IOException closeException) { // Ignore } } if (wasJar != null) { try { wasJar.close(); } catch (IOException closeException) { // Ignore } } if (newJarStream != null) { try { newJarStream.close(); } catch (IOException closeException) { // Ignore } try { FILE_UTILS.rename(newwasJarFile, websphereJarFile); } catch (IOException renameException) { log(renameException.getMessage(), Project.MSG_WARN); rebuild = true; } } if (genericLoader != null && genericLoader instanceof AntClassLoader) { AntClassLoader loader = (AntClassLoader) genericLoader; loader.cleanup(); } } return rebuild; } /** * Helper method invoked by isRebuildRequired to get a ClassLoader for a * Jar File passed to it. * * @param classjar java.io.File representing jar file to get classes from. * @return a classloader for the jar file. * @throws IOException if there is an error. */ protected ClassLoader getClassLoaderFromJar(File classjar) throws IOException { Path lookupPath = new Path(getTask().getProject()); lookupPath.setLocation(classjar); Path classpath = getCombinedClasspath(); if (classpath != null) { lookupPath.append(classpath); } return getTask().getProject().createClassLoader(lookupPath); } }