blob: b297747d733a5978b1c7ed04536f6a36600a20ba [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.tomcat.jakartaee;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.input.CloseShieldInputStream;
import org.apache.commons.io.output.CloseShieldOutputStream;
public class Migration {
private static final Logger logger = Logger.getLogger(Migration.class.getCanonicalName());
private static final StringManager sm = StringManager.getManager(Migration.class);
private EESpecProfile profile = EESpecProfile.TOMCAT;
private File source;
private File destination;
private final List<Converter> converters;
public Migration() {
// Initialise the converters
converters = new ArrayList<>();
converters.add(new TextConverter());
converters.add(new ClassConverter());
// Final converter is the pass-through converter
converters.add(new PassThroughConverter());
}
/**
* Set the Jakarta EE specifications that should be used.
*
* @param profile the Jakarta EE specification profile
*/
public void setEESpecProfile(EESpecProfile profile) {
this.profile = profile;
}
/**
* Get the Jakarta EE profile being used.
*
* @return the profile
*/
public EESpecProfile getEESpecProfile() {
return profile;
}
public void setSource(File source) {
if (!source.canRead()) {
throw new IllegalArgumentException(sm.getString("migration.cannotReadSource",
source.getAbsolutePath()));
}
this.source = source;
}
public void setDestination(File destination) {
this.destination = destination;
}
public boolean execute() throws IOException {
logger.log(Level.INFO, sm.getString("migration.execute", source.getAbsolutePath(),
destination.getAbsolutePath(), profile.toString()));
boolean result = true;
long t1 = System.nanoTime();
if (source.isDirectory()) {
if (destination.mkdirs()) {
result = result && migrateDirectory(source, destination);
} else {
logger.log(Level.WARNING, sm.getString("migration.mkdirError", destination.getAbsolutePath()));
result = false;
}
} else {
// Single file
File parentDestination = destination.getAbsoluteFile().getParentFile();
if (parentDestination.exists() || parentDestination.mkdirs()) {
result = result && migrateFile(source, destination);
} else {
logger.log(Level.WARNING, sm.getString("migration.mkdirError", parentDestination.getAbsolutePath()));
result = false;
}
}
logger.log(Level.INFO, sm.getString("migration.done",
Long.valueOf(TimeUnit.MILLISECONDS.convert(System.nanoTime() - t1, TimeUnit.NANOSECONDS)),
Boolean.valueOf(result)));
return result;
}
private boolean migrateDirectory(File src, File dest) throws IOException {
boolean result = true;
String[] files = src.list();
for (String file : files) {
File srcFile = new File(src, file);
File destFile = new File(dest, file);
if (srcFile.isDirectory()) {
if (destFile.mkdir()) {
result = result && migrateDirectory(srcFile, destFile);
} else {
logger.log(Level.WARNING, sm.getString("migration.mkdirError", destFile.getAbsolutePath()));
result = false;
}
} else {
result = result && migrateFile(srcFile, destFile);
}
}
return result;
}
private boolean migrateFile(File src, File dest) throws IOException {
boolean result = false;
boolean inplace = src.equals(dest);
if (!inplace) {
try (InputStream is = new FileInputStream(src);
OutputStream os = new FileOutputStream(dest)) {
result = migrateStream(src.getName(), is, os);
}
} else {
ByteArrayOutputStream buffer = new ByteArrayOutputStream((int) (src.length() * 1.05));
try (InputStream is = new FileInputStream(src)) {
result = migrateStream(src.getName(), is, buffer);
}
try (OutputStream os = new FileOutputStream(dest)) {
os.write(buffer.toByteArray());
}
}
return result;
}
private boolean migrateArchive(InputStream src, OutputStream dest) throws IOException {
boolean result = true;
try (JarInputStream jarIs = new JarInputStream(new CloseShieldInputStream(src));
JarOutputStream jarOs = new JarOutputStream(new CloseShieldOutputStream(dest))) {
Manifest manifest = jarIs.getManifest();
if (manifest != null) {
// Make a safe copy to leave original manifest untouched.
// Otherwise messing with signatures will fail
manifest = new Manifest(manifest);
updateVersion(manifest);
if (removeSignatures(manifest)) {
logger.log(Level.WARNING, sm.getString("migration.warnSignatureRemoval"));
}
JarEntry manifestEntry = new JarEntry(JarFile.MANIFEST_NAME);
jarOs.putNextEntry(manifestEntry);
manifest.write(jarOs);
}
JarEntry jarEntry;
while ((jarEntry = jarIs.getNextJarEntry()) != null) {
String sourceName = jarEntry.getName();
logger.log(Level.FINE, sm.getString("migration.entry", sourceName));
if (isSignatureFile(sourceName)) {
logger.log(Level.FINE, sm.getString("migration.skipSignatureFile", sourceName));
continue;
}
String destName = profile.convert(sourceName);
JarEntry destEntry = new JarEntry(destName);
jarOs.putNextEntry(destEntry);
result = result && migrateStream(destEntry.getName(), jarIs, jarOs);
}
}
return result;
}
private boolean isSignatureFile(String sourceName) {
return sourceName.startsWith("META-INF/")
&& (sourceName.endsWith(".SF") || sourceName.endsWith(".RSA") || sourceName.endsWith(".DSA") || sourceName.endsWith(".EC"));
}
private boolean migrateStream(String name, InputStream src, OutputStream dest) throws IOException {
if (isArchive(name)) {
logger.log(Level.INFO, sm.getString("migration.archive", name));
return migrateArchive(src, dest);
} else {
logger.log(Level.FINE, sm.getString("migration.stream", name));
for (Converter converter : converters) {
if (converter.accepts(name)) {
converter.convert(src, dest, profile);
break;
}
}
return true;
}
}
private boolean removeSignatures(Manifest manifest) {
boolean removedSignatures = manifest.getMainAttributes().remove(Attributes.Name.SIGNATURE_VERSION) != null;
List<String> signatureEntries = new ArrayList<>();
Map<String, Attributes> manifestAttributeEntries = manifest.getEntries();
for (Entry<String, Attributes> entry : manifestAttributeEntries.entrySet()) {
if (isCryptoSignatureEntry(entry.getValue())) {
String entryName = entry.getKey();
signatureEntries.add(entryName);
logger.log(Level.FINE, sm.getString("migration.removeSignature", entryName));
removedSignatures = true;
}
}
for (String entry : signatureEntries) {
manifestAttributeEntries.remove(entry);
}
return removedSignatures;
}
private boolean isCryptoSignatureEntry(Attributes attributes) {
for (Object attributeKey : attributes.keySet()) {
if (attributeKey.toString().endsWith("-Digest")) {
return true;
}
}
return false;
}
private void updateVersion(Manifest manifest) {
updateVersion(manifest.getMainAttributes());
for (Attributes attributes : manifest.getEntries().values()) {
updateVersion(attributes);
}
}
private void updateVersion(Attributes attributes) {
if (attributes.containsKey(Attributes.Name.IMPLEMENTATION_VERSION)) {
String newValue = attributes.get(Attributes.Name.IMPLEMENTATION_VERSION) + "-" + Info.getVersion();
attributes.put(Attributes.Name.IMPLEMENTATION_VERSION, newValue);
}
}
private static boolean isArchive(String fileName) {
return fileName.endsWith(".jar") || fileName.endsWith(".war") || fileName.endsWith(".zip");
}
}