Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
65 changes: 64 additions & 1 deletion scr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The Apache Felix Service Component Runtime described by the [OSGi Declarative Se

The Java annotations defined by the specification make implementing components easy and reduce the amount of code that needs be written. These annotations are processed at build time and translated into XML descriptor files which in turn are listed in the `Service-Component` header of the declaring bundle. But the good news is, you usually don't have to worry about this XML, however in case things don't work as expected , it's good to know how these things work.

The Apache Felix Declarative Services implementation is the reference implementation for the OSGi Declarative Services Specification Version 1.4 (R7) and therefore passes the OSGi CT. This implementation also includes support for OSGi R8 features such as the Satisfying Condition specification.
The Apache Felix Declarative Services implementation is the reference implementation for the OSGi Declarative Services Specification Version 1.4 (R7) and therefore passes the OSGi CT. This implementation also includes support for OSGi R8 features such as the Satisfying Condition and Retention Policy specifications.

## Example Usage

Expand Down Expand Up @@ -158,6 +158,69 @@ Satisfying conditions are useful for:
For more details, see
- [112.3.13 Satisfying Condition](https://docs.osgi.org/specification/osgi.cmpn/8.0.0/service.component.html#service.component-satisfying.condition)

## Retention Policy (OSGi R8)

Apache Felix SCR implements the Retention Policy feature as specified in the OSGi R8 Declarative Services specification. This feature allows control over whether component instances are retained when their use count drops to zero.

### Problem Statement

Components that are expensive to activate/deactivate or that maintain caches have limited control over their lifecycle when the service use count drops to zero. They either:
- Stay permanently active with `immediate="true"`
- Get deactivated on every idle period with `immediate="false"` (default for delayed components)

The retention policy feature solves this by allowing components to specify whether they should be retained even when not in use.

### Using Retention Policy

The `retention-policy` element can have two values:

- **`retain`**: Keep the component instance active even when use count is zero. The component will not be deactivated until explicitly disabled or when dependencies become unsatisfied.
- **`discard`** (default): Dispose of the component instance when use count drops to zero (standard DS behavior).

#### Example with `retain`:

```xml
<scr:component name="expensive.cache.component" xmlns:scr="http://www.osgi.org/xmlns/scr/v1.5.0">
<implementation class="com.example.ExpensiveCacheComponent"/>
<retention-policy>retain</retention-policy>
</scr:component>
```

#### Example with `discard`:

```xml
<scr:component name="disposable.component" xmlns:scr="http://www.osgi.org/xmlns/scr/v1.5.0">
<implementation class="com.example.DisposableComponent"/>
<retention-policy>discard</retention-policy>
</scr:component>
```

### Use Cases

Retention policy is particularly useful for:
- Components with expensive initialization (e.g., loading large datasets, establishing connections)
- Components that maintain caches that should persist across service usage periods
- Event handler services that are frequently used but have idle periods
- Components that provide utility services that are accessed sporadically

### Compatibility Note

The retention policy feature maps to the existing Felix-specific `delayedKeepInstances` extension. Components using the Felix extension attribute will continue to work:

```xml
<scr:component name="my.component"
xmlns:scr="http://www.osgi.org/xmlns/scr/v1.5.0"
xmlns:felix="http://felix.apache.org/xmlns/scr/extensions/v1.0.0"
felix:delayedKeepInstances="true">
<implementation class="com.example.MyComponent"/>
</scr:component>
```

The standard `retention-policy` element should be preferred in new component descriptors for better portability across DS implementations.

For more details, see
- [112.3.14 Retention Policy](https://docs.osgi.org/specification/osgi.cmpn/8.0.0/service.component.html#service.component-retention.policy)

## Apache Maven Support

Both, the [maven-bundle-plugin](http://felix.apache.org/documentation/subprojects/apache-felix-maven-bundle-plugin-bnd.html) as well as the [bnd-maven-plugin](https://github.com/bndtools/bnd/tree/master/maven) supports processing the annotations and creating the XML component descriptors.
Expand Down
6 changes: 6 additions & 0 deletions scr/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ Changes in 2.2.15
- See https://docs.osgi.org/specification/osgi.cmpn/8.0.0/service.component.html#service.component-satisfying.condition
- Added comprehensive documentation in README.md
- Updated code comments to reflect specification compliance
* Implement Retention Policy element (OSGi R8)
- Added support for the <retention-policy> element from OSGi R8 specification
- Controls whether component instances are retained when use count drops to zero
- Values: "retain" (keep instances) or "discard" (dispose instances, default)
- Maps to existing delayedKeepInstances functionality for backward compatibility
- See https://docs.osgi.org/specification/osgi.cmpn/8.0.0/service.component.html#service.component-retention.policy

Changes in 2.2.14
-----------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public abstract class XmlConstants
public static final String EL_PROPERTIES = "properties";
public static final String EL_PROVIDE = "provide";
public static final String EL_REF = "reference";
public static final String EL_RETENTION_POLICY = "retention-policy";
public static final String EL_SERVICE = "service";

// Attributes
Expand Down Expand Up @@ -101,6 +102,10 @@ public abstract class XmlConstants

public static final String ATTR_DELAYED_KEEP_INSTANCES = "delayedKeepInstances";

// Retention policy values (OSGi R8)
public static final String RETENTION_POLICY_RETAIN = "retain";
public static final String RETENTION_POLICY_DISCARD = "discard";

// mapping of namespace URI to namespace code
public static final Map<String, DSVersion> NAMESPACE_CODE_MAP;

Expand Down
48 changes: 48 additions & 0 deletions scr/src/main/java/org/apache/felix/scr/impl/xml/XmlHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ public class XmlHandler extends DefaultHandler

private StringBuilder propertyBuilder;

// Flag to indicate we're parsing retention-policy element
private boolean m_parsingRetentionPolicy = false;

// StringBuilder for retention-policy element content
private StringBuilder m_retentionPolicyBuilder;

/** Flag for detecting the first element. */
protected boolean firstElement = true;

Expand Down Expand Up @@ -419,6 +425,14 @@ else if ( localName.equals( XmlConstants.EL_REF ) )
m_currentComponent.addDependency( ref );
}

// 112.4.x Retention Policy element (OSGi R8)
// Controls whether component instances are kept when use count drops to zero
else if ( localName.equals( XmlConstants.EL_RETENTION_POLICY ) )
{
m_parsingRetentionPolicy = true;
m_retentionPolicyBuilder = new StringBuilder();
}

// unexpected element (except the root element "components"
// used by the Maven SCR Plugin, which is just silently ignored)
else if ( !localName.equals( XmlConstants.EL_COMPONENTS ) )
Expand Down Expand Up @@ -482,6 +496,32 @@ else if ( localName.equals( XmlConstants.EL_FACTORY_PROPERTY ) && m_pendingFacto
}
m_pendingFactoryProperty = null;
}
else if ( localName.equals( XmlConstants.EL_RETENTION_POLICY ) && m_parsingRetentionPolicy )
{
if (m_retentionPolicyBuilder != null)
{
String retentionPolicy = m_retentionPolicyBuilder.toString().trim();
// Map OSGi R8 retention-policy values to the existing delayedKeepInstances field
// "retain" means keep instances when use count drops to zero
// "discard" means dispose instances when use count drops to zero (default)
if (XmlConstants.RETENTION_POLICY_RETAIN.equals(retentionPolicy))
{
m_currentComponent.setDelayedKeepInstances(true);
}
else if (XmlConstants.RETENTION_POLICY_DISCARD.equals(retentionPolicy))
{
m_currentComponent.setDelayedKeepInstances(false);
}
else
{
m_logger.log(Level.WARN,
"Invalid retention-policy value ''{0}'' (bundle {1}). Expected ''retain'' or ''discard''.", null,
retentionPolicy, m_bundle.getLocation());
}
m_retentionPolicyBuilder = null;
}
m_parsingRetentionPolicy = false;
}
}
// Add implicit satisfying condition reference as per OSGi R8 Declarative Services specification
// (see https://github.com/osgi/osgi/pull/875 and https://github.com/osgi/osgi/issues/720).
Expand Down Expand Up @@ -534,6 +574,14 @@ public void characters(char[] ch, int start, int length) throws SAXException
}
propertyBuilder.append(String.valueOf( ch, start, length));
}
// Capture retention-policy element content
else if ( m_parsingRetentionPolicy )
{
if (m_retentionPolicyBuilder == null) {
m_retentionPolicyBuilder = new StringBuilder();
}
m_retentionPolicyBuilder.append(String.valueOf( ch, start, length));
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,30 @@ private List<ComponentMetadata> parse(final URL descriptorURL,
}

}

@Test
public void testRetentionPolicyRetain() throws Exception
{
final URL url = getClass().getResource("/retention-policy-retain.xml");
final List<ComponentMetadata> components = parse(url, null);
assertEquals(1, components.size());

final ComponentMetadata cm = components.get(0);
cm.validate();
assertEquals("Component should have delayedKeepInstances set to true for retention-policy=retain",
true, cm.isDelayedKeepInstances());
}

@Test
public void testRetentionPolicyDiscard() throws Exception
{
final URL url = getClass().getResource("/retention-policy-discard.xml");
final List<ComponentMetadata> components = parse(url, null);
assertEquals(1, components.size());

final ComponentMetadata cm = components.get(0);
cm.validate();
assertEquals("Component should have delayedKeepInstances set to false for retention-policy=discard",
false, cm.isDelayedKeepInstances());
}
}
24 changes: 24 additions & 0 deletions scr/src/test/resources/retention-policy-discard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.5.0"
name="test.retention.discard">
<implementation class="org.example.TestComponent"/>
<retention-policy>discard</retention-policy>
</scr:component>
24 changes: 24 additions & 0 deletions scr/src/test/resources/retention-policy-retain.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.5.0"
name="test.retention.retain">
<implementation class="org.example.TestComponent"/>
<retention-policy>retain</retention-policy>
</scr:component>