/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Ant", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.tools.ant.taskdefs.optional.ejb;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.taskdefs.Java;

import java.io.File;
import java.util.Hashtable;


/**
 * The deployment tool to add the jonas specific deployment descriptor to 
 * the ejb jar file. JONAS only requires one additional file jonas-ejb-jar.xml.
 *
 * @author <a href="cmorvan@ingenosya.com">Cyrille Morvan</a>,
 *       <a href="http://www.ingenosya.com">Ingenosya France</a>
 * @version 1.0
 * @see EjbJar#createJonas
 */
public class JonasDeploymentTool extends GenericDeploymentTool {

    protected static final String JONAS_DD = "jonas-ejb-jar.xml";

    protected static final String GENIC_CLASS =
               "org.objectweb.jonas_ejb.tools.GenWholeIC";

    protected static final String OLD_GENIC_CLASS =
               "org.objectweb.jonas_ejb.tools.GenIC";

    protected static final String DEFAULT_ORB = "RMI";

    /** Instance variable that stores the suffix for the jonas jarfile. */
    private String jarSuffix = ".jar";

    /**
     * Instance variable that stores the fully qualified classname
     * of the JOnAS GenIC compiler.
     **/
    private String genicClass;

    private String additionalArgs = "";

    /** Instance variable that determines do not delete intermediate generated source files */
    private boolean keepgenerated = false;

    /** Instance variable that determines the JOnAS Root directory */
    private String jonasroot;

    /** Instance variable that determines the ouput directory */
    private String ouputdirectory;

    /** Instance variable that determines the path to the compiler to use */
    private String compiler;

    /** Instance variable that determines if GenIC is verbose */
    private boolean verbose;

    /** Instance variable that determines the ORB to use (RMI, JEREMIE, DAVID)*/
    private String orb;


    private boolean noGENIC = false;

    /**
     * set the name of the GenIC compiler class.
     **/
    public void setGenicClass(final String inGenicClass) {
       genicClass = inGenicClass;
    }


    /**
     * Set the ORB to construct classpath.
     * @param inValue RMI, JEREMIE, DAVID.
     **/
    public void setOrb(final String inValue) {
       orb = inValue;
    }

    /**
     * The compiler (switch <code>-javac</code>) to use.
     **/
    public void setCompiler(final String inCompiler) {
        compiler = inCompiler;
    }

    /**
     * GenIC verbose or not
     * @param inValue either 'true' or 'false'
     **/
    public void setVerbose(final String inValue) {
        verbose = Boolean.valueOf(inValue).booleanValue();
    }

    /**
     * GenIC run or not.
     * @param inValue either 'true' or 'false'
     **/
    public void setNoGENIC(final String inValue) {
        noGENIC = Boolean.valueOf(inValue).booleanValue();
    }

    /**
     * Sets whether -keepgenerated is passed to GenIC (that is,
     * the .java source files are kept).
     * @param inValue either 'true' or 'false'
     **/
    public void setKeepgenerated(final String inValue) {
        keepgenerated = Boolean.valueOf(inValue).booleanValue();
    }

    /**
     * set the jonas root directory (-Dinstall.root=).
     **/
    public void setJonasroot(final String inValue) {
        jonasroot = inValue;
    }

    /**
     * set the output directory (-d ...).
     **/
    public void setOuputdir(final String inValue) {
        ouputdirectory = inValue;
    }

    /**
     * set the output directory (-d ...).
     * Same as setOuputdir().
     **/
    public void setWorkdir(final String inValue) {
        setOuputdir(inValue);
    }

    /**
     * Setter used to store the suffix for the generated JOnAS jar file.
     * @param inString the string to use as the suffix.
     **/
    public void setSuffix(String inString) {
        this.jarSuffix = inString;
    }

    /**
     * sets some additional args to send to GenIC.
     **/
    public void setArgs(final String inArgs) {
        additionalArgs = inArgs;
    }

    /**
     * Add any vendor specific files which should be included in the
     * EJB Jar.
     **/
    protected void addVendorFiles(final Hashtable someEjbFiles,final String aDdPrefix) {
        File aJonasDD = new File(getConfig().descriptorDir,aDdPrefix + "jar.xml");
        if ( aJonasDD.exists() ) {
            someEjbFiles.put(META_DIR + JONAS_DD, aJonasDD);
        } else {
            log("Unable to locate JOnAS deployment descriptor. It was expected to be in "
               + aJonasDD.getPath(), Project.MSG_WARN);
            return;
        }
    }

    // include javadoc
    protected String getJarBaseName(String aDescriptorFileName) {
      String aBaseName = null;
      EjbJar.Config aConfig = super.getConfig();
      if (aConfig.namingScheme.getValue().equals(EjbJar.NamingScheme.DESCRIPTOR)) {
         // try to find JOnAS specific convention name
         // ??/MyEJB.xml   and  ??/jonas-MyEJB-jar.xml
         if( aDescriptorFileName.indexOf(aConfig.baseNameTerminator) == -1 ) {
            int lastSeparatorIndex = aDescriptorFileName.lastIndexOf(File.separator);
            int endBaseName;
            if (lastSeparatorIndex != -1) {
                endBaseName = aDescriptorFileName.indexOf(".xml", lastSeparatorIndex);
            } else {
                endBaseName = aDescriptorFileName.indexOf(".xml");
            }

            if (endBaseName != -1) {
                aBaseName = aDescriptorFileName.substring(0, endBaseName);
            }
         }
      }
      if( aBaseName == null ) {
         // else get standard Ant BaseName
         aBaseName = super.getJarBaseName(aDescriptorFileName);
      }
      return aBaseName;
    }

    // include javadoc
    public String getVendorDDPrefix(final String inBaseName,final String aDescriptorFileName) {
      String aBaseName = inBaseName;
      String aDdDirectory;
      String anEjbName = null;
      EjbJar.Config aConfig = super.getConfig();

      if (aConfig.namingScheme.getValue().equals(EjbJar.NamingScheme.DIRECTORY)) {
         aBaseName = inBaseName + '/';
      }
      
      // find directory name
      String aCanonicalBaseName = aBaseName.replace('\\', '/');
      int index = aCanonicalBaseName.lastIndexOf('/');
      aDdDirectory = aBaseName.substring(0, index + 1); // could be ""

      // find EJB name      
      if( ! aConfig.namingScheme.getValue().equals(EjbJar.NamingScheme.BASEJARNAME) ) {
         anEjbName = aBaseName.substring(index + 1); // could be ""
      }
      if( anEjbName == null || anEjbName.length() == 0 ) {
         anEjbName = "ejb";
      }

      // MyDirectory/jonas-MyEjb-
      return aDdDirectory + "jonas-" + anEjbName + "-";
    }

    /**
     * Method used to encapsulate the writing of the JAR file. Iterates over the
     * filenames/java.io.Files in the Hashtable stored on the instance variable
     * ejbFiles.
     **/
    protected void writeJar(String baseName, File jarFile, Hashtable files,
                            String publicId) throws BuildException {
      super.writeJar(baseName, jarFile, files, publicId);
      if( ! noGENIC ) {
         buildJOnASJar(baseName,jarFile, files,publicId);
      }
    }

    /**
     * Helper method invoked by execute() for each JOnAS jar to be built.
     * Encapsulates the logic of constructing a java task for calling
     * GenIC and executing it.
     * @param inBaseName the base name of the jar
     * @param inSourceJar java.io.File representing the source (EJB1.1) jarfile.
     * @param someFiles list of files in the jar. Add all the new genererated
     *   files.
     * @param inPublicId the link to DTD (to rewrite JAR).
     **/
    private void buildJOnASJar(final String inBaseName,final File inSourceJar,
                               final Hashtable someFiles,String inPublicId) {
        org.apache.tools.ant.taskdefs.Java aJavaTask = null;
        String aGenIcClassName = genicClass;
        boolean isOldGenIC = false;
        boolean isTempDirectory = false;
        File anOutputDirectoryFile = null;

        try {
            aJavaTask = (Java) getTask().getProject().createTask("java");
            aJavaTask.setTaskName("genic");
            if(aGenIcClassName == null) {
               aGenIcClassName = GENIC_CLASS;
            } else if( OLD_GENIC_CLASS.equals(aGenIcClassName) ){
               isOldGenIC = true;
            }
            // ClassName
            aJavaTask.setClassname(aGenIcClassName);

            // JVM Args
            if( jonasroot == null ) {
               throw new BuildException("Error : set the jonasroot parameter");
            }
            aJavaTask.createJvmarg().setValue("-Dinstall.root=" + jonasroot);
            File aJavaPolicyFile = new File(jonasroot,"config/java.policy");
            if( aJavaPolicyFile.exists() ) {
               aJavaTask.createJvmarg().setValue("-Djava.security.policy=" 
                                           + aJavaPolicyFile.toString() );
            }

            // Find output directory
            if( ouputdirectory == null ) {
               anOutputDirectoryFile = createTempDir();
               isTempDirectory = true;
            } else {
               anOutputDirectoryFile = new File(ouputdirectory);
            }
            aJavaTask.createArg().setValue("-d");
            aJavaTask.createArg().setFile(anOutputDirectoryFile);

            // Additionnal args
            aJavaTask.createArg().setLine(additionalArgs);
            // KeepGenerated
            if (keepgenerated) {
                aJavaTask.createArg().setValue("-keepgenerated");
            }

            // Verbose
            if( verbose ) {
               aJavaTask.createArg().setValue("-verbose");
            }

            // The compiler
            if (compiler == null) {
                // try to use the compiler specified by build.compiler. Right now we are just going
                // to allow Jikes
                String aBuildCompiler = getTask().getProject().getProperty("build.compiler");
                if ("jikes".equals(aBuildCompiler) ) {
                    aJavaTask.createArg().setValue("-javac");
                    aJavaTask.createArg().setValue("jikes");
                }
            } else {
               if( ! "default".equals( compiler ) ) {
                  aJavaTask.createArg().setValue("-javac");
                  aJavaTask.createArg().setLine(compiler);
               }
            }

            if( ! isOldGenIC ) {
               // the add in jar features is buggy...
               aJavaTask.createArg().setValue("-noaddinjar");
            }

            aJavaTask.createArg().setValue(inSourceJar.getPath());

            // try to create the classpath for the correct ORB
            Path aClasspath = getCombinedClasspath();
            if( aClasspath == null ) {
               aClasspath = new Path(getTask().getProject());
            }
            if( orb != null ) {
               String aOrbJar = new File(jonasroot,"lib/" + orb + "_jonas.jar").toString();
               String aConfigDir = new File(jonasroot,"config/").toString();
               Path aJOnASOrbPath = new Path(aClasspath.getProject(),
                                          aOrbJar + File.pathSeparator + aConfigDir );
               aClasspath.append( aJOnASOrbPath );
            } else {
               log("No ORB propertie setup (RMI, JEREMIE, DAVID).", Project.MSG_WARN);
            }

            // append the output directory
            aClasspath.append( new Path(aClasspath.getProject(), anOutputDirectoryFile.getPath()));
            aJavaTask.setClasspath(aClasspath);

            aJavaTask.setFork(true);

            log("Calling " + aGenIcClassName + " for " + inSourceJar.toString(),
                          Project.MSG_VERBOSE);

            if (aJavaTask.executeJava() != 0) {
                throw new BuildException("GenIC reported an error");
            }
            // Update the list of files.
            addAllFiles(anOutputDirectoryFile,"",someFiles);

            // rewrite the jar with the new files
            super.writeJar(inBaseName, inSourceJar, someFiles, inPublicId);
        } catch(BuildException aBuildException) {
            throw aBuildException;
        } catch (Exception e) {
            // Have to catch this because of the semantics of calling main()
            String msg = "Exception while calling " + aGenIcClassName + ". Details: " + e.toString();
            throw new BuildException(msg, e);
        } finally {
           if( isTempDirectory && anOutputDirectoryFile != null) {
              dellAllFiles(anOutputDirectoryFile);
           }
        }
    }

    /**
     * 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(final String aBaseName) {
        return new File(getDestDir(), aBaseName + jarSuffix);
    }

   /**
    * Create a free tempory directory for GenIC output.
    * @return directory file
    * @throws BuildException if impossible to find a tempory directory
    **/
   private File createTempDir() {
      String theTempDir = System.getProperty("java.io.tmpdir");
      int anIndice = 0;
      File aFile = null;
      // try 50 times to find a free tempory directory
      while( anIndice < 50 && aFile == null) {
         aFile = new File(theTempDir,"GenicTemp" + anIndice);
         if(aFile.exists()) {
            anIndice++;
            aFile = null;
         }
      }

      if( aFile == null ) {
         // problem in temp directory
         throw new BuildException("Impossible to find a free temp directory for output.");
      } else {
         aFile.mkdirs();
         log("Use temporary output directory : " + aFile, Project.MSG_VERBOSE);
         return(aFile);
      }
   }


   /**
    * add all files in anOutputDir + ' / ' + aRootDir to the HashTable someFiles.
    *
    * @param anOutputDir - start directory
    * @param aCurrentDirOrFile - a sub-directory to scan or a file to add.
    * @param someFiles - where to add the files
    **/
   private void addAllFiles(final File anOutputDir, String aCurrentDirOrFile, Hashtable someFiles) {
      File aFile = new File(anOutputDir,aCurrentDirOrFile);
      if( aFile.isDirectory() ) {
         String aCurrentDir = "";
         if( aCurrentDirOrFile.length() > 0 ) {
            aCurrentDir = aCurrentDirOrFile + '/';
         }
         File theFiles[] = aFile.listFiles();
         for(int i=0;i<theFiles.length;i++) {
            addAllFiles(anOutputDir,aCurrentDir + theFiles[i].getName(),someFiles);
         }
      } else {
         // is a file
         someFiles.put(aCurrentDirOrFile,aFile);
      }
   }

   /**
    * Delete all the files in a directory
    * @param aFile file to delete recursivly
    **/
   private void dellAllFiles(File aFile) {
     if(aFile.isDirectory()) {
       File someFiles[] = aFile.listFiles();
       for(int i=0;i<someFiles.length;i++) {
          dellAllFiles(someFiles[i]);
       }
     }
     aFile.delete();
   }

}
// eof