Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
69 changes: 69 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Eclipse Gemini Blueprint is the reference implementation of the OSGi Alliance Blueprint Service (OSGi 4.2 Compendium Specification). It integrates Spring Framework with OSGi, enabling Spring-based applications to run inside OSGi containers. Current version: 4.0.0.BUILD-SNAPSHOT, targeting Java 17+ (compiled with source/target 17), Spring 7.0.6, OSGi Core 8.0.0/Compendium 7.0.0.

## Build Commands

```bash
# Build and run unit tests (all modules except integration-tests)
./mvnw install

# Integration tests with a specific OSGi framework (run from project root)
# Must pick one profile: equinox or felix
./mvnw clean install -P equinox
./mvnw clean install -P felix

# Run integration tests only (after initial install)
cd integration-tests && ./mvnw clean install -P equinox

# Run a single unit test
./mvnw test -pl core -Dtest=ClassName

# Run a single integration test
cd integration-tests/tests && ./mvnw test -P equinox -Dtest=ClassName
```

Integration tests fork a fresh JVM per run with a 45-minute timeout. Test bundles and tests must be built together per profile — separating these stages causes OSGi dependency issues.

## Module Architecture

```
mock/ → Mock implementations of OSGi interfaces (BundleContext, ServiceReference, etc.)
io/ → Resource loading abstractions for OSGi (OsgiBundleResource, ResourcePatternResolver)
core/ → Main module: application context, service import/export, Blueprint container,
XML config namespace, ConfigurationAdmin integration
extender/ → OSGi extender pattern: listens for bundle events and bootstraps Blueprint
containers for bundles containing Spring/Blueprint configuration
extensions/ → Proprietary extensions beyond the OSGi Blueprint specification
test-support/ → JUnit-based framework for running integration tests inside live OSGi containers
integration-tests/
bundles/ → 50+ test bundles exercising various Blueprint scenarios
tests/ → Integration test runner module
```

**Dependency flow:** mock → io → core → extender → extensions; test-support depends on core + extender.

## Key Architectural Concepts

- **Service Import/Export** (`core/.../service/`): Proxies that dynamically track OSGi services. Importers create proxies for consumed services; exporters register Spring beans as OSGi services.
- **Blueprint Container** (`core/.../blueprint/container/`): Implementation of the OSGi Blueprint Container spec — manages component lifecycle, dependency injection, and service binding.
- **Extender Pattern** (`extender/.../internal/activator/`): The extender bundle watches for other bundles being installed/started and automatically creates Blueprint containers for them.
- **OSGi-Spring Context Bridge** (`core/.../context/`): Extends Spring's `ApplicationContext` to work within OSGi, handling classloader delegation and bundle-scoped contexts.

## OSGi Bundle Configuration

Each module uses a `bnd.bnd` file (processed by the `bnd-maven-plugin`) to generate OSGi manifest headers (Export-Package, Import-Package, Bundle-Activator, etc.). These are in each module's root directory.

## Maven Profiles

| Profile | Purpose |
|---------|---------|
| `equinox` | Run integration tests on Eclipse Equinox |
| `felix` | Run integration tests on Apache Felix |
| `security` | Enable Java 2 Security Manager for tests |
| `clover` | Enable Clover code coverage |
| `release` | Full release build with GPG signing and docs |
10 changes: 0 additions & 10 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,5 @@ spec:
}
}
}
stage('Integration tests with Knopflerfish profile') {
steps {
container('maven') {
dir("integration-tests") {
sh 'mvn clean install -P knopflerfish'
junit '**/target/surefire-reports/*.xml'
}
}
}
}
}
}
6 changes: 3 additions & 3 deletions core/bnd.bnd
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ Export-Package: !*.internal.*, \
org.eclipse.gemini.blueprint;version=${project.version}

Import-Package: \
org.springframework.aop;version="${spring.version.range.nq}", \
org.springframework.beans.propertyeditors;version="${spring.version.range.nq}", \
org.springframework.aop, \
org.springframework.beans.propertyeditors, \
org.osgi.framework;version=1.3, \
org.aopalliance.*;version="0.0.0", \
org.osgi.service.cm;version=1.2;resolution:=optional, \
org.osgi.service.startlevel;version=1.0;resolution:=optional, \
org.apache.commons.logging.*;version=${logging.version.range}, \
org.springframework.*;version=${spring.version.range}, \
org.springframework.*, \
*;resolution:="optional"


Expand Down
35 changes: 15 additions & 20 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>gemini-blueprint</artifactId>
<groupId>org.eclipse.gemini.blueprint</groupId>
<version>3.0.1.BUILD-SNAPSHOT</version>
<version>4.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down Expand Up @@ -36,43 +36,38 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.spring-aop</artifactId>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.spring-beans</artifactId>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.spring-context</artifactId>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.spring-core</artifactId>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.spring-expression</artifactId>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.spring-test</artifactId>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.aopalliance</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<artifactId>osgi.core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
<artifactId>osgi.cmpn</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ class UpdateMethodAdapter {
* @return
*/
static Map determineUpdateMethod(final Class<?> target, final String methodName) {
Assert.notNull(target);
Assert.notNull(methodName);
Assert.notNull(target, "argument must not be null");
Assert.notNull(methodName, "argument must not be null");

final Map methods = new LinkedHashMap(2);
final boolean trace = log.isTraceEnabled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ protected AbstractBeanDefinition parseInternal(Element element, ParserContext pa
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();

Class<?> beanClass = getBeanClass(element);
Assert.notNull(beanClass);
Assert.notNull(beanClass, "argument must not be null");

if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class OsgiDefaultsDefinition {
private Availability availability = Availability.MANDATORY;

public OsgiDefaultsDefinition(Document document, ParserContext parserContext) {
Assert.notNull(document);
Assert.notNull(document, "argument must not be null");
Element root = document.getDocumentElement();

ReferenceParsingUtil.checkAvailabilityAndCardinalityDuplication(root, DEFAULT_AVAILABILITY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ static Map<Class<?>, List<Method>> determineCustomMethods(final Class<?> target,
return Collections.emptyMap();
}

Assert.notEmpty(possibleArgumentTypes);
Assert.notEmpty(possibleArgumentTypes, "argument must not be empty");

if (System.getSecurityManager() != null) {
return AccessController.doPrivileged(new PrivilegedAction<Map<Class<?>, List<Method>>>() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public class OsgiServiceLifecycleListenerAdapter implements OsgiServiceLifecycle
private boolean initialized;

public void afterPropertiesSet() {
Assert.notNull(beanFactory);
Assert.notNull(beanFactory, "argument must not be null");
Assert.isTrue(target != null || StringUtils.hasText(targetBeanName),
"one of 'target' or 'targetBeanName' properties has to be set");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public class OsgiServiceRegistrationListenerAdapter implements OsgiServiceRegist
private boolean isBlueprintCompliant = false;

public void afterPropertiesSet() {
Assert.notNull(beanFactory);
Assert.notNull(beanFactory, "argument must not be null");
Assert.isTrue(target != null || StringUtils.hasText(targetBeanName),
"one of 'target' or 'targetBeanName' properties has to be set");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public abstract class OsgiBundleApplicationContextEvent extends ApplicationConte
*/
public OsgiBundleApplicationContextEvent(ApplicationContext source, Bundle bundle) {
super(source);
Assert.notNull(bundle);
Assert.notNull(bundle, "argument must not be null");
this.bundle = bundle;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class OsgiBundleApplicationContextEventMulticasterAdapter implements
* @param delegatedMulticaster
*/
public OsgiBundleApplicationContextEventMulticasterAdapter(ApplicationEventMulticaster delegatedMulticaster) {
Assert.notNull(delegatedMulticaster);
Assert.notNull(delegatedMulticaster, "argument must not be null");
this.delegatedMulticaster = delegatedMulticaster;
}

Expand All @@ -47,7 +47,7 @@ public OsgiBundleApplicationContextEventMulticasterAdapter(ApplicationEventMulti
* listener instance. However, depending on the equals implementation, this might affect the object identity.
*/
public void addApplicationListener(OsgiBundleApplicationContextListener osgiListener) {
Assert.notNull(osgiListener);
Assert.notNull(osgiListener, "argument must not be null");
delegatedMulticaster.addApplicationListener(ApplicationListenerAdapter.createAdapter(osgiListener));
}

Expand All @@ -66,7 +66,7 @@ public void removeAllListeners() {
* listener instance. However, depending on the equals implementation, this might affect the object identity.
*/
public void removeApplicationListener(OsgiBundleApplicationContextListener osgiListener) {
Assert.notNull(null);
Assert.notNull(null, "argument must not be null");
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removeApplicationListener currently calls Assert.notNull(null, ...), which will always throw and makes it impossible to remove listeners. This should validate osgiListener (or simply delegate without asserting) instead of asserting on null.

Suggested change
Assert.notNull(null, "argument must not be null");
Assert.notNull(osgiListener, "argument must not be null");

Copilot uses AI. Check for mistakes.
delegatedMulticaster.removeApplicationListener(ApplicationListenerAdapter.createAdapter(osgiListener));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.support.AbstractBeanFactory;
import org.springframework.beans.factory.support.SecurityContextProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.support.AbstractRefreshableApplicationContext;
Expand Down Expand Up @@ -229,16 +228,6 @@ protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactor
beanFactory.addBeanPostProcessor(new BundleContextAwareProcessor(this.bundleContext));
beanFactory.ignoreDependencyInterface(BundleContextAware.class);

if (beanFactory instanceof AbstractBeanFactory) {
AbstractBeanFactory bf = (AbstractBeanFactory) beanFactory;
bf.setSecurityContextProvider(new SecurityContextProvider() {

public AccessControlContext getAccessControlContext() {
return acc;
}
});
}

enforceExporterImporterDependency(beanFactory);

// add predefined beans
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class ChainedEntityResolver implements EntityResolver {


public void addEntityResolver(EntityResolver resolver, String resolverToString) {
Assert.notNull(resolver);
Assert.notNull(resolver, "argument must not be null");
resolvers.put(resolver, resolverToString);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class DelegatedNamespaceHandlerResolver implements NamespaceHandlerResolver {


public void addNamespaceHandler(NamespaceHandlerResolver resolver, String resolverToString) {
Assert.notNull(resolver);
Assert.notNull(resolver, "argument must not be null");
resolvers.put(resolver, resolverToString);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,9 @@ private EntityResolver lookupEntityResolver(final BundleContext bundleContext, S
public String[] getConfigLocations() {
return super.getConfigLocations();
}

@Override
public void destroy() {
close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public ChainedClassLoader(ClassLoader[] loaders, ClassLoader parent) {

this.parent = parent;

Assert.notEmpty(loaders);
Assert.notEmpty(loaders, "argument must not be empty");

synchronized (this.loaders) {
for (int i = 0; i < loaders.length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public abstract class ClassLoaderFactory {
* @return AOP class loader created using the given argument
*/
public static ChainedClassLoader getAopClassLoaderFor(ClassLoader classLoader) {
Assert.notNull(classLoader);
Assert.notNull(classLoader, "argument must not be null");
return aopClassLoaderFactory.createClassLoader(classLoader);
}

Expand All @@ -65,7 +65,7 @@ public static ChainedClassLoader getAopClassLoaderFor(ClassLoader classLoader) {
* @return associated wrapping class loader
*/
public static ClassLoader getBundleClassLoaderFor(Bundle bundle) {
Assert.notNull(bundle);
Assert.notNull(bundle, "argument must not be null");
return bundleClassLoaderFactory.createClassLoader(bundle);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public static class BundleScopeServiceFactory implements ServiceFactory {


public BundleScopeServiceFactory(ServiceFactory serviceFactory) {
Assert.notNull(serviceFactory);
Assert.notNull(serviceFactory, "argument must not be null");
this.decoratedServiceFactory = serviceFactory;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
package org.eclipse.gemini.blueprint.context.support.internal.security;

import java.security.AccessControlContext;
import java.security.AccessController;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;

/**
Expand All @@ -26,13 +26,9 @@
public abstract class SecurityUtils {

public static AccessControlContext getAccFrom(BeanFactory beanFactory) {
AccessControlContext acc = null;
if (beanFactory != null) {
if (beanFactory instanceof ConfigurableBeanFactory) {
return ((ConfigurableBeanFactory) beanFactory).getAccessControlContext();
}
}
return acc;
// Security Manager is deprecated since Java 17 and getAccessControlContext()
// was removed from Spring 6. Return the current context as a fallback.
return AccessController.getContext();
}

public static AccessControlContext getAccFrom(ApplicationContext ac) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public void importerUnsatisfied(Object importer, OsgiServiceDependency dependenc
private ConfigurableListableBeanFactory beanFactory;

public void addServiceExporter(Object exporter, String exporterBeanName) {
Assert.hasText(exporterBeanName);
Assert.hasText(exporterBeanName, "argument must have text");

if (exportersSeen.putIfAbsent(exporterBeanName, VALUE) == null) {

Expand Down
Loading
Loading