blob: 43e34f6f617483d2ad12770b38bf03f5e56515ab [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2011 Sonatype Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sonatype Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.core.osgitools;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.util.IOUtil;
import org.eclipse.tycho.core.shared.LRUCache;
import org.eclipse.tycho.locking.facade.FileLockService;
import org.eclipse.tycho.locking.facade.FileLocker;
@Component(role = BundleReader.class)
public class DefaultBundleReader extends AbstractLogEnabled implements BundleReader {
public static final String CACHE_PATH = ".cache/tycho";
private final LRUCache<String, OsgiManifest> manifestCache = new LRUCache<>(50);
private File cacheDir;
private Set<String> extractedFiles = new HashSet<>();
@Requirement
private FileLockService fileLockService;
@Override
public OsgiManifest loadManifest(File bundleLocation) {
String locationPath = bundleLocation.getAbsolutePath();
OsgiManifest manifest = manifestCache.get(locationPath);
if (manifest == null) {
manifest = doLoadManifest(bundleLocation);
manifestCache.put(locationPath, manifest);
}
return manifest;
}
private OsgiManifest doLoadManifest(File bundleLocation) {
try {
if (bundleLocation.isDirectory()) {
return loadManifestFromDirectory(bundleLocation);
} else if (bundleLocation.isFile()) {
return loadManifestFromFile(bundleLocation);
} else {
// file does not exist
throw new OsgiManifestParserException(
new File(bundleLocation, JarFile.MANIFEST_NAME).getAbsolutePath(), "Manifest file not found");
}
} catch (IOException e) {
throw new OsgiManifestParserException(bundleLocation.getAbsolutePath(), e);
}
}
private OsgiManifest loadManifestFromFile(File bundleLocation) throws IOException {
if (!bundleLocation.getName().toLowerCase().endsWith(".jar")) {
// file but not a jar, assume it is MANIFEST.MF
return loadManifestFile(bundleLocation);
}
try ( // it is a jar, let's see if it has OSGi bundle manifest
ZipFile jar = new ZipFile(bundleLocation, ZipFile.OPEN_READ)) {
ZipEntry manifestEntry = jar.getEntry(JarFile.MANIFEST_NAME);
if (manifestEntry != null) {
InputStream stream = jar.getInputStream(manifestEntry);
return OsgiManifest.parse(stream, bundleLocation.getAbsolutePath() + "!/" + JarFile.MANIFEST_NAME);
}
}
throw new OsgiManifestParserException(bundleLocation.getAbsolutePath(),
"Manifest file not found in JAR archive");
}
private OsgiManifest loadManifestFromDirectory(File directory) throws IOException {
File manifestFile = new File(directory, JarFile.MANIFEST_NAME);
if (!manifestFile.isFile()) {
throw new OsgiManifestParserException(manifestFile.getAbsolutePath(), "Manifest file not found");
}
return loadManifestFile(manifestFile);
}
private OsgiManifest loadManifestFile(File manifestFile) throws IOException, OsgiManifestParserException {
return OsgiManifest.parse(new FileInputStream(manifestFile), manifestFile.getAbsolutePath());
}
public void setLocationRepository(File basedir) {
this.cacheDir = new File(basedir, CACHE_PATH);
}
@Override
public File getEntry(File bundleLocation, String path) {
if (path.startsWith("external:")) {
getLogger().warn("Ignoring Bundle-ClassPath entry '" + path + "' of bundle " + bundleLocation);
return null;
}
final File result;
if (bundleLocation.isDirectory()) {
result = new File(bundleLocation, path);
} else {
try {
File outputDirectory = new File(cacheDir, bundleLocation.getName());
result = new File(outputDirectory, path);
String resultPath = result.getCanonicalPath();
if (extractedFiles.contains(resultPath) && result.exists()) {
return result;
} else {
FileLocker locker = fileLockService.getFileLocker(outputDirectory);
locker.lock(5 * 60 * 1000L);
try {
extractZipEntries(bundleLocation, path, outputDirectory);
} finally {
locker.release();
}
extractedFiles.add(resultPath);
}
} catch (IOException e) {
throw new RuntimeException("IOException while extracting '" + path + "' from " + bundleLocation, e);
}
}
if (result.exists()) {
return result;
} else {
getLogger().debug("Bundle-ClassPath entry " + path + " does not exist in " + bundleLocation);
return null;
}
}
private void extractZipEntries(File bundleLocation, String path, File outputDirectory) throws IOException {
try (ZipFile zip = new ZipFile(bundleLocation)) {
ZipEntry singleEntry = zip.getEntry(path);
InputStream singleEntryStream;
if (singleEntry != null && !singleEntry.isDirectory()
&& (singleEntryStream = zip.getInputStream(singleEntry)) != null) {
// fix for performance bug 367098: avoid loop if path is a single zip file entry
copyStreamToFile(singleEntryStream, new File(outputDirectory, singleEntry.getName()),
singleEntry.getTime());
} else {
// loop over all entries and extract matching
for (Enumeration<? extends ZipEntry> entries = zip.entries(); entries.hasMoreElements();) {
ZipEntry zipEntry = entries.nextElement();
if (!zipEntry.isDirectory() && zipEntry.getName().startsWith(path)) {
copyStreamToFile(zip.getInputStream(zipEntry), new File(outputDirectory, zipEntry.getName()),
zipEntry.getTime());
}
}
}
}
}
private static void copyStreamToFile(InputStream in, File outputFile, long timestamp) throws IOException {
if (in == null) {
return;
}
outputFile.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(outputFile);
try {
IOUtil.copy(in, out);
} finally {
in.close();
out.close();
}
if (timestamp > 0) {
outputFile.setLastModified(timestamp);
}
}
}