/* * 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.deployment.management; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Hashtable; import java.util.List; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.Manifest; import org.apache.aries.application.ApplicationMetadata; import org.apache.aries.application.Content; import org.apache.aries.application.InvalidAttributeException; import org.apache.aries.application.deployment.management.impl.DeploymentManifestManagerImpl; import org.apache.aries.application.management.AriesApplication; import org.apache.aries.application.management.BundleInfo; import org.apache.aries.application.management.ResolveConstraint; import org.apache.aries.application.management.ResolverException; import org.apache.aries.application.management.spi.resolve.AriesApplicationResolver; import org.apache.aries.application.management.spi.resolve.PreResolveHook; import org.apache.aries.application.management.spi.runtime.LocalPlatform; import org.apache.aries.application.modelling.DeployedBundles; import org.apache.aries.application.modelling.ExportedPackage; import org.apache.aries.application.modelling.ModelledResource; import org.apache.aries.application.modelling.ModellingManager; import org.apache.aries.application.modelling.impl.ModellingManagerImpl; import org.apache.aries.application.modelling.utils.ModellingHelper; import org.apache.aries.application.modelling.utils.impl.ModellingHelperImpl; import org.apache.aries.application.utils.AppConstants; import org.apache.aries.application.utils.manifest.ContentFactory; import org.apache.aries.mocks.BundleContextMock; import org.apache.aries.unittest.mocks.MethodCall; import org.apache.aries.unittest.mocks.Skeleton; import org.apache.aries.util.VersionRange; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.Version; /** * Tests to ensure we generate DEPLOYMENT.MF artifacts correctly. */ public class DeploymentGeneratorTest { private DeploymentManifestManagerImpl deplMFMgr; private AriesApplication app; private ApplicationMetadata appMetadata; private static class MockResolver implements AriesApplicationResolver { boolean returnAppContentNextTime = true; @Override public Collection<ModelledResource> resolve(String appName, String appVersion, Collection<ModelledResource> byValueBundles, Collection<Content> inputs) throws ResolverException { if (_nextResults != null && !_nextResults.isEmpty()) { Collection<ModelledResource> result = _nextResults.remove(0); return result; } Collection<ModelledResource> res = new ArrayList<ModelledResource>(); if (returnAppContentNextTime) { res.add(CAPABILITY_A.getBundle()); res.add(CAPABILITY_B.getBundle()); } res.add(CAPABILITY_C.getBundle()); res.add(CAPABILITY_E.getBundle()); boolean addD = false; for(Content ib : inputs) { if(ib.getContentName().equals("aries.test.d")) addD = true; } if(addD) { try { res.add(createModelledResource("aries.test.d", "1.0.0", new ArrayList<String>(), new ArrayList<String>())); } catch (InvalidAttributeException e) { fail("Cannot resolve import for d"); } } // deployment manifest manager calls resolve() an extra time, providing // just the shared bundles. // If we added D, then the next resolve will be one trying to winnow D out: // AppContent should be returned in that one. We should not return app content // next time if we did so last time, unless we just added D returnAppContentNextTime = !returnAppContentNextTime || addD; return res; } List<Collection<ModelledResource>> _nextResults = null; // Some tests want to override the default behaviour of the resolve() method public void addResult (Collection<ModelledResource> result) { if (_nextResults == null) { _nextResults = new ArrayList<Collection<ModelledResource>>(); } _nextResults.add(result); } public BundleInfo getBundleInfo(String bundleSymbolicName, Version bundleVersion) { return null; } public Set<BundleInfo> resolve(AriesApplication app, ResolveConstraint... constraints) throws ResolverException { return null; } @Override public Collection<ModelledResource> resolveInIsolation(String appName, String appVersion, Collection<ModelledResource> byValueBundles, Collection<Content> inputs) throws ResolverException { // TODO Auto-generated method stub return null; } } static MockResolver _resolver = new MockResolver(); static class DummyLocalPlatform implements LocalPlatform { public File getTemporaryDirectory() throws IOException { File f = File.createTempFile("ebaTmp", null); f.delete(); f.mkdir(); return f; } public File getTemporaryFile () throws IOException { // Not used return File.createTempFile("ebaTmp", null); } } static LocalPlatform localPlatform = new DummyLocalPlatform(); static ModellingManager modellingManager = new ModellingManagerImpl(); static ModellingHelper modellingHelper = new ModellingHelperImpl(); @BeforeClass public static void classSetup() throws Exception { BundleContext bc = Skeleton.newMock(BundleContext.class); bc.registerService(AriesApplicationResolver.class.getName(), _resolver, new Hashtable<String, String>()); bc.registerService(ModellingManager.class.getName(), modellingManager, new Hashtable<String, String>()); bc.registerService(ModellingHelper.class.getName(), modellingHelper, new Hashtable<String, String>()); } @AfterClass public static void afterClass() throws Exception { BundleContextMock.clear(); } @Before public void setup() throws Exception { appMetadata = Skeleton.newMock(ApplicationMetadata.class); Skeleton.getSkeleton(appMetadata).setReturnValue( new MethodCall(ApplicationMetadata.class, "getApplicationSymbolicName"), "aries.test"); Skeleton.getSkeleton(appMetadata).setReturnValue( new MethodCall(ApplicationMetadata.class, "getApplicationVersion"), new Version("1.0.0")); Skeleton.getSkeleton(appMetadata).setReturnValue( new MethodCall(ApplicationMetadata.class, "getUseBundles"), Collections.EMPTY_LIST); app = Skeleton.newMock(AriesApplication.class); Skeleton.getSkeleton(app).setReturnValue(new MethodCall(AriesApplication.class, "getApplicationMetadata"), appMetadata); deplMFMgr = new DeploymentManifestManagerImpl(); deplMFMgr.setResolver(_resolver); deplMFMgr.setLocalPlatform(localPlatform); deplMFMgr.setModellingManager(modellingManager); deplMFMgr.setModellingHelper(modellingHelper); deplMFMgr.setPreResolveHooks(new ArrayList<PreResolveHook>()); } private static ExportedPackage CAPABILITY_A; private static ExportedPackage CAPABILITY_B; private static ExportedPackage CAPABILITY_C; private static ExportedPackage CAPABILITY_E; // use bundle private static Content BUNDLE_C; private static Content BUNDLE_D; public static ExportedPackage createExportedPackage (String bundleName, String bundleVersion, String[] exportedPackages, String[] importedPackages ) throws InvalidAttributeException { ModelledResource mb = createModelledResource(bundleName, bundleVersion, Arrays.asList(importedPackages) , Arrays.asList(exportedPackages)); return mb.getExportedPackages().iterator().next(); } static { try { CAPABILITY_A = createExportedPackage ("aries.test.a", "1.0.0", new String[] {"aries.test.a"}, new String[] {"aries.test.c"}); CAPABILITY_B = createExportedPackage("aries.test.b", "1.1.0", new String[] {"aries.test.b"}, new String[] {"aries.test.e"}); BUNDLE_C = ContentFactory.parseContent("aries.test.c","[1.0.0,1.1.0)"); CAPABILITY_C = createExportedPackage("aries.test.c", "1.0.5", new String[] {"aries.test.c"}, new String[] {}); BUNDLE_D = ContentFactory.parseContent("aries.test.d","1.0.0"); // = new ImportedBundleImpl("aries.test.e", "1.0.0"); CAPABILITY_E = createExportedPackage("aries.test.e", "1.0.0", new String[] {"aries.test.e"}, new String[] {}); } catch (InvalidAttributeException iae) { throw new RuntimeException(iae); } } @Test public void testResolve() throws Exception { Skeleton.getSkeleton(appMetadata).setReturnValue(new MethodCall(ApplicationMetadata.class, "getApplicationContents"), Arrays.asList(mockContent("aries.test.a", "1.0.0"), mockContent("aries.test.b", "[1.0.0, 2.0.0)" ))); Skeleton.getSkeleton(appMetadata).setReturnValue(new MethodCall(ApplicationMetadata.class, "getUseBundles"), Arrays.asList(BUNDLE_C, BUNDLE_D)); DeployedBundles deployedBundles = deplMFMgr.generateDeployedBundles (appMetadata, new ArrayList<ModelledResource>(), Collections.<Content>emptyList()); Manifest man = deplMFMgr.generateDeploymentManifest(appMetadata.getApplicationSymbolicName(), appMetadata.getApplicationVersion().toString(), deployedBundles); Attributes attrs = man.getMainAttributes(); assertEquals("aries.test", attrs.getValue(AppConstants.APPLICATION_SYMBOLIC_NAME)); assertEquals("1.0.0", attrs.getValue(AppConstants.APPLICATION_VERSION)); String content = attrs.getValue(AppConstants.DEPLOYMENT_CONTENT); String useBundle = attrs.getValue(AppConstants.DEPLOYMENT_USE_BUNDLE); String provisioned =attrs.getValue(AppConstants.DEPLOYMENT_PROVISION_BUNDLE); assertTrue(content.contains("aries.test.a;deployed-version=1.0.0")); assertTrue(content.contains("aries.test.b;deployed-version=1.1.0")); assertTrue(useBundle.contains("aries.test.c;deployed-version=1.0.5")); assertFalse(useBundle.contains("aries.test.d")); assertTrue(provisioned.contains("aries.test.e;deployed-version=1.0.0")); } @Test public void checkBasicCircularDependenciesDetected() throws Exception { // Override Resolver behaviour. //ImportedBundle isolated = new ImportedBundleImpl ("test.isolated" , "1.0.0"); // When we resolve isolated, we're going to get another bundle which has a dependency on isolated. Collection<ModelledResource> cmr = new ArrayList<ModelledResource>(); ExportedPackage testIsolatedPkg = createExportedPackage ("test.isolated", "1.0.0", new String[] {"test.shared"}, new String[] {"test.isolated.pkg"}); cmr.add (testIsolatedPkg.getBundle()); ExportedPackage testSharedPkg = createExportedPackage ("test.shared", "1.0.0", new String[] {"test.isolated.pkg"}, new String[] {"test.shared"}); cmr.add (testSharedPkg.getBundle()); _resolver.addResult(cmr); // The second time DeploymentGenerator calls the Resolver, it will provide just // test.shared. The resolver will return test.shared _plus_ test.isolated. _resolver.addResult(cmr); Skeleton.getSkeleton(appMetadata).setReturnValue(new MethodCall(ApplicationMetadata.class, "getApplicationContents"), Arrays.asList(mockContent("test.isolated" , "1.0.0"))); try { DeployedBundles deployedBundles = deplMFMgr.generateDeployedBundles (appMetadata, new ArrayList<ModelledResource>(), new ArrayList<Content>()); deplMFMgr.generateDeploymentManifest(appMetadata.getApplicationSymbolicName(), appMetadata.getApplicationVersion().toString(), deployedBundles); } catch (ResolverException rx) { List<String> usr = rx.getUnsatisfiedRequirements(); assertEquals ("One unsatisfied requirement expected, not " + usr.size(), usr.size(), 1); String chkMsg = "Shared bundle test.shared_1.0.0 has a dependency for package " + "test.shared which is exported from application bundle [test.isolated_1.0.0]"; assertTrue (chkMsg + " expected, not " + usr, usr.contains(chkMsg)); return; } fail ("ResolverException expected"); } /** * This method checks that the a more complicated circular dependency issues the correct error message * and checks that the details listed in the exception are correct. * @throws Exception */ @Test public void checkMultipleCircularDependenciesDetected() throws Exception { Collection<ModelledResource> cmr = new ArrayList<ModelledResource>(); ExportedPackage testIsolated1 = createExportedPackage ("test.isolated1", "1.0.0", new String[] {"test.isolated1","test.isolated2"}, new String[] {"test.shared1", "test.shared2"}); cmr.add (testIsolated1.getBundle()); ExportedPackage testIsolated2 = createExportedPackage ("test.isolated2", "1.0.0", new String[] {"test.isolated1","test.isolated2"}, new String[] {"test.shared1", "test.shared2"}); cmr.add (testIsolated2.getBundle()); ExportedPackage testShared1 = createExportedPackage ("test.shared1", "1.0.0", new String[] {"test.shared1", "test.shared2"}, new String[] {"test.isolated1","test.isolated2"}); cmr.add (testShared1.getBundle()); ExportedPackage testShared2 = createExportedPackage ("test.shared2", "1.0.0", new String[] {"test.shared1", "test.shared2"}, new String[] {"test.isolated1","test.isolated2"}); cmr.add (testShared2.getBundle()); _resolver.addResult(cmr); // The second time DeploymentGenerator calls the Resolver, it will provide just // test.shared. The resolver will return test.shared _plus_ test.isolated. _resolver.addResult(cmr); Skeleton.getSkeleton(appMetadata).setReturnValue(new MethodCall(ApplicationMetadata.class, "getApplicationContents"), Arrays.asList(mockContent("test.isolated1" , "1.0.0"), mockContent("test.isolated2" , "1.0.0"))); app = Skeleton.newMock(AriesApplication.class); Skeleton.getSkeleton(app).setReturnValue(new MethodCall(AriesApplication.class, "getApplicationMetadata"), appMetadata); try { DeployedBundles deployedBundles = deplMFMgr.generateDeployedBundles (appMetadata, Arrays.asList(new ModelledResource[] {testIsolated1.getBundle(), testIsolated2.getBundle()}), new ArrayList<Content>()); deplMFMgr.generateDeploymentManifest(appMetadata.getApplicationSymbolicName(), appMetadata.getApplicationVersion().toString(), deployedBundles); } catch (ResolverException rx) { // Get the unsatisfied Requirements List<String> unsatisfiedReqs = rx.getUnsatisfiedRequirements(); // Ensure we've got 4 unsatisfied Requirements assertEquals ("4 unsatisfied requirements expected, but got " + Arrays.toString(unsatisfiedReqs.toArray()), 4, unsatisfiedReqs.size()); List<String> checkMessages = new ArrayList<String>(); // Now load an array with the expected messages. checkMessages.add("Shared bundle test.shared1_1.0.0 has a dependency for package test.isolated1 which " + "is exported from application bundles [test.isolated1_1.0.0, test.isolated2_1.0.0]"); checkMessages.add("Shared bundle test.shared1_1.0.0 has a dependency for package test.isolated2 which " + "is exported from application bundles [test.isolated1_1.0.0, test.isolated2_1.0.0]"); checkMessages.add("Shared bundle test.shared2_1.0.0 has a dependency for package test.isolated1 which " + "is exported from application bundles [test.isolated1_1.0.0, test.isolated2_1.0.0]"); checkMessages.add("Shared bundle test.shared2_1.0.0 has a dependency for package test.isolated2 which " + "is exported from application bundles [test.isolated1_1.0.0, test.isolated2_1.0.0]"); // Loop through the unsatisfied Requirements and compare them to the expected msgs. We trim the strings // because some unsatisfied reqs have spaces at the end of the string. for (String unsatisfiedReq : unsatisfiedReqs) { assertTrue(unsatisfiedReq + " is not an expected msg", checkMessages.contains(unsatisfiedReq.trim())); } } } @Test public void checkBundleInAppContentAndProvisionContent() throws Exception { List<ModelledResource> cmr = new ArrayList<ModelledResource>(); cmr.add(createModelledResource("test.api", "1.1.0", Collections.<String>emptyList(), Arrays.asList("test.api.pack;version=1.1.0"))); cmr.add(createModelledResource("test.api", "1.0.0", Collections.<String>emptyList(), Arrays.asList("test.api.pack;version=1.0.0"))); cmr.add(createModelledResource("test.consumer", "1.0.0", Arrays.asList("test.api.pack;version=\"[1.0.0,2.0.0)\""), Collections.<String>emptyList())); cmr.add(createModelledResource("test.provider", "1.0.0", Arrays.asList("test.api.pack;version=\"[1.0.0,1.1.0)\""), Collections.<String>emptyList())); // The second time DeploymentGenerator calls the Resolver, it will provide just // test.shared. The resolver will return test.shared _plus_ test.isolated. _resolver.addResult(cmr); Skeleton.getSkeleton(appMetadata).setReturnValue( new MethodCall(ApplicationMetadata.class, "getApplicationContents"), Arrays.asList( mockContent("test.api" , "1.1.0"), mockContent("test.consumer" , "1.0.0"), mockContent("test.provider", "1.0.0"))); app = Skeleton.newMock(AriesApplication.class); Skeleton.getSkeleton(app).setReturnValue(new MethodCall(AriesApplication.class, "getApplicationMetadata"), appMetadata); try { DeployedBundles deployedBundles = deplMFMgr.generateDeployedBundles (appMetadata, Arrays.asList(new ModelledResource[] {cmr.get(0), cmr.get(2), cmr.get(3)}), new ArrayList<Content>()); deplMFMgr.generateDeploymentManifest(appMetadata.getApplicationSymbolicName(), appMetadata.getApplicationVersion().toString(), deployedBundles); fail("Expected exception because we can't provision an isolated bundle twice"); } catch (ResolverException rx) {} } /** * Similar to the checkBundleInAppContentAndProvisionContent scenario. However, this time the provisioned bundle does not provide * a package or service to the isolated content, so there is no problem. * @throws Exception */ @Test public void checkBundleInAppContentAndProvisionContentButNothingSharedToIsolatedContent() throws Exception { List<ModelledResource> cmr = new ArrayList<ModelledResource>(); cmr.add(createModelledResource("test.util", "1.1.0", Collections.<String>emptyList(), Arrays.asList("test.api.pack;version=1.1.0"))); cmr.add(createModelledResource("test.bundle", "1.0.0", Arrays.asList("test.api.pack;version=\"[1.1.0,2.0.0)\""), Collections.<String>emptyList())); cmr.add(createModelledResource("test.provisioned", "1.0.0", Arrays.asList("test.api.pack;version=\"[1.0.0,1.1.0)\""), Collections.<String>emptyList())); cmr.add(createModelledResource("test.util", "1.0.0", Collections.<String>emptyList(), Arrays.asList("test.api.pack;version=1.0.0"))); // The second time DeploymentGenerator calls the Resolver, it will provide just // test.shared. The resolver will return test.shared _plus_ test.isolated. _resolver.addResult(cmr); Skeleton.getSkeleton(appMetadata).setReturnValue( new MethodCall(ApplicationMetadata.class, "getApplicationContents"), Arrays.asList( mockContent("test.util" , "1.1.0"), mockContent("test.bundle", "1.0.0"))); app = Skeleton.newMock(AriesApplication.class); Skeleton.getSkeleton(app).setReturnValue(new MethodCall(AriesApplication.class, "getApplicationMetadata"), appMetadata); DeployedBundles deployedBundles = deplMFMgr.generateDeployedBundles (appMetadata, Arrays.asList(new ModelledResource[] {cmr.get(0), cmr.get(1)}), new ArrayList<Content>()); Manifest mf = deplMFMgr.generateDeploymentManifest(appMetadata.getApplicationSymbolicName(), appMetadata.getApplicationVersion().toString(), deployedBundles); assertTrue(mf.getMainAttributes().getValue("Deployed-Content").contains("test.util;deployed-version=1.1.0")); assertTrue(mf.getMainAttributes().getValue("Provision-Bundle").contains("test.util;deployed-version=1.0.0")); } @Test public void checkBundleInAppContentAndUseContent() throws Exception { List<ModelledResource> cmr = new ArrayList<ModelledResource>(); cmr.add(createModelledResource("test.api", "1.1.0", Collections.<String>emptyList(), Arrays.asList("test.api.pack;version=1.1.0"))); cmr.add(createModelledResource("test.api", "1.0.0", Collections.<String>emptyList(), Arrays.asList("test.api.pack;version=1.0.0"))); cmr.add(createModelledResource("test.consumer", "1.0.0", Arrays.asList("test.api.pack;version=\"[1.0.0,2.0.0)\""), Collections.<String>emptyList())); cmr.add(createModelledResource("test.provider", "1.0.0", Arrays.asList("test.api.pack;version=\"[1.0.0,1.1.0)\""), Collections.<String>emptyList())); // The second time DeploymentGenerator calls the Resolver, it will provide just // test.shared. The resolver will return test.shared _plus_ test.isolated. _resolver.addResult(cmr); Skeleton.getSkeleton(appMetadata).setReturnValue( new MethodCall(ApplicationMetadata.class, "getApplicationContents"), Arrays.asList( mockContent("test.api" , "1.1.0"), mockContent("test.consumer" , "1.0.0"), mockContent("test.provider", "1.0.0"))); Skeleton.getSkeleton(appMetadata).setReturnValue( new MethodCall(ApplicationMetadata.class, "getUseBundles"), Arrays.asList(mockContent("test.api", "1.0.0"))); app = Skeleton.newMock(AriesApplication.class); Skeleton.getSkeleton(app).setReturnValue(new MethodCall(AriesApplication.class, "getApplicationMetadata"), appMetadata); DeployedBundles deployedBundles = deplMFMgr.generateDeployedBundles (appMetadata, Arrays.asList(new ModelledResource[] {cmr.get(0), cmr.get(2), cmr.get(3)}), new ArrayList<Content>()); Manifest mf = deplMFMgr.generateDeploymentManifest(appMetadata.getApplicationSymbolicName(), appMetadata.getApplicationVersion().toString(), deployedBundles); mf.write(System.out); assertTrue(mf.getMainAttributes().getValue("Deployed-Content").contains("test.api;deployed-version=1.1.0")); assertTrue(mf.getMainAttributes().getValue("Deployed-Use-Bundle").contains("test.api;deployed-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 ModellingManagerImpl().getModelledResource(null, att, null, null); } private Content mockContent(String symbolicName, String version) { Content bundle = Skeleton.newMock(Content.class); VersionRange vr = new VersionRange(version); Skeleton.getSkeleton(bundle).setReturnValue(new MethodCall(Content.class, "getContentName"), symbolicName); Skeleton.getSkeleton(bundle).setReturnValue(new MethodCall(Content.class, "getVersion"), vr); return bundle; } }