diff --git a/pom.xml b/pom.xml index 18aa920..d7f23fc 100644 --- a/pom.xml +++ b/pom.xml @@ -282,6 +282,13 @@ 2.2.6 test + + + org.jetbrains + annotations + 24.0.1 + provided + org.slf4j diff --git a/src/main/java/org/ops4j/pax/tinybundles/Builder.java b/src/main/java/org/ops4j/pax/tinybundles/Builder.java index 1a6833a..2d3b592 100644 --- a/src/main/java/org/ops4j/pax/tinybundles/Builder.java +++ b/src/main/java/org/ops4j/pax/tinybundles/Builder.java @@ -21,12 +21,11 @@ import java.net.URL; import java.util.Map; +import org.jetbrains.annotations.NotNull; import org.osgi.annotation.versioning.ProviderType; /** - * Builder. - * Implementors should be stateless so builders can be shared. - * Usually the "build" action is an atomic, functional operation. + * Builder for TinyBundles. The builder is used by {@link TinyBundle} internally. * * @author Toni Menzel (tonit) * @since Apr 20, 2009 @@ -35,12 +34,13 @@ public interface Builder { /** - * perform the actual build. + * Builds the bundle with given resources and headers. * - * @param resources resources to be considered in the build. - * @param headers headers to be considered in the build - * @return an {@link InputStream} containing built assembly (usually a jar/bundle in this context) + * @param resources the resources to be considered in the build + * @param headers the headers to be considered in the build + * @return the built assembly (bundle or jar) */ - InputStream build(final Map resources, final Map headers); + @NotNull + InputStream build(@NotNull final Map resources, @NotNull final Map headers); } diff --git a/src/main/java/org/ops4j/pax/tinybundles/TinyBundle.java b/src/main/java/org/ops4j/pax/tinybundles/TinyBundle.java index b0e8549..137a429 100644 --- a/src/main/java/org/ops4j/pax/tinybundles/TinyBundle.java +++ b/src/main/java/org/ops4j/pax/tinybundles/TinyBundle.java @@ -19,12 +19,16 @@ import java.io.InputStream; import java.net.URL; +import java.util.jar.JarInputStream; +import java.util.jar.Manifest; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.osgi.annotation.versioning.ProviderType; +import org.osgi.framework.BundleActivator; /** - * Main type when making bundles with the {@link TinyBundles} library. - * Get an instance from {@link TinyBundles} Factory, add resources and call {@link #build()} to go to the final step. + * TinyBundle provides a fluent API to create and modify OSGi bundles on the fly. * * @author Toni Menzel (tonit) * @since Apr 9, 2009 @@ -33,121 +37,142 @@ public interface TinyBundle { /** - * Add a resource to the current bundle (to be built). + * Adds the class into the bundle and sets it as bundle activator. * - * @param name final path inside the jar - * @param content content to be copied into bundle. - * @return *this* + * @param activator the bundle activator + * @return the tiny bundle */ - TinyBundle add(final String name, final URL content); + @NotNull + TinyBundle activator(@NotNull final Class activator); /** - * Add a resource to the current bundle (to be built). + * Sets the bundle symbolic name. * - * @param name final path inside the jar - * @param content content to be copied into bundle. - * @return *this* + * @param symbolicName the bundle symbolic name + * @return the tiny bundle */ - TinyBundle add(final String name, final InputStream content); + @NotNull + TinyBundle symbolicName(@NotNull final String symbolicName); /** - * Add a class to the current bundle. Uses InnerClassStrategy.ALL + * Adds the resource into the bundle. * - * @param clazz content to be copied into bundle. - * @return {@literal this} + * @param path the path where the resource gets stored in the bundle + * @param resource the resource to be added + * @return the tiny bundle */ - TinyBundle add(final Class clazz); + @NotNull + TinyBundle addResource(@NotNull final String path, @NotNull final URL resource); /** - * Add a class to the current bundle. + * Adds the resource into the bundle. * - * @param clazz + * @param path the path where the resource gets stored in the bundle + * @param resource the resource to be added + * @return the tiny bundle */ - TinyBundle add(final Class clazz, final InnerClassStrategy strategy); + @NotNull + TinyBundle addResource(@NotNull final String path, @NotNull final InputStream resource); /** - * Add a class to the current bundle and set it as Activator + * Removes a resource from the bundle. * - * @param activator + * @param path the path of the to be removed resource + * @return the tiny bundle */ - TinyBundle activator(final Class activator); + @NotNull + TinyBundle removeResource(@NotNull final String path); /** - * Set symbolic name of bundle + * Adds the class into the bundle with default {@link InnerClassStrategy#ALL} strategy. * - * @param name + * @param clazz the class to be added + * @return the tiny bundle */ - TinyBundle symbolicName(final String name); + @NotNull + TinyBundle addClass(@NotNull final Class clazz); /** - * remove a class to the current bundle. + * Adds the class into the bundle. * - * @param clazz class to be removed - * @return *this* + * @param clazz the class to be added + * @param strategy the inner class strategy for the given class + * @return the tiny bundle */ - TinyBundle remove(final Class clazz); + @NotNull + TinyBundle addClass(@NotNull final Class clazz, @NotNull final InnerClassStrategy strategy); /** - * When you are done adding stuff to *this* you can call this method to go to next step. + * Removes the class from the bundle. * - * @param builder builder to be used. - * @return Next step in the bundle making process. + * @param clazz the class to be removed + * @return the tiny bundle */ - InputStream build(final Builder builder); + @NotNull + TinyBundle removeClass(@NotNull final Class clazz); /** - * Build bundle with default builder. + * Sets the {@link Manifest} header. * - * @return Next step in the bundle making process. + * @param name the header name + * @param value the header value + * @return the tiny bundle */ - InputStream build(); + @NotNull + TinyBundle setHeader(@NotNull final String name, @NotNull final String value); /** - * Set header values that go into the Manifest. + * Gets the {@link Manifest} header value for the given name. * - * @param key a key - * @param value a value - * @return {@literal this} + * @param name the header name + * @return the header value or null if header is not present */ - TinyBundle set(final String key, final String value); + @Nullable + String getHeader(@NotNull final String name); /** - * Remove a previously added resource. - * Usually useful if you loaded an existing bundle into {@link TinyBundles} before. + * Removes the {@link Manifest} header. * - * @param key a key as String - * @return {@literal this} + * @param name the name of the to be removed header + * @return the tiny bundle */ - TinyBundle removeResource(final String key); + @NotNull + TinyBundle removeHeader(@NotNull final String name); /** - * Remove a previously added header. - * Usually useful if you loaded an existing bundle into {@link TinyBundles} before. + * Reads an existing bundle or jar into this TinyBundle. * - * @param key a key as String - * @return {@literal this} + * @param jar the stream of JarInputStream + * @return the tiny bundle */ - TinyBundle removeHeader(final String key); + @NotNull + TinyBundle readIn(@NotNull final JarInputStream jar); /** - * Read an existing bundle or jar into TinyBundles for modification. + * Reads an existing jar or bundle into this tiny bundle. * - * @param inputStream stream of JarInputStream - * @return {@literal this} + * @param jar the source jar or bundle + * @param skipContent true to read jar content also, false to read {@link Manifest} only + * @return the tiny bundle */ - TinyBundle read(final InputStream inputStream); + @NotNull + TinyBundle readIn(@NotNull final JarInputStream jar, final boolean skipContent); /** - * Read an existing bundle or jar into TinyBundles for modification. + * Builds the bundle with default bnd {@link Builder}. * - * @param input stream of JarInputStream - * @return {@literal this} + * @return the built bundle */ - TinyBundle read(final InputStream input, final boolean readContent); + @NotNull + InputStream build(); /** - * Get header value. + * Builds the bundle with given {@link Builder}. + * + * @param builder the builder to be used for building + * @return the built bundle */ - String getHeader(final String key); + @NotNull + InputStream build(@NotNull final Builder builder); } diff --git a/src/main/java/org/ops4j/pax/tinybundles/TinyBundles.java b/src/main/java/org/ops4j/pax/tinybundles/TinyBundles.java index d468a40..33492e3 100644 --- a/src/main/java/org/ops4j/pax/tinybundles/TinyBundles.java +++ b/src/main/java/org/ops4j/pax/tinybundles/TinyBundles.java @@ -27,8 +27,7 @@ import org.osgi.framework.ServiceReference; /** - * Statically usable TinyBundles API. - * Usually the entry point for using TinyBundles. + * Statically usable TinyBundles factory. * * @author Toni Menzel * @since 1.0.0 @@ -54,42 +53,37 @@ private static TinyBundlesFactory factory() { } /** - * {@see #bundle(BuildableBundle, org.ops4j.store.Store)} + * Creates a new {@link TinyBundle}. * - * @return a new instance of {@link TinyBundle}. + * @return the new tiny bundle */ public static TinyBundle bundle() { return factory().bundle(); } /** - * Start with a fresh bundle with this factory method. - * You can then chain method calls thanks to the humane nature of {@link TinyBundle} interface. + * Creates a new {@link TinyBundle}. * - * @param store cache backend - * @return a new instance of {@link TinyBundle}. + * @param store the cache backend to use + * @return the new tiny bundle */ public static TinyBundle bundle(final Store store) { return factory().bundle(store); } /** - * @param inner builder when using bnd builder. - * @return a builder to be used with {@link TinyBundle#build(Builder)} using bnd with given builder. - */ - public static Builder bndBuilder(final Builder inner) { - return factory().bndBuilder(inner); - } - - /** - * @return a builder to be used with {@link TinyBundle#build(Builder)} using bnd with raw builder. + * Creates a new bnd builder. + * + * @return the new bnd builder */ public static Builder bndBuilder() { - return factory().bndBuilder(rawBuilder()); + return factory().bndBuilder(); } /** - * @return a builder to be used with {@link TinyBundle#build(Builder)} using no extra manifest computation logic. + * Creates a new raw builder. + * + * @return the new raw builder */ public static Builder rawBuilder() { return factory().rawBuilder(); diff --git a/src/main/java/org/ops4j/pax/tinybundles/TinyBundlesFactory.java b/src/main/java/org/ops4j/pax/tinybundles/TinyBundlesFactory.java index 5603827..abbdf4b 100644 --- a/src/main/java/org/ops4j/pax/tinybundles/TinyBundlesFactory.java +++ b/src/main/java/org/ops4j/pax/tinybundles/TinyBundlesFactory.java @@ -19,20 +19,47 @@ import java.io.InputStream; +import org.jetbrains.annotations.NotNull; import org.ops4j.store.Store; import org.osgi.annotation.versioning.ProviderType; +/** + * TinyBundles factory (OSGi) service. + */ @ProviderType public interface TinyBundlesFactory { + /** + * Creates a new {@link TinyBundle}. + * + * @return the new tiny bundle + */ + @NotNull TinyBundle bundle(); - TinyBundle bundle(final Store store); - - Builder rawBuilder(); + /** + * Creates a new {@link TinyBundle}. + * + * @param store the cache backend to use + * @return the new tiny bundle + */ + @NotNull + TinyBundle bundle(@NotNull final Store store); + /** + * Creates a new bnd builder. + * + * @return the new bnd builder + */ + @NotNull Builder bndBuilder(); - Builder bndBuilder(final Builder inner); + /** + * Creates a new raw builder. + * + * @return the new raw builder + */ + @NotNull + Builder rawBuilder(); } diff --git a/src/main/java/org/ops4j/pax/tinybundles/internal/AbstractBuilder.java b/src/main/java/org/ops4j/pax/tinybundles/internal/AbstractBuilder.java new file mode 100644 index 0000000..6ab0995 --- /dev/null +++ b/src/main/java/org/ops4j/pax/tinybundles/internal/AbstractBuilder.java @@ -0,0 +1,122 @@ +/* + * Copyright 2009 Toni Menzel. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ops4j.pax.tinybundles.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedOutputStream; +import java.net.URL; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +import org.ops4j.pax.tinybundles.Builder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Toni Menzel (tonit) + * @since Apr 20, 2009 + */ +public abstract class AbstractBuilder implements Builder { + + private static final String BUILT_BY = "Built-By"; + + private static final String ENTRY_MANIFEST = "META-INF/MANIFEST.MF"; + + private static final String TOOL = "Tool"; + + private static final String CREATED_BY = "Created-By"; + + private final Logger logger = LoggerFactory.getLogger(AbstractBuilder.class); + + protected void build(final Map resources, final Map headers, final PipedOutputStream pout) { + try (JarOutputStream jarOut = new JarOutputStream(pout)) { + build(resources, headers, jarOut); + } catch (IOException e) { + if (!"Pipe closed".equals(e.getMessage())) { + throw new RuntimeException("Problem while writing jar.", e); + } else { + logger.debug("Pipe closed.", e); + } + } finally { + logger.debug("Copy thread finished."); + } + } + + protected void build(final Map resources, final Map headers, final JarOutputStream jarOut) throws IOException { + addManifest(headers, jarOut); + for (final Map.Entry entry : resources.entrySet()) { + addResource(entry, jarOut); + } + } + + private void addManifest(final Map headers, final JarOutputStream jarOut) throws IOException { + final Manifest manifest = createManifest(headers.entrySet()); + final JarEntry entry = new JarEntry(ENTRY_MANIFEST); + jarOut.putNextEntry(entry); + manifest.write(jarOut); + jarOut.closeEntry(); + } + + private void addResource(final Map.Entry entrySet, final JarOutputStream jarOut) throws IOException { + final JarEntry entry = new JarEntry(entrySet.getKey()); + logger.debug("Adding resource {}, {}", entry.getName(), entrySet.getValue()); + jarOut.putNextEntry(entry); + try (InputStream inputStream = entrySet.getValue().openStream()) { + copy(inputStream, jarOut); + } + } + + private void copy(final InputStream source, final OutputStream sink) throws IOException { + final byte[] buffer = new byte[1024]; + int n; + while ((n = source.read(buffer)) > 0) { + sink.write(buffer, 0, n); + } + } + + /** + * The calculated manifest for this build output. + * Relies on input given. + * + * @param headers headers will be merged into resulting manifest instance. + * @return a fresh manifest instance + */ + protected Manifest createManifest(final Set> headers) { + logger.debug("Creating manifest from added headers."); + final Manifest manifest = new Manifest(); + final String tool = "pax-tinybundles-" + Info.getPaxTinybundlesVersion(); + + manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); + manifest.getMainAttributes().putValue(BUILT_BY, System.getProperty("user.name")); + manifest.getMainAttributes().putValue(CREATED_BY, tool); + manifest.getMainAttributes().putValue(TOOL, tool); + manifest.getMainAttributes().putValue("TinybundlesVersion", tool); + + for (final Map.Entry entry : headers) { + logger.debug("{} = {}", entry.getKey(), entry.getValue()); + manifest.getMainAttributes().putValue(entry.getKey(), entry.getValue()); + } + return manifest; + } + +} diff --git a/src/main/java/org/ops4j/pax/tinybundles/internal/AsyncRawBuilder.java b/src/main/java/org/ops4j/pax/tinybundles/internal/AsyncRawBuilder.java deleted file mode 100644 index f61873d..0000000 --- a/src/main/java/org/ops4j/pax/tinybundles/internal/AsyncRawBuilder.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2011 Toni Menzel. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - * implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.ops4j.pax.tinybundles.internal; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.net.URL; -import java.util.Map; -import java.util.jar.JarOutputStream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Async Builder that uses PipedOutputStream. Bundle is not built until it gets flushed. - */ -public class AsyncRawBuilder extends RawBuilder { - - private final Logger logger = LoggerFactory.getLogger(AsyncRawBuilder.class); - - @Override - public InputStream build(final Map resources, final Map headers) { - logger.debug("building..."); - try { - final PipedInputStream pin = new PipedInputStream(); - final PipedOutputStream pout = new PipedOutputStream(pin); - new Thread(() -> buildFrom(resources, headers, pout)).start(); - return pin; - } catch (IOException e) { - throw new RuntimeException("Error opening pipe.", e); - } - } - - private void buildFrom(final Map resources, final Map headers, final PipedOutputStream pout) { - try (JarOutputStream jarOut = new JarOutputStream(pout)) { - build(resources, headers, jarOut); - } catch (IOException e) { - if (!"Pipe closed".equals(e.getMessage())) { - throw new RuntimeException("Problem while writing jar.", e); - } - } finally { - logger.debug("Copy thread finished."); - } - } - -} diff --git a/src/main/java/org/ops4j/pax/tinybundles/internal/BndBuilder.java b/src/main/java/org/ops4j/pax/tinybundles/internal/BndBuilder.java index 685cbf8..f644110 100644 --- a/src/main/java/org/ops4j/pax/tinybundles/internal/BndBuilder.java +++ b/src/main/java/org/ops4j/pax/tinybundles/internal/BndBuilder.java @@ -28,8 +28,9 @@ import java.util.jar.Manifest; import aQute.bnd.osgi.Analyzer; +import aQute.bnd.osgi.Builder; import aQute.bnd.osgi.Jar; -import org.ops4j.pax.tinybundles.Builder; +import org.jetbrains.annotations.NotNull; import org.osgi.framework.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,60 +39,39 @@ * @author Toni Menzel (tonit) * @since Apr 20, 2009 */ -public class BndBuilder implements Builder { +public class BndBuilder extends AbstractBuilder { private final Logger logger = LoggerFactory.getLogger(BndBuilder.class); - private final Builder builder; - - public BndBuilder(final Builder builder) { - this.builder = builder; - } - @Override - public InputStream build(final Map resources, final Map headers) { - return wrapWithBnd(headers, builder.build(resources, headers)); - } - - private InputStream wrapWithBnd(final Map headers, final InputStream inputStream) { - try { - final Properties instructions = new Properties(); - instructions.putAll(headers); - return createBundle(inputStream, instructions, String.format("BuildByTinyBundles%s", UUID.randomUUID())); + @NotNull + public InputStream build(@NotNull final Map resources, @NotNull final Map headers) { + final PipedInputStream pin = new PipedInputStream(); + try (PipedOutputStream pout = new PipedOutputStream(pin)) { + // creating the jar from pre-build stream instead of putting resources one by one + // allows finer control over added resources + new Thread(() -> build(resources, headers, pout)).start(); + final Jar jar = new Jar("dot", pin); + jar.setManifest(createManifest(headers.entrySet())); + + final Properties properties = new Properties(); + properties.putAll(headers); + final Builder builder = new Builder(); + builder.setJar(jar); + builder.setProperties(properties); + // throw away already existing headers that we overwrite: + builder.mergeManifest(jar.getManifest()); + ensureSanitizedSymbolicName(builder); + final Manifest manifest = builder.calcManifest(); + jar.setManifest(manifest); + return createInputStream(jar); } catch (Exception e) { throw new RuntimeException(e); } } - /** - * All the bnd magic happens here. - * - * @param inputStream On what to operate. - * @param instructions bnd instructions from user API - * @param symbolicName Mandatory Header. In case user does not set it. - * @return Bundle Jar Stream - * @throws Exception Problems go here - */ - private InputStream createBundle(final InputStream inputStream, final Properties instructions, final String symbolicName) throws Exception { - final Jar jar = new Jar("dot", inputStream); - final Properties properties = new Properties(); - properties.putAll(instructions); - - aQute.bnd.osgi.Builder builder = new aQute.bnd.osgi.Builder(); - builder.setJar(jar); - builder.setProperties(properties); - // throw away already existing headers that we overwrite: - builder.mergeManifest(jar.getManifest()); - ensureSanitizedSymbolicName(builder, symbolicName); - final Manifest manifest = builder.calcManifest(); - jar.setManifest(manifest); - - return createInputStream(jar); - } - /** * Creates a piped input stream for the wrapped jar. - * This is done in a thread, so we can return quickly. * * @param jar the wrapped jar * @return an input stream for the wrapped jar @@ -118,12 +98,12 @@ private PipedInputStream createInputStream(final Jar jar) throws IOException { } /** - * Processes symbolic name and replaces OSGi spec invalid characters with "_". + * Sanitizes symbolic name and replaces OSGi spec invalid characters with underscore (_). * - * @param analyzer bnd analyzer - * @param defaultSymbolicName bundle symbolic name + * @param analyzer bnd analyzer */ - private void ensureSanitizedSymbolicName(final Analyzer analyzer, final String defaultSymbolicName) { + private void ensureSanitizedSymbolicName(final Analyzer analyzer) { + final String defaultSymbolicName = String.format("BuildByTinyBundles-%s", UUID.randomUUID()); final String symbolicName = analyzer.getProperty(Constants.BUNDLE_SYMBOLICNAME, defaultSymbolicName); final String sanitizedSymbolicName = symbolicName.replaceAll("[^a-zA-Z_0-9.-]", "_"); analyzer.setProperty(Constants.BUNDLE_SYMBOLICNAME, sanitizedSymbolicName); diff --git a/src/main/java/org/ops4j/pax/tinybundles/internal/DefaultTinyBundlesFactory.java b/src/main/java/org/ops4j/pax/tinybundles/internal/DefaultTinyBundlesFactory.java index e01a2ef..003d8bc 100644 --- a/src/main/java/org/ops4j/pax/tinybundles/internal/DefaultTinyBundlesFactory.java +++ b/src/main/java/org/ops4j/pax/tinybundles/internal/DefaultTinyBundlesFactory.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.util.Objects; +import org.jetbrains.annotations.NotNull; import org.ops4j.pax.tinybundles.Builder; import org.ops4j.pax.tinybundles.TinyBundle; import org.ops4j.pax.tinybundles.TinyBundlesFactory; @@ -31,8 +32,6 @@ public class DefaultTinyBundlesFactory implements TinyBundlesFactory { private Store store; - static final Builder asyncRawBuilder = new AsyncRawBuilder(); - public DefaultTinyBundlesFactory() { // } @@ -48,28 +47,27 @@ private synchronized Store defaultStore() { } @Override + @NotNull public TinyBundle bundle() { return new TinyBundleImpl(defaultStore()); } @Override - public TinyBundle bundle(final Store store) { + @NotNull + public TinyBundle bundle(@NotNull final Store store) { return new TinyBundleImpl(store); } @Override + @NotNull public Builder rawBuilder() { - return asyncRawBuilder; + return new RawBuilder(); } @Override + @NotNull public Builder bndBuilder() { - return new BndBuilder(rawBuilder()); - } - - @Override - public Builder bndBuilder(final Builder inner) { - return new BndBuilder(inner); + return new BndBuilder(); } } diff --git a/src/main/java/org/ops4j/pax/tinybundles/internal/RawBuilder.java b/src/main/java/org/ops4j/pax/tinybundles/internal/RawBuilder.java index 738a52a..da1af21 100644 --- a/src/main/java/org/ops4j/pax/tinybundles/internal/RawBuilder.java +++ b/src/main/java/org/ops4j/pax/tinybundles/internal/RawBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2009 Toni Menzel. + * Copyright 2011 Toni Menzel. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,89 +19,32 @@ import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.net.URL; import java.util.Map; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; +import org.jetbrains.annotations.NotNull; import org.ops4j.pax.tinybundles.Builder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * @author Toni Menzel (tonit) - * @since Apr 20, 2009 - */ -public abstract class RawBuilder implements Builder { +public class RawBuilder extends AbstractBuilder implements Builder { private final Logger logger = LoggerFactory.getLogger(RawBuilder.class); - private static final String BUILT_BY = "Built-By"; - - private static final String ENTRY_MANIFEST = "META-INF/MANIFEST.MF"; - - private static final String TOOL = "Tool"; - - private static final String CREATED_BY = "Created-By"; - - // This is what implementations need to call. - protected void build(final Map resources, final Map headers, final JarOutputStream jarOut) throws IOException { - addManifest(headers, jarOut); - for (final Map.Entry entry : resources.entrySet()) { - addResource(jarOut, entry); - } - } - - private void addManifest(final Map headers, final JarOutputStream jarOut) throws IOException { - final JarEntry entry = new JarEntry(ENTRY_MANIFEST); - jarOut.putNextEntry(entry); - getManifest(headers.entrySet()).write(jarOut); - jarOut.closeEntry(); - } - - private void addResource(final JarOutputStream jarOut, final Map.Entry entrySet) throws IOException { - final JarEntry entry = new JarEntry(entrySet.getKey()); - logger.debug("Copying resource {}, {}", entry.getName(), entrySet.getValue()); - jarOut.putNextEntry(entry); - try (InputStream inputStream = entrySet.getValue().openStream()) { - copy(inputStream, jarOut); - } - } - - private void copy(final InputStream source, final OutputStream sink) throws IOException { - final byte[] buffer = new byte[1024]; - int n; - while ((n = source.read(buffer)) > 0) { - sink.write(buffer, 0, n); - } - } - - /** - * The calculated manifest for this build output. - * Relies on input given. - * - * @param headers headers will be merged into resulting manifest instance. - * @return a fresh manifest instance - */ - private Manifest getManifest(final Set> headers) { - logger.debug("Creating manifest from added headers."); - final Manifest manifest = new Manifest(); - final String tool = "pax-tinybundles-" + Info.getPaxTinybundlesVersion(); - - manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); - manifest.getMainAttributes().putValue(BUILT_BY, System.getProperty("user.name")); - manifest.getMainAttributes().putValue(CREATED_BY, tool); - manifest.getMainAttributes().putValue(TOOL, tool); - manifest.getMainAttributes().putValue("TinybundlesVersion", tool); - - for (final Map.Entry entry : headers) { - logger.debug("{} = {}", entry.getKey(), entry.getValue()); - manifest.getMainAttributes().putValue(entry.getKey(), entry.getValue()); + @Override + @NotNull + public InputStream build(@NotNull final Map resources, @NotNull final Map headers) { + logger.debug("building..."); + try { + final PipedInputStream pin = new PipedInputStream(); + final PipedOutputStream pout = new PipedOutputStream(pin); + new Thread(() -> build(resources, headers, pout)).start(); + return pin; + } catch (IOException e) { + throw new RuntimeException("Error opening pipe.", e); } - return manifest; } } diff --git a/src/main/java/org/ops4j/pax/tinybundles/internal/TinyBundleImpl.java b/src/main/java/org/ops4j/pax/tinybundles/internal/TinyBundleImpl.java index 5566db4..1de1578 100644 --- a/src/main/java/org/ops4j/pax/tinybundles/internal/TinyBundleImpl.java +++ b/src/main/java/org/ops4j/pax/tinybundles/internal/TinyBundleImpl.java @@ -30,10 +30,13 @@ import java.util.jar.JarInputStream; import java.util.jar.Manifest; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.ops4j.pax.tinybundles.Builder; import org.ops4j.pax.tinybundles.InnerClassStrategy; import org.ops4j.pax.tinybundles.TinyBundle; import org.ops4j.store.Store; +import org.osgi.framework.BundleActivator; import org.osgi.framework.Constants; import static org.ops4j.pax.tinybundles.TinyBundles.bndBuilder; @@ -68,14 +71,14 @@ private void addManifestAttributes(final JarInputStream jarIn) { for (final Object key : attributes.keySet()) { final String k = key.toString(); final String v = attributes.getValue(k); - set(k, v); + setHeader(k, v); } } private void addContents(final JarInputStream jarIn) throws IOException { JarEntry entry; while (!Objects.isNull(entry = jarIn.getNextJarEntry())) { - add(entry.getName(), jarIn); + addResource(entry.getName(), jarIn); } } @@ -97,40 +100,42 @@ private Collection findInnerClasses(final Class clazz, final } @Override - public TinyBundle read(final InputStream inputStream, final boolean readContent) { - if (!Objects.isNull(inputStream)) { - try (JarInputStream jarIn = new JarInputStream(inputStream)) { - addManifestAttributes(jarIn); - if (readContent) { - addContents(jarIn); - } - } catch (IOException e) { - throw new RuntimeException("Problem loading bundle.", e); + @NotNull + public TinyBundle readIn(@NotNull final JarInputStream jar, final boolean skipContent) { + addManifestAttributes(jar); + try { + if (!skipContent) { + addContents(jar); } + } catch (IOException e) { + throw new RuntimeException("Problem loading bundle.", e); } return this; } @Override - public TinyBundle read(final InputStream inputStream) { - return read(inputStream, true); + @NotNull + public TinyBundle readIn(@NotNull final JarInputStream jar) { + return readIn(jar, false); } @Override - public TinyBundle add(final Class clazz) { - add(clazz, InnerClassStrategy.ALL); + @NotNull + public TinyBundle addClass(@NotNull final Class clazz) { + addClass(clazz, InnerClassStrategy.ALL); return this; } @Override - public TinyBundle add(final Class clazz, final InnerClassStrategy strategy) { - final String name = ClassFinder.asResource(clazz); - final URL resource = clazz.getResource("/" + name); + @NotNull + public TinyBundle addClass(@NotNull final Class clazz, @NotNull final InnerClassStrategy strategy) { + final String path = ClassFinder.asResource(clazz); + final URL resource = clazz.getResource("/" + path); if (Objects.isNull(resource)) { - throw new IllegalArgumentException(String.format("Class %s not found! (resource: %s )", clazz.getName(), name)); + throw new IllegalArgumentException(String.format("Class %s not found! (resource: %s )", clazz.getName(), path)); } - add(name, resource); + addResource(path, resource); final Collection innerClasses = findInnerClasses(clazz, strategy); for (final ClassDescriptor descriptor : innerClasses) { @@ -140,71 +145,82 @@ public TinyBundle add(final Class clazz, final InnerClassStrategy strategy) { } @Override - public TinyBundle activator(final Class activator) { - this.add(activator); - this.set(Constants.BUNDLE_ACTIVATOR, activator.getName()); + @NotNull + public TinyBundle activator(@NotNull final Class activator) { + this.addClass(activator); + this.setHeader(Constants.BUNDLE_ACTIVATOR, activator.getName()); return this; } @Override - public TinyBundle symbolicName(final String name) { - this.set(Constants.BUNDLE_SYMBOLICNAME, name); + @NotNull + public TinyBundle symbolicName(@NotNull final String symbolicName) { + this.setHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicName); return this; } @Override - public TinyBundle remove(final Class clazz) { + @NotNull + public TinyBundle removeClass(@NotNull final Class clazz) { final String name = ClassFinder.asResource(clazz); removeResource(name); return this; } @Override - public TinyBundle add(final String name, final URL url) { - resources.put(name, url); + @NotNull + public TinyBundle addResource(@NotNull final String path, @NotNull final URL resource) { + resources.put(path, resource); return this; } @Override - public TinyBundle add(final String name, final InputStream content) { + @NotNull + public TinyBundle addResource(@NotNull final String path, @NotNull final InputStream resource) { try { - return add(name, store.getLocation(store.store(content)).toURL()); + return addResource(path, store.getLocation(store.store(resource)).toURL()); } catch (IOException e) { throw new RuntimeException(e); } } @Override + @NotNull public InputStream build() { return bndBuilder().build(resources, headers); } @Override - public InputStream build(final Builder builder) { + @NotNull + public InputStream build(@NotNull final Builder builder) { return builder.build(resources, headers); } @Override - public TinyBundle set(final String key, final String value) { - headers.put(key, value); + @NotNull + public TinyBundle setHeader(@NotNull final String name, @NotNull final String value) { + headers.put(name, value); return this; } @Override - public TinyBundle removeResource(final String key) { + @NotNull + public TinyBundle removeResource(@NotNull final String key) { resources.remove(key); return this; } @Override - public TinyBundle removeHeader(final String key) { - headers.remove(key); + @NotNull + public TinyBundle removeHeader(@NotNull final String name) { + headers.remove(name); return this; } @Override - public String getHeader(final String key) { - return headers.get(key); + @Nullable + public String getHeader(@NotNull final String name) { + return headers.get(name); } } diff --git a/src/main/java/org/ops4j/pax/tinybundles/package-info.java b/src/main/java/org/ops4j/pax/tinybundles/package-info.java index b4a6e60..16f25d6 100644 --- a/src/main/java/org/ops4j/pax/tinybundles/package-info.java +++ b/src/main/java/org/ops4j/pax/tinybundles/package-info.java @@ -17,9 +17,7 @@ */ /** - * Provides the classes necessary to create and modify OSGi bundles from java api. - *

- * The {@link org.ops4j.pax.tinybundles.TinyBundles} class is the factory to get everything started. + * Provides a fluent Java API to create and modify OSGi bundles on the fly. * * @since 1.0 */ diff --git a/src/test/java/org/ops4j/pax/tinybundles/it/DeclarativeServiceBndBundleBuildIT.java b/src/test/java/org/ops4j/pax/tinybundles/it/DeclarativeServiceBndBundleBuildIT.java index b47bb22..f58a35c 100644 --- a/src/test/java/org/ops4j/pax/tinybundles/it/DeclarativeServiceBndBundleBuildIT.java +++ b/src/test/java/org/ops4j/pax/tinybundles/it/DeclarativeServiceBndBundleBuildIT.java @@ -54,7 +54,7 @@ public class DeclarativeServiceBndBundleBuildIT extends TinybundlesTestSupport { private InputStream dsBundle() { return bundle() .symbolicName(BUNDLE_SYMBOLICNAME) - .add(DsService.class) + .addClass(DsService.class) .build(bndBuilder()); } diff --git a/src/test/java/org/ops4j/pax/tinybundles/test/BndTest.java b/src/test/java/org/ops4j/pax/tinybundles/test/BndTest.java index f2c0fcd..4263d33 100644 --- a/src/test/java/org/ops4j/pax/tinybundles/test/BndTest.java +++ b/src/test/java/org/ops4j/pax/tinybundles/test/BndTest.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.jar.Attributes; +import java.util.jar.JarInputStream; import org.junit.Test; import org.ops4j.pax.tinybundles.demo.HelloWorld; @@ -61,7 +62,7 @@ public void bndCustomHeaders() throws IOException { @Test public void bndDeclarativeServices() throws IOException { final InputStream bundle = bundle() - .add(DsService.class) + .addClass(DsService.class) .build(bndBuilder()); final Attributes attributes = getManifest(bundle).getMainAttributes(); assertThat(attributes.getValue("Service-Component"), is("OSGI-INF/org.ops4j.pax.tinybundles.demo.ds.DsService.xml")); @@ -70,11 +71,11 @@ public void bndDeclarativeServices() throws IOException { @Test public void createTestAllDefault() throws IOException { final InputStream bundle = bundle() - .add(HelloWorldActivator.class) - .add(HelloWorld.class) - .add(HelloWorldImpl.class) - .set(Constants.BUNDLE_SYMBOLICNAME, "MyFirstTinyBundle") - .set(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) + .addClass(HelloWorldActivator.class) + .addClass(HelloWorld.class) + .addClass(HelloWorldImpl.class) + .setHeader(Constants.BUNDLE_SYMBOLICNAME, "MyFirstTinyBundle") + .setHeader(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) .build(bndBuilder()); final Attributes attributes = getManifest(bundle).getMainAttributes(); // calculated import @@ -86,12 +87,12 @@ public void createTestAllDefault() throws IOException { @Test public void createTestExport() throws IOException { final InputStream bundle = bundle() - .add(HelloWorldActivator.class) - .add(HelloWorld.class) - .add(HelloWorldImpl.class) - .set(Constants.BUNDLE_SYMBOLICNAME, "MyFirstTinyBundle") - .set(Constants.EXPORT_PACKAGE, HelloWorld.class.getPackage().getName()) - .set(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) + .addClass(HelloWorldActivator.class) + .addClass(HelloWorld.class) + .addClass(HelloWorldImpl.class) + .setHeader(Constants.BUNDLE_SYMBOLICNAME, "MyFirstTinyBundle") + .setHeader(Constants.EXPORT_PACKAGE, HelloWorld.class.getPackage().getName()) + .setHeader(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) .build(bndBuilder()); final Attributes attributes = getManifest(bundle).getMainAttributes(); // calculated import and re-import @@ -103,15 +104,14 @@ public void createTestExport() throws IOException { @Test public void embedDependency() throws IOException { final InputStream bundle = bundle() - .add(HelloWorldActivator.class) - .add(HelloWorld.class) - .add(HelloWorldImpl.class) - .set(Constants.BUNDLE_SYMBOLICNAME, "MyFirstTinyBundle") - .set(Constants.EXPORT_PACKAGE, HelloWorld.class.getPackage().getName()) - .set(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) - .set(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) - .set("Bundle-Classpath", ".,ant-1.8.1.jar") - .set("Include-Resource", "@/Users/tonit/devel/gradle/lib/ant-1.8.1.jar") + .addClass(HelloWorldActivator.class) + .addClass(HelloWorld.class) + .addClass(HelloWorldImpl.class) + .setHeader(Constants.BUNDLE_SYMBOLICNAME, "MyFirstTinyBundle") + .setHeader(Constants.EXPORT_PACKAGE, HelloWorld.class.getPackage().getName()) + .setHeader(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) + .setHeader("Bundle-Classpath", ".,ant-1.8.1.jar") + .setHeader("Include-Resource", "@/Users/tonit/devel/gradle/lib/ant-1.8.1.jar") .build(bndBuilder()); final Attributes attributes = getManifest(bundle).getMainAttributes(); // calculated import and re-import @@ -123,18 +123,18 @@ public void embedDependency() throws IOException { @Test public void modifyTest() throws IOException { final InputStream bundle1 = bundle() - .add(HelloWorldActivator.class) - .add(HelloWorld.class) - .add(HelloWorldImpl.class) - .set(Constants.BUNDLE_SYMBOLICNAME, "MyFirstTinyBundle") - .set(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) + .addClass(HelloWorldActivator.class) + .addClass(HelloWorld.class) + .addClass(HelloWorldImpl.class) + .setHeader(Constants.BUNDLE_SYMBOLICNAME, "MyFirstTinyBundle") + .setHeader(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) .build(bndBuilder()); // Add an export: final InputStream bundle2 = bundle() - .read(bundle1) - .set(Constants.EXPORT_PACKAGE, HelloWorld.class.getPackage().getName()) - .set(Constants.IMPORT_PACKAGE, "*") - .set("another", "property") + .readIn(new JarInputStream(bundle1)) + .setHeader(Constants.EXPORT_PACKAGE, HelloWorld.class.getPackage().getName()) + .setHeader(Constants.IMPORT_PACKAGE, "*") + .setHeader("another", "property") .build(bndBuilder()); // test output final Attributes attributes = getManifest(bundle2).getMainAttributes(); diff --git a/src/test/java/org/ops4j/pax/tinybundles/test/InnerClassesTest.java b/src/test/java/org/ops4j/pax/tinybundles/test/InnerClassesTest.java index b858094..b22dae0 100644 --- a/src/test/java/org/ops4j/pax/tinybundles/test/InnerClassesTest.java +++ b/src/test/java/org/ops4j/pax/tinybundles/test/InnerClassesTest.java @@ -33,7 +33,7 @@ public class InnerClassesTest { @Test public void allInnerClassesTest() { - bundle().add(DemoAnonymousInnerClass.class, InnerClassStrategy.ALL).build( + bundle().addClass(DemoAnonymousInnerClass.class, InnerClassStrategy.ALL).build( (resources, headers) -> { assertThat(resources.keySet(), hasItems( "org/ops4j/pax/tinybundles/demo/DemoAnonymousInnerClass.class", @@ -49,7 +49,7 @@ public void allInnerClassesTest() { @Test public void anonymousInnerClassesTest() { - bundle().add(DemoAnonymousInnerClass.class, InnerClassStrategy.ANONYMOUS).build( + bundle().addClass(DemoAnonymousInnerClass.class, InnerClassStrategy.ANONYMOUS).build( (resources, headers) -> { final Set keys = resources.keySet(); assertThat(keys, hasItem("org/ops4j/pax/tinybundles/demo/DemoAnonymousInnerClass.class")); @@ -63,7 +63,7 @@ public void anonymousInnerClassesTest() { @Test public void noInnerClassesTest() { - bundle().add(DemoAnonymousInnerClass.class, InnerClassStrategy.NONE).build( + bundle().addClass(DemoAnonymousInnerClass.class, InnerClassStrategy.NONE).build( (resources, headers) -> { final Set keys = resources.keySet(); assertThat(keys, hasItem("org/ops4j/pax/tinybundles/demo/DemoAnonymousInnerClass.class")); diff --git a/src/test/java/org/ops4j/pax/tinybundles/test/RawTest.java b/src/test/java/org/ops4j/pax/tinybundles/test/RawTest.java index 914a383..f040e4b 100644 --- a/src/test/java/org/ops4j/pax/tinybundles/test/RawTest.java +++ b/src/test/java/org/ops4j/pax/tinybundles/test/RawTest.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.util.jar.Attributes; +import java.util.jar.JarInputStream; import org.junit.Test; import org.ops4j.pax.tinybundles.demo.HelloWorld; @@ -51,15 +52,15 @@ public class RawTest { private static final String HEADER_BUILT_BY = "Built-By"; - private static InputStream createTestBundle(final String caption) { + private static InputStream createTestBundle(final String symbolicName) { return bundle() - .add(HelloWorldActivator.class) - .add(HelloWorld.class) - .add(HelloWorldImpl.class) - .set(Constants.BUNDLE_SYMBOLICNAME, caption) - .set(Constants.EXPORT_PACKAGE, "demo") - .set(Constants.IMPORT_PACKAGE, "demo") - .set(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) + .addClass(HelloWorldActivator.class) + .addClass(HelloWorld.class) + .addClass(HelloWorldImpl.class) + .setHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicName) + .setHeader(Constants.EXPORT_PACKAGE, "demo") + .setHeader(Constants.IMPORT_PACKAGE, "demo") + .setHeader(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) .build(rawBuilder()); } @@ -80,7 +81,8 @@ public void testReadBundleWithoutManifestDoesNotThrowException() throws Exceptio final File file = File.createTempFile("test", ".jar"); file.deleteOnExit(); createEmptyJar(file); - bundle().read(Files.newInputStream(file.toPath())); + final JarInputStream jarIn = new JarInputStream(Files.newInputStream(file.toPath())); + bundle().readIn(jarIn); } @Test @@ -109,18 +111,18 @@ public void testDefaultPropertiesAreSetCorrectly() throws IOException { public void modifyTest() throws IOException { // create a bundle final InputStream originalBundle = bundle() - .add(HelloWorldActivator.class) - .add(HelloWorld.class) - .add(HelloWorldImpl.class) - .set(Constants.BUNDLE_SYMBOLICNAME, "MyFirstTinyBundle") - .set(Constants.EXPORT_PACKAGE, "demo") - .set(Constants.IMPORT_PACKAGE, "demo") - .set(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) + .addClass(HelloWorldActivator.class) + .addClass(HelloWorld.class) + .addClass(HelloWorldImpl.class) + .setHeader(Constants.BUNDLE_SYMBOLICNAME, "MyFirstTinyBundle") + .setHeader(Constants.EXPORT_PACKAGE, "demo") + .setHeader(Constants.IMPORT_PACKAGE, "demo") + .setHeader(Constants.BUNDLE_ACTIVATOR, HelloWorldActivator.class.getName()) .build(rawBuilder()); // create a new bundle from the original and modify it final InputStream modifiedBundle = bundle() - .read(originalBundle) - .set(Constants.EXPORT_PACKAGE, "bacon") + .readIn(new JarInputStream(originalBundle)) + .setHeader(Constants.EXPORT_PACKAGE, "bacon") .build(rawBuilder()); final Attributes attributes = getManifest(modifiedBundle).getMainAttributes(); assertThat(attributes.getValue(Constants.IMPORT_PACKAGE), is("demo"));