280 lines
11 KiB
Java
280 lines
11 KiB
Java
/*
|
|
* 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.catalina.webresources;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.net.MalformedURLException;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.jar.JarEntry;
|
|
import java.util.jar.JarFile;
|
|
import java.util.jar.JarInputStream;
|
|
import java.util.jar.Manifest;
|
|
|
|
import org.apache.catalina.LifecycleException;
|
|
import org.apache.catalina.WebResource;
|
|
import org.apache.catalina.WebResourceRoot;
|
|
import org.apache.tomcat.util.buf.UriUtil;
|
|
import org.apache.tomcat.util.compat.JreCompat;
|
|
|
|
/**
|
|
* Represents a {@link org.apache.catalina.WebResourceSet} based on a JAR file
|
|
* that is nested inside a packed WAR file. This is only intended for internal
|
|
* use within Tomcat and therefore cannot be created via configuration.
|
|
*/
|
|
public class JarWarResourceSet extends AbstractArchiveResourceSet {
|
|
|
|
private final String archivePath;
|
|
|
|
/**
|
|
* Creates a new {@link org.apache.catalina.WebResourceSet} based on a JAR
|
|
* file that is nested inside a WAR.
|
|
*
|
|
* @param root The {@link WebResourceRoot} this new
|
|
* {@link org.apache.catalina.WebResourceSet} will
|
|
* be added to.
|
|
* @param webAppMount The path within the web application at which this
|
|
* {@link org.apache.catalina.WebResourceSet} will
|
|
* be mounted.
|
|
* @param base The absolute path to the WAR file on the file system
|
|
* in which the JAR is located.
|
|
* @param archivePath The path within the WAR file where the JAR file is
|
|
* located.
|
|
* @param internalPath The path within this new {@link
|
|
* org.apache.catalina.WebResourceSet} where
|
|
* resources will be served from. E.g. for a
|
|
* resource JAR, this would be "META-INF/resources"
|
|
*
|
|
* @throws IllegalArgumentException if the webAppMount or internalPath is
|
|
* not valid (valid paths must start with '/')
|
|
*/
|
|
public JarWarResourceSet(WebResourceRoot root, String webAppMount,
|
|
String base, String archivePath, String internalPath)
|
|
throws IllegalArgumentException {
|
|
setRoot(root);
|
|
setWebAppMount(webAppMount);
|
|
setBase(base);
|
|
this.archivePath = archivePath;
|
|
setInternalPath(internalPath);
|
|
|
|
if (getRoot().getState().isAvailable()) {
|
|
try {
|
|
start();
|
|
} catch (LifecycleException e) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected WebResource createArchiveResource(JarEntry jarEntry,
|
|
String webAppPath, Manifest manifest) {
|
|
return new JarWarResource(this, webAppPath, getBaseUrlString(), jarEntry, archivePath);
|
|
}
|
|
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* <p>
|
|
* JarWar can't optimise for a single resource so the Map is always
|
|
* returned.
|
|
*/
|
|
@Override
|
|
protected HashMap<String,JarEntry> getArchiveEntries(boolean single) {
|
|
synchronized (archiveLock) {
|
|
if (archiveEntries == null) {
|
|
JarFile warFile = null;
|
|
InputStream jarFileIs = null;
|
|
archiveEntries = new HashMap<>();
|
|
boolean multiRelease = false;
|
|
try {
|
|
warFile = openJarFile();
|
|
JarEntry jarFileInWar = warFile.getJarEntry(archivePath);
|
|
jarFileIs = warFile.getInputStream(jarFileInWar);
|
|
|
|
try (TomcatJarInputStream jarIs = new TomcatJarInputStream(jarFileIs)) {
|
|
JarEntry entry = jarIs.getNextJarEntry();
|
|
while (entry != null) {
|
|
archiveEntries.put(entry.getName(), entry);
|
|
entry = jarIs.getNextJarEntry();
|
|
}
|
|
Manifest m = jarIs.getManifest();
|
|
setManifest(m);
|
|
if (m != null && JreCompat.isJre9Available()) {
|
|
String value = m.getMainAttributes().getValue("Multi-Release");
|
|
if (value != null) {
|
|
multiRelease = Boolean.parseBoolean(value);
|
|
}
|
|
}
|
|
// Hack to work-around JarInputStream swallowing these
|
|
// entries. TomcatJarInputStream is used above which
|
|
// extends JarInputStream and the method that creates
|
|
// the entries over-ridden so we can a) tell if the
|
|
// entries are present and b) cache them so we can
|
|
// access them here.
|
|
entry = jarIs.getMetaInfEntry();
|
|
if (entry != null) {
|
|
archiveEntries.put(entry.getName(), entry);
|
|
}
|
|
entry = jarIs.getManifestEntry();
|
|
if (entry != null) {
|
|
archiveEntries.put(entry.getName(), entry);
|
|
}
|
|
}
|
|
if (multiRelease) {
|
|
processArchivesEntriesForMultiRelease();
|
|
}
|
|
} catch (IOException ioe) {
|
|
// Should never happen
|
|
archiveEntries = null;
|
|
throw new IllegalStateException(ioe);
|
|
} finally {
|
|
if (warFile != null) {
|
|
closeJarFile();
|
|
}
|
|
if (jarFileIs != null) {
|
|
try {
|
|
jarFileIs.close();
|
|
} catch (IOException e) {
|
|
// Ignore
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return archiveEntries;
|
|
}
|
|
}
|
|
|
|
|
|
protected void processArchivesEntriesForMultiRelease() {
|
|
|
|
int targetVersion = JreCompat.getInstance().jarFileRuntimeMajorVersion();
|
|
|
|
Map<String,VersionedJarEntry> versionedEntries = new HashMap<>();
|
|
Iterator<Entry<String,JarEntry>> iter = archiveEntries.entrySet().iterator();
|
|
while (iter.hasNext()) {
|
|
Entry<String,JarEntry> entry = iter.next();
|
|
String name = entry.getKey();
|
|
if (name.startsWith("META-INF/versions/")) {
|
|
// Remove the multi-release version
|
|
iter.remove();
|
|
|
|
// Get the base name and version for this versioned entry
|
|
int i = name.indexOf('/', 18);
|
|
if (i > 0) {
|
|
String baseName = name.substring(i + 1);
|
|
int version = Integer.parseInt(name.substring(18, i));
|
|
|
|
// Ignore any entries targeting for a later version than
|
|
// the target for this runtime
|
|
if (version <= targetVersion) {
|
|
VersionedJarEntry versionedJarEntry = versionedEntries.get(baseName);
|
|
if (versionedJarEntry == null) {
|
|
// No versioned entry found for this name. Create
|
|
// one.
|
|
versionedEntries.put(baseName,
|
|
new VersionedJarEntry(version, entry.getValue()));
|
|
} else {
|
|
// Ignore any entry for which we have already found
|
|
// a later version
|
|
if (version > versionedJarEntry.getVersion()) {
|
|
// Replace the entry targeted at an earlier
|
|
// version
|
|
versionedEntries.put(baseName,
|
|
new VersionedJarEntry(version, entry.getValue()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (Entry<String,VersionedJarEntry> versionedJarEntry : versionedEntries.entrySet()) {
|
|
archiveEntries.put(versionedJarEntry.getKey(),
|
|
versionedJarEntry.getValue().getJarEntry());
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* <p>
|
|
* Should never be called since {@link #getArchiveEntries(boolean)} always
|
|
* returns a Map.
|
|
*/
|
|
@Override
|
|
protected JarEntry getArchiveEntry(String pathInArchive) {
|
|
throw new IllegalStateException(sm.getString("jarWarResourceSet.codingError"));
|
|
}
|
|
|
|
|
|
@Override
|
|
protected boolean isMultiRelease() {
|
|
// This always returns false otherwise the superclass will call
|
|
// #getArchiveEntry(String)
|
|
return false;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------- Lifecycle methods
|
|
@Override
|
|
protected void initInternal() throws LifecycleException {
|
|
|
|
try (JarFile warFile = new JarFile(getBase())) {
|
|
JarEntry jarFileInWar = warFile.getJarEntry(archivePath);
|
|
InputStream jarFileIs = warFile.getInputStream(jarFileInWar);
|
|
|
|
try (JarInputStream jarIs = new JarInputStream(jarFileIs)) {
|
|
setManifest(jarIs.getManifest());
|
|
}
|
|
} catch (IOException ioe) {
|
|
throw new IllegalArgumentException(ioe);
|
|
}
|
|
|
|
try {
|
|
setBaseUrl(UriUtil.buildJarSafeUrl(new File(getBase())));
|
|
} catch (MalformedURLException e) {
|
|
throw new IllegalArgumentException(e);
|
|
}
|
|
}
|
|
|
|
|
|
private static final class VersionedJarEntry {
|
|
private final int version;
|
|
private final JarEntry jarEntry;
|
|
|
|
public VersionedJarEntry(int version, JarEntry jarEntry) {
|
|
this.version = version;
|
|
this.jarEntry = jarEntry;
|
|
}
|
|
|
|
|
|
public int getVersion() {
|
|
return version;
|
|
}
|
|
|
|
|
|
public JarEntry getJarEntry() {
|
|
return jarEntry;
|
|
}
|
|
}
|
|
}
|