From 33f7940c18ba8cd17fd83500486e02376d3b8fd8 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 12 Dec 2016 15:43:50 +0000 Subject: [PATCH 1/4] `BundleMaker` utility routines making it easy to add osgi bundles dynamically and testing things like inserting a manifest and loading files from the bundles --- .../catalog/CatalogMakeOsgiBundleTest.java | 201 +++++++++++ .../catalog/GetFileContentsEffector.java | 46 +++ .../catalog-bundle-1/META-INF/MANIFEST.MF | 3 + .../catalog/osgi/catalog-bundle-1/catalog.bom | 31 ++ .../core/BrooklynFeatureEnablement.java | 2 +- .../catalog/internal/CatalogBomScanner.java | 72 ++-- .../brooklyn/util/core/osgi/BundleMaker.java | 340 ++++++++++++++++++ 7 files changed, 674 insertions(+), 21 deletions(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/GetFileContentsEffector.java create mode 100644 camp/camp-brooklyn/src/test/resources/org/apache/brooklyn/camp/brooklyn/catalog/osgi/catalog-bundle-1/META-INF/MANIFEST.MF create mode 100644 camp/camp-brooklyn/src/test/resources/org/apache/brooklyn/camp/brooklyn/catalog/osgi/catalog-bundle-1/catalog.bom create mode 100644 core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java new file mode 100644 index 0000000000..a08c7b6a21 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogMakeOsgiBundleTest.java @@ -0,0 +1,201 @@ +/* + * 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 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.apache.brooklyn.camp.brooklyn.catalog; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; +import org.apache.brooklyn.core.BrooklynFeatureEnablement; +import org.apache.brooklyn.core.catalog.internal.CatalogBomScanner; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.EntityAsserts; +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.core.osgi.BundleMaker; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.Identifiers; +import org.apache.brooklyn.util.text.Strings; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; + +public class CatalogMakeOsgiBundleTest extends AbstractYamlTest { + + private BundleMaker bm; + List bundlesToRemove = MutableList.of(); + + @Override + protected LocalManagementContext newTestManagementContext() { + return LocalManagementContextForTests.builder(true) + .disableOsgi(false) + .build(); + } + + // keep OSGi framework around for all tests + @BeforeClass(alwaysRun = true) + public void setUp() throws Exception { + super.setUp(); + manuallyEnableBomScanner(mgmt()); + bm = new BundleMaker( ((LocalManagementContext)mgmt()).getOsgiManager().get().getFramework(), ResourceUtils.create(this) ); + } + + // just clean it up between tests, to speed things up + @AfterMethod(alwaysRun = true) + public void cleanUpButKeepMgmt() throws Exception { + for (Application app: MutableList.copyOf(mgmt().getApplications())) { + Entities.destroy(app); + } + for (Bundle b: bundlesToRemove) { + b.uninstall(); + } + bundlesToRemove.clear(); + } + + @AfterClass(alwaysRun = true) + public void tearDown() throws Exception { + super.tearDown(); + } + + public static void manuallyEnableBomScanner(ManagementContext mgmt) { + BrooklynFeatureEnablement.enable(BrooklynFeatureEnablement.FEATURE_LOAD_BUNDLE_CATALOG_BOM); + + CatalogBomScanner scanner = new CatalogBomScanner(); + BundleContext context = ((LocalManagementContext)mgmt).getOsgiManager().get().getFramework().getBundleContext(); + + context.registerService(ManagementContext.class.getName(), mgmt, null); + ServiceReference ref = context.getServiceReference(ManagementContext.class); + try { + scanner.bind(ref); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + + @AfterMethod(alwaysRun=true) + public void clearFeatureEnablement() throws Exception { + BrooklynFeatureEnablement.clearCache(); + } + + @Test + public void testCatalogBomFromBundleWithManifest() throws Exception { + bm.setDefaultClassForLoading(getClass()); + File jf = bm.createJarFromClasspathDir("osgi/catalog-bundle-1"); + + Assert.assertTrue(bm.hasOsgiManifest(jf)); + + installBundle(jf); + assertHasBasic1(); + assertBasic1DeploysAndHasSensor(); + } + + private Entity assertBasic1DeploysAndHasSensor() throws Exception { + String yaml = "name: simple-app-yaml\n" + + "services: \n" + + "- type: " + "basic1"; + Entity app = createAndStartApplication(yaml); + Entity basic1 = Iterables.getOnlyElement( app.getChildren() ); + EntityAsserts.assertAttribute(basic1, Sensors.newStringSensor("a.sensor"), Predicates.equalTo("A")); + + return basic1; + } + + private void assertHasBasic1() { + RegisteredType basic1T = mgmt().getTypeRegistry().get("basic1"); + Asserts.assertNotNull(basic1T, "basic1 not in catalog"); + } + + @Test + public void testCatalogBomFromBundleWithManualManifest() throws Exception { + bm.setDefaultClassForLoading(getClass()); + File jf = bm.createJarFromClasspathDir("osgi/catalog-bundle-1"); + jf = bm.copyRemoving(jf, MutableSet.of(JarFile.MANIFEST_NAME)); + String customName = "catalog-bundle-1-manual-"+Identifiers.makeRandomId(4); + + jf = bm.copyAddingManifest(jf, MutableMap.of( + "Manifest-Version", "1.0", + "Bundle-SymbolicName", customName)); + + Assert.assertTrue(bm.hasOsgiManifest(jf)); + + installBundle(jf); + assertHasBasic1(); + Entity basic1 = assertBasic1DeploysAndHasSensor(); + + RegisteredType item = mgmt().getTypeRegistry().get( basic1.getCatalogItemId() ); + Collection libs = item.getLibraries(); + Asserts.assertSize(libs, 1); + Assert.assertEquals(Iterables.getOnlyElement(libs).getSymbolicName(), customName); + } + + private void installBundle(File jf) { + Bundle bundle = bm.installBundle(jf, true); + bundlesToRemove.add(bundle); + } + + @Test + public void testCatalogBomLoadsFileInBundle() throws Exception { + bm.setDefaultClassForLoading(getClass()); + File jf = bm.createJarFromClasspathDir("osgi/catalog-bundle-1"); + + // add a file in the bundle + String customText = "Sample data "+Identifiers.makeRandomId(4); + jf = bm.copyAdding(jf, MutableMap.of( + new ZipEntry("sample.txt"), (InputStream) new ByteArrayInputStream(customText.getBytes()))); + + installBundle(jf); + + String yaml = Strings.lines("name: simple-app-yaml", + "services:", + "- type: " + "basic1", + " brooklyn.initializers:", + " - type: "+GetFileContentsEffector.class.getName()); + Entity app = createAndStartApplication(yaml); + Entity basic1 = Iterables.getOnlyElement( app.getChildren() ); + + // check the file put in the bundle gets loaded without needing to do anything special + String contents = basic1.invoke(GetFileContentsEffector.GET_FILE_CONTENTS, MutableMap.of(GetFileContentsEffector.FILENAME.getName(), "classpath://sample.txt")).get(); + Asserts.assertEquals(contents, customText); + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/GetFileContentsEffector.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/GetFileContentsEffector.java new file mode 100644 index 0000000000..5a21f8eb7e --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/GetFileContentsEffector.java @@ -0,0 +1,46 @@ +/* + * 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 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.apache.brooklyn.camp.brooklyn.catalog; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.AddEffector; +import org.apache.brooklyn.core.effector.EffectorBody; +import org.apache.brooklyn.core.effector.Effectors; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.core.config.ConfigBag; + +/** simple effector which returns the contents of a file, used to test resolution of */ +public class GetFileContentsEffector extends AddEffector { + + final static ConfigKey FILENAME = ConfigKeys.newStringConfigKey("filename"); + final static Effector GET_FILE_CONTENTS = Effectors.effector(String.class, "getFileContents").parameter(FILENAME).buildAbstract(); + + public GetFileContentsEffector() { + super(Effectors.effector(GET_FILE_CONTENTS).impl(new Body()).build()); + } + + static class Body extends EffectorBody { + @Override + public String call(ConfigBag parameters) { + return ResourceUtils.create(entity()).getResourceAsString( parameters.get(FILENAME) ); + } + } +} diff --git a/camp/camp-brooklyn/src/test/resources/org/apache/brooklyn/camp/brooklyn/catalog/osgi/catalog-bundle-1/META-INF/MANIFEST.MF b/camp/camp-brooklyn/src/test/resources/org/apache/brooklyn/camp/brooklyn/catalog/osgi/catalog-bundle-1/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..9ec6aafb29 --- /dev/null +++ b/camp/camp-brooklyn/src/test/resources/org/apache/brooklyn/camp/brooklyn/catalog/osgi/catalog-bundle-1/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Bundle-SymbolicName: catalog-bundle-1 +Bundle-Version: 0.0.0.SNAPSHOT diff --git a/camp/camp-brooklyn/src/test/resources/org/apache/brooklyn/camp/brooklyn/catalog/osgi/catalog-bundle-1/catalog.bom b/camp/camp-brooklyn/src/test/resources/org/apache/brooklyn/camp/brooklyn/catalog/osgi/catalog-bundle-1/catalog.bom new file mode 100644 index 0000000000..17c8e6d76a --- /dev/null +++ b/camp/camp-brooklyn/src/test/resources/org/apache/brooklyn/camp/brooklyn/catalog/osgi/catalog-bundle-1/catalog.bom @@ -0,0 +1,31 @@ +# 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 WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +brooklyn.catalog: + version: 0.0.0-SNAPSHOT + itemType: entity + items: + - id: basic1 + item: + type: org.apache.brooklyn.entity.stock.BasicEntity + brooklyn.initializers: + - type: org.apache.brooklyn.core.sensor.StaticSensor + brooklyn.config: + name: a.sensor + targetType: string + static.value: A + diff --git a/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java b/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java index 8f0aff1a1a..4008b39a81 100644 --- a/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java +++ b/core/src/main/java/org/apache/brooklyn/core/BrooklynFeatureEnablement.java @@ -257,7 +257,7 @@ public static void setDefault(String property, boolean val) { } @VisibleForTesting - static void clearCache() { + public static void clearCache() { synchronized (MUTEX) { FEATURE_ENABLEMENTS.clear(); FEATURE_ENABLEMENTS_PROPERTIES.clear(); diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java index b7e0041cc0..6184b1a207 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java @@ -18,9 +18,14 @@ */ package org.apache.brooklyn.core.catalog.internal; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; +import static org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType.TEMPLATE; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.List; +import java.util.Map; + import org.apache.brooklyn.api.catalog.CatalogItem; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.core.BrooklynFeatureEnablement; @@ -30,6 +35,7 @@ import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yaml.Yamls; import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.BundleTracker; @@ -38,14 +44,13 @@ import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.List; -import java.util.Map; - -import static org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType.TEMPLATE; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +/** Scans bundles being added, filtered by a whitelist and blacklist, and adds catalog.bom files to the catalog. + * See karaf blueprint.xml for configuration, and tests in dist project. */ public class CatalogBomScanner { private final String ACCEPT_ALL_BY_DEFAULT = ".*"; @@ -118,31 +123,52 @@ public void setBlackList(String blackListText) { public class CatalogPopulator extends BundleTracker>> { - - private ServiceReference mgmtContextReference; - private ManagementContext managementContext; + private ServiceReference mgmtContextReference = null; + private BundleContext bundleContext = null; + private ManagementContext managementContext = null; public CatalogPopulator(ServiceReference serviceReference) { super(serviceReference.getBundle().getBundleContext(), Bundle.ACTIVE, null); this.mgmtContextReference = serviceReference; open(); } + + public CatalogPopulator(BundleContext bundleContext, ManagementContext managementContext) { + super(Preconditions.checkNotNull(bundleContext, "bundleContext required; is OSGi running?"), Bundle.ACTIVE, null); + this.bundleContext = bundleContext; + this.managementContext = managementContext; + open(); + } @Override public void open() { - managementContext = mgmtContextReference.getBundle().getBundleContext().getService(mgmtContextReference); + if (mgmtContextReference!=null) { + bundleContext = getBundleContext(); + managementContext = getManagementContext(); + } super.open(); } @Override public void close() { super.close(); - managementContext = null; - mgmtContextReference.getBundle().getBundleContext().ungetService(mgmtContextReference); + if (mgmtContextReference!=null) { + managementContext = null; + getBundleContext().ungetService(mgmtContextReference); + bundleContext = null; + } } + public BundleContext getBundleContext() { + if (bundleContext!=null) return bundleContext; + if (mgmtContextReference!=null) return mgmtContextReference.getBundle().getBundleContext(); + throw new IllegalStateException("Bundle context or management context reference must be supplied"); + } + public ManagementContext getManagementContext() { - return managementContext; + if (managementContext!=null) return managementContext; + if (mgmtContextReference!=null) return getBundleContext().getService(mgmtContextReference); + throw new IllegalStateException("Bundle context or management context reference must be supplied"); } /** @@ -239,11 +265,13 @@ private boolean on(Bundle bundle, List list) { } private String addLibraryDetails(Bundle bundle, String bomText) { + @SuppressWarnings("unchecked") final Map bom = (Map)Iterables.getOnlyElement(Yamls.parseAll(bomText)); - final Object catalog = bom.get(BROOKLYN_CATALOG); + @SuppressWarnings("unchecked") + final Map catalog = (Map) bom.get(BROOKLYN_CATALOG); if (null != catalog) { if (catalog instanceof Map) { - addLibraryDetails(bundle, (Map) catalog); + addLibraryDetails(bundle, catalog); } else { LOG.warn("Unexpected syntax for {} (expected Map), ignoring", BROOKLYN_CATALOG); } @@ -268,7 +296,11 @@ private void addLibraryDetails(Bundle bundle, Map catalog) { if (!(librarySpec instanceof List)) { throw new RuntimeException("expected " + BROOKLYN_LIBRARIES + " to be a list"); } - List libraries = (List)librarySpec; + @SuppressWarnings("unchecked") + List> libraries = (List>)librarySpec; + if (bundle.getSymbolicName()==null || bundle.getVersion()==null) { + throw new IllegalStateException("Cannot scan "+bundle+" for catalog files: name or version is null"); + } libraries.add(ImmutableMap.of( "name", bundle.getSymbolicName(), "version", bundle.getVersion().toString())); diff --git a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java new file mode 100644 index 0000000000..55ed7aa883 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java @@ -0,0 +1,340 @@ +/* + * 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 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.apache.brooklyn.util.core.osgi; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Enumeration; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.net.Urls; +import org.apache.brooklyn.util.os.Os; +import org.apache.brooklyn.util.stream.Streams; +import org.apache.brooklyn.util.text.Strings; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; +import org.osgi.framework.launch.Framework; + +import com.google.common.annotations.Beta; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.io.LineReader; + +public class BundleMaker { + + final static String MANIFEST_PATH = JarFile.MANIFEST_NAME; + private Framework framework; + private ResourceUtils resources; + private Class optionalDefaultClassForLoading; + + public BundleMaker(Framework f, ResourceUtils resources) { + this.framework = f; + this.resources = resources; + } + + public BundleMaker(ManagementContext mgmt) { + this( ((LocalManagementContext)mgmt).getOsgiManager().get().getFramework(), ResourceUtils.create() ); + } + + /** if set, this will be used to resolve relative classpath fragments */ + public void setDefaultClassForLoading(Class optionalDefaultClassForLoading) { + this.optionalDefaultClassForLoading = optionalDefaultClassForLoading; + } + + /** creates a ZIP in a temp file from the given classpath folder, + * by recursively taking everything in the referenced directories, + * treating the given folder as the root, + * respecting the MANIFEST.MF if present (ie putting it first so it is a valid JAR) */ + public File createJarFromClasspathDir(String path) { + File f = Os.newTempFile(path, "zip"); + ZipOutputStream zout = null; + try { + if (Urls.getProtocol(path)==null) { + // default if no URL is classpath + path = Os.tidyPath(path); + if (!path.startsWith("/") && optionalDefaultClassForLoading!=null) { + path = "/"+optionalDefaultClassForLoading.getPackage().getName().replace('.', '/') + "/" + path; + } + path = "classpath:"+path; + } + + InputStream min = resources.getResourceFromUrl(Urls.mergePaths(path, MANIFEST_PATH)); + if (min==null) { + addUrlItemRecursively(zout, path, path, Predicates.alwaysTrue()); + } else { + zout = new JarOutputStream(new FileOutputStream(f), new Manifest(min)); + addUrlItemRecursively(zout, path, path, Predicates.not(Predicates.equalTo(MANIFEST_PATH))); + } + + Streams.closeQuietly(zout); + + return f; + + } catch (Exception e) { + throw Exceptions.propagate("Error creating ZIP from classpath spec "+path, e); + + } finally { + Streams.closeQuietly(zout); + } + } + + /** true iff given ZIP/JAR file contains a MANIFEST.MF file defining a bundle symbolic name */ + public boolean hasOsgiManifest(File f) { + Manifest mf = getManifest(f); + if (mf==null) return false; + String sn = mf.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME); + return Strings.isNonBlank(sn); + } + + /** returns the manifest in a JAR file, or null if no manifest contained therein */ + public Manifest getManifest(File f) { + JarFile jf = null; + try { + jf = new JarFile(f); + return jf.getManifest(); + } catch (IOException e) { + throw Exceptions.propagate("Unable to read "+f+" when looking for manifest", e); + } finally { + Streams.closeQuietly(jf); + } + } + + /** as {@link #copyAddingManifest(File, Manifest)} but taking manifest entries as a map for convenience */ + public File copyAddingManifest(File f, Map attrs) { + Manifest mf = new Manifest(); + for (Map.Entry attr: attrs.entrySet()) { + mf.getMainAttributes().putValue(attr.getKey(), attr.getValue()); + } + return copyAddingManifest(f, mf); + } + + /** create a copy of the given ZIP as a JAR with the given manifest, returning the new temp file */ + public File copyAddingManifest(File f, Manifest mf) { + File f2 = Os.newTempFile(f.getName(), "zip"); + ZipOutputStream zout = null; + ZipFile zf = null; + try { + zout = new JarOutputStream(new FileOutputStream(f2), mf); + writeZipEntriesFromFile(zout, f, Predicates.not(Predicates.equalTo(MANIFEST_PATH))); + } catch (IOException e) { + throw Exceptions.propagate("Unable to read "+f+" when looking for manifest", e); + } finally { + Streams.closeQuietly(zf); + Streams.closeQuietly(zout); + } + return f2; + } + + /** create a copy of the given ZIP as a JAR with the given entries added at the end (removing any duplicates), returning the new temp file */ + public File copyAdding(File f, Map entries) { + return copyAdding(f, entries, Predicates.alwaysTrue(), false); + } + + /** create a copy of the given ZIP as a JAR with the given entries added at the end, returning the new temp file */ + public File copyAddingAtEnd(File f, Map entries) { + return copyAdding(f, entries, Predicates.alwaysTrue(), true); + } + + /** create a copy of the given ZIP as a JAR with the given entries removed, returning the new temp file */ + public File copyRemoving(File f, final Set itemsToRemove) { + return copyRemoving(f, new Predicate(){ + @Override + public boolean apply(String input) { + return !itemsToRemove.contains(input); + } + }); + } + + /** create a copy of the given ZIP as a JAR with the given entries removed, returning the new temp file */ + public File copyRemoving(File f, Predicate filter) { + return copyAdding(f, MutableMap.of(), filter, true); + } + + private File copyAdding(File f, Map entries, final Predicate filter, boolean addAtStart) { + final Set entryNames = MutableSet.of(); + for (ZipEntry ze: entries.keySet()) { + entryNames.add(ze.getName()); + } + + File f2 = Os.newTempFile(f.getName(), "zip"); + ZipOutputStream zout = null; + ZipFile zf = null; + try { + zout = new ZipOutputStream(new FileOutputStream(f2)); + + if (addAtStart) { + writeZipEntries(zout, entries); + } + + writeZipEntriesFromFile(zout, f, new Predicate() { + @Override + public boolean apply(String input) { + return filter.apply(input) && !entryNames.contains(input); + } + }); + + if (!addAtStart) { + writeZipEntries(zout, entries); + } + + return f2; + } catch (IOException e) { + throw Exceptions.propagate("Unable to read "+f+" when looking for manifest", e); + } finally { + Streams.closeQuietly(zf); + Streams.closeQuietly(zout); + } + } + + private void writeZipEntries(ZipOutputStream zout, Map entries) throws IOException { + for (Map.Entry ze: entries.entrySet()) { + zout.putNextEntry(ze.getKey()); + InputStream zin = ze.getValue(); + Streams.copy(zin, zout); + Streams.closeQuietly(zin); + zout.closeEntry(); + } + } + + private void writeZipEntriesFromFile(ZipOutputStream zout, File existingZip, Predicate filter) throws IOException { + ZipFile zf = new ZipFile(existingZip); + try { + Enumeration zfe = zf.entries(); + while (zfe.hasMoreElements()) { + ZipEntry ze = zfe.nextElement(); + if (filter.apply(ze.getName())) { + zout.putNextEntry(ze); + InputStream zin = zf.getInputStream(ze); + Streams.copy(zin, zout); + Streams.closeQuietly(zin); + zout.closeEntry(); + } + } + } finally { + Streams.closeQuietly(zf); + } + } + + /** installs the given JAR file as an OSGi bundle; all manifest info should be already set up. + * bundle-start semantics are TBD. */ + @Beta + public Bundle installBundle(File f, boolean start) { + try { + Bundle b = Osgis.install( framework, "file://"+f.getAbsolutePath() ); + if (start) { + // benefits of start: + // a) we get wiring issues thrown here, and + // b) catalog.bom in root will be scanned synchronously here + // however drawbacks: + // c) other code doesn't always do it (method above) + // d) heavier-weight earlier + // e) tests in IDE break (but mvn fine) + b.start(); + } + + return b; + + } catch (Exception e) { + throw Exceptions.propagate("Error starting bundle from "+f, e); + } + } + + private boolean addUrlItemRecursively(ZipOutputStream zout, String root, String item, Predicate filter) throws IOException { + InputStream itemFound = null; + try { + itemFound = resources.getResourceFromUrl(item); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + return false; + } + + if (!addUrlDirToZipRecursively(zout, root, item, itemFound, filter)) { + // can't reliably tell if item a file or a folder (listing files), esp w classpath where folder is just a list of files, + // so try the latter and see if it succeeds + // slightly inefficient but should work fine + // only issue is that an empty dir and a file of size 0 will appear identical? + addUrlFileToZip(zout, root, item, filter); + } + return true; + } + + private boolean addUrlDirToZipRecursively(ZipOutputStream zout, String root, String item, InputStream itemFound, Predicate filter) throws IOException { + LineReader lr = new LineReader(new InputStreamReader(itemFound)); + boolean readSubdirFile = false; + while (true) { + String line = lr.readLine(); + if (line==null) { + // at end of file return true if we were able to recurse, else false + return readSubdirFile; + } + boolean isFile = addUrlItemRecursively(zout, root, item+"/"+line, filter); + if (isFile) { + readSubdirFile = true; + } else { + if (!readSubdirFile) { + // not a folder + return false; + } else { + // previous entry made clear it was a folder, but this one didn't work! + throw new IllegalStateException("Failed to read entry "+line+" in "+item+" but previous entry implied it was a directory"); + } + } + } + } + + private void addUrlFileToZip(ZipOutputStream zout, String root, String item, Predicate filter) throws IOException { + InputStream itemFound = null; + try { + itemFound = resources.getResourceFromUrl(item); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + + int startPos = item.indexOf(root); + if (startPos<0) { + throw new IllegalStateException("URL of "+item+" does not appear relative to root "+root); + } + String itemE = item.substring(startPos + root.length()+1); + if (!filter.apply(itemE)) { + return; + } + ZipEntry e = new ZipEntry(itemE); + zout.putNextEntry(e); + Streams.copy(itemFound, zout); + Streams.closeQuietly(itemFound); + zout.closeEntry(); + } + +} From 7d1d47b6e8c3ef919d1d52be3d23a5dc036a2d5c Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Tue, 28 Feb 2017 22:04:35 +0000 Subject: [PATCH 2/4] BundleMaker: add tests, and fix --- core/pom.xml | 10 + .../brooklyn/util/core/osgi/BundleMaker.java | 36 ++- .../util/core/osgi/BundleMakerTest.java | 270 ++++++++++++++++++ .../test/bundlemaker/nomanifest/myfile.txt | 1 + .../bundlemaker/nomanifest/subdir/myfile2.txt | 1 + .../withmanifest/META-INF/MANIFEST.MF | 2 + .../test/bundlemaker/withmanifest/myfile.txt | 1 + .../withmanifest/subdir/myfile2.txt | 1 + 8 files changed, 310 insertions(+), 12 deletions(-) create mode 100644 core/src/test/java/org/apache/brooklyn/util/core/osgi/BundleMakerTest.java create mode 100644 core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/nomanifest/myfile.txt create mode 100644 core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/nomanifest/subdir/myfile2.txt create mode 100644 core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/META-INF/MANIFEST.MF create mode 100644 core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/myfile.txt create mode 100644 core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/subdir/myfile2.txt diff --git a/core/pom.xml b/core/pom.xml index 71f911e1b9..c4c3d9b78c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -307,6 +307,16 @@ + + org.apache.rat + apache-rat-plugin + + + **/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/nomanifest/** + **/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/** + + + diff --git a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java index 55ed7aa883..b36e13861e 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java @@ -90,9 +90,21 @@ public File createJarFromClasspathDir(String path) { path = "classpath:"+path; } - InputStream min = resources.getResourceFromUrl(Urls.mergePaths(path, MANIFEST_PATH)); + InputStream min; + try { + min = resources.getResourceFromUrl(Urls.mergePaths(path, MANIFEST_PATH)); + } catch (RuntimeException e) { + Exceptions.propagateIfFatal(e); + IOException ioe = Exceptions.getFirstThrowableOfType(e, IOException.class); + if (ioe != null && ioe.toString().contains("not found on classpath")) { + min = null; + } else { + throw e; + } + } if (min==null) { - addUrlItemRecursively(zout, path, path, Predicates.alwaysTrue()); + zout = new JarOutputStream(new FileOutputStream(f)); + addUrlItemRecursively(zout, path, path, Predicates.alwaysTrue()); } else { zout = new JarOutputStream(new FileOutputStream(f), new Manifest(min)); addUrlItemRecursively(zout, path, path, Predicates.not(Predicates.equalTo(MANIFEST_PATH))); @@ -158,12 +170,12 @@ public File copyAddingManifest(File f, Manifest mf) { } /** create a copy of the given ZIP as a JAR with the given entries added at the end (removing any duplicates), returning the new temp file */ - public File copyAdding(File f, Map entries) { + public File copyAdding(File f, Map entries) { return copyAdding(f, entries, Predicates.alwaysTrue(), false); } /** create a copy of the given ZIP as a JAR with the given entries added at the end, returning the new temp file */ - public File copyAddingAtEnd(File f, Map entries) { + public File copyAddingAtEnd(File f, Map entries) { return copyAdding(f, entries, Predicates.alwaysTrue(), true); } @@ -178,11 +190,11 @@ public boolean apply(String input) { } /** create a copy of the given ZIP as a JAR with the given entries removed, returning the new temp file */ - public File copyRemoving(File f, Predicate filter) { + public File copyRemoving(File f, Predicate filter) { return copyAdding(f, MutableMap.of(), filter, true); } - private File copyAdding(File f, Map entries, final Predicate filter, boolean addAtStart) { + private File copyAdding(File f, Map entries, final Predicate filter, boolean addAtStart) { final Set entryNames = MutableSet.of(); for (ZipEntry ze: entries.keySet()) { entryNames.add(ze.getName()); @@ -218,8 +230,8 @@ public boolean apply(String input) { } } - private void writeZipEntries(ZipOutputStream zout, Map entries) throws IOException { - for (Map.Entry ze: entries.entrySet()) { + private void writeZipEntries(ZipOutputStream zout, Map entries) throws IOException { + for (Map.Entry ze: entries.entrySet()) { zout.putNextEntry(ze.getKey()); InputStream zin = ze.getValue(); Streams.copy(zin, zout); @@ -228,7 +240,7 @@ private void writeZipEntries(ZipOutputStream zout, Map en } } - private void writeZipEntriesFromFile(ZipOutputStream zout, File existingZip, Predicate filter) throws IOException { + private void writeZipEntriesFromFile(ZipOutputStream zout, File existingZip, Predicate filter) throws IOException { ZipFile zf = new ZipFile(existingZip); try { Enumeration zfe = zf.entries(); @@ -271,7 +283,7 @@ public Bundle installBundle(File f, boolean start) { } } - private boolean addUrlItemRecursively(ZipOutputStream zout, String root, String item, Predicate filter) throws IOException { + private boolean addUrlItemRecursively(ZipOutputStream zout, String root, String item, Predicate filter) throws IOException { InputStream itemFound = null; try { itemFound = resources.getResourceFromUrl(item); @@ -290,7 +302,7 @@ private boolean addUrlItemRecursively(ZipOutputStream zout, String root, String return true; } - private boolean addUrlDirToZipRecursively(ZipOutputStream zout, String root, String item, InputStream itemFound, Predicate filter) throws IOException { + private boolean addUrlDirToZipRecursively(ZipOutputStream zout, String root, String item, InputStream itemFound, Predicate filter) throws IOException { LineReader lr = new LineReader(new InputStreamReader(itemFound)); boolean readSubdirFile = false; while (true) { @@ -314,7 +326,7 @@ private boolean addUrlDirToZipRecursively(ZipOutputStream zout, String root, Str } } - private void addUrlFileToZip(ZipOutputStream zout, String root, String item, Predicate filter) throws IOException { + private void addUrlFileToZip(ZipOutputStream zout, String root, String item, Predicate filter) throws IOException { InputStream itemFound = null; try { itemFound = resources.getResourceFromUrl(item); diff --git a/core/src/test/java/org/apache/brooklyn/util/core/osgi/BundleMakerTest.java b/core/src/test/java/org/apache/brooklyn/util/core/osgi/BundleMakerTest.java new file mode 100644 index 0000000000..599531dd99 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/util/core/osgi/BundleMakerTest.java @@ -0,0 +1,270 @@ +/* + * 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 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.apache.brooklyn.util.core.osgi; + +import static org.apache.brooklyn.test.Asserts.assertEquals; +import static org.apache.brooklyn.test.Asserts.assertFalse; +import static org.apache.brooklyn.test.Asserts.assertNotNull; +import static org.apache.brooklyn.test.Asserts.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; +import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport; +import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.os.Os; +import org.apache.brooklyn.util.stream.Streams; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + +public class BundleMakerTest extends BrooklynMgmtUnitTestSupport { + + private BundleMaker bundleMaker; + private File emptyJar; + private File tempJar; + private File generatedJar; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).build(); + super.setUp(); + + bundleMaker = new BundleMaker(mgmt); + emptyJar = createEmptyJarFile(); + } + + @AfterMethod(alwaysRun=true) + @Override + public void tearDown() throws Exception { + super.tearDown(); + if (emptyJar != null) emptyJar.delete(); + if (tempJar != null) tempJar.delete(); + if (generatedJar != null) generatedJar.delete(); + } + + @Test + public void testCopyAdding() throws Exception { + generatedJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext")); + } + + @Test + public void testCopyAddingToNonEmpty() throws Exception { + tempJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of(new ZipEntry("preExisting.txt"), new ByteArrayInputStream("myPreExisting".getBytes()))); + generatedJar = bundleMaker.copyAdding(tempJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); + assertJarContents(generatedJar, ImmutableMap.of("preExisting.txt", "myPreExisting", "myfile.txt", "mytext")); + } + + @Test + public void testCopyAddingOverwritesEntry() throws Exception { + tempJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("myPreExisting".getBytes()))); + generatedJar = bundleMaker.copyAdding(tempJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext")); + } + + @Test + public void testCopyAddingManifest() throws Exception { + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.2.3"); // needs version, or nothing added to manifest! + manifest.getMainAttributes().putValue("mykey", "myval"); + generatedJar = bundleMaker.copyAddingManifest(emptyJar, manifest); + + String expectedManifest = + "Manifest-Version: 1.2.3\r\n" + + "mykey: myval\r\n" + + "\r\n"; + assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest)); + } + + @Test + public void testCopyAddingManifestByMap() throws Exception { + Map manifest = ImmutableMap.of(Attributes.Name.MANIFEST_VERSION.toString(), "1.2.3", "mykey", "myval"); + generatedJar = bundleMaker.copyAddingManifest(emptyJar, manifest); + + String expectedManifest = + "Manifest-Version: 1.2.3\r\n" + + "mykey: myval\r\n" + + "\r\n"; + assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest)); + } + + @Test + public void testCopyAddingManifestOverwritesExisting() throws Exception { + Map origManifest = ImmutableMap.of(Attributes.Name.MANIFEST_VERSION.toString(), "4.5.6", "preExistingKey", "preExistingVal"); + tempJar = bundleMaker.copyAddingManifest(emptyJar, origManifest); + + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.2.3"); + manifest.getMainAttributes().putValue("mykey", "myval"); + generatedJar = bundleMaker.copyAddingManifest(tempJar, manifest); + + String expectedManifest = + "Manifest-Version: 1.2.3\r\n" + + "mykey: myval\r\n" + + "\r\n"; + assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest)); + } + + @Test + public void testCopyRemovingPredicate() throws Exception { + tempJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of( + new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()), + new ZipEntry("myfile2.txt"), new ByteArrayInputStream("mytext2".getBytes()))); + generatedJar = bundleMaker.copyRemoving(tempJar, Predicates.equalTo("myfile.txt")); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext")); + } + + @Test + public void testCopyRemovingItems() throws Exception { + tempJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of( + new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()), + new ZipEntry("myfile2.txt"), new ByteArrayInputStream("mytext2".getBytes()))); + generatedJar = bundleMaker.copyRemoving(tempJar, ImmutableSet.of("myfile.txt")); + assertJarContents(generatedJar, ImmutableMap.of("myfile2.txt", "mytext2")); + } + + // TODO Not supported - can't remove an entire directory like this + @Test(enabled=false) + public void testCopyRemovingItemDir() throws Exception { + tempJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of( + new ZipEntry("mydir/myfile.txt"), new ByteArrayInputStream("mytext".getBytes()), + new ZipEntry("mydir2/myfile2.txt"), new ByteArrayInputStream("mytext2".getBytes()))); + generatedJar = bundleMaker.copyRemoving(tempJar, ImmutableSet.of("mydir")); + assertJarContents(generatedJar, ImmutableMap.of("mydir2/myfile2.txt", "mytext2")); + } + + @Test + public void testCopyRemovingItemsUnmatched() throws Exception { + tempJar = bundleMaker.copyAdding(emptyJar, ImmutableMap.of(new ZipEntry("myfile.txt"), new ByteArrayInputStream("mytext".getBytes()))); + generatedJar = bundleMaker.copyRemoving(tempJar, ImmutableSet.of("wrong.txt")); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext")); + } + + @Test + public void testHasOsgiManifestWhenNoManifest() throws Exception { + assertFalse(bundleMaker.hasOsgiManifest(emptyJar)); + } + + @Test + public void testHasOsgiManifestWhenInvalidManifest() throws Exception { + Map manifest = ImmutableMap.of(Attributes.Name.MANIFEST_VERSION.toString(), "1.2.3", "mykey", "myval"); + generatedJar = bundleMaker.copyAddingManifest(emptyJar, manifest); + assertFalse(bundleMaker.hasOsgiManifest(generatedJar)); + } + + @Test + public void testHasOsgiManifestWhenValidManifest() throws Exception { + Map manifest = ImmutableMap.of(Attributes.Name.MANIFEST_VERSION.toString(), "1.2.3", Constants.BUNDLE_SYMBOLICNAME, "myname"); + generatedJar = bundleMaker.copyAddingManifest(emptyJar, manifest); + assertTrue(bundleMaker.hasOsgiManifest(generatedJar)); + } + + @Test + public void testCreateJarFromClasspathDirNoManifest() throws Exception { + generatedJar = bundleMaker.createJarFromClasspathDir("/org/apache/brooklyn/util/core/osgi/test/bundlemaker/nomanifest"); + assertJarContents(generatedJar, ImmutableMap.of("myfile.txt", "mytext", "subdir/myfile2.txt", "mytext2")); + } + + @Test + public void testCreateJarFromClasspathDirWithManifest() throws Exception { + generatedJar = bundleMaker.createJarFromClasspathDir("/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest"); + + String expectedManifest = + "Manifest-Version: 1.2.3\r\n" + + "mykey: myval\r\n" + + "\r\n"; + assertJarContents(generatedJar, ImmutableMap.of(JarFile.MANIFEST_NAME, expectedManifest, "myfile.txt", "mytext", "subdir/myfile2.txt", "mytext2")); + } + + @Test + public void testInstallBundle() throws Exception { + Map manifest = ImmutableMap.of( + Attributes.Name.MANIFEST_VERSION.toString(), "1.2.3", + Constants.BUNDLE_VERSION, "4.5.6", + Constants.BUNDLE_SYMBOLICNAME, "myname"); + generatedJar = bundleMaker.copyAddingManifest(emptyJar, manifest); + + Bundle bundle = bundleMaker.installBundle(generatedJar, false); + assertEquals(bundle.getSymbolicName(), "myname"); + assertEquals(bundle.getVersion(), new Version("4.5.6")); + + // Confirm it really is installed in the management context's OSGi framework + Bundle bundle2 = Osgis.bundleFinder(mgmt.getOsgiManager().get().getFramework()) + .symbolicName("myname") + .version("4.5.6") + .findUnique() + .get(); + assertEquals(bundle2, bundle); + } + + private File createEmptyJarFile() throws Exception { + File result = Os.newTempFile("base", "jar"); + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(result)); + zos.close(); + return result; + } + + private void assertJarContents(File f, Map expectedContents) throws Exception { + ZipFile zipFile = new ZipFile(f); + String zipEntriesMsg = "entries="+enumerationToList(zipFile.entries()); + try { + for (Map.Entry entry : expectedContents.entrySet()) { + ZipEntry zipEntry = zipFile.getEntry(entry.getKey()); + assertNotNull(zipEntry, "No entry for "+entry.getKey()+"; "+zipEntriesMsg); + String entryContents = Streams.readFullyString(zipFile.getInputStream(zipEntry)); + assertEquals(entryContents, entry.getValue()); + } + assertEquals(zipFile.size(), expectedContents.size(), zipEntriesMsg); + + } finally { + zipFile.close(); + } + } + + private List enumerationToList(Enumeration e) { + List result = Lists.newArrayList(); + while (e.hasMoreElements()) { + result.add(e.nextElement()); + } + return result; + + } +} diff --git a/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/nomanifest/myfile.txt b/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/nomanifest/myfile.txt new file mode 100644 index 0000000000..917065755b --- /dev/null +++ b/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/nomanifest/myfile.txt @@ -0,0 +1 @@ +mytext \ No newline at end of file diff --git a/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/nomanifest/subdir/myfile2.txt b/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/nomanifest/subdir/myfile2.txt new file mode 100644 index 0000000000..f61890bed4 --- /dev/null +++ b/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/nomanifest/subdir/myfile2.txt @@ -0,0 +1 @@ +mytext2 \ No newline at end of file diff --git a/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/META-INF/MANIFEST.MF b/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..bc5306a44a --- /dev/null +++ b/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.2.3 +mykey: myval diff --git a/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/myfile.txt b/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/myfile.txt new file mode 100644 index 0000000000..917065755b --- /dev/null +++ b/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/myfile.txt @@ -0,0 +1 @@ +mytext \ No newline at end of file diff --git a/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/subdir/myfile2.txt b/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/subdir/myfile2.txt new file mode 100644 index 0000000000..f61890bed4 --- /dev/null +++ b/core/src/test/resources/org/apache/brooklyn/util/core/osgi/test/bundlemaker/withmanifest/subdir/myfile2.txt @@ -0,0 +1 @@ +mytext2 \ No newline at end of file From f20cdecd2fef5c7ff2f6ef33ab7731c7acfb6de4 Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Tue, 28 Feb 2017 22:26:55 +0000 Subject: [PATCH 3/4] CatalogBomScanner tidy --- .../catalog/internal/CatalogBomScanner.java | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java index 6184b1a207..8bbb34d59f 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBomScanner.java @@ -123,9 +123,9 @@ public void setBlackList(String blackListText) { public class CatalogPopulator extends BundleTracker>> { - private ServiceReference mgmtContextReference = null; - private BundleContext bundleContext = null; - private ManagementContext managementContext = null; + private ServiceReference mgmtContextReference; + private BundleContext bundleContext; + private ManagementContext managementContext; public CatalogPopulator(ServiceReference serviceReference) { super(serviceReference.getBundle().getBundleContext(), Bundle.ACTIVE, null); @@ -142,7 +142,7 @@ public CatalogPopulator(BundleContext bundleContext, ManagementContext managemen @Override public void open() { - if (mgmtContextReference!=null) { + if (mgmtContextReference != null) { bundleContext = getBundleContext(); managementContext = getManagementContext(); } @@ -152,7 +152,7 @@ public void open() { @Override public void close() { super.close(); - if (mgmtContextReference!=null) { + if (mgmtContextReference != null) { managementContext = null; getBundleContext().ungetService(mgmtContextReference); bundleContext = null; @@ -160,14 +160,14 @@ public void close() { } public BundleContext getBundleContext() { - if (bundleContext!=null) return bundleContext; - if (mgmtContextReference!=null) return mgmtContextReference.getBundle().getBundleContext(); + if (bundleContext != null) return bundleContext; + if (mgmtContextReference != null) return mgmtContextReference.getBundle().getBundleContext(); throw new IllegalStateException("Bundle context or management context reference must be supplied"); } public ManagementContext getManagementContext() { - if (managementContext!=null) return managementContext; - if (mgmtContextReference!=null) return getBundleContext().getService(mgmtContextReference); + if (managementContext != null) return managementContext; + if (mgmtContextReference != null) return getBundleContext().getService(mgmtContextReference); throw new IllegalStateException("Bundle context or management context reference must be supplied"); } @@ -267,13 +267,14 @@ private boolean on(Bundle bundle, List list) { private String addLibraryDetails(Bundle bundle, String bomText) { @SuppressWarnings("unchecked") final Map bom = (Map)Iterables.getOnlyElement(Yamls.parseAll(bomText)); - @SuppressWarnings("unchecked") - final Map catalog = (Map) bom.get(BROOKLYN_CATALOG); + final Object catalog = bom.get(BROOKLYN_CATALOG); if (null != catalog) { if (catalog instanceof Map) { - addLibraryDetails(bundle, catalog); + @SuppressWarnings("unchecked") + Map catalogMap = (Map) catalog; + addLibraryDetails(bundle, catalogMap); } else { - LOG.warn("Unexpected syntax for {} (expected Map), ignoring", BROOKLYN_CATALOG); + LOG.warn("Unexpected syntax for {} (expected Map, but got {}), ignoring", BROOKLYN_CATALOG, catalog.getClass().getName()); } } final String updatedBom = backToYaml(bom); @@ -294,11 +295,12 @@ private void addLibraryDetails(Bundle bundle, Map catalog) { } final Object librarySpec = catalog.get(BROOKLYN_LIBRARIES); if (!(librarySpec instanceof List)) { - throw new RuntimeException("expected " + BROOKLYN_LIBRARIES + " to be a list"); + throw new RuntimeException("expected " + BROOKLYN_LIBRARIES + " to be a list, but got " + + (librarySpec == null ? "null" : librarySpec.getClass().getName())); } @SuppressWarnings("unchecked") List> libraries = (List>)librarySpec; - if (bundle.getSymbolicName()==null || bundle.getVersion()==null) { + if (bundle.getSymbolicName() == null || bundle.getVersion() == null) { throw new IllegalStateException("Cannot scan "+bundle+" for catalog files: name or version is null"); } libraries.add(ImmutableMap.of( From 1d27f1aada201ab39109625e80bbcf21ea7318c8 Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Tue, 28 Feb 2017 22:38:22 +0000 Subject: [PATCH 4/4] Mark BundleMaker as `@Beta` --- .../java/org/apache/brooklyn/util/core/osgi/BundleMaker.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java index b36e13861e..2be261249e 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/osgi/BundleMaker.java @@ -52,6 +52,7 @@ import com.google.common.base.Predicates; import com.google.common.io.LineReader; +@Beta public class BundleMaker { final static String MANIFEST_PATH = JarFile.MANIFEST_NAME;