/*
* (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed 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.
*
* Contributors:
* Nuxeo - initial API and implementation
*/
package org.nuxeo.osgi;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.common.utils.StringUtils;
import org.nuxeo.osgi.util.EntryFilter;
import org.osgi.framework.Constants;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class JarBundleFile implements BundleFile {
private static final Log log = LogFactory.getLog(JarBundleFile.class);
protected JarFile jarFile;
protected String urlBase;
public JarBundleFile(File file) throws IOException {
this(new JarFile(file));
}
public JarBundleFile(JarFile jarFile) {
this.jarFile = jarFile;
try {
urlBase = "jar:" + new File(jarFile.getName()).toURI().toURL() + "!/";
} catch (MalformedURLException e) {
log.error("Failed to convert bundle location to an URL: " + jarFile.getName()
+ ". Bundle getEntry will not work.", e);
}
}
@Override
public Enumeration<URL> findEntries(String name, String pattern, boolean recurse) {
if (name.startsWith("/")) {
name = name.substring(1);
}
String prefix;
if (name.length() == 0) {
name = null;
prefix = "";
} else if (!name.endsWith("/")) {
prefix = name + "/";
} else {
prefix = name;
}
int len = prefix.length();
EntryFilter filter = EntryFilter.newFilter(pattern);
Enumeration<JarEntry> entries = jarFile.entries();
ArrayList<URL> result = new ArrayList<>();
try {
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.isDirectory()) {
continue;
}
String ename = entry.getName();
if (name != null && !ename.startsWith(prefix)) {
continue;
}
int i = ename.lastIndexOf('/');
if (!recurse) {
if (i > -1) {
if (ename.indexOf('/', len) > -1) {
continue;
}
}
}
String n = i > -1 ? ename.substring(i + 1) : ename;
if (filter.match(n)) {
result.add(getEntryUrl(ename));
}
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
return Collections.enumeration(result);
}
@Override
public URL getEntry(String name) {
ZipEntry entry = jarFile.getEntry(name);
if (entry == null) {
return null;
}
if (name.startsWith("/")) {
name = name.substring(1);
}
try {
return new URL(urlBase + name);
} catch (MalformedURLException e) {
return null;
}
}
@Override
public Enumeration<String> getEntryPaths(String path) {
throw new UnsupportedOperationException("The operation BundleFile.geEntryPaths() was not yet implemented");
}
@Override
public File getFile() {
return new File(jarFile.getName());
}
@Override
public String getFileName() {
String path = jarFile.getName();
int punix = path.lastIndexOf('/');
int pwin = path.lastIndexOf('\\');
int p = punix > pwin ? punix : pwin;
if (p == -1) {
return path;
}
if (p == 0) {
return "";
}
return path.substring(p + 1);
}
@Override
public String getLocation() {
return jarFile.getName();
}
@Override
public Manifest getManifest() {
try {
return jarFile.getManifest();
} catch (IOException e) {
return null;
}
}
@Override
public Collection<BundleFile> getNestedBundles(File tmpDir) throws IOException {
Attributes attrs = jarFile.getManifest().getMainAttributes();
String cp = attrs.getValue(Constants.BUNDLE_CLASSPATH);
if (cp == null) {
cp = attrs.getValue("Class-Path");
}
if (cp == null) {
return null;
}
String[] paths = StringUtils.split(cp, ',', true);
URL base = new URL("jar:" + new File(jarFile.getName()).toURI().toURL().toExternalForm() + "!/");
String fileName = getFileName();
List<BundleFile> nested = new ArrayList<>();
for (String path : paths) {
if (path.equals(".")) {
continue; // TODO
}
String location = base + path;
String name = path.replace('/', '_');
File dest = new File(tmpDir, fileName + '-' + name);
try {
extractNestedJar(jarFile, path, dest);
nested.add(new NestedJarBundleFile(location, dest));
} catch (FileNotFoundException e) {
log.error("A nested jar is referenced in manifest but not found: " + location);
} catch (IOException e) {
log.error(e);
}
}
return nested;
}
public static void extractNestedJar(JarFile file, String path, File dest) throws IOException {
extractNestedJar(file, file.getEntry(path), dest);
}
public static void extractNestedJar(JarFile file, ZipEntry entry, File dest) throws IOException {
try (InputStream in = file.getInputStream(entry)) {
FileUtils.copyInputStreamToFile(in, dest);
}
}
@Override
public Collection<BundleFile> findNestedBundles(File tmpDir) throws IOException {
URL base = new URL("jar:" + new File(jarFile.getName()).toURI().toURL().toExternalForm() + "!/");
String fileName = getFileName();
Enumeration<JarEntry> entries = jarFile.entries();
List<BundleFile> nested = new ArrayList<>();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String path = entry.getName();
if (entry.getName().endsWith(".jar")) {
String location = base + path;
String name = path.replace('/', '_');
File dest = new File(tmpDir, fileName + '-' + name);
extractNestedJar(jarFile, entry, dest);
nested.add(new NestedJarBundleFile(location, dest));
}
}
return nested;
}
@Override
public String getSymbolicName() {
try {
String value = jarFile.getManifest().getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
return value != null ? BundleManifestReader.removePropertiesFromHeaderValue(value) : null;
} catch (IOException e) {
return null;
}
}
@Override
public URL getURL() {
try {
return new File(jarFile.getName()).toURI().toURL();
} catch (MalformedURLException e) {
return null;
}
}
public URL getJarURL() {
try {
String url = new File(jarFile.getName()).toURI().toURL().toExternalForm();
return new URL("jar:" + url + "!/");
} catch (MalformedURLException e) {
return null;
}
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public boolean isJar() {
return true;
}
@Override
public String toString() {
return getLocation();
}
protected final URL getEntryUrl(String name) throws MalformedURLException {
return new URL(urlBase + name);
}
@Override
public void close(OSGiAdapter osgi) throws IOException {
if (jarFile == null) {
return;
}
try {
osgi.getURLJarFileCloser().close(jarFile);
} finally {
jarFile = null;
}
}
}