All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.plexus.archiver.jar.JarToolModularJarArchiver Maven / Gradle / Ivy

/**
 *
 * Copyright 2018 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.codehaus.plexus.archiver.jar;

import javax.inject.Named;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.apache.commons.compress.parallel.InputStreamSupplier;
import org.apache.commons.io.output.NullPrintStream;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.zip.ConcurrentJarCreator;
import org.codehaus.plexus.util.IOUtil;

/**
 * A {@link ModularJarArchiver} implementation that uses
 * the {@code jar} tool provided by
 * {@code java.util.spi.ToolProvider} to create
 * modular JAR files.
 *
 * 

* The basic JAR archive is created by {@link JarArchiver} * and the {@code jar} tool is used to upgrade it to modular JAR. * *

* If the JAR file does not contain module descriptor * or the JDK does not provide the {@code jar} tool * (for example JDK prior to Java 9), then the * archive created by {@link JarArchiver} * is left unchanged. */ @Named( "mjar" ) public class JarToolModularJarArchiver extends ModularJarArchiver { private static final String MODULE_DESCRIPTOR_FILE_NAME = "module-info.class"; private static final Pattern MRJAR_VERSION_AREA = Pattern.compile( "META-INF/versions/\\d+/" ); private Object jarTool; private boolean moduleDescriptorFound; private boolean hasJarDateOption; public JarToolModularJarArchiver() { try { Class toolProviderClass = Class.forName( "java.util.spi.ToolProvider" ); Object jarToolOptional = toolProviderClass .getMethod( "findFirst", String.class ) .invoke( null, "jar" ); jarTool = jarToolOptional.getClass().getMethod( "get" ) .invoke( jarToolOptional ); } catch ( ReflectiveOperationException | SecurityException e ) { // Ignore. It is expected that the jar tool // may not be available. } } @Override protected void zipFile( InputStreamSupplier is, ConcurrentJarCreator zOut, String vPath, long lastModified, File fromArchive, int mode, String symlinkDestination, boolean addInParallel ) throws IOException, ArchiverException { if ( jarTool != null && isModuleDescriptor( vPath ) ) { getLogger().debug( "Module descriptor found: " + vPath ); moduleDescriptorFound = true; } super.zipFile( is, zOut, vPath, lastModified, fromArchive, mode, symlinkDestination, addInParallel ); } @Override protected void postCreateArchive() throws ArchiverException { if ( !moduleDescriptorFound ) { // no need to update the JAR archive return; } try { getLogger().debug( "Using the jar tool to " + "update the archive to modular JAR." ); final Method jarRun = jarTool.getClass() .getMethod( "run", PrintStream.class, PrintStream.class, String[].class ); if ( getLastModifiedTime() != null ) { hasJarDateOption = isJarDateOptionSupported( jarRun ); getLogger().debug( "jar tool --date option is supported: " + hasJarDateOption ); } Integer result = (Integer) jarRun.invoke( jarTool, System.out, System.err, getJarToolArguments() ); if ( result != null && result != 0 ) { throw new ArchiverException( "Could not create modular JAR file. " + "The JDK jar tool exited with " + result ); } if ( !hasJarDateOption && getLastModifiedTime() != null ) { getLogger().debug( "Fix last modified time zip entries." ); // --date option not supported, fallback to rewrite the JAR file // https://github.com/codehaus-plexus/plexus-archiver/issues/164 fixLastModifiedTimeZipEntries(); } } catch ( IOException | ReflectiveOperationException | SecurityException e ) { throw new ArchiverException( "Exception occurred " + "while creating modular JAR file", e ); } } /** * Fallback to rewrite the JAR file with the correct timestamp if the {@code --date} option is not available. */ private void fixLastModifiedTimeZipEntries() throws IOException { long timeMillis = getLastModifiedTime().toMillis(); Path destFile = getDestFile().toPath(); Path tmpZip = Files.createTempFile( destFile.getParent(), null, null ); try ( ZipFile zipFile = new ZipFile( getDestFile() ); ZipOutputStream out = new ZipOutputStream( Files.newOutputStream( tmpZip ) ) ) { Enumeration entries = zipFile.entries(); while ( entries.hasMoreElements() ) { ZipEntry entry = entries.nextElement(); // Not using setLastModifiedTime(FileTime) as it sets the extended timestamp // which is not compatible with the jar tool output. entry.setTime( timeMillis ); out.putNextEntry( entry ); if ( !entry.isDirectory() ) { IOUtil.copy( zipFile.getInputStream( entry ), out ); } out.closeEntry(); } } Files.move( tmpZip, destFile, StandardCopyOption.REPLACE_EXISTING ); } /** * Returns {@code true} if {@code path} * is a module descriptor. */ private boolean isModuleDescriptor( String path ) { if ( path.endsWith( MODULE_DESCRIPTOR_FILE_NAME ) ) { String prefix = path.substring( 0, path.lastIndexOf( MODULE_DESCRIPTOR_FILE_NAME ) ); // the path is a module descriptor if it located // into the root of the archive or into the // version area of a multi-release JAR file return prefix.isEmpty() || MRJAR_VERSION_AREA.matcher( prefix ).matches(); } else { return false; } } /** * Prepares the arguments for the jar tool. * It takes into account the module version, * main class, etc. */ private String[] getJarToolArguments() throws IOException { // We add empty temporary directory to the JAR file. // It may look strange at first, but to update a JAR file // you need to add new files[1]. If we add empty directory // it will be ignored (not added to the archive), but // the module descriptor will be updated and validated. // // [1] There are some exceptions (such as when the main class // is updated) but we need at least empty directory // to ensure it will work in all cases. File tempEmptyDir = Files.createTempDirectory( null ).toFile(); tempEmptyDir.deleteOnExit(); List args = new ArrayList<>(); args.add( "--update" ); args.add( "--file" ); args.add( getDestFile().getAbsolutePath() ); String mainClass = getModuleMainClass() != null ? getModuleMainClass() : getManifestMainClass(); if ( mainClass != null ) { args.add( "--main-class" ); args.add( mainClass ); } if ( getModuleVersion() != null ) { args.add( "--module-version" ); args.add( getModuleVersion() ); } if ( !isCompress() ) { args.add( "--no-compress" ); } if ( hasJarDateOption ) { // The --date option already normalize the time, so revert to the local time FileTime localTime = revertToLocalTime( getLastModifiedTime() ); args.add( "--date" ); args.add( localTime.toString() ); } args.add( "-C" ); args.add( tempEmptyDir.getAbsolutePath() ); args.add( "." ); return args.toArray( new String[0] ); } private static FileTime revertToLocalTime( FileTime time ) { long restoreToLocalTime = time.toMillis(); Calendar cal = Calendar.getInstance( TimeZone.getDefault(), Locale.ROOT ); cal.setTimeInMillis( restoreToLocalTime ); restoreToLocalTime = restoreToLocalTime + ( cal.get( Calendar.ZONE_OFFSET ) + cal.get( Calendar.DST_OFFSET ) ); return FileTime.fromMillis( restoreToLocalTime ); } /** * Check support for {@code --date} option introduced since Java 17.0.3 (JDK-8279925). * * @return true if the JAR tool supports the {@code --date} option */ private boolean isJarDateOptionSupported( Method runMethod ) { try { // Test the output code validating the --date option. String[] args = { "--date", "2099-12-31T23:59:59Z", "--version" }; PrintStream nullPrintStream = NullPrintStream.NULL_PRINT_STREAM; Integer result = (Integer) runMethod.invoke( jarTool, nullPrintStream, nullPrintStream, args ); return result != null && result.intValue() == 0; } catch ( ReflectiveOperationException | SecurityException e ) { return false; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy