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