eclipse-archived / concierge

Eclipse Concierge™ project
https://www.eclipse.org/concierge/
Eclipse Public License 1.0
33 stars 22 forks source link

BundleContext.getBundle(String) not consistent with BundleContext.getBundles() #83

Open pdolezal opened 4 years ago

pdolezal commented 4 years ago

When I tried Concierge with own launcher I found out that BundleContext.getBundle(String) does not work correctly when the framework uses an existing storage.

Basically the scenario follows this pseudocode:

Framework framework = factory.newFramework(properties); // Here the path to the storage is set
framework.init();
BundleContext systemBundle = framework.getBundleContext();
Bundle bundle = systemBundle.getBundle(location);
if (bundle == null) {
    try (InputStream is = open(location)) {
        bundle = systemBundle.installBundle(location, is);
    }
}

The bundle variable is always null, while it should return the installed bundle when the code is run second time with the same storage. I tried replacing getBundle with something like:

Bundle bundle = Stream.of(systemBundle.getBundles())
    .filter(b -> location.equals(b.getLocation())
    .findAny()
    .orElse(null);

And this always works. So the framework apparently knows which bundles were installed, it just does not update its structures for getBundle to work properly.

The described behavior of getBundle in this use case results in duplicated installation of the bundle – and what is even more interesting: the newly installed bundle has the same location as the existing bundle. I believe this is wrong as well and the location should be unique. Maybe the framework implementation fails to check the location duplicity for the same reason.

tmarkwardt commented 4 years ago

Hi, i don't reproduce this problem. When the bundle is installed, then i can found this bundle with getBundle( String) and with getBundles() and search on array ...

give us a code to reproduce.

Regards Torsten

pdolezal commented 4 years ago

I extracted some code to reproduce the issue, or actually a part of it. There is probably yet something else in my original code that contributed to the described difference between getBundles and getBundle and I'll have to investigate it more thoroughly to isolate it.

However, the code sample below shows a part of the problem, which might be the root cause for the described difference: the framework forgets which bundles were installed in the previous run and therefore allows repeated installations of the same bundle, so that the storage area grows with each run.

package org.example.bug;

import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.stream.Stream;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;

public final class Main {
    public static void main(String... args) throws Exception {
        final String location = args[0];
        final FrameworkFactory factory = ServiceLoader.load(FrameworkFactory.class).iterator().next();
        final Map<String, String> properties = Collections.singletonMap(Constants.FRAMEWORK_STORAGE, "./storage");
        final Framework framework = factory.newFramework(properties);
        framework.init();
        final BundleContext systemBundle = framework.getBundleContext();

        if ((systemBundle.getBundle(location) == null)) {
            System.out.println("Installing: " + location);
            try (InputStream is = Files.newInputStream(Paths.get(location))) {
                final Bundle bundle = systemBundle.installBundle(location, is);
                System.out.println("Installed: " + bundle);
            }
        }

        Stream.of(systemBundle.getBundles()).forEach(bundle -> System.out.format("%d: %s%n", bundle.getBundleId(), bundle));
        framework.stop();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>bug</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <properties>
    <java.version>1.8</java.version>
    <java.compiler.source>${java.version}</java.compiler.source>
    <java.compiler.target>${java.version}</java.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>osgi.core</artifactId>
      <version>5.0.0</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>org.eclipse.concierge</groupId>
      <artifactId>org.eclipse.concierge</artifactId>
      <version>5.1.0</version>
      <scope>runtime</scope>
    </dependency>

    <!--
    <dependency>
      <groupId>org.apache.felix</groupId>
      <artifactId>org.apache.felix.framework</artifactId>
      <version>6.0.3</version>
      <scope>runtime</scope>
    </dependency>
    -->
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>

        <configuration>
          <source>${java.compiler.source}</source>
          <target>${java.compiler.target}</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Execute several times mvn exec:java -Dexec.mainClass=org.example.bug.Main -Dexec.args=some-bundle.jar (where some-bundle.jar points to an actual bundle). IMHO, the more appropriate behavior shows Felix (just swap the framework implementation in the POM as hinted) which does not install the bundle again.

I wonder if it is by design and the storage area should be then cleared for Concierge always to prevent growing it.

tmarkwardt commented 4 years ago

Ok ... i think understand the problem. you want an installed bundle to persist across the life cycle of the framework. when concierge started, concierge don't know which bundles existed on last run. And every new start from framework without clean storage added a new storage folder for the install bundle. persistent data don't store on temporary storage, better the bundle define a persistent storage location outside from the storage.

to implement this functionaly please implement a own startup.