/* * 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 WARRANTIESOR 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.aries.application.modelling.utils; import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.jar.Attributes; import org.apache.aries.application.InvalidAttributeException; import org.apache.aries.application.management.ResolverException; import org.apache.aries.application.modelling.DeployedBundles; import org.apache.aries.application.modelling.ExportedService; import org.apache.aries.application.modelling.ImportedBundle; import org.apache.aries.application.modelling.ImportedService; import org.apache.aries.application.modelling.ModelledResource; import org.apache.aries.application.modelling.impl.ExportedServiceImpl; import org.apache.aries.application.modelling.impl.ImportedBundleImpl; import org.apache.aries.application.modelling.impl.ImportedServiceImpl; import org.apache.aries.application.modelling.impl.ModelledResourceImpl; import org.apache.aries.application.modelling.utils.impl.ModellingHelperImpl; import org.apache.aries.util.manifest.ManifestHeaderProcessor; import org.apache.aries.util.manifest.ManifestHeaderProcessor.NameValuePair; import org.junit.Assert; import org.junit.Test; import org.osgi.framework.Constants; public final class DeployedBundlesTest { private DeployedBundles validDeployedBundles() throws Exception { Collection<ImportedBundle> content = new ArrayList<ImportedBundle>(); Collection<ImportedBundle> uses = new ArrayList<ImportedBundle>(); content.add(new ImportedBundleImpl("bundle.a", "1.0.0")); content.add(new ImportedBundleImpl("bundle.b", "1.0.0")); uses.add(new ImportedBundleImpl("bundle.c", "1.0.0")); uses.add(new ImportedBundleImpl("bundle.d", "1.0.0")); return new ModellingHelperImpl().createDeployedBundles("test",content, uses, null); } private void basicResolve(DeployedBundles db, boolean cPersistent) throws InvalidAttributeException { db.addBundle(createModelledResource("bundle.a", "1.0.0", Arrays.asList("package.b", "package.c"), Arrays.asList("package.a;version=1.0.0"))); db.addBundle(createModelledResource("bundle.b", "1.0.0", Arrays.asList("package.d;version=1.0.0", "package.e;version=\"[1.0.0,2.0.0)\"", "package.g"), Arrays.asList("package.b;version=1.0.0"))); db.addBundle(createModelledResource("bundle.c", "1.0.0", (cPersistent) ? Arrays.asList("package.d;version=\"[1.0.0,2.0.0)\"", "javax.persistence;version=1.1.0") : Arrays.asList("package.d;version=\"[1.0.0,2.0.0)\""), Arrays.asList("package.c;version=1.0.0"))); db.addBundle(createModelledResource("bundle.d", "1.0.0", Arrays.asList("package.e;version=\"[1.0.0,1.0.0]\""), Arrays.asList("package.d;version=1.0.0"))); } private void packagesResolve(DeployedBundles db) throws InvalidAttributeException { basicResolve(db, false); db.addBundle(createModelledResource("bundle.e", "1.0.0", new ArrayList<String>(), Arrays.asList("package.e;version=1.0.0"))); } public static ModelledResource createModelledResource(String bundleName, String bundleVersion, Collection<String> importedPackages, Collection<String> exportedPackages) throws InvalidAttributeException { Attributes att = new Attributes(); att.put(new Attributes.Name(Constants.BUNDLE_SYMBOLICNAME), bundleName); att.put(new Attributes.Name(Constants.BUNDLE_VERSION), bundleVersion); att.put(new Attributes.Name(Constants.BUNDLE_MANIFESTVERSION), "2"); StringBuilder builder = new StringBuilder(); for(String iPackage : importedPackages) { builder.append(iPackage).append(","); } if(builder.length() > 0) { builder.deleteCharAt(builder.length() - 1); att.put(new Attributes.Name(Constants.IMPORT_PACKAGE), builder.toString()); } builder = new StringBuilder(); for(String ePackage : exportedPackages) { builder.append(ePackage).append(","); } if(builder.length() > 0) { builder.deleteCharAt(builder.length() - 1); att.put(new Attributes.Name(Constants.EXPORT_PACKAGE), builder.toString()); } return new ModelledResourceImpl(null, att, null, null); } public static ModelledResource createModelledServiceBundle(String bundleName, String bundleVersion, Collection<String> importService, Collection<String> exportService) throws InvalidAttributeException { Attributes att = new Attributes(); att.put(new Attributes.Name(Constants.BUNDLE_SYMBOLICNAME), bundleName); att.put(new Attributes.Name(Constants.BUNDLE_VERSION), bundleVersion); att.put(new Attributes.Name(Constants.BUNDLE_MANIFESTVERSION), "2"); List<ImportedService> importedServices = new ArrayList<ImportedService>(); for (String s : importService) { importedServices.add(new ImportedServiceImpl(false, s, null, null, null, false)); } List<ExportedService> exportedServices = new ArrayList<ExportedService>(); for (String s : exportService) { exportedServices.add(new ExportedServiceImpl(null, 0, Collections.singleton(s), Collections.<String,Object>emptyMap())); } return new ModelledResourceImpl(null, att, importedServices, exportedServices); } /** * Check the actual results match the expected values, regardless of order of the parts. * @param entry the actual manifest entry. * @param expected the expected manifest entry. * @return true if they match; false otherwise. */ private static boolean isEqual(String actual, String expected) { Map<NameValuePair, Integer> actualEntries = parseEntries(actual); Map<NameValuePair, Integer> expectedEntries = parseEntries(expected); return actualEntries.equals(expectedEntries); } /** * Parse manifest entries into a set of values and associated attributes, which can * be directly compared for equality regardless of ordering. * <p> * Example manifest entry format: value1;attrName1=attrValue1;attrName2=attrValue2,value2;attrName1=attrValue1 * @param entries a manifest header entry. * @return a set of parsed entries. */ private static Map<NameValuePair, Integer> parseEntries(String entries) { Map<NameValuePair, Integer> result = new HashMap<NameValuePair, Integer>(); for (NameValuePair entry : ManifestHeaderProcessor.parseExportString(entries)) { Integer count = result.get(entry); if (count != null) { // This entry already exists to increment the count. count++; } else { count = 1; } result.put(entry, count); } return result; } @Test public void testGetContent_Valid() throws Exception { // Get a valid set of deployment information. DeployedBundles deployedBundles = validDeployedBundles(); packagesResolve(deployedBundles); // Check the deployed content entry is correct. String contentEntry = deployedBundles.getContent(); String expectedResult = "bundle.a;deployed-version=1.0.0,bundle.b;deployed-version=1.0.0"; Assert.assertTrue("Content=" + contentEntry, isEqual(contentEntry, expectedResult)); } @Test public void testGetUseBundle_Valid() throws Exception { // Get a valid set of deployment information. DeployedBundles deployedBundles = validDeployedBundles(); packagesResolve(deployedBundles); // Check the deployed use bundle entry is correct. String useBundleEntry = deployedBundles.getUseBundle(); String expectedResult = "bundle.c;deployed-version=1.0.0,bundle.d;deployed-version=1.0.0"; Assert.assertTrue("UseBundle=" + useBundleEntry, isEqual(useBundleEntry, expectedResult)); } @Test public void testGetProvisionBundle_Valid() throws Exception { // Check the provision bundle entry is correct. DeployedBundles deployedBundles = validDeployedBundles(); packagesResolve(deployedBundles); String provisionBundleEntry = deployedBundles.getProvisionBundle(); String expectedResult = "bundle.e;deployed-version=1.0.0"; Assert.assertTrue("ProvisionBundle=" + provisionBundleEntry, isEqual(provisionBundleEntry, expectedResult)); } @Test public void testGetImportPackage_Valid() throws Exception { // Check the import package entry is correct. String importPackageEntry = null; try { DeployedBundles deployedBundles = validDeployedBundles(); packagesResolve(deployedBundles); importPackageEntry = deployedBundles.getImportPackage(); } catch (ResolverException e) { e.printStackTrace(); Assert.fail(e.toString()); } String expectedResult = "package.c;version=\"1.0.0\";bundle-symbolic-name=\"bundle.c\";bundle-version=\"[1.0.0,1.0.0]\"," + "package.d;version=\"1.0.0\";bundle-symbolic-name=\"bundle.d\";bundle-version=\"[1.0.0,1.0.0]\"," + "package.e;version=\"[1.0.0,2.0.0)\"," + "package.g;version=\"0.0.0\""; /* * String expectedResult = "package.c;bundle-symbolic-name=bundle.c;bundle-version=\"[1.0.0,1.0.0]\"" + ",package.d;version=\"1.0.0\";bundle-symbolic-name=bundle.d;bundle-version=\"[1.0.0,1.0.0]\"" + ",package.e;version=\"[1.0.0,2.0.0)\"" + ",package.g"; */ Assert.assertTrue("ImportPackage=" + importPackageEntry, isEqual(importPackageEntry, expectedResult)); } private enum ternary { CONTENT,USES,NONE } private DeployedBundles getSimpleDeployedBundles(ternary a, ternary b, ternary c) throws InvalidAttributeException { Collection<ImportedBundle> content = new ArrayList<ImportedBundle>(); Collection<ImportedBundle> uses = new ArrayList<ImportedBundle>(); if(a == ternary.CONTENT) content.add(new ImportedBundleImpl("bundle.a", "1.0.0")); else if (a == ternary.USES) uses.add(new ImportedBundleImpl("bundle.a", "1.0.0")); if (b == ternary.CONTENT) content.add(new ImportedBundleImpl("bundle.b", "1.0.0")); else if (b == ternary.USES) uses.add(new ImportedBundleImpl("bundle.b", "1.0.0")); if (c == ternary.CONTENT) content.add(new ImportedBundleImpl("bundle.c", "1.0.0")); else if (c == ternary.USES) uses.add(new ImportedBundleImpl("bundle.c", "1.0.0")); // In a unit test we could go straight to the static method; choosing not to in this case. return new ModellingHelperImpl().createDeployedBundles("test",content, uses, null); } @Test public void testGetImportPackage_ValidDuplicates() throws Exception { DeployedBundles deployedBundles = getSimpleDeployedBundles(ternary.CONTENT, ternary.CONTENT, ternary.CONTENT); deployedBundles.addBundle(createModelledResource("bundle.a", "1.0.0", Arrays.asList("package.d;version=\"[1.0.0,3.0.0)\""), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.b", "1.0.0", Arrays.asList("package.d;version=\"2.0.0\""), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.c", "1.0.0", Arrays.asList("package.d;version=\"1.0.0\""), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.d", "1.0.0", new ArrayList<String>(), Arrays.asList("package.d;version=2.0.1"))); // Check that package D is not duplicated in Import-Package, and that the version range // has been narrowed to the intersection of the original requirements. String importPackageEntry = null; try { importPackageEntry = deployedBundles.getImportPackage(); } catch (ResolverException e) { e.printStackTrace(); Assert.fail(e.toString()); } String expectedResult = "package.d;version=\"[2.0.0,3.0.0)\""; Assert.assertTrue("ImportPackage=" + importPackageEntry, isEqual(importPackageEntry, expectedResult)); } @Test public void testGetImportPackage_ValidDuplicatesWithAttributes() throws Exception { DeployedBundles deployedBundles = getSimpleDeployedBundles(ternary.CONTENT, ternary.CONTENT, ternary.NONE); deployedBundles.addBundle(createModelledResource("bundle.a", "1.0.0", Arrays.asList("package.c;version=1.0.0;was_internal=true"), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.b", "1.0.0", Arrays.asList("package.c;version=2.0.0;was_internal=true"), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.c", "1.0.0", new ArrayList<String>(), Arrays.asList("package.c;version=2.0.0;was_internal=true"))); // Check that package C is not duplicated in Import-Package, and that the version range // has been narrowed to the intersection of the original requirements. String importPackageEntry = null; try { importPackageEntry = deployedBundles.getImportPackage(); } catch (ResolverException e) { e.printStackTrace(); Assert.fail(e.toString()); } String expectedResult = "package.c;was_internal=\"true\";version=\"2.0.0\""; Assert.assertTrue("ImportPackage=" + importPackageEntry, isEqual(importPackageEntry, expectedResult)); } @Test public void testGetImportPackage_InvalidDuplicates() throws Exception { DeployedBundles deployedBundles = getSimpleDeployedBundles(ternary.CONTENT, ternary.CONTENT, ternary.NONE); deployedBundles.addBundle(createModelledResource("bundle.a", "1.0.0", Arrays.asList("package.c;version=\"[1.0.0,2.0.0)\""), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.b", "1.0.0", Arrays.asList("package.c;version=2.0.0"), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.c", "1.0.0", new ArrayList<String>(), Arrays.asList("package.c;version=2.0.0;was_internal=true"))); // Check that the incompatible version requirements cannot be resolved. String importPackageEntry = null; try { importPackageEntry = deployedBundles.getImportPackage(); Assert.fail("Expected exception. ImportPackage=" + importPackageEntry); } catch (ResolverException e) { // We expect to reach this point if the test passes. } } @Test public void testGetImportPackage_InvalidDuplicatesWithAttributes() throws Exception { DeployedBundles deployedBundles = getSimpleDeployedBundles(ternary.CONTENT, ternary.CONTENT, ternary.NONE); deployedBundles.addBundle(createModelledResource("bundle.a", "1.0.0", Arrays.asList("package.c;version=1.0.0;was_internal=true"), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.b", "1.0.0", Arrays.asList("package.c;version=2.0.0"), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.c", "1.0.0", new ArrayList<String>(), Arrays.asList("package.c;version=2.0.0;was_internal=true"))); // Check that the incompatible package requirement attributes cause an exception. String importPackageEntry = null; try { importPackageEntry = deployedBundles.getImportPackage(); Assert.fail("Expected exception. ImportPackage=" + importPackageEntry); } catch (ResolverException e) { // We expect to reach this point if the test passes. } } @Test public void testGetImportPackage_bundleSymbolicNameOK() throws Exception { DeployedBundles deployedBundles = getSimpleDeployedBundles(ternary.CONTENT, ternary.CONTENT, ternary.NONE); deployedBundles.addBundle(createModelledResource("bundle.a", "1.0.0", Arrays.asList("package.b;version=1.0.0;bundle-symbolic-name=bundle.b;bundle-version=\"[1.0.0,2.0.0)\""), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.b", "1.0.0", new ArrayList<String>(), Arrays.asList("package.b;version=2.0.0"))); // Check that the bundle-symbolic-name attribute for a bundle within deployed-content is ok. String importPackageEntry = null; try { importPackageEntry = deployedBundles.getImportPackage(); } catch (ResolverException e) { e.printStackTrace(); Assert.fail(e.toString()); } String expectedResult = ""; // All packages are satisfied internally Assert.assertTrue("ImportPackage=" + importPackageEntry, isEqual(importPackageEntry, expectedResult)); } @Test public void testGetImportPackage_rfc138PreventsBundleSymbolicNameWorking() throws Exception { DeployedBundles deployedBundles = getSimpleDeployedBundles(ternary.CONTENT, ternary.USES, ternary.NONE); deployedBundles.addBundle(createModelledResource("bundle.a", "1.0.0", Arrays.asList("package.b;version=1.0.0;bundle-symbolic-name=bundle.b"), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.b", "1.0.0", new ArrayList<String>(), Arrays.asList("package.b;version=2.0.0"))); // Check that the bundle-symbolic-name attribute for a bundle outside use-bundle causes an exception. String importPackageEntry = null; try { importPackageEntry = deployedBundles.getImportPackage(); Assert.fail("Expected exception. ImportPackage=" + importPackageEntry); } catch (ResolverException e) { // We expect to reach this point if the test passes. } } @Test public void testGetImportPackage_rfc138PreventsBundleVersionWorking() throws Exception { DeployedBundles deployedBundles = getSimpleDeployedBundles(ternary.CONTENT, ternary.NONE, ternary.NONE); deployedBundles.addBundle(createModelledResource("bundle.a", "1.0.0", Arrays.asList("package.b;version=1.0.0;bundle-version=1.0.0"), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.b", "1.0.0", new ArrayList<String>(), Arrays.asList("package.b;version=2.0.0"))); // Check that the bundle-symbolic-name attribute for a bundle outside use-bundle causes an exception. String importPackageEntry = null; try { importPackageEntry = deployedBundles.getImportPackage(); Assert.fail("Expected exception. ImportPackage=" + importPackageEntry); } catch (ResolverException e) { // We expect to reach this point if the test passes. } } @Test public void testGetImportPackage_ValidResolutionAttribute() throws Exception { DeployedBundles deployedBundles = getSimpleDeployedBundles(ternary.CONTENT, ternary.CONTENT, ternary.NONE); deployedBundles.addBundle(createModelledResource("bundle.a", "1.0.0", Arrays.asList("package.c;version=1.0.0;resolution:=optional"), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.b", "1.0.0", Arrays.asList("package.c;version=1.0.0"), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.c", "1.0.0", new ArrayList<String>(), Arrays.asList("package.c;version=1.0.0"))); // Check that the resulting resolution directive is not optional. String importPackageEntry = null; try { importPackageEntry = deployedBundles.getImportPackage(); } catch (ResolverException e) { e.printStackTrace(); Assert.fail(e.toString()); } String expectedResult = "package.c;version=1.0.0"; Assert.assertTrue("ImportPackage=" + importPackageEntry, isEqual(importPackageEntry, expectedResult)); } @Test public void testGetRequiredUseBundle_RedundantEntry() throws Exception { // Bundle A requires package B from bundle B with no version requirement. // Bundle B requires package C from bundle C with no version requirement. // Bundle C requires package B from bundle B with explicit version requirement. DeployedBundles deployedBundles = getSimpleDeployedBundles(ternary.CONTENT, ternary.USES, ternary.USES); deployedBundles.addBundle(createModelledResource("bundle.a", "1.0.0", Arrays.asList("package.b"), new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.b", "1.0.0", Arrays.asList("package.c"), Arrays.asList("package.b;version=1.0.0"))); deployedBundles.addBundle(createModelledResource("bundle.c", "1.0.0", Arrays.asList("package.b;version=1.0.0"), Arrays.asList("package.c;version=1.0.0"))); // Check the redundant use-bundle entry is identified. // Bundle C is not required by app content, although it is specified in use-bundle. Collection<ModelledResource> requiredUseBundle = null; try { requiredUseBundle = deployedBundles.getRequiredUseBundle(); } catch (ResolverException e) { e.printStackTrace(); Assert.fail(e.toString()); } Assert.assertTrue("RequiredUseBundle=" + requiredUseBundle, requiredUseBundle.size() == 1); } @Test public void testGetRequiredUseBundle_Valid() throws Exception { // Get a valid set of deployment information. DeployedBundles deployedBundles = validDeployedBundles(); packagesResolve(deployedBundles); // Check all the use-bundle entries are required. Collection<ModelledResource> requiredUseBundle = null; try { requiredUseBundle = deployedBundles.getRequiredUseBundle(); } catch (ResolverException e) { e.printStackTrace(); Assert.fail(e.toString()); } Assert.assertTrue("RequiredUseBundle=" + requiredUseBundle, requiredUseBundle.size() == 2); } //Inside cannot bundle-symbolic-name an outside bundle until the new RFC 138! @Test public void testGetImportPackage_InvalidBundleVersion() throws Exception { DeployedBundles deployedBundles = getSimpleDeployedBundles(ternary.CONTENT, ternary.USES, ternary.NONE); deployedBundles.addBundle(createModelledResource("bundle.a", "1.0.0", Arrays.asList("package.b;version=\"[1.0.0,1.0.0]\";bundle-symbolic-name=bundle.b;bundle-version=\"[0.0.0,1.0.0)\"") , new ArrayList<String>())); deployedBundles.addBundle(createModelledResource("bundle.b", "1.0.0", new ArrayList<String>(), Arrays.asList("package.b;version=1.0.0"))); // Check that the bundle version requirement generates an error because it doesn't match the a bundle in use-bundle. String importPackageEntry = null; try { importPackageEntry = deployedBundles.getImportPackage(); Assert.fail("Expected exception. ImportPackage=" + importPackageEntry); } catch (ResolverException e) { // We expect to reach this point if the test passes. } } @Test public void testImportedService() throws Exception { DeployedBundles deployedBundles = getSimpleDeployedBundles(ternary.CONTENT, ternary.NONE, ternary.NONE); deployedBundles.addBundle(createModelledServiceBundle("bundle.a", "1.0.0", Collections.singleton("java.util.List"), Collections.<String>emptyList())); deployedBundles.addBundle(createModelledServiceBundle("bundle.b", "1.0.0", Collections.singleton("java.util.Set"), Collections.singleton("java.util.List"))); deployedBundles.addBundle(createModelledServiceBundle("bundle.c", "1.0.0", Collections.<String>emptyList(), Collections.singleton("java.util.Set"))); assertEquals("(objectClass=java.util.List)", deployedBundles.getDeployedImportService()); } }