/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 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", "Tomcat", 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.weblogic51;

import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.util.jar.*;

import org.apache.tools.ant.*;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.taskdefs.Jar;

import com.sun.xml.tree.XmlDocument;
import com.sun.xml.tree.ElementNode;

import weblogic.ejb.deployment.dd.DescriptorLoader;
import weblogic.ejb.deployment.dd.DeploymentUnit;
import weblogic.ejb.deployment.dd.DDProcessingException ;
import weblogic.ejb.deployment.dd.BeanDescriptor;
import weblogic.xml.dom.DOMParserException ;

/**
 * This task build EJB jar files for the WebLogic 5.1 AppServer.
 *
 * It requires that the XML files (ejb-jar.xml, weblogic-ejb.jar and xml files
 * for the CMP Entity beans) are stored in a separate directory. It uses 
 * classes of the WebLogic 5.1 distribution, so you need these in you classpath.
 * 
 * The following arguments are available for this task
 * <TABLE BORDER=1>
 * <TR><TD>srcDir</TD>
 *     <TD>The base directory under which the EJBs can be found. Typically the top
 *		   of your sources tree.</TD>
 *     <TD>Mandatory</TD><TR>
 * <TR><TD>ejbLibDir</TD>
 *     <TD>The directory where the ejb jar files will be stored.</TD>
 *     <TD>Mandatory</TD><TR>
 * <TR><TD>classesDir</TD>
 *     <TD>The directory under which all the class files can be found.</TD>
 *     <TD>Mandatory</TD><TR>
 * </TABLE>
 *
 * Some ideas and parts of source are copied from the 
 * org.apache.tools.ant.taskdefs.optional.ejb package that in intended for 
 * pervious releases of weblogic.
 * 
 * @author Gero Vermaas <a href="mailto:Gero.Vermaas@sun.com">Gero.Vermaas@sun.com</a>
 */
public class EJBC_WL51 extends MatchingTask {

  /**
   * Constant to define the name of the META-INF dir
   */
  private final static String META_INF 	= "META-INF";
  /**
   * Constant to define the name of the temporary build directory
   */
  private final static String BUILD 	= "build";
  /**
   * Constant to define the name of the weblogic ejb jar filename. 
   */
  private final static String WEBLOGIC_EJB_JAR = "weblogic-ejb-jar.xml";
  /**
   * Constant for the ejb-jar filename. This is used to
   * determine if there are EJBs stored in a specific directory
   */
  private final static String EJB_JAR = "ejb-jar.xml";

  /**
   * Contains the source directory setting
   */
  private String srcDir;
  /**
   * Contains directory setting for the directory in which the EJB jar files must be stored.
   */
  private String ejbLibDir;
  /**
   * Contains directory setting for the directory in which the class files must be stored.
   */
  private String classesDir;
  /**
   * Contains wildcards of files that must be included in the EJB jar file
   */
  private String specialIncludes;

  /**
   * Initiates the whole build proces
   */
  public void execute() throws BuildException {
    try {
      DirectoryScanner ds = new DirectoryScanner();
      ds.setBasedir(new File(srcDir));
      String[] includes = {"**\\" + EJB_JAR};
      ds.setIncludes(includes);
      String[] excludes = {"**\\" + BUILD + " \\" + EJB_JAR}; // To exclude previous build.
      ds.setExcludes(excludes);
      ds.scan();
      String[] files = ds.getIncludedFiles();
      for (int i = 0; i < files.length; i++) {
        buildEjbs(files[i]);
      }	  
    } catch (Exception e) {
        throw new BuildException("Failed to build EJB", e);	  
    }
    
  }

  /**
   * Utility method to delete a directory
   */
  void deldir(File dir) throws IOException 
  {
	if (!dir.exists()) {
	  return;
	}
	String as[] = dir.list();
	for(int i = 0; i < as.length; i++) {
            File file1 = new File(dir.getAbsolutePath() + File.separator + as[i]);
            if (file1.isDirectory()) {
              deldir(file1);
            } else {
              file1.delete();
            }
	}

	dir.delete();
  }
  /**
   * Build the the EJB jar file for the directory in which the ejb-jar.xml file given as 
   * input parameter is stored.
   */
  void buildEjbs(String strXmlJarFile) 
  			throws IOException, DOMParserException, DDProcessingException, ClassNotFoundException {

    int iPos = strXmlJarFile.lastIndexOf(File.separator);
    String relativePath = "";
    if (iPos != -1) {
        relativePath = strXmlJarFile.substring(0, iPos);
    }
    
    // Determine the source dir directory
    File baseDir = new File(srcDir + File.separator + strXmlJarFile).getParentFile();
    String strBaseDir = baseDir.getCanonicalPath();

    // Determine the file name of the final jar
    String strShortFileName = "ejb_" + generateJarName(strXmlJarFile);
    String strLibJarFileName = ejbLibDir + File.separator + strShortFileName;
    strLibJarFileName = strLibJarFileName.replace('/', File.separatorChar);
    strLibJarFileName = strLibJarFileName.replace('\\', File.separatorChar);

    // Check if any of the source files or desployment descriptors is newer than
    // the JAr file to create
    File fileFinalJar = new File(strLibJarFileName);
    if (fileFinalJar.exists()) {
        long lJarFileTS = fileFinalJar.lastModified();
        DirectoryScanner ds = new DirectoryScanner();
        ds.setBasedir(baseDir);
//      String[] includes = {"**\\*.xml", "**\\*.java"};
//	ds.setIncludes(includes);
        ds.scan();
        String[] files = ds.getIncludedFiles();
        boolean mustDoIt = false;
        for (int i = 0; !mustDoIt && i < files.length; i++) {
          File compFile = new File(strBaseDir + File.separator + files[i]);
          mustDoIt = compFile.lastModified() >= lJarFileTS; 		  
        }
        if (!mustDoIt) {
            return;
        }
    }


    log("Building " + strShortFileName, Project.MSG_INFO);
    // Create META_INF dir and move xml files to it
    File metaInfDir = new File(	classesDir + File.separator + relativePath  + 
                                File.separator + BUILD  + File.separator + META_INF);
    File buildDir = metaInfDir.getParentFile();	
    deldir(buildDir);
    metaInfDir.mkdirs();
    String strMetaInfDir = metaInfDir.getCanonicalPath();
    String strBuildDir = buildDir.getCanonicalPath();

    String strJarFileName = strBuildDir + File.separator + "std_" + generateJarName(strXmlJarFile);

    DirectoryScanner ds = new DirectoryScanner();
    ds.setBasedir(baseDir);
    String[] includes = {"**\\*.xml"};
    ds.setIncludes(includes);
    ds.scan();
    String[] files = ds.getIncludedFiles();
    for (int i = 0; i < files.length; i++) {
      getProject().copyFile(strBaseDir + File.separator + files[i], strMetaInfDir + File.separator + files[i], false);		
    }

    // Extract the required classes from the ejb-jar.xml file
    Vector vecClasses = getEjbClassesFromXML(strXmlJarFile);

    // Get compiled classes into build directory
    for (int i = 0; i < vecClasses.size(); i++) {
        String strClass = (String)vecClasses.elementAt(i);
        String fromClass = classesDir + File.separator + strClass;
        String toClass = buildDir.getCanonicalPath() + File.separator + strClass;		 
        fromClass = fromClass.replace('.', File.separatorChar) + ".class";
        toClass = toClass.replace('.', File.separatorChar) + ".class";
        getProject().copyFile(fromClass, toClass, false, true);		
    }
    String strClassesDir = strBaseDir;
    File classDir = new File(classesDir + File.separator + relativePath);
    ds = new DirectoryScanner();
    ds.setBasedir(classDir);
//	includes[0] = "**\\*.class";
//	ds.setIncludes(includes);
    ds.scan();
    files = ds.getIncludedFiles();
    for (int i = 0; i < files.length; i++) {
        getProject().copyFile(classesDir + File.separator + relativePath + File.separator + files[i],
                              buildDir.getCanonicalPath() + File.separator + relativePath + File.separator + files[i], false, true);		
    }

    // Copy other files (specialIncludes) in the src directory to the  build directory and to the class directory
    setIncludes(specialIncludes);
    ds = getDirectoryScanner(baseDir);
    ds.scan();
    files = ds.getIncludedFiles();
    for (int i = 0; i < files.length; i++) {
        getProject().copyFile(strBaseDir + File.separator + files[i], buildDir.getCanonicalPath() + File.separator + relativePath + File.separator + files[i], false);		
        getProject().copyFile(strBaseDir + File.separator + files[i], buildDir.getParentFile().getCanonicalPath() + File.separator + files[i], false);		
    }
	
    // Jar it
    File jarFile = new File(strJarFileName);
    writeJar(jarFile, buildDir);

    // EJBC it
    String[] args = {"-noexit",
                     "-compiler", 
                     "javac", 
                     jarFile.getCanonicalPath(), 
                     strLibJarFileName};
    try {
      weblogic.ejbc.main(args);
    } catch (Exception e2) {
      System.out.println("weblogic.ejbc: " + e2.toString());
	  //e2.printStackTrace();
    }
    deldir(buildDir);
	
  }
  
  /**
   * Determines which class files must be included in the JAR. It does this by
   * finding out what the home, remote interfaces are and which class implements
   * the bean.
   */
  Vector getEjbClassesFromXML(String strXmlJarFile) throws IOException, DOMParserException, DDProcessingException, ClassNotFoundException
  {
  	Vector vecClasses = new Vector();
	
  	String strFullEjb = srcDir + File.separator + strXmlJarFile;
	int iPos = strFullEjb.lastIndexOf(File.separatorChar);
	String strFullWlXml = strFullEjb.substring(0, iPos+1);
	strFullWlXml += WEBLOGIC_EJB_JAR;
	strFullEjb = strFullEjb.replace('/', File.separatorChar);
	strFullWlXml = strFullWlXml.replace('/', File.separatorChar);

	DescriptorLoader descriptorloader = new DescriptorLoader(new File(strFullEjb), new File(strFullWlXml));	
	DeploymentUnit deploymentunit = descriptorloader.createDeploymentUnit();
	com.sun.java.util.collections.Collection colBeanDescs = deploymentunit.getAllBeanDescriptors();
	com.sun.java.util.collections.Iterator iter =  colBeanDescs.iterator();
	while (iter.hasNext()) {
            BeanDescriptor bd = (BeanDescriptor)iter.next();
            vecClasses.add(bd.getHomeInterfaceClass().getName());
            vecClasses.add(bd.getRemoteInterface().getName());
            vecClasses.add(bd.getBeanClass().getName());
	}
		
	return vecClasses;
  }

  /**
   * Generates the name of the JAR file. This name is the reverted name of package with
   * the '.' replaced by a '_'.
   */
  String generateJarName(String strXmlJarFile) {
    File fl = new File(strXmlJarFile);
    File basedir = new File(srcDir);
    String strBaseDitPaths = basedir.getAbsolutePath();
    String strJarFile = "";
    
    fl = fl.getParentFile();
    while (fl != null && !fl.getAbsoluteFile().equals(basedir)) {
      strJarFile += fl.getName();
      fl = fl.getParentFile();
      if (fl != null && !fl.getAbsoluteFile().equals(basedir)) {
        strJarFile += "_";
      }
    }
    
    strJarFile += ".jar";
    return strJarFile;
  }

  /**
   */
  public void writeJar(File jarfile, File basedir) throws IOException {  

    String basePath = basedir.getCanonicalPath();
    JarOutputStream jarStream = null;

    DirectoryScanner ds = new DirectoryScanner();
    ds.setBasedir(basedir);
//    String[] includes = new String[3];
//    includes[0] = "**/*.class";
//    includes[1] = "**/*.xml";
//    includes[2] = "**/*.mf";
//    ds.setIncludes(includes);

	ds.scan();
    String[] files = ds.getIncludedFiles();

	// Get rid of old jar file
    if (jarfile.exists()) {
        jarfile.delete();
    }
    jarfile.getParentFile().mkdirs();
    jarfile.createNewFile();

	// Create the manifest file
    String strManifest = "Manifest-Version: 1.0";		
    File manifestFile = new File(basePath + File.separator + META_INF + File.separator + "MANIFEST.MF");
    manifestFile.createNewFile();
    FileWriter fw = new FileWriter(manifestFile);
    fw.write(strManifest + "\n\n");
    for (int i = 0; i < files.length; i++) {			
      fw.write("Name: " + files[i].replace('\\', '/') + "\n\n");
    }        
    fw.close();        
		
    // Create the streams necessary to write the jarfile
    jarStream = new JarOutputStream(new FileOutputStream(jarfile), 
                                    new Manifest(new FileInputStream(manifestFile)));
    jarStream.setMethod(JarOutputStream.DEFLATED);

	// Add the files to the jar			
    for (int i = 0; i < files.length; i++) {			
      String strFileName = basePath + File.separator + files[i];
      FileInputStream fis = new FileInputStream(strFileName.replace(File.separatorChar, '/'));
      addFileToJar(jarStream, fis, files[i]);
      fis.close();
    }
	
    // All done.  Close the jar stream.
    jarStream.close();
  } // end of writeJar
    
  /**
   * Utility method that encapsulates the logic of adding a file entry to
   * a .jar file.  Used by execute() to add entries to the jar file as it is
   * constructed.
   * @param jStream A JarOutputStream into which to write the
   *        jar entry.
   * @param iStream A FileInputStream from which to read the
   *        contents the file being added.
   * @param filename A String representing the name, including
   *        all relevant path information, that should be stored for the entry
   *        being added.
   */
  protected void addFileToJar(JarOutputStream jStream,
                              InputStream iStream,
                              String          filename)
	  throws BuildException {
      try {
          filename = filename.replace('\\', '/');
          // Create the zip entry and add it to the jar file
          ZipEntry zipEntry = new ZipEntry(filename);
          jStream.putNextEntry(zipEntry);

          // Create the file input stream, and buffer everything over
          // to the jar output stream
          byte[] byteBuffer = new byte[2 * 1024];
          int count = 0;
          do {
              jStream.write(byteBuffer, 0, count);
              count = iStream.read(byteBuffer, 0, byteBuffer.length);
          } while (count != -1);

          // Close up the file input stream for the class file
          iStream.close();
      }
      catch (IOException ioe) {
          String msg = "IOException while adding entry "
                       + filename + "to jarfile."
                       + ioe.getMessage();
          throw new BuildException(msg, ioe);
      }
  }

  /**
   * Setter for the source directory
   */
  public void setSrcdir(String src) {
    srcDir = src;
  }
  
  /**
   * Setter for the ejb lib directory
   */
  public void setEjblibdir(String dir) {
    ejbLibDir = dir;
  }
  
  /**
   * Setter for the classes directory
   */
  public void setClassesdir(String dir) {
  	classesDir = dir;
  }
  
  /**
   * Setter for the special includes
   */
  public void setSpecialincludes(String str) {
  	specialIncludes = str;
  }
  
}  

