diff --git a/scr/README.md b/scr/README.md index 8bbf468b53..98620cbf82 100644 --- a/scr/README.md +++ b/scr/README.md @@ -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 @@ -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 + + + retain + +``` + +#### Example with `discard`: + +```xml + + + discard + +``` + +### 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 + + + +``` + +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. diff --git a/scr/changelog.txt b/scr/changelog.txt index a9d0b19d64..b393693145 100644 --- a/scr/changelog.txt +++ b/scr/changelog.txt @@ -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 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 ----------------- diff --git a/scr/src/main/java/org/apache/felix/scr/impl/xml/XmlConstants.java b/scr/src/main/java/org/apache/felix/scr/impl/xml/XmlConstants.java index 42624d77d3..a179a3499d 100644 --- a/scr/src/main/java/org/apache/felix/scr/impl/xml/XmlConstants.java +++ b/scr/src/main/java/org/apache/felix/scr/impl/xml/XmlConstants.java @@ -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 @@ -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 NAMESPACE_CODE_MAP; diff --git a/scr/src/main/java/org/apache/felix/scr/impl/xml/XmlHandler.java b/scr/src/main/java/org/apache/felix/scr/impl/xml/XmlHandler.java index a7588f6d68..5a022b7ff0 100644 --- a/scr/src/main/java/org/apache/felix/scr/impl/xml/XmlHandler.java +++ b/scr/src/main/java/org/apache/felix/scr/impl/xml/XmlHandler.java @@ -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; @@ -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 ) ) @@ -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). @@ -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)); + } } diff --git a/scr/src/test/java/org/apache/felix/scr/impl/xml/XmlHandlerTest.java b/scr/src/test/java/org/apache/felix/scr/impl/xml/XmlHandlerTest.java index e59e5dccc2..07970fa32a 100755 --- a/scr/src/test/java/org/apache/felix/scr/impl/xml/XmlHandlerTest.java +++ b/scr/src/test/java/org/apache/felix/scr/impl/xml/XmlHandlerTest.java @@ -140,4 +140,30 @@ private List parse(final URL descriptorURL, } } + + @Test + public void testRetentionPolicyRetain() throws Exception + { + final URL url = getClass().getResource("/retention-policy-retain.xml"); + final List 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 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()); + } } diff --git a/scr/src/test/resources/retention-policy-discard.xml b/scr/src/test/resources/retention-policy-discard.xml new file mode 100644 index 0000000000..a5ad052545 --- /dev/null +++ b/scr/src/test/resources/retention-policy-discard.xml @@ -0,0 +1,24 @@ + + + + + discard + diff --git a/scr/src/test/resources/retention-policy-retain.xml b/scr/src/test/resources/retention-policy-retain.xml new file mode 100644 index 0000000000..8eee88ded3 --- /dev/null +++ b/scr/src/test/resources/retention-policy-retain.xml @@ -0,0 +1,24 @@ + + + + + retain +