stevel      2005/03/22 09:20:06

  Modified:    src/main/org/apache/tools/ant/taskdefs SignJar.java
  Log:
  This is an interim checkin, with new features and some refactoring. All 
existing (minimal) tests should work, let's see what gump thinks.
  This is still a work in progress...
  
  Revision  Changes    Path
  1.47      +257 -95   ant/src/main/org/apache/tools/ant/taskdefs/SignJar.java
  
  Index: SignJar.java
  ===================================================================
  RCS file: /home/cvs/ant/src/main/org/apache/tools/ant/taskdefs/SignJar.java,v
  retrieving revision 1.46
  retrieving revision 1.47
  diff -u -r1.46 -r1.47
  --- SignJar.java      18 Feb 2005 23:27:59 -0000      1.46
  +++ SignJar.java      22 Mar 2005 17:20:06 -0000      1.47
  @@ -1,24 +1,25 @@
   /*
  - * Copyright  2000-2005 The Apache Software Foundation
  - *
  - *  Licensed 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.
  - *
  - */
  +* Copyright  2000-2005 The Apache Software Foundation
  +*
  +*  Licensed 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;
   
   import java.io.File;
   import java.io.IOException;
   import java.util.Vector;
  +
   import org.apache.tools.ant.BuildException;
   import org.apache.tools.ant.DirectoryScanner;
   import org.apache.tools.ant.Project;
  @@ -26,18 +27,21 @@
   import org.apache.tools.ant.taskdefs.condition.IsSigned;
   import org.apache.tools.ant.types.FileSet;
   import org.apache.tools.ant.types.RedirectorElement;
  +import org.apache.tools.ant.types.Mapper;
   import org.apache.tools.ant.util.JavaEnvUtils;
   import org.apache.tools.ant.util.FileUtils;
  +import org.apache.tools.ant.util.IdentityMapper;
  +import org.apache.tools.ant.util.FileNameMapper;
   
   /**
  - * Signs JAR or ZIP files with the javasign command line tool. The
  - * tool detailed dependency checking: files are only signed if they
  - * are not signed. The <tt>signjar</tt> attribute can point to the file to
  - * generate; if this file exists then
  - * its modification date is used as a cue as to whether to resign any JAR 
file.
  + * Signs JAR or ZIP files with the javasign command line tool. The tool 
detailed
  + * dependency checking: files are only signed if they are not signed. The
  + * <tt>signjar</tt> attribute can point to the file to generate; if this file
  + * exists then its modification date is used as a cue as to whether to resign
  + * any JAR file.
    *
  - * @since Ant 1.1
    * @ant.task category="java"
  + * @since Ant 1.1
    */
   public class SignJar extends Task {
   
  @@ -66,10 +70,12 @@
       protected boolean verbose;
       protected boolean internalsf;
       protected boolean sectionsonly;
  -    private   boolean preserveLastModified;
  +    private boolean preserveLastModified;
       private RedirectorElement redirector;
   
  -    /** The maximum amount of memory to use for Jar signer */
  +    /**
  +     * The maximum amount of memory to use for Jar signer
  +     */
       private String maxMemory;
   
       /**
  @@ -83,12 +89,37 @@
        */
       protected boolean lazy;
   
  +    /**
  +     * the output directory when using filesets.
  +     */
  +    protected File destDir;
  +
  +    /**
  +     * mapper for todir work
  +     */
  +    private Mapper mapper;
  +
  +    public static final String ERROR_SIGNEDJAR_AND_FILESET =
  +            "The signedjar attribute is not supported with filesets";
  +    public static final String ERROR_TODIR_AND_SIGNEDJAR
  +            = "'destdir' and 'signedjar' cannot both be set";
  +    public static final String ERROR_TOO_MANY_MAPPERS = "Too many mappers";
  +    public static final String ERROR_SIGNEDJAR_AND_FILESETS = "You cannot 
specify the signed JAR when using filesets";
  +    public static final String WARN_JAR_AND_FILESET = "nested filesets will 
be ignored if the jar attribute has"
  +            + " been specified.";
  +    public static final String ERROR_BAD_MAP = "Cannot map source file to 
anything sensible: ";
  +    public static final String ERROR_MAPPER_WITHOUT_DEST = "The destDir 
attribute is required if a mapper is set";
  +    public static final String ERROR_NO_SOURCE = "jar must be set through 
jar attribute "
  +            + "or nested filesets";
  +    public static final String ERROR_NO_ALIAS = "alias attribute must be 
set";
  +    public static final String ERROR_NO_STOREPASS = "storepass attribute 
must be set";
  +    protected static final String JARSIGNER_COMMAND = "jarsigner";
   
       /**
        * Set the maximum memory to be used by the jarsigner process
        *
  -     * @param max a string indicating the maximum memory according to the
  -     *        JVM conventions (e.g. 128m is 128 Megabytes)
  +     * @param max a string indicating the maximum memory according to the JVM
  +     *            conventions (e.g. 128m is 128 Megabytes)
        */
       public void setMaxmemory(String max) {
           maxMemory = max;
  @@ -96,6 +127,7 @@
   
       /**
        * the jar file to sign; required
  +     *
        * @param jar the jar file to sign
        */
       public void setJar(final File jar) {
  @@ -104,6 +136,7 @@
   
       /**
        * the alias to sign under; required
  +     *
        * @param alias the alias to sign under
        */
       public void setAlias(final String alias) {
  @@ -112,6 +145,7 @@
   
       /**
        * keystore location; required
  +     *
        * @param keystore the keystore location
        */
       public void setKeystore(final String keystore) {
  @@ -120,6 +154,7 @@
   
       /**
        * password for keystore integrity; required
  +     *
        * @param storepass the password for the keystore
        */
       public void setStorepass(final String storepass) {
  @@ -128,6 +163,7 @@
   
       /**
        * keystore type; optional
  +     *
        * @param storetype the keystore type
        */
       public void setStoretype(final String storetype) {
  @@ -136,6 +172,7 @@
   
       /**
        * password for private key (if different); optional
  +     *
        * @param keypass the password for the key (if different)
        */
       public void setKeypass(final String keypass) {
  @@ -144,6 +181,7 @@
   
       /**
        * name of .SF/.DSA file; optional
  +     *
        * @param sigfile the name of the .SF/.DSA file
        */
       public void setSigfile(final String sigfile) {
  @@ -152,6 +190,7 @@
   
       /**
        * name of signed JAR file; optional
  +     *
        * @param signedjar the name of the signed jar file
        */
       public void setSignedjar(final File signedjar) {
  @@ -159,8 +198,8 @@
       }
   
       /**
  -     * Enable verbose output when signing
  -     * ; optional: default false
  +     * Enable verbose output when signing ; optional: default false
  +     *
        * @param verbose if true enable verbose output
        */
       public void setVerbose(final boolean verbose) {
  @@ -168,8 +207,9 @@
       }
   
       /**
  -     * Flag to include the .SF file inside the signature;
  -     * optional; default false
  +     * Flag to include the .SF file inside the signature; optional; default
  +     * false
  +     *
        * @param internalsf if true include the .SF file inside the signature
        */
       public void setInternalsf(final boolean internalsf) {
  @@ -177,8 +217,8 @@
       }
   
       /**
  -     * flag to compute hash of entire manifest;
  -     * optional, default false
  +     * flag to compute hash of entire manifest; optional, default false
  +     *
        * @param sectionsonly flag to compute hash of entire manifest
        */
       public void setSectionsonly(final boolean sectionsonly) {
  @@ -186,9 +226,9 @@
       }
   
       /**
  -     * flag to control whether the presence of a signature
  -     * file means a JAR is signed;
  -     * optional, default false
  +     * flag to control whether the presence of a signature file means a JAR 
is
  +     * signed; optional, default false
  +     *
        * @param lazy flag to control whether the presence of a signature
        */
       public void setLazy(final boolean lazy) {
  @@ -197,54 +237,150 @@
   
       /**
        * Adds a set of files to sign
  -     * @since Ant 1.4
  +     *
        * @param set a set of files to sign
  +     * @since Ant 1.4
        */
       public void addFileset(final FileSet set) {
           filesets.addElement(set);
       }
   
  +    /**
  +     * Optionally sets the output directory to be used.
  +     *
  +     * @param destDir the directory in which to place signed jars
  +     * @since Ant 1.7
  +     */
  +    public void setDestDir(File destDir) {
  +        this.destDir = destDir;
  +    }
  +
  +
  +    /**
  +     * add a mapper to determine file naming policy. Only used with toDir
  +     * processing.
  +     *
  +     * @param newMapper
  +     * @since Ant 1.7
  +     */
  +    public void addMapper(Mapper newMapper) {
  +        if (mapper != null) {
  +            throw new BuildException(ERROR_TOO_MANY_MAPPERS);
  +        }
  +        mapper = newMapper;
  +    }
  +
  +    public Mapper getMapper() {
  +        return mapper;
  +    }
   
       /**
        * sign the jar(s)
  +     *
        * @throws BuildException on errors
        */
       public void execute() throws BuildException {
  -        if (null == jar && filesets.size() == 0) {
  -            throw new BuildException("jar must be set through jar attribute "
  -                                     + "or nested filesets");
  +        //validation logic
  +        final boolean hasFileset = filesets.size() > 0;
  +        final boolean hasJar = jar != null;
  +        final boolean hasSignedJar = signedjar != null;
  +        final boolean hasDestDir = destDir != null;
  +        final boolean hasMapper = mapper != null;
  +
  +        if (!hasJar && !hasFileset) {
  +            throw new BuildException(ERROR_NO_SOURCE);
           }
           if (null == alias) {
  -            throw new BuildException("alias attribute must be set");
  +            throw new BuildException(ERROR_NO_ALIAS);
           }
   
           if (null == storepass) {
  -            throw new BuildException("storepass attribute must be set");
  +            throw new BuildException(ERROR_NO_STOREPASS);
           }
  +
  +        if (hasDestDir && hasSignedJar) {
  +            throw new BuildException(ERROR_TODIR_AND_SIGNEDJAR);
  +        }
  +
  +
  +        if (hasFileset && hasSignedJar) {
  +            throw new BuildException(ERROR_SIGNEDJAR_AND_FILESETS);
  +        }
  +
  +        //this isnt strictly needed, but by being fussy now,
  +        //we can change implementation details later
  +        if (!hasDestDir && hasMapper) {
  +            throw new BuildException(ERROR_MAPPER_WITHOUT_DEST);
  +        }
  +
  +        //init processing logic; this is retained through our execution(s)
           redirector = createRedirector();
  -        if (null != jar) {
  -            if (filesets.size() != 0) {
  -                log("nested filesets will be ignored if the jar attribute 
has"
  -                    + " been specified.", Project.MSG_WARN);
  -            }
   
  -            doOneJar(jar, signedjar);
  +
  +        //special case single jar handling with signedjar attribute set
  +        if (hasJar && hasSignedJar) {
  +            // single jar processing
  +            signOneJar(jar, signedjar);
  +            //return here.
               return;
  +        }
  +
  +        //the rest of the method treats single jar like
  +        //a nested fileset with one file
  +
  +        if (hasJar) {
  +            //we create a fileset with the source file.
  +            //this lets us combine our logic for handling output directories,
  +            //mapping etc.
  +            FileSet sourceJar = new FileSet();
  +            sourceJar.setFile(jar);
  +            sourceJar.setDir(jar.getParentFile());
  +            addFileset(sourceJar);
  +        }
  +        //set up our mapping policy
  +        FileNameMapper destMapper;
  +        if (hasMapper) {
  +            destMapper = mapper.getImplementation();
           } else {
  -            // deal with the filesets
  -            for (int i = 0; i < filesets.size(); i++) {
  -                FileSet fs = (FileSet) filesets.elementAt(i);
  -                DirectoryScanner ds = fs.getDirectoryScanner(getProject());
  -                String[] jarFiles = ds.getIncludedFiles();
  -                for (int j = 0; j < jarFiles.length; j++) {
  -                    doOneJar(new File(fs.getDir(getProject()), jarFiles[j]), 
null);
  +            //no mapper? use the identity policy
  +            destMapper = new IdentityMapper();
  +        }
  +
  +
  +        //at this point the filesets are set up with lists of files,
  +        //and the mapper is ready to map from source dirs to dest files
  +        //now we iterate through every JAR giving source and dest names
  +        // deal with the filesets
  +        for (int i = 0; i < filesets.size(); i++) {
  +            FileSet fs = (FileSet) filesets.elementAt(i);
  +            //get all included files in a fileset
  +            DirectoryScanner ds = fs.getDirectoryScanner(getProject());
  +            String[] jarFiles = ds.getIncludedFiles();
  +            File baseDir = fs.getDir(getProject());
  +
  +            //calculate our destination directory; it is either the destDir
  +            //attribute, or the base dir of the fileset (for in situ updates)
  +            File toDir = hasDestDir ? destDir : baseDir;
  +
  +            //loop through all jars in the fileset
  +            for (int j = 0; j < jarFiles.length; j++) {
  +                String jarFile = jarFiles[j];
  +                //determine the destination filename via the mapper
  +                String[] destFilenames = destMapper.mapFileName(jarFile);
  +                if (destFilenames == null || destFilenames.length != 1) {
  +                    //we only like simple mappers.
  +                    throw new BuildException(ERROR_BAD_MAP + jarFile);
                   }
  +                File destFile = new File(toDir, destFilenames[0]);
  +                File jarSource = new File(baseDir, jarFile);
  +                signOneJar(jarSource, destFile);
               }
           }
       }
   
       /**
        * Create the redirector to use, if any.
  +     *
        * @return a configured RedirectorElement.
        */
       private RedirectorElement createRedirector() {
  @@ -259,18 +395,31 @@
       }
   
       /**
  -     * sign one jar
  +     * Sign one jar.
  +     * <p/>
  +     * The signing only takes place if [EMAIL PROTECTED] #isUpToDate(File, 
File)} indicates
  +     * that it is needed.
  +     *
  +     * @param jarSource source to sign
  +     * @param jarTarget target; may be null
  +     * @throws BuildException
        */
  -    private void doOneJar(File jarSource, File jarTarget)
  -        throws BuildException {
  +    private void signOneJar(File jarSource, File jarTarget)
  +            throws BuildException {
  +
   
  -        if (isUpToDate(jarSource, jarTarget)) {
  +        File target = jarTarget;
  +        if (target == null) {
  +            target = jarSource;
  +        }
  +        if (isUpToDate(jarSource, target)) {
               return;
           }
   
           long lastModified = jarSource.lastModified();
           final ExecTask cmd = new ExecTask(this);
  -        cmd.setExecutable(JavaEnvUtils.getJdkExecutable("jarsigner"));
  +        cmd.setExecutable(JavaEnvUtils.getJdkExecutable(JARSIGNER_COMMAND));
  +        cmd.setTaskType(JARSIGNER_COMMAND);
   
           if (maxMemory != null) {
               cmd.createArg().setValue("-J-Xmx" + maxMemory);
  @@ -278,15 +427,16 @@
   
           if (null != keystore) {
               // is the keystore a file
  +            cmd.createArg().setValue("-keystore");
  +            String location;
               File keystoreFile = getProject().resolveFile(keystore);
               if (keystoreFile.exists()) {
  -                cmd.createArg().setValue("-keystore");
  -                cmd.createArg().setValue(keystoreFile.getPath());
  +                location = keystoreFile.getPath();
               } else {
                   // must be a URL - just pass as is
  -                cmd.createArg().setValue("-keystore");
  -                cmd.createArg().setValue(keystore);
  +                location = keystore;
               }
  +            cmd.createArg().setValue(location);
           }
           if (null != storetype) {
               cmd.createArg().setValue("-storetype");
  @@ -297,9 +447,11 @@
               cmd.createArg().setValue(sigfile);
           }
   
  -        if (null != jarTarget) {
  +        //DO NOT SET THE -signedjar OPTION if source==dest
  +        //unless you like fielding hotspot crash reports
  +        if (null != target && !jarSource.equals(target)) {
               cmd.createArg().setValue("-signedjar");
  -            cmd.createArg().setValue(jarTarget.toString());
  +            cmd.createArg().setValue(target.getPath());
           }
   
           if (verbose) {
  @@ -314,80 +466,90 @@
               cmd.createArg().setValue("-sectionsonly");
           }
   
  -        cmd.createArg().setValue(jarSource.toString());
  +        //JAR source is required
  +        cmd.createArg().setValue(jarSource.getPath());
   
  +        //alias is required for signing
           cmd.createArg().setValue(alias);
   
  -        log("Signing JAR: " + jarSource.getAbsolutePath());
  +        log("Signing JAR: " +
  +                jarSource.getAbsolutePath()
  +                +" to " +
  +                target.getAbsolutePath()
  +                + " as " + alias);
           cmd.setFailonerror(true);
  -        cmd.setTaskName(getTaskName());
           cmd.addConfiguredRedirector(redirector);
           cmd.execute();
   
           // restore the lastModified attribute
           if (preserveLastModified) {
  -            if (jarTarget != null) {
  -                jarTarget.setLastModified(lastModified);
  -            } else {
  -                jarSource.setLastModified(lastModified);
  -            }
  +            target.setLastModified(lastModified);
           }
       }
   
       /**
  -     * Compare a jar file with its corresponding signed jar
  +     * Compare a jar file with its corresponding signed jar. The logic for 
this
  +     * is complex, and best explained in the source itself. Essentially if
  +     * either file doesnt exist, or the destfile has an out of date 
timestamp,
  +     * then the return value is false.
  +     * <p/>
  +     * If we are signing ourself, the check [EMAIL PROTECTED] 
#isSigned(File)} is used to
  +     * trigger the process.
        *
  -     * @param jarFile  the unsigned jar file
  +     * @param jarFile       the unsigned jar file
        * @param signedjarFile the result signed jar file
  -     * @return true if the signedjarfile is newer than the jar file
  -     *         false if the signedjarfile is the same as the jarfile or if
  -     *         jarfile or the signedjar does not exist.
  +     * @return true if the signedjarFile is considered up to date
        */
       protected boolean isUpToDate(File jarFile, File signedjarFile) {
  -        if (null == jarFile) {
  +        if (null == jarFile && !jarFile.exists()) {
  +            //these are pathological case, but retained in case somebody
  +            //subclassed us.
               return false;
           }
   
  -        if (null != signedjarFile) {
  +        //we normally compare destination with source
  +        File destFile = signedjarFile;
  +        if (destFile == null) {
  +            //but if no dest is specified, compare source to source
  +            destFile = jarFile;
  +        }
   
  -            if (!jarFile.exists()) {
  -              return false;
  -            }
  -            if (!signedjarFile.exists()) {
  -              return false;
  -            }
  -            if (jarFile.equals(signedjarFile)) {
  -              return false;
  -            }
  -            if (FILE_UTILS.isUpToDate(jarFile, signedjarFile)) {
  -                return true;
  -            }
  -        } else {
  +        //if, by any means, the destfile and source match,
  +        if (jarFile.equals(destFile)) {
               if (lazy) {
  +                //we check the presence of signatures on lazy signing
                   return isSigned(jarFile);
               }
  +            //unsigned or non-lazy self signings are always false
  +            return false;
           }
   
  -        return false;
  +        //if they are different, the timestamps are used
  +        return FILE_UTILS.isUpToDate(jarFile, destFile);
       }
   
       /**
        * test for a file being signed, by looking for a signature in the 
META-INF
        * directory
  +     *
        * @param file the file to be checked
        * @return true if the file is signed
  +     * @see IsSigned#isSigned(File, String)
        */
       protected boolean isSigned(File file) {
           try {
               return IsSigned.isSigned(file, alias);
           } catch (IOException e) {
  +            //just log this
  +            log(e.toString(), Project.MSG_VERBOSE);
               return false;
           }
       }
   
       /**
  -     * true to indicate that the signed jar modification date remains the 
same as the original.
  -     * Defaults to false
  +     * true to indicate that the signed jar modification date remains the 
same
  +     * as the original. Defaults to false
  +     *
        * @param preserveLastModified if true preserve the last modified time
        */
       public void setPreserveLastModified(boolean preserveLastModified) {
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to