Clement Escoffier | 491a123 | 2007-12-04 16:09:03 +0000 | [diff] [blame] | 1 | /*
|
| 2 | * $Id: BundleInfo.java 44 2007-07-13 20:49:41Z hargrave@us.ibm.com $
|
| 3 | *
|
| 4 | * Copyright (c) OSGi Alliance (2002, 2006, 2007). All Rights Reserved.
|
| 5 | *
|
| 6 | * Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 | * you may not use this file except in compliance with the License.
|
| 8 | * You may obtain a copy of the License at
|
| 9 | *
|
| 10 | * http://www.apache.org/licenses/LICENSE-2.0
|
| 11 | *
|
| 12 | * Unless required by applicable law or agreed to in writing, software
|
| 13 | * distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 | * See the License for the specific language governing permissions and
|
| 16 | * limitations under the License.
|
| 17 | */
|
Stefano Lenzi | 476013d | 2007-09-21 23:59:54 +0000 | [diff] [blame] | 18 | package org.osgi.impl.bundle.obr.resource;
|
| 19 |
|
| 20 | import java.io.*;
|
| 21 | import java.net.URL;
|
| 22 | import java.util.*;
|
| 23 | import java.util.zip.*;
|
| 24 |
|
| 25 | import org.osgi.service.obr.Resource;
|
| 26 |
|
| 27 | /**
|
| 28 | * Convert a bundle to a generic resource description and store its local
|
| 29 | * dependencies (like for example a license file in the JAR) in a zip file.
|
| 30 | *
|
Clement Escoffier | 491a123 | 2007-12-04 16:09:03 +0000 | [diff] [blame] | 31 | * @version $Revision: 44 $
|
Stefano Lenzi | 476013d | 2007-09-21 23:59:54 +0000 | [diff] [blame] | 32 | */
|
| 33 | public class BundleInfo {
|
| 34 | Manifest manifest;
|
| 35 | File bundleJar;
|
| 36 | ZipFile jar;
|
| 37 | String license;
|
| 38 | Properties localization;
|
| 39 | RepositoryImpl repository;
|
| 40 |
|
| 41 | /**
|
| 42 | * Parse a zipFile from the file system. We only need the manifest and the
|
| 43 | * localization. So a zip file is used to minimze memory consumption.
|
| 44 | *
|
| 45 | * @param bundleJar Path name
|
| 46 | * @throws Exception Any errors that occur
|
| 47 | */
|
| 48 | public BundleInfo(RepositoryImpl repository, File bundleJar) throws Exception {
|
| 49 | this.bundleJar = bundleJar;
|
| 50 | this.repository = repository;
|
| 51 |
|
| 52 | if (!this.bundleJar.exists())
|
| 53 | throw new FileNotFoundException(bundleJar.toString());
|
| 54 |
|
| 55 | jar = new ZipFile(bundleJar);
|
| 56 | ZipEntry entry = jar.getEntry("META-INF/MANIFEST.MF");
|
| 57 | if (entry == null)
|
| 58 | throw new FileNotFoundException("No Manifest in "
|
| 59 | + bundleJar.toString());
|
| 60 | manifest = new Manifest(jar.getInputStream(entry));
|
| 61 | }
|
| 62 |
|
| 63 | public BundleInfo(Manifest manifest) throws Exception {
|
| 64 | this.manifest = manifest;
|
| 65 | }
|
| 66 |
|
| 67 | /**
|
| 68 | * Convert the bundle to a Resource. All URIs are going to be abslute, but
|
| 69 | * could be local.
|
| 70 | *
|
| 71 | * @return the resource
|
| 72 | * @throws Exception
|
| 73 | */
|
| 74 | public ResourceImpl build() throws Exception {
|
| 75 | ResourceImpl resource;
|
| 76 | // Setup the manifest
|
| 77 | // and create a resource
|
| 78 | resource = new ResourceImpl(repository, manifest.getSymbolicName(), manifest
|
| 79 | .getVersion());
|
| 80 |
|
| 81 | try {
|
| 82 |
|
| 83 | // Calculate the location URL of the JAR
|
| 84 | URL location = new URL("jar:" + bundleJar.toURL().toString() + "!/");
|
| 85 | resource.setURL(bundleJar.toURL());
|
| 86 | resource.setFile(bundleJar);
|
| 87 |
|
| 88 | doReferences(resource, location);
|
| 89 | doSize(resource);
|
| 90 | doCategories(resource);
|
| 91 | doImportExportServices(resource);
|
| 92 | doDeclarativeServices(resource);
|
| 93 | doFragment(resource);
|
| 94 | doRequires(resource);
|
| 95 | doBundle(resource);
|
| 96 | doExports(resource);
|
| 97 | doImports(resource);
|
| 98 | doExecutionEnvironment(resource);
|
| 99 |
|
| 100 | return resource;
|
| 101 | }
|
| 102 | finally {
|
| 103 | try {
|
| 104 | jar.close();
|
| 105 | }
|
| 106 | catch (Exception e) {
|
| 107 | // ignore
|
| 108 | }
|
| 109 | }
|
| 110 | }
|
| 111 |
|
| 112 | /**
|
| 113 | * Check the size and add it.
|
| 114 | *
|
| 115 | * @param resource
|
| 116 | */
|
| 117 | void doSize(ResourceImpl resource) {
|
| 118 | long size = bundleJar.length();
|
| 119 | if (size > 0)
|
| 120 | resource.setSize(size);
|
| 121 | }
|
| 122 |
|
| 123 | /**
|
| 124 | * Find the categories, break them up and add them.
|
| 125 | *
|
| 126 | * @param resource
|
| 127 | */
|
| 128 | void doCategories(ResourceImpl resource) {
|
| 129 | for (int i = 0; i < manifest.getCategories().length; i++) {
|
| 130 | String category = manifest.getCategories()[i];
|
| 131 | resource.addCategory(category);
|
| 132 | }
|
| 133 | }
|
| 134 |
|
| 135 | void doReferences(ResourceImpl resource, URL location) {
|
| 136 | // Presentation name
|
| 137 | String name = translated("Bundle-Name");
|
| 138 | if (name != null)
|
| 139 | resource.setPresentationName(name);
|
| 140 |
|
| 141 | // Handle license. -l allows a global license
|
| 142 | // set when no license is included.
|
| 143 |
|
| 144 | String license = translated("Bundle-License");
|
| 145 | if (license != null)
|
| 146 | resource.setLicense(toURL(location, license));
|
| 147 | else if (this.license != null)
|
| 148 | resource.setLicense(toURL(location, this.license));
|
| 149 |
|
| 150 | String description = translated("Bundle-Description");
|
| 151 | if (description != null)
|
| 152 | resource.setDescription(description);
|
| 153 |
|
| 154 | String copyright = translated("Bundle-Copyright");
|
| 155 | if (copyright != null)
|
| 156 | resource.setCopyright(copyright);
|
| 157 |
|
| 158 | String documentation = translated("Bundle-DocURL");
|
| 159 | if (documentation != null)
|
| 160 | resource.setDocumentation(toURL(location, documentation));
|
| 161 |
|
| 162 | String source = manifest.getValue("Bundle-Source");
|
| 163 | if (source != null)
|
| 164 | resource.setSource(toURL(location, source));
|
| 165 | }
|
| 166 |
|
| 167 | URL toURL(URL location, String source) {
|
| 168 | try {
|
| 169 | return new URL(location, source);
|
| 170 | }
|
| 171 | catch (Exception e) {
|
| 172 | System.err.println("Error in converting url: " + location + " : "
|
| 173 | + source);
|
| 174 | return null;
|
| 175 | }
|
| 176 | }
|
| 177 |
|
| 178 | void doDeclarativeServices(ResourceImpl resource) throws Exception {
|
| 179 | String serviceComponent = manifest.getValue("service-component");
|
| 180 | if (serviceComponent == null)
|
| 181 | return;
|
| 182 |
|
| 183 | StringTokenizer st = new StringTokenizer(serviceComponent, " ,\t");
|
| 184 | String parts[] = new String[st.countTokens()];
|
| 185 | for (int i = 0; i < parts.length; i++)
|
| 186 | parts[i] = st.nextToken();
|
| 187 |
|
| 188 | for (int i = 0; i < parts.length; i++) {
|
| 189 | ZipEntry entry = jar.getEntry(parts[i]);
|
| 190 | if (entry == null) {
|
| 191 | System.err.println("Bad Service-Component header: "
|
| 192 | + serviceComponent + ", no such file " + parts[i]);
|
| 193 | }
|
| 194 | InputStream in = jar.getInputStream(entry);
|
| 195 | // TODO parse declarative services files.
|
| 196 | in.close();
|
| 197 | }
|
| 198 | }
|
| 199 |
|
| 200 | void doImportExportServices(ResourceImpl resource) throws IOException {
|
| 201 | String importServices = manifest.getValue("import-service");
|
| 202 | if (importServices != null) {
|
| 203 | List entries = manifest.getEntries(importServices);
|
| 204 | for (Iterator i = entries.iterator(); i.hasNext();) {
|
| 205 | ManifestEntry entry = (ManifestEntry) i.next();
|
| 206 | RequirementImpl ri = new RequirementImpl("service");
|
| 207 | ri.setFilter(createServiceFilter(entry));
|
| 208 | ri.setComment("Import Service " + entry.getName());
|
| 209 |
|
| 210 | // TODO the following is arbitrary
|
Clement Escoffier | 491a123 | 2007-12-04 16:09:03 +0000 | [diff] [blame] | 211 | ri.setOptional(false);
|
| 212 | ri.setMultiple(true);
|
Stefano Lenzi | 476013d | 2007-09-21 23:59:54 +0000 | [diff] [blame] | 213 | resource.addRequirement(ri);
|
| 214 | }
|
| 215 | }
|
| 216 |
|
| 217 | String exportServices = manifest.getValue("export-service");
|
| 218 | if (exportServices != null) {
|
| 219 | List entries = manifest.getEntries(exportServices);
|
| 220 | for (Iterator i = entries.iterator(); i.hasNext();) {
|
| 221 | ManifestEntry entry = (ManifestEntry) i.next();
|
| 222 | CapabilityImpl cap = createServiceCapability(entry);
|
| 223 | resource.addCapability(cap);
|
| 224 | }
|
| 225 | }
|
| 226 | }
|
| 227 |
|
| 228 | String translated(String key) {
|
| 229 | return translate(manifest.getValue(key));
|
| 230 | }
|
| 231 |
|
| 232 | void doFragment(ResourceImpl resource) {
|
| 233 | // Check if we are a fragment
|
| 234 | ManifestEntry entry = manifest.getHost();
|
| 235 | if (entry == null) {
|
| 236 | return;
|
| 237 | }
|
| 238 | else {
|
| 239 | // We are a fragment, create a requirement
|
| 240 | // to our host.
|
| 241 | RequirementImpl r = new RequirementImpl("bundle");
|
| 242 | StringBuffer sb = new StringBuffer();
|
| 243 | sb.append("(&(symbolicname=");
|
| 244 | sb.append(entry.getName());
|
| 245 | sb.append(")(version>=");
|
| 246 | sb.append(entry.getVersion());
|
| 247 | sb.append("))");
|
| 248 | r.setFilter(sb.toString());
|
| 249 | r.setComment("Required Host " + entry.getName() );
|
| 250 | r.setExtend(true);
|
| 251 | r.setOptional(false);
|
| 252 | r.setMultiple(false);
|
| 253 | resource.addRequirement(r);
|
| 254 |
|
| 255 | // And insert a capability that we are available
|
| 256 | // as a fragment. ### Do we need that with extend?
|
| 257 | CapabilityImpl capability = new CapabilityImpl("fragment");
|
| 258 | capability.addProperty("host", entry.getName());
|
| 259 | capability.addProperty("version", entry.getVersion());
|
| 260 | resource.addCapability(capability);
|
| 261 | }
|
| 262 | }
|
| 263 |
|
| 264 | void doRequires(ResourceImpl resource) {
|
| 265 | List entries = manifest.getRequire();
|
| 266 | if (entries == null)
|
| 267 | return;
|
| 268 |
|
| 269 | for (Iterator i = entries.iterator(); i.hasNext();) {
|
| 270 | ManifestEntry entry = (ManifestEntry) i.next();
|
| 271 | RequirementImpl r = new RequirementImpl("bundle");
|
| 272 |
|
| 273 | StringBuffer sb = new StringBuffer();
|
| 274 | sb.append("(&(symbolicname=");
|
| 275 | sb.append(entry.getName());
|
| 276 | sb.append(")(version>=");
|
| 277 | sb.append(entry.getVersion());
|
| 278 | sb.append("))");
|
| 279 | r.setFilter(sb.toString());
|
| 280 | r.setComment("Require Bundle " + entry.getName() + "; "
|
| 281 | + entry.getVersion());
|
| 282 | if (entry.directives == null
|
| 283 | || "true".equalsIgnoreCase((String) entry.directives
|
| 284 | .get("resolution")))
|
| 285 | r.setOptional(false);
|
| 286 | else
|
| 287 | r.setOptional(true);
|
| 288 | resource.addRequirement(r);
|
| 289 | }
|
| 290 | }
|
| 291 |
|
| 292 | void doExecutionEnvironment(ResourceImpl resource) {
|
| 293 | String[] parts = manifest.getRequiredExecutionEnvironments();
|
| 294 | if (parts == null)
|
| 295 | return;
|
| 296 |
|
| 297 | StringBuffer sb = new StringBuffer();
|
| 298 | sb.append("(|");
|
| 299 | for (int i = 0; i < parts.length; i++) {
|
| 300 | String part = parts[i];
|
| 301 | sb.append("(ee=");
|
| 302 | sb.append(part);
|
| 303 | sb.append(")");
|
| 304 | }
|
| 305 | sb.append(")");
|
| 306 |
|
| 307 | RequirementImpl req = new RequirementImpl("ee");
|
| 308 | req.setFilter(sb.toString());
|
| 309 | req.setComment("Execution Environment " + sb.toString());
|
| 310 | resource.addRequirement(req);
|
| 311 | }
|
| 312 |
|
| 313 | void doImports(ResourceImpl resource) {
|
| 314 | List requirements = new ArrayList();
|
| 315 | List packages = manifest.getImports();
|
| 316 | if (packages == null)
|
| 317 | return;
|
| 318 |
|
| 319 | for (Iterator i = packages.iterator(); i.hasNext();) {
|
| 320 | ManifestEntry pack = (ManifestEntry) i.next();
|
| 321 | RequirementImpl requirement = new RequirementImpl("package");
|
| 322 |
|
| 323 | createImportFilter(requirement, "package", pack);
|
| 324 | requirement.setComment("Import package " + pack);
|
| 325 | requirements.add(requirement);
|
| 326 | }
|
| 327 | for (Iterator i = requirements.iterator(); i.hasNext();)
|
| 328 | resource.addRequirement((RequirementImpl) i.next());
|
| 329 | }
|
| 330 |
|
| 331 | String createServiceFilter(ManifestEntry pack) {
|
| 332 | StringBuffer filter = new StringBuffer();
|
| 333 | filter.append("(service=");
|
| 334 | filter.append(pack.getName());
|
| 335 | filter.append(")");
|
| 336 | return filter.toString();
|
| 337 | }
|
| 338 |
|
| 339 | void createImportFilter(RequirementImpl req, String name, ManifestEntry pack) {
|
| 340 | StringBuffer filter = new StringBuffer();
|
| 341 | filter.append("(&(");
|
| 342 | filter.append(name);
|
| 343 | filter.append("=");
|
| 344 | filter.append(pack.getName());
|
| 345 | filter.append(")");
|
Clement Escoffier | 491a123 | 2007-12-04 16:09:03 +0000 | [diff] [blame] | 346 | VersionRange version = pack.getVersion();
|
Stefano Lenzi | 476013d | 2007-09-21 23:59:54 +0000 | [diff] [blame] | 347 | if (version != null) {
|
Clement Escoffier | 491a123 | 2007-12-04 16:09:03 +0000 | [diff] [blame] | 348 | if ( version.isRange() ) {
|
Stefano Lenzi | 476013d | 2007-09-21 23:59:54 +0000 | [diff] [blame] | 349 | filter.append("(version");
|
| 350 | filter.append(">");
|
Clement Escoffier | 491a123 | 2007-12-04 16:09:03 +0000 | [diff] [blame] | 351 | if (version.includeLow())
|
Stefano Lenzi | 476013d | 2007-09-21 23:59:54 +0000 | [diff] [blame] | 352 | filter.append("=");
|
Clement Escoffier | 491a123 | 2007-12-04 16:09:03 +0000 | [diff] [blame] | 353 | filter.append(version.low);
|
Stefano Lenzi | 476013d | 2007-09-21 23:59:54 +0000 | [diff] [blame] | 354 | filter.append(")");
|
| 355 |
|
| 356 | filter.append("(version");
|
| 357 | filter.append("<");
|
Clement Escoffier | 491a123 | 2007-12-04 16:09:03 +0000 | [diff] [blame] | 358 | if (version.includeHigh())
|
Stefano Lenzi | 476013d | 2007-09-21 23:59:54 +0000 | [diff] [blame] | 359 | filter.append("=");
|
Clement Escoffier | 491a123 | 2007-12-04 16:09:03 +0000 | [diff] [blame] | 360 | filter.append(version.high);
|
Stefano Lenzi | 476013d | 2007-09-21 23:59:54 +0000 | [diff] [blame] | 361 | filter.append(")");
|
| 362 | }
|
| 363 | else {
|
| 364 | filter.append("(version>=");
|
| 365 | filter.append(pack.getVersion());
|
| 366 | filter.append(")");
|
| 367 | }
|
| 368 | }
|
| 369 | Map attributes = pack.getAttributes();
|
| 370 | Set attrs = doImportPackageAttributes(req, filter, attributes);
|
| 371 | if (attrs.size() > 0) {
|
| 372 | String del = "";
|
| 373 | filter.append("(mandatory:<*");
|
| 374 | for (Iterator i = attrs.iterator(); i.hasNext();) {
|
| 375 | filter.append(del);
|
| 376 | filter.append(i.next());
|
| 377 | del = ", ";
|
| 378 | }
|
| 379 | filter.append(")");
|
| 380 | }
|
| 381 | filter.append(")");
|
| 382 | req.setFilter(filter.toString());
|
| 383 | }
|
| 384 |
|
| 385 | Set doImportPackageAttributes(RequirementImpl req, StringBuffer filter,
|
| 386 | Map attributes) {
|
| 387 | HashSet set = new HashSet();
|
| 388 |
|
| 389 | if (attributes != null)
|
| 390 | for (Iterator i = attributes.keySet().iterator(); i.hasNext();) {
|
| 391 | String attribute = (String) i.next();
|
| 392 | String value = (String) attributes.get(attribute);
|
| 393 | if (attribute.equalsIgnoreCase("specification-version")
|
| 394 | || attribute.equalsIgnoreCase("version"))
|
| 395 | continue;
|
| 396 | else if (attribute.equalsIgnoreCase("resolution:")) {
|
| 397 | req.setOptional(value.equalsIgnoreCase("optional"));
|
| 398 | }
|
| 399 | if (attribute.endsWith(":")) {
|
| 400 | // Ignore
|
| 401 | }
|
| 402 | else {
|
| 403 | filter.append("(");
|
| 404 | filter.append(attribute);
|
| 405 | filter.append("=");
|
| 406 | filter.append(attributes.get(attribute));
|
| 407 | filter.append(")");
|
| 408 | set.add(attribute);
|
| 409 | }
|
| 410 | }
|
| 411 | return set;
|
| 412 | }
|
| 413 |
|
| 414 | void doBundle(ResourceImpl resource) {
|
| 415 | CapabilityImpl capability = new CapabilityImpl("bundle");
|
| 416 | capability.addProperty("symbolicname", manifest.getSymbolicName());
|
| 417 | if (manifest.getValue("Bundle-Name") != null)
|
| 418 | capability.addProperty(
|
| 419 | Resource.PRESENTATION_NAME,
|
| 420 | translated("Bundle-Name"));
|
| 421 | capability.addProperty("version", manifest.getVersion());
|
| 422 | capability
|
| 423 | .addProperty("manifestversion", manifest.getManifestVersion());
|
| 424 |
|
| 425 | /**
|
| 426 | * Is this needed TODO
|
| 427 | */
|
| 428 | ManifestEntry host = manifest.getHost();
|
| 429 | if (host != null) {
|
| 430 | capability.addProperty("host", host.getName());
|
| 431 | if (host.getVersion() != null)
|
| 432 | capability.addProperty("version", host.getVersion());
|
| 433 | }
|
| 434 | resource.addCapability(capability);
|
| 435 | }
|
| 436 |
|
| 437 | void doExports(ResourceImpl resource) {
|
| 438 | List capabilities = new ArrayList();
|
| 439 | List packages = manifest.getExports();
|
| 440 | if (packages != null) {
|
| 441 | for (Iterator i = packages.iterator(); i.hasNext();) {
|
| 442 | ManifestEntry pack = (ManifestEntry) i.next();
|
| 443 | CapabilityImpl capability = createCapability("package", pack);
|
| 444 | capabilities.add(capability);
|
| 445 | }
|
| 446 | }
|
| 447 | for (Iterator i = capabilities.iterator(); i.hasNext();)
|
| 448 | resource.addCapability((CapabilityImpl) i.next());
|
| 449 | }
|
| 450 |
|
| 451 | CapabilityImpl createServiceCapability(ManifestEntry pack) {
|
| 452 | CapabilityImpl capability = new CapabilityImpl("service");
|
| 453 | capability.addProperty("service", pack.getName());
|
| 454 | return capability;
|
| 455 | }
|
| 456 |
|
| 457 | CapabilityImpl createCapability(String name, ManifestEntry pack) {
|
| 458 | CapabilityImpl capability = new CapabilityImpl(name);
|
| 459 | capability.addProperty(name, pack.getName());
|
| 460 | capability.addProperty("version", pack.getVersion());
|
| 461 | Map attributes = pack.getAttributes();
|
| 462 | if (attributes != null)
|
| 463 | for (Iterator at = attributes.keySet().iterator(); at.hasNext();) {
|
| 464 | String key = (String) at.next();
|
| 465 | if (key.equalsIgnoreCase("specification-version")
|
| 466 | || key.equalsIgnoreCase("version"))
|
| 467 | continue;
|
| 468 | else {
|
| 469 | Object value = attributes.get(key);
|
| 470 | capability.addProperty(key, value);
|
| 471 | }
|
| 472 | }
|
| 473 | return capability;
|
| 474 | }
|
| 475 |
|
| 476 | String translate(String s) {
|
| 477 | if (s == null)
|
| 478 | return null;
|
| 479 |
|
| 480 | if (!s.startsWith("%")) {
|
| 481 | return s;
|
| 482 | }
|
| 483 |
|
| 484 | if (localization == null)
|
| 485 | try {
|
| 486 | localization = new Properties();
|
| 487 | String path = manifest
|
| 488 | .getValue("Bundle-Localization", "bundle");
|
| 489 | path += ".properties";
|
| 490 | InputStream in = jar.getInputStream(new ZipEntry(path));
|
| 491 | if (in != null) {
|
| 492 | localization.load(in);
|
| 493 | in.close();
|
| 494 | }
|
| 495 | }
|
| 496 | catch (IOException e) {
|
| 497 | e.printStackTrace();
|
| 498 | }
|
| 499 | s = s.substring(1);
|
| 500 | return localization.getProperty(s, s);
|
| 501 | }
|
| 502 |
|
| 503 | File getZipFile() {
|
| 504 | return bundleJar;
|
| 505 | }
|
| 506 | } |