/* * Proof-of-concept bytecode relocator for the Jetty 12 migration. * * This is deliberately a small build tool, not YaCy runtime code. It keeps * Solr's public packages unchanged while moving its private Jetty 9 linkage * below net.yacy.solr9.jetty. */ package net.yacy.test.jetty; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.ClassRemapper; import org.objectweb.asm.commons.Remapper; public final class RelocateJettyPackages { private static final String SOURCE_INTERNAL = "org/eclipse/jetty"; private static final String TARGET_INTERNAL = "net/yacy/solr9/jetty"; private static final String SOURCE_BINARY = "org.eclipse.jetty"; private static final String TARGET_BINARY = "net.yacy.solr9.jetty"; private RelocateJettyPackages() { } public static void main(final String[] args) throws IOException { if (args.length != 2) { throw new IllegalArgumentException("usage: RelocateJettyPackages INPUT.jar OUTPUT.jar"); } relocate(Path.of(args[0]), Path.of(args[1])); } private static void relocate(final Path input, final Path output) throws IOException { Files.createDirectories(output.toAbsolutePath().getParent()); final Remapper remapper = new JettyRemapper(); try (JarFile source = new JarFile(input.toFile()); JarOutputStream target = new JarOutputStream(Files.newOutputStream(output))) { final Enumeration entries = source.entries(); while (entries.hasMoreElements()) { final JarEntry entry = entries.nextElement(); if (entry.isDirectory() || isSignature(entry.getName())) { continue; } final String outputName = relocateEntryName(entry.getName()); final JarEntry outputEntry = new JarEntry(outputName); outputEntry.setTime(entry.getTime()); target.putNextEntry(outputEntry); try (InputStream stream = source.getInputStream(entry)) { final byte[] content = readAll(stream); if (entry.getName().endsWith(".class")) { target.write(relocateClass(content, remapper)); } else if (entry.getName().startsWith("META-INF/services/")) { target.write(new String(content, StandardCharsets.UTF_8) .replace(SOURCE_BINARY, TARGET_BINARY) .getBytes(StandardCharsets.UTF_8)); } else { target.write(content); } } target.closeEntry(); } } } private static final class JettyRemapper extends Remapper { private JettyRemapper() { super(Opcodes.ASM9); } @Override public String map(final String internalName) { return replacePrefix(internalName, SOURCE_INTERNAL, TARGET_INTERNAL); } @Override public Object mapValue(final Object value) { if (value instanceof String) { final String text = (String) value; return text.replace(SOURCE_BINARY, TARGET_BINARY) .replace(SOURCE_INTERNAL, TARGET_INTERNAL); } return super.mapValue(value); } } private static byte[] relocateClass(final byte[] content, final Remapper remapper) { final ClassReader reader = new ClassReader(content); final ClassWriter writer = new ClassWriter(0); reader.accept(new ClassRemapper(writer, remapper), 0); return writer.toByteArray(); } private static boolean isSignature(final String name) { final String upper = name.toUpperCase(java.util.Locale.ROOT); return upper.startsWith("META-INF/") && (upper.endsWith(".SF") || upper.endsWith(".RSA") || upper.endsWith(".DSA")); } private static String replacePrefix(final String value, final String source, final String target) { if (value.equals(source)) { return target; } if (value.startsWith(source + "/")) { return target + value.substring(source.length()); } return value; } private static String relocateEntryName(final String name) { final String internalName = replacePrefix(name, SOURCE_INTERNAL, TARGET_INTERNAL); if (internalName.startsWith("META-INF/services/")) { return internalName.replace(SOURCE_BINARY, TARGET_BINARY); } return internalName; } private static byte[] readAll(final InputStream stream) throws IOException { final ByteArrayOutputStream output = new ByteArrayOutputStream(); stream.transferTo(output); return output.toByteArray(); } }