/* | |
* $Id: BundleInfo.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $ | |
* | |
* Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved. | |
* | |
* 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. | |
*/ | |
package org.osgi.impl.bundle.obr.resource; | |
import java.io.*; | |
import java.net.URL; | |
import java.util.*; | |
import java.util.zip.*; | |
import org.osgi.service.obr.Resource; | |
/** | |
* Convert a bundle to a generic resource description and store its local | |
* dependencies (like for example a license file in the JAR) in a zip file. | |
* | |
* @version $Revision: 44 $ | |
*/ | |
public class BundleInfo { | |
Manifest manifest; | |
File bundleJar; | |
ZipFile jar; | |
String license; | |
Properties localization; | |
RepositoryImpl repository; | |
/** | |
* Parse a zipFile from the file system. We only need the manifest and the | |
* localization. So a zip file is used to minimze memory consumption. | |
* | |
* @param bundleJar Path name | |
* @throws Exception Any errors that occur | |
*/ | |
public BundleInfo(RepositoryImpl repository, File bundleJar) throws Exception { | |
this.bundleJar = bundleJar; | |
this.repository = repository; | |
if (!this.bundleJar.exists()) | |
throw new FileNotFoundException(bundleJar.toString()); | |
jar = new ZipFile(bundleJar); | |
ZipEntry entry = jar.getEntry("META-INF/MANIFEST.MF"); | |
if (entry == null) | |
throw new FileNotFoundException("No Manifest in " | |
+ bundleJar.toString()); | |
manifest = new Manifest(jar.getInputStream(entry)); | |
} | |
public BundleInfo(Manifest manifest) throws Exception { | |
this.manifest = manifest; | |
} | |
/** | |
* Convert the bundle to a Resource. All URIs are going to be abslute, but | |
* could be local. | |
* | |
* @return the resource | |
* @throws Exception | |
*/ | |
public ResourceImpl build() throws Exception { | |
ResourceImpl resource; | |
// Setup the manifest | |
// and create a resource | |
resource = new ResourceImpl(repository, manifest.getSymbolicName(), manifest | |
.getVersion()); | |
try { | |
// Calculate the location URL of the JAR | |
URL location = new URL("jar:" + bundleJar.toURL().toString() + "!/"); | |
resource.setURL(bundleJar.toURL()); | |
resource.setFile(bundleJar); | |
doReferences(resource, location); | |
doSize(resource); | |
doCategories(resource); | |
doImportExportServices(resource); | |
doDeclarativeServices(resource); | |
doFragment(resource); | |
doRequires(resource); | |
doBundle(resource); | |
doExports(resource); | |
doImports(resource); | |
doExecutionEnvironment(resource); | |
return resource; | |
} | |
finally { | |
try { | |
jar.close(); | |
} | |
catch (Exception e) { | |
// ignore | |
} | |
} | |
} | |
/** | |
* Check the size and add it. | |
* | |
* @param resource | |
*/ | |
void doSize(ResourceImpl resource) { | |
long size = bundleJar.length(); | |
if (size > 0) | |
resource.setSize(size); | |
} | |
/** | |
* Find the categories, break them up and add them. | |
* | |
* @param resource | |
*/ | |
void doCategories(ResourceImpl resource) { | |
for (int i = 0; i < manifest.getCategories().length; i++) { | |
String category = manifest.getCategories()[i]; | |
resource.addCategory(category); | |
} | |
} | |
void doReferences(ResourceImpl resource, URL location) { | |
// Presentation name | |
String name = translated("Bundle-Name"); | |
if (name != null) | |
resource.setPresentationName(name); | |
// Handle license. -l allows a global license | |
// set when no license is included. | |
String license = translated("Bundle-License"); | |
if (license != null) | |
resource.setLicense(toURL(location, license)); | |
else if (this.license != null) | |
resource.setLicense(toURL(location, this.license)); | |
String description = translated("Bundle-Description"); | |
if (description != null) | |
resource.setDescription(description); | |
String copyright = translated("Bundle-Copyright"); | |
if (copyright != null) | |
resource.setCopyright(copyright); | |
String documentation = translated("Bundle-DocURL"); | |
if (documentation != null) | |
resource.setDocumentation(toURL(location, documentation)); | |
String source = manifest.getValue("Bundle-Source"); | |
if (source != null) | |
resource.setSource(toURL(location, source)); | |
} | |
URL toURL(URL location, String source) { | |
try { | |
return new URL(location, source); | |
} | |
catch (Exception e) { | |
System.err.println("Error in converting url: " + location + " : " | |
+ source); | |
return null; | |
} | |
} | |
void doDeclarativeServices(ResourceImpl resource) throws Exception { | |
String serviceComponent = manifest.getValue("service-component"); | |
if (serviceComponent == null) | |
return; | |
StringTokenizer st = new StringTokenizer(serviceComponent, " ,\t"); | |
String parts[] = new String[st.countTokens()]; | |
for (int i = 0; i < parts.length; i++) | |
parts[i] = st.nextToken(); | |
for (int i = 0; i < parts.length; i++) { | |
ZipEntry entry = jar.getEntry(parts[i]); | |
if (entry == null) { | |
System.err.println("Bad Service-Component header: " | |
+ serviceComponent + ", no such file " + parts[i]); | |
} | |
InputStream in = jar.getInputStream(entry); | |
// TODO parse declarative services files. | |
in.close(); | |
} | |
} | |
void doImportExportServices(ResourceImpl resource) throws IOException { | |
String importServices = manifest.getValue("import-service"); | |
if (importServices != null) { | |
List entries = manifest.getEntries(importServices); | |
for (Iterator i = entries.iterator(); i.hasNext();) { | |
ManifestEntry entry = (ManifestEntry) i.next(); | |
RequirementImpl ri = new RequirementImpl("service"); | |
ri.setFilter(createServiceFilter(entry)); | |
ri.setComment("Import Service " + entry.getName()); | |
// TODO the following is arbitrary | |
ri.setOptional(false); | |
ri.setMultiple(true); | |
resource.addRequirement(ri); | |
} | |
} | |
String exportServices = manifest.getValue("export-service"); | |
if (exportServices != null) { | |
List entries = manifest.getEntries(exportServices); | |
for (Iterator i = entries.iterator(); i.hasNext();) { | |
ManifestEntry entry = (ManifestEntry) i.next(); | |
CapabilityImpl cap = createServiceCapability(entry); | |
resource.addCapability(cap); | |
} | |
} | |
} | |
String translated(String key) { | |
return translate(manifest.getValue(key)); | |
} | |
void doFragment(ResourceImpl resource) { | |
// Check if we are a fragment | |
ManifestEntry entry = manifest.getHost(); | |
if (entry == null) { | |
return; | |
} | |
else { | |
// We are a fragment, create a requirement | |
// to our host. | |
RequirementImpl r = new RequirementImpl("bundle"); | |
StringBuffer sb = new StringBuffer(); | |
sb.append("(&(symbolicname="); | |
sb.append(entry.getName()); | |
sb.append(")(version>="); | |
sb.append(entry.getVersion()); | |
sb.append("))"); | |
r.setFilter(sb.toString()); | |
r.setComment("Required Host " + entry.getName() ); | |
r.setExtend(true); | |
r.setOptional(false); | |
r.setMultiple(false); | |
resource.addRequirement(r); | |
// And insert a capability that we are available | |
// as a fragment. ### Do we need that with extend? | |
CapabilityImpl capability = new CapabilityImpl("fragment"); | |
capability.addProperty("host", entry.getName()); | |
capability.addProperty("version", entry.getVersion()); | |
resource.addCapability(capability); | |
} | |
} | |
void doRequires(ResourceImpl resource) { | |
List entries = manifest.getRequire(); | |
if (entries == null) | |
return; | |
for (Iterator i = entries.iterator(); i.hasNext();) { | |
ManifestEntry entry = (ManifestEntry) i.next(); | |
RequirementImpl r = new RequirementImpl("bundle"); | |
StringBuffer sb = new StringBuffer(); | |
sb.append("(&(symbolicname="); | |
sb.append(entry.getName()); | |
sb.append(")(version>="); | |
sb.append(entry.getVersion()); | |
sb.append("))"); | |
r.setFilter(sb.toString()); | |
r.setComment("Require Bundle " + entry.getName() + "; " | |
+ entry.getVersion()); | |
if (entry.directives == null | |
|| "true".equalsIgnoreCase((String) entry.directives | |
.get("resolution"))) | |
r.setOptional(false); | |
else | |
r.setOptional(true); | |
resource.addRequirement(r); | |
} | |
} | |
void doExecutionEnvironment(ResourceImpl resource) { | |
String[] parts = manifest.getRequiredExecutionEnvironments(); | |
if (parts == null) | |
return; | |
StringBuffer sb = new StringBuffer(); | |
sb.append("(|"); | |
for (int i = 0; i < parts.length; i++) { | |
String part = parts[i]; | |
sb.append("(ee="); | |
sb.append(part); | |
sb.append(")"); | |
} | |
sb.append(")"); | |
RequirementImpl req = new RequirementImpl("ee"); | |
req.setFilter(sb.toString()); | |
req.setComment("Execution Environment " + sb.toString()); | |
resource.addRequirement(req); | |
} | |
void doImports(ResourceImpl resource) { | |
List requirements = new ArrayList(); | |
List packages = manifest.getImports(); | |
if (packages == null) | |
return; | |
for (Iterator i = packages.iterator(); i.hasNext();) { | |
ManifestEntry pack = (ManifestEntry) i.next(); | |
RequirementImpl requirement = new RequirementImpl("package"); | |
createImportFilter(requirement, "package", pack); | |
requirement.setComment("Import package " + pack); | |
requirements.add(requirement); | |
} | |
for (Iterator i = requirements.iterator(); i.hasNext();) | |
resource.addRequirement((RequirementImpl) i.next()); | |
} | |
String createServiceFilter(ManifestEntry pack) { | |
StringBuffer filter = new StringBuffer(); | |
filter.append("(service="); | |
filter.append(pack.getName()); | |
filter.append(")"); | |
return filter.toString(); | |
} | |
void createImportFilter(RequirementImpl req, String name, ManifestEntry pack) { | |
StringBuffer filter = new StringBuffer(); | |
filter.append("(&("); | |
filter.append(name); | |
filter.append("="); | |
filter.append(pack.getName()); | |
filter.append(")"); | |
VersionRange version = pack.getVersion(); | |
if (version != null) { | |
if ( version.isRange() ) { | |
filter.append("(version"); | |
filter.append(">"); | |
if (version.includeLow()) | |
filter.append("="); | |
filter.append(version.low); | |
filter.append(")"); | |
filter.append("(version"); | |
filter.append("<"); | |
if (version.includeHigh()) | |
filter.append("="); | |
filter.append(version.high); | |
filter.append(")"); | |
} | |
else { | |
filter.append("(version>="); | |
filter.append(pack.getVersion()); | |
filter.append(")"); | |
} | |
} | |
Map attributes = pack.getAttributes(); | |
Set attrs = doImportPackageAttributes(req, filter, attributes); | |
if (attrs.size() > 0) { | |
String del = ""; | |
filter.append("(mandatory:<*"); | |
for (Iterator i = attrs.iterator(); i.hasNext();) { | |
filter.append(del); | |
filter.append(i.next()); | |
del = ", "; | |
} | |
filter.append(")"); | |
} | |
filter.append(")"); | |
req.setFilter(filter.toString()); | |
} | |
Set doImportPackageAttributes(RequirementImpl req, StringBuffer filter, | |
Map attributes) { | |
HashSet set = new HashSet(); | |
if (attributes != null) | |
for (Iterator i = attributes.keySet().iterator(); i.hasNext();) { | |
String attribute = (String) i.next(); | |
String value = (String) attributes.get(attribute); | |
if (attribute.equalsIgnoreCase("specification-version") | |
|| attribute.equalsIgnoreCase("version")) | |
continue; | |
else if (attribute.equalsIgnoreCase("resolution:")) { | |
req.setOptional(value.equalsIgnoreCase("optional")); | |
} | |
if (attribute.endsWith(":")) { | |
// Ignore | |
} | |
else { | |
filter.append("("); | |
filter.append(attribute); | |
filter.append("="); | |
filter.append(attributes.get(attribute)); | |
filter.append(")"); | |
set.add(attribute); | |
} | |
} | |
return set; | |
} | |
void doBundle(ResourceImpl resource) { | |
CapabilityImpl capability = new CapabilityImpl("bundle"); | |
capability.addProperty("symbolicname", manifest.getSymbolicName()); | |
if (manifest.getValue("Bundle-Name") != null) | |
capability.addProperty( | |
Resource.PRESENTATION_NAME, | |
translated("Bundle-Name")); | |
capability.addProperty("version", manifest.getVersion()); | |
capability | |
.addProperty("manifestversion", manifest.getManifestVersion()); | |
/** | |
* Is this needed TODO | |
*/ | |
ManifestEntry host = manifest.getHost(); | |
if (host != null) { | |
capability.addProperty("host", host.getName()); | |
if (host.getVersion() != null) | |
capability.addProperty("version", host.getVersion()); | |
} | |
resource.addCapability(capability); | |
} | |
void doExports(ResourceImpl resource) { | |
List capabilities = new ArrayList(); | |
List packages = manifest.getExports(); | |
if (packages != null) { | |
for (Iterator i = packages.iterator(); i.hasNext();) { | |
ManifestEntry pack = (ManifestEntry) i.next(); | |
CapabilityImpl capability = createCapability("package", pack); | |
capabilities.add(capability); | |
} | |
} | |
for (Iterator i = capabilities.iterator(); i.hasNext();) | |
resource.addCapability((CapabilityImpl) i.next()); | |
} | |
CapabilityImpl createServiceCapability(ManifestEntry pack) { | |
CapabilityImpl capability = new CapabilityImpl("service"); | |
capability.addProperty("service", pack.getName()); | |
return capability; | |
} | |
CapabilityImpl createCapability(String name, ManifestEntry pack) { | |
CapabilityImpl capability = new CapabilityImpl(name); | |
capability.addProperty(name, pack.getName()); | |
capability.addProperty("version", pack.getVersion()); | |
Map attributes = pack.getAttributes(); | |
if (attributes != null) | |
for (Iterator at = attributes.keySet().iterator(); at.hasNext();) { | |
String key = (String) at.next(); | |
if (key.equalsIgnoreCase("specification-version") | |
|| key.equalsIgnoreCase("version")) | |
continue; | |
else { | |
Object value = attributes.get(key); | |
capability.addProperty(key, value); | |
} | |
} | |
return capability; | |
} | |
String translate(String s) { | |
if (s == null) | |
return null; | |
if (!s.startsWith("%")) { | |
return s; | |
} | |
if (localization == null) | |
try { | |
localization = new Properties(); | |
String path = manifest | |
.getValue("Bundle-Localization", "bundle"); | |
path += ".properties"; | |
InputStream in = jar.getInputStream(new ZipEntry(path)); | |
if (in != null) { | |
localization.load(in); | |
in.close(); | |
} | |
} | |
catch (IOException e) { | |
e.printStackTrace(); | |
} | |
s = s.substring(1); | |
return localization.getProperty(s, s); | |
} | |
File getZipFile() { | |
return bundleJar; | |
} | |
} |