summaryrefslogtreecommitdiff
path: root/test/jetty/RelocateJettyPackages.java
blob: 01939f5d21fd844f4a3247f080bcb785d50c1441 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/*
 * Build-time 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 and its SLF4J 1.7 linkage below
 * net.yacy.solr9.slf4j.
 */
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 Relocation[] RELOCATIONS = {
            new Relocation("org/eclipse/jetty", "net/yacy/solr9/jetty",
                    "org.eclipse.jetty", "net.yacy.solr9.jetty"),
            new Relocation("org/slf4j", "net/yacy/solr9/slf4j",
                    "org.slf4j", "net.yacy.solr9.slf4j")
    };

    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 BridgeRemapper();

        try (JarFile source = new JarFile(input.toFile());
                JarOutputStream target = new JarOutputStream(Files.newOutputStream(output))) {
            final Enumeration<JarEntry> 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(relocateText(new String(content, StandardCharsets.UTF_8))
                                .getBytes(StandardCharsets.UTF_8));
                    } else {
                        target.write(content);
                    }
                }
                target.closeEntry();
            }
        }
    }

    private static final class BridgeRemapper extends Remapper {

        private BridgeRemapper() {
            super(Opcodes.ASM9);
        }

        @Override
        public String map(final String internalName) {
            return relocateInternalName(internalName);
        }

        @Override
        public Object mapValue(final Object value) {
            if (value instanceof String) {
                final String text = (String) value;
                return relocateText(text);
            }
            return super.mapValue(value);
        }
    }

    private static final class Relocation {
        private final String sourceInternal;
        private final String targetInternal;
        private final String sourceBinary;
        private final String targetBinary;

        private Relocation(final String sourceInternal, final String targetInternal,
                final String sourceBinary, final String targetBinary) {
            this.sourceInternal = sourceInternal;
            this.targetInternal = targetInternal;
            this.sourceBinary = sourceBinary;
            this.targetBinary = targetBinary;
        }
    }

    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 = relocateInternalName(name);
        if (internalName.startsWith("META-INF/services/")) {
            return relocateText(internalName);
        }
        return internalName;
    }

    private static String relocateInternalName(final String name) {
        String relocated = name;
        for (final Relocation relocation : RELOCATIONS) {
            relocated = replacePrefix(relocated, relocation.sourceInternal, relocation.targetInternal);
        }
        return relocated;
    }

    private static String relocateText(final String text) {
        String relocated = text;
        for (final Relocation relocation : RELOCATIONS) {
            relocated = relocated.replace(relocation.sourceBinary, relocation.targetBinary)
                    .replace(relocation.sourceInternal, relocation.targetInternal);
        }
        return relocated;
    }

    private static byte[] readAll(final InputStream stream) throws IOException {
        final ByteArrayOutputStream output = new ByteArrayOutputStream();
        stream.transferTo(output);
        return output.toByteArray();
    }
}