content: /*
   *  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
   *
   *      https://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.util.Hashtable;
  
  import javax.xml.parsers.SAXParser;
  
  import org.apache.tools.ant.BuildException;
  import org.apache.tools.ant.Project;
  import org.apache.tools.ant.taskdefs.optional.ejb.EjbJar.DTDLocation;
  import org.xml.sax.SAXException;
  
  /**
   * This class is used to generate iPlanet Application Server (iAS) 6.0 stubs and
   * skeletons and build an EJB Jar file.  It is designed to be used with the Ant
   * <code>ejbjar</code> task.  If only stubs and skeletons need to be generated
   * (in other words, if no JAR file needs to be created), refer to the
   * <code>iplanet-ejbc</code> task and the <code>IPlanetEjbcTask</code> class.
   * <p>The following attributes may be specified by the user:</p>
   * <ul>
   *     <li><i>destdir</i> -- The base directory into which the generated JAR
   *                           files will be written.  Each JAR file is written
   *                           in directories which correspond to their location
   *                           within the "descriptordir" namespace.  This is a
   *                           required attribute.
   *     <li><i>classpath</i> -- The classpath used when generating EJB stubs and
   *                             skeletons.  This is an optional attribute (if
   *                             omitted, the classpath specified in the "ejbjar"
   *                             parent task will be used).  If specified, the
   *                             classpath elements will be prepended to the
   *                             classpath specified in the parent "ejbjar" task.
   *                             Note that nested "classpath" elements may also be
   *                             used.
   *     <li><i>keepgenerated</i> -- Indicates whether or not the Java source
   *                                 files which are generated by ejbc will be
   *                                 saved or automatically deleted.  If "yes",
   *                                 the source files will be retained.  This is
   *                                 an optional attribute (if omitted, it
   *                                 defaults to "no").
   *     <li><i>debug</i> -- Indicates whether or not the ejbc utility should
   *                         log additional debugging statements to the standard
   *                         output.  If "yes", the additional debugging statements
   *                         will be generated (if omitted, it defaults to "no").
   *     <li><i>iashome</i> -- May be used to specify the "home" directory for
   *                           this iPlanet Application server installation.  This
   *                           is used to find the ejbc utility if it isn't
   *                           included in the user's system path.  This is an
   *                           optional attribute (if specified, it should refer
   *                           to the <code>[install-location]/iplanet/ias6/ias
   *                           </code> directory).  If omitted, the ejbc utility
   *                           must be on the user's system path.
   *     <li><i>suffix</i> -- String value appended to the JAR filename when
   *                          creating each JAR.  This attribute is not required
   *                          (if omitted, it defaults to ".jar").
   * </ul>
   * <p>For each EJB descriptor found in the "ejbjar" parent task, this deployment
   * tool will locate the three classes that comprise the EJB.  If these class
   * files cannot be located in the specified <code>srcdir</code> directory, the
   * task will fail.  The task will also attempt to locate the EJB stubs and
   * skeletons in this directory.  If found, the timestamps on the stubs and
   * skeletons will be checked to ensure they are up to date.  Only if these files
   * cannot be found or if they are out of date will ejbc be called.</p>
   *
   * @see    IPlanetEjbc
   */
  public class IPlanetDeploymentTool extends GenericDeploymentTool {
  
      /* Attributes set by the Ant build file */
      private File    iashome;
      private String  jarSuffix     = ".jar";
      private boolean keepgenerated = false;
      private boolean debug         = false;
  
      /*
       * Filenames of the standard EJB descriptor (which is passed to this class
       * from the parent "ejbjar" task) and the iAS-specific EJB descriptor
       * (whose name is determined by this class).  Both filenames are relative
       * to the directory specified by the "srcdir" attribute in the ejbjar task.
       */
      private String  descriptorName;
      private String  iasDescriptorName;
  
      /*
       * The displayName variable stores the value of the "display-name" element
       * from the standard EJB descriptor.  As a future enhancement to this task,
       * we may determine the name of the EJB JAR file using this display-name,
       * but this has not be implemented yet.
       */
      @SuppressWarnings("unused")
      private String displayName;
  
      /*
       * Regardless of the name of the iAS-specific EJB descriptor file, it will
       * written in the completed JAR file as "ias-ejb-jar.xml".  This is the
       * naming convention implemented by iAS.
       */
      private static final String IAS_DD = "ias-ejb-jar.xml";
  
      /**
       * Setter method used to store the "home" directory of the user's iAS
       * installation.  The directory specified should typically be
       * <code>[install-location]/iplanet/ias6/ias</code>.
       *
       * @param iashome The home directory for the user's iAS installation.
       */
      public void setIashome(File iashome) {
          this.iashome = iashome;
      }
  
      /**
       * Setter method used to specify whether the Java source files generated by
       * the ejbc utility should be saved or automatically deleted.
       *
       * @param keepgenerated boolean which, if <code>true</code>, indicates that
       *                      Java source files generated by ejbc for the stubs
       *                      and skeletons should be kept.
       */
      public void setKeepgenerated(boolean keepgenerated) {
          this.keepgenerated = keepgenerated;
      }
  
      /**
       * Sets whether or not debugging output will be generated when ejbc is
       * executed.
       *
       * @param debug A boolean indicating if debugging output should be generated
       */
      public void setDebug(boolean debug) {
          this.debug = debug;
      }
  
      /**
       * Setter method used to specify the filename suffix (for example, ".jar")
       * for the JAR files to be created.
       *
       * @param jarSuffix The string to use as the JAR filename suffix.
       */
      public void setSuffix(String jarSuffix) {
          this.jarSuffix = jarSuffix;
      }
  
      /**
       * Since iAS doesn't generate a "generic" JAR as part of its processing,
       * this attribute is ignored and a warning message is displayed to the user.
       *
       * @param inString the string to use as the suffix.  This parameter is
       *                 ignored.
       */
      @Override
      public void setGenericJarSuffix(String inString) {
          log("Since a generic JAR file is not created during processing, the "
                  + "iPlanet Deployment Tool does not support the "
                  + "\"genericjarsuffix\" attribute.  It will be ignored.",
              Project.MSG_WARN);
      }
  
      /** {@inheritDoc}. */
      @Override
      public void processDescriptor(String descriptorName, SAXParser saxParser) {
          this.descriptorName = descriptorName;
          this.iasDescriptorName = null;
  
          log("iPlanet Deployment Tool processing: " + descriptorName + " (and "
                  + getIasDescriptorName() + ")", Project.MSG_VERBOSE);
  
          super.processDescriptor(descriptorName, saxParser);
      }
  
      /**
       * Verifies that the user selections are valid.
       *
       * @param descriptorFileName String representing the file name of an EJB
       *                           descriptor to be processed
       * @param saxParser          SAXParser which may be used to parse the XML
       *                           descriptor
       * @throws BuildException If the user selections are invalid.
       */
      @Override
      protected void checkConfiguration(String descriptorFileName,
                                      SAXParser saxParser) throws BuildException {
  
          int startOfName = descriptorFileName.lastIndexOf(File.separatorChar) + 1;
          String stdXml = descriptorFileName.substring(startOfName);
          if (stdXml.equals(EJB_DD) && (getConfig().baseJarName == null)) {
              throw new BuildException(
                  "No name specified for the completed JAR file.  The EJB"
                      + " descriptor should be prepended with the JAR "
                      + "name or it should be specified using the "
                      + "attribute \"basejarname\" in the \"ejbjar\" task.",
                  getLocation());
          }
  
          File iasDescriptor = new File(getConfig().descriptorDir,
                                          getIasDescriptorName());
          if (!iasDescriptor.exists() || !iasDescriptor.isFile()) {
              throw new BuildException("The iAS-specific EJB descriptor ("
                  + iasDescriptor + ") was not found.", getLocation());
          }
  
          if (iashome != null && !iashome.isDirectory()) {
              throw new BuildException(
                  "If \"iashome\" is specified, it must be a valid directory (it was set to "
                      + iashome + ").",
                  getLocation());
          }
      }
  
      /**
       * This method returns a list of EJB files found when the specified EJB
       * descriptor is parsed and processed.
       *
       * @param descriptorFileName String representing the file name of an EJB
       *                           descriptor to be processed
       * @param saxParser          SAXParser which may be used to parse the XML
       *                           descriptor
       * @return                   Hashtable of EJB class (and other) files to be
       *                           added to the completed JAR file
       * @throws IOException       An IOException from the parser, possibly from
       *                           the byte stream or character stream
       * @throws SAXException      Any SAX exception, possibly wrapping another
       *                           exception
       */
      @Override
      protected Hashtable<String, File> parseEjbFiles(String descriptorFileName,
                           SAXParser saxParser) throws IOException, SAXException {
  
          Hashtable<String, File> files;
  
          /* Build and populate an instance of the ejbc utility */
          IPlanetEjbc ejbc = new IPlanetEjbc(
                                      new File(getConfig().descriptorDir,
                                                  descriptorFileName),
                                      new File(getConfig().descriptorDir,
                                                  getIasDescriptorName()),
                                      getConfig().srcDir,
                                      getCombinedClasspath().toString(),
                                      saxParser);
          ejbc.setRetainSource(keepgenerated);
          ejbc.setDebugOutput(debug);
          if (iashome != null) {
              ejbc.setIasHomeDir(iashome);
          }
          if (getConfig().dtdLocations != null) {
              for (DTDLocation dtdLocation : getConfig().dtdLocations) {
                  ejbc.registerDTD(dtdLocation.getPublicId(),
                      dtdLocation.getLocation());
              }
          }
  
          /* Execute the ejbc utility -- stubs/skeletons are rebuilt, if needed */
          try {
              ejbc.execute();
          } catch (IPlanetEjbc.EjbcException e) {
              throw new BuildException("An error has occurred while trying to "
                          + "execute the iAS ejbc utility", e, getLocation());
          }
  
          displayName    = ejbc.getDisplayName();
          files          = ejbc.getEjbFiles();
  
          /* Add CMP descriptors to the list of EJB files */
          String[] cmpDescriptors = ejbc.getCmpDescriptors();
          if (cmpDescriptors.length > 0) {
              File baseDir = getConfig().descriptorDir;
  
              int endOfPath = descriptorFileName.lastIndexOf(File.separator);
              String relativePath = descriptorFileName.substring(0, endOfPath + 1);
  
              for (String descriptor : cmpDescriptors) {
                  int endOfCmp = descriptor.lastIndexOf('/');
                  String cmpDescriptor = descriptor.substring(endOfCmp + 1);
  
                  File cmpFile = new File(baseDir, relativePath + cmpDescriptor);
                  if (!cmpFile.exists()) {
                      throw new BuildException("The CMP descriptor file ("
                              + cmpFile + ") could not be found.", getLocation());
                  }
                  files.put(descriptor, cmpFile);
              }
          }
          return files;
      }
  
      /**
       * Add the iAS-specific EJB descriptor to the list of files which will be
       * written to the JAR file.
       *
       * @param ejbFiles Hashtable of EJB class (and other) files to be added to
       *                 the completed JAR file.
       * @param ddPrefix not used
       */
      @Override
      protected void addVendorFiles(Hashtable<String, File> ejbFiles, String ddPrefix) {
          ejbFiles.put(META_DIR + IAS_DD, new File(getConfig().descriptorDir,
                       getIasDescriptorName()));
      }
  
      /**
       * Get the name of the Jar that will be written. The modification date
       * of this jar will be checked against the dependent bean classes.
       *
       * @param baseName String name of the EJB JAR file to be written (without
       *                 a filename extension).
       *
       * @return File representing the JAR file which will be written.
       */
      @Override
      File getVendorOutputJarFile(String baseName) {
          File jarFile = new File(getDestDir(), baseName + jarSuffix);
          log("JAR file name: " + jarFile.toString(), Project.MSG_VERBOSE);
          return jarFile;
      }
  
      /**
       * The iAS ejbc utility doesn't require the Public ID of the descriptor's
       * DTD for it to process correctly--this method always returns <code>null
       * </code>.
       *
       * @return <code>null</code>.
       */
      @Override
      protected String getPublicId() {
          return null;
      }
  
      /**
       * Determines the name of the iAS-specific EJB descriptor using the
       * specified standard EJB descriptor name.  In general, the standard
       * descriptor will be named "[basename]-ejb-jar.xml", and this method will
       * return "[basename]-ias-ejb-jar.xml".
       *
       * @return The name of the iAS-specific EJB descriptor file.
       */
      private String getIasDescriptorName() {
  
          /* Only calculate the descriptor name once */
          if (iasDescriptorName != null) {
              return iasDescriptorName;
          }
  
          String path = "";   // Directory path of the EJB descriptor
          String basename;    // Filename appearing before name terminator
          String remainder;   // Filename appearing after the name terminator
  
          /* Find the end of the standard descriptor's relative path */
          int startOfFileName = descriptorName.lastIndexOf(File.separatorChar);
          if (startOfFileName != -1) {
              path = descriptorName.substring(0, startOfFileName + 1);
          }
  
          /* Check to see if the standard name is used (there's no basename) */
          if (descriptorName.substring(startOfFileName + 1).equals(EJB_DD)) {
              basename = "";
              remainder = EJB_DD;
  
          } else {
              int endOfBaseName = descriptorName.indexOf(
                                                  getConfig().baseNameTerminator,
                                                  startOfFileName);
              /*
               * Check for the odd case where the terminator and/or filename
               * extension aren't found.  These will ensure "ias-" appears at the
               * end of the name and before the '.' (if present).
               */
              if (endOfBaseName < 0) {
                  endOfBaseName = descriptorName.lastIndexOf('.') - 1;
                  if (endOfBaseName < 0) {
                      endOfBaseName = descriptorName.length() - 1;
                  }
              }
  
              basename = descriptorName.substring(startOfFileName + 1,
                                                  endOfBaseName + 1);
              remainder = descriptorName.substring(endOfBaseName + 1);
          }
  
          iasDescriptorName = path + basename + "ias-" + remainder;
          return iasDescriptorName;
      }
  }
  