You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2022/03/13 14:09:15 UTC
[sling-org-apache-sling-feature] branch master updated: SLING-11199 : Support OSGi Feature Implementation
This is an automated email from the ASF dual-hosted git repository.
cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git
The following commit(s) were added to refs/heads/master by this push:
new 47e790f SLING-11199 : Support OSGi Feature Implementation
47e790f is described below
commit 47e790fba6dfe78c6eafc44fa59dcc43de3cf8ef
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Sun Mar 13 15:09:05 2022 +0100
SLING-11199 : Support OSGi Feature Implementation
---
bnd.bnd | 1 +
pom.xml | 14 +-
.../org/apache/sling/feature/osgi/Converters.java | 322 +++++++++++++++++++++
.../apache/sling/feature/osgi/package-info.java | 23 ++
.../apache/sling/feature/osgi/ConvertersTest.java | 305 +++++++++++++++++++
5 files changed, 664 insertions(+), 1 deletion(-)
diff --git a/bnd.bnd b/bnd.bnd
index e65563c..501d090 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -1,2 +1,3 @@
+Import-Package: org.osgi.service.feature;resolution:=dynamic,*
-noimportjava: true
-conditionalpackage: org.apache.felix.utils.*
diff --git a/pom.xml b/pom.xml
index 38a9174..acde3aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
</parent>
<artifactId>org.apache.sling.feature</artifactId>
- <version>1.2.31-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<name>Apache Sling Feature Model</name>
<description>
@@ -105,6 +105,12 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.feature</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.utils</artifactId>
<version>1.11.8</version>
@@ -141,6 +147,12 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.feature</artifactId>
+ <version>0.9.4-RC3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.apache.johnzon</groupId>
<artifactId>johnzon-core</artifactId>
<version>1.2.14</version>
diff --git a/src/main/java/org/apache/sling/feature/osgi/Converters.java b/src/main/java/org/apache/sling/feature/osgi/Converters.java
new file mode 100755
index 0000000..4169914
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/osgi/Converters.java
@@ -0,0 +1,322 @@
+/*
+ * 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.
+ */
+package org.apache.sling.feature.osgi;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import org.apache.felix.cm.json.Configurations;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionState;
+import org.apache.sling.feature.ExtensionType;
+import org.osgi.service.feature.Feature;
+import org.osgi.service.feature.FeatureArtifact;
+import org.osgi.service.feature.FeatureArtifactBuilder;
+import org.osgi.service.feature.FeatureBuilder;
+import org.osgi.service.feature.FeatureBundle;
+import org.osgi.service.feature.FeatureBundleBuilder;
+import org.osgi.service.feature.FeatureConfiguration;
+import org.osgi.service.feature.FeatureConfigurationBuilder;
+import org.osgi.service.feature.FeatureExtension;
+import org.osgi.service.feature.FeatureExtensionBuilder;
+import org.osgi.service.feature.FeatureService;
+import org.osgi.service.feature.ID;
+import org.osgi.service.feature.FeatureExtension.Kind;
+import org.osgi.service.feature.FeatureExtension.Type;
+
+/**
+ * Utility class to convert Apache Sling features to OSGi feature and vice versa.
+ */
+public class Converters {
+
+ /** Use the first available feature service */
+ private static final FeatureService service = ServiceLoader.load(FeatureService.class).iterator().next();
+
+ /** Constant for framework launching properties extension */
+ private static final String FRAMEWORK_PROPERTIES_EXTENSION = "framework-launching-properties";
+
+ /** Constant for framework properties metadata */
+ private static final String FRAMEWORK_PROPERTIES_METADATA = "framework-properties-metadata";
+
+ /** Constant for metadata of variables */
+ private static final String VARIABLES_METADATA = "variables-metadata";
+
+ /**
+ * Convert an Apache Sling feature into an OSGi feature
+ * @param feature The feature to convert
+ * @return The OSGi feature or {@code null} if feature is {@code null}
+ * @throws IOException If the conversion fails
+ */
+ public static Feature convert(final org.apache.sling.feature.Feature feature) throws IOException {
+ if ( feature == null ) {
+ return null;
+ }
+ final ID id = service.getIDfromMavenCoordinates(feature.getId().toMvnId());
+ final FeatureBuilder builder = service.getBuilderFactory().newFeatureBuilder(id);
+
+ // metadata
+ builder.setComplete(feature.isComplete());
+ builder.setDescription(feature.getDescription());
+ builder.setLicense(feature.getLicense());
+ builder.setName(feature.getTitle());
+ builder.setVendor(feature.getVendor());
+ builder.setDocURL(feature.getDocURL());
+ builder.setSCM(feature.getSCMInfo());
+ for(final String v : feature.getCategories()) {
+ builder.addCategories(v);
+ }
+
+ // variables
+ feature.getVariables().entrySet().stream().forEach(entry -> builder.addVariable(entry.getKey(), entry.getValue()));
+
+ // bundles
+ for(final Artifact bundle : feature.getBundles()) {
+ final FeatureBundleBuilder b = service.getBuilderFactory().newBundleBuilder(service.getIDfromMavenCoordinates(bundle.getId().toMvnId()));
+ bundle.getMetadata().entrySet().stream().forEach(entry -> b.addMetadata(entry.getKey(), entry.getValue()));
+ builder.addBundles(b.build());
+ }
+
+ // configurations
+ for(final Configuration cfg : feature.getConfigurations()) {
+ final FeatureConfigurationBuilder b;
+ if ( cfg.isFactoryConfiguration() ) {
+ b = service.getBuilderFactory().newConfigurationBuilder(cfg.getFactoryPid(), cfg.getName());
+ } else {
+ b = service.getBuilderFactory().newConfigurationBuilder(cfg.getPid());
+ }
+ for(final String name : Collections.list(cfg.getProperties().keys()) ) {
+ b.addValue(name, cfg.getProperties().get(name));
+ }
+ builder.addConfigurations(b.build());
+ }
+
+ // extensions
+ for(final Extension ext : feature.getExtensions()) {
+ FeatureExtension.Type type;
+ if ( ext.getType() == ExtensionType.ARTIFACTS ) {
+ type = Type.ARTIFACTS;
+ } else if ( ext.getType() == ExtensionType.TEXT ) {
+ type = Type.TEXT;
+ } else {
+ type = Type.JSON;
+ }
+ FeatureExtension.Kind kind;
+ if ( ext.getState() == ExtensionState.OPTIONAL ) {
+ kind = Kind.OPTIONAL;
+ } else if ( ext.getState() == ExtensionState.REQUIRED ) {
+ kind = Kind.MANDATORY;
+ } else {
+ kind = Kind.TRANSIENT;
+ }
+ final FeatureExtensionBuilder b = service.getBuilderFactory().newExtensionBuilder(ext.getName(), type, kind);
+ if ( ext.getType() == ExtensionType.ARTIFACTS ) {
+ for(final Artifact artifact : ext.getArtifacts()) {
+ final FeatureArtifactBuilder ab = service.getBuilderFactory().newArtifactBuilder(service.getIDfromMavenCoordinates(artifact.getId().toMvnId()));
+ artifact.getMetadata().entrySet().stream().forEach(entry -> ab.addMetadata(entry.getKey(), entry.getValue()));
+ b.addArtifact(ab.build());
+ }
+ } else if ( ext.getType() == ExtensionType.TEXT ) {
+ if ( ext.getText() != null ) {
+ for(final String t : ext.getText().split("\n")) {
+ b.addText(t);
+ }
+ }
+ } else {
+ if ( ext.getJSON() != null ) {
+ b.setJSON(ext.getJSON());
+ } else {
+ b.setJSON("{}");
+ }
+ }
+ builder.addExtensions(b.build());
+ }
+
+ // framework properties
+ if ( ! feature.getFrameworkProperties().isEmpty() ) {
+ final FeatureExtensionBuilder b = service.getBuilderFactory().newExtensionBuilder(FRAMEWORK_PROPERTIES_EXTENSION, Type.JSON, Kind.MANDATORY);
+ final Dictionary<String, Object> properties = new Hashtable<>();
+ feature.getFrameworkProperties().entrySet().stream().forEach(entry -> properties.put(entry.getKey(), entry.getValue()));
+ try ( final Writer writer = new StringWriter()) {
+ Configurations.buildWriter().build(writer).writeConfiguration(properties);
+ writer.flush();
+ b.setJSON(writer.toString());
+ }
+ builder.addExtensions(b.build());
+ }
+
+ // Write metadata for variables and framework properties in the internal extension
+ final Hashtable<String, Object> output = Configurations.newConfiguration();
+ if ( !feature.getFrameworkProperties().isEmpty() ) {
+ final Map<String, Object> fwkMetadata = Configurations.newConfiguration();
+ for(final String fwkPropName : feature.getFrameworkProperties().keySet()) {
+ final Map<String, Object> metadata = feature.getFrameworkPropertyMetadata(fwkPropName);
+ if ( !metadata.isEmpty() ) {
+ fwkMetadata.put(fwkPropName, metadata);
+ }
+ }
+ if ( !fwkMetadata.isEmpty() ) {
+ output.put(FRAMEWORK_PROPERTIES_METADATA, fwkMetadata);
+ }
+ }
+ if ( !feature.getVariables().isEmpty() ) {
+ final Map<String, Object> varMetadata = Configurations.newConfiguration();
+ for(final String varName : feature.getVariables().keySet()) {
+ final Map<String, Object> metadata = feature.getVariableMetadata(varName);
+ if ( !metadata.isEmpty() ) {
+ varMetadata.put(varName, metadata);
+ }
+ }
+ if ( !varMetadata.isEmpty() ) {
+ output.put(VARIABLES_METADATA, varMetadata);
+ }
+ }
+ if ( !output.isEmpty() ) {
+ final FeatureExtensionBuilder b = service.getBuilderFactory().newExtensionBuilder(Extension.EXTENSION_NAME_INTERNAL_DATA,
+ Type.JSON, Kind.OPTIONAL);
+ try ( final Writer writer = new StringWriter()) {
+ Configurations.buildWriter().build(writer).writeConfiguration(output);
+ writer.flush();
+ b.setJSON(writer.toString());
+ }
+ builder.addExtensions(b.build());
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Convert an OSGi feature into an Apache Sling feature
+ * @param feature The feature to convert
+ * @return The Apache Sling feature or {@code null} if feature is {@code null}
+ * @throws IOException If the conversion fails
+ */
+ public static org.apache.sling.feature.Feature convert(final Feature feature) throws IOException {
+ if ( feature == null ) {
+ return null;
+ }
+ final org.apache.sling.feature.Feature f = new org.apache.sling.feature.Feature(ArtifactId.parse(feature.getID().toString()));
+
+ // metadata
+ f.setComplete(feature.isComplete());
+ f.setDescription(feature.getDescription().orElse(null));
+ f.setLicense(feature.getLicense().orElse(null));
+ f.setTitle(feature.getName().orElse(null));
+ f.setVendor(feature.getVendor().orElse(null));
+ f.setDocURL(feature.getDocURL().orElse(null));
+ f.setSCMInfo(feature.getSCM().orElse(null));
+ f.getCategories().addAll(feature.getCategories());
+
+ // variables
+ feature.getVariables().entrySet().stream().forEach(entry -> f.getVariables().put(entry.getKey(), entry.getValue().toString()));
+
+ // bundles
+ for(final FeatureBundle bundle : feature.getBundles()) {
+ final Artifact b = new Artifact(ArtifactId.parse(bundle.getID().toString()));
+ bundle.getMetadata().entrySet().stream().forEach(entry -> b.getMetadata().put(entry.getKey(), entry.getValue().toString()));
+ f.getBundles().add(b);
+ }
+
+ // configurations
+ for(final FeatureConfiguration cfg : feature.getConfigurations().values()) {
+ final Configuration c = new Configuration(cfg.getPid());
+ cfg.getValues().entrySet().stream().forEach(entry -> c.getProperties().put(entry.getKey(), entry.getValue()));
+ f.getConfigurations().add(c);
+ }
+
+ // extensions
+ for(final FeatureExtension ext : feature.getExtensions().values()) {
+ if ( FRAMEWORK_PROPERTIES_EXTENSION.equals(ext.getName()) ) {
+ // framework properties
+ try ( final Reader reader = new StringReader(ext.getJSON())) {
+ Configurations.buildReader().build(reader).readConfiguration().entrySet()
+ .stream().forEach(entry -> f.getFrameworkProperties().put(entry.getKey(), entry.getValue().toString()));
+ }
+ } else if ( Extension.EXTENSION_NAME_INTERNAL_DATA.equals(ext.getName()) ) {
+ // metadata for variables and framework properties
+ try ( final Reader reader = new StringReader(ext.getJSON())) {
+ final Hashtable<String, Object> md = Configurations.buildReader().build(reader).readConfiguration();
+
+ final String varMetadata = (String) md.get(VARIABLES_METADATA);
+ if ( varMetadata != null ) {
+ try ( final StringReader r = new StringReader(varMetadata)) {
+ for(final Map.Entry<String, Hashtable<String, Object>> entry : Configurations.buildReader()
+ .verifyAsBundleResource(true)
+ .build(r)
+ .readConfigurationResource().getConfigurations().entrySet()) {
+ f.getVariableMetadata(entry.getKey()).putAll(entry.getValue());
+ }
+ }
+ }
+
+ final String fwkMetadata = (String) md.get(FRAMEWORK_PROPERTIES_METADATA);
+ if ( fwkMetadata != null ) {
+ try ( final StringReader r = new StringReader(fwkMetadata)) {
+ for(final Map.Entry<String, Hashtable<String, Object>> entry : Configurations.buildReader()
+ .verifyAsBundleResource(true)
+ .build(r)
+ .readConfigurationResource().getConfigurations().entrySet()) {
+ f.getFrameworkPropertyMetadata(entry.getKey()).putAll(entry.getValue());
+ }
+ }
+ }
+ }
+ } else {
+ ExtensionType type;
+ if ( ext.getType() == Type.ARTIFACTS ) {
+ type = ExtensionType.ARTIFACTS;
+ } else if ( ext.getType() == Type.TEXT ) {
+ type = ExtensionType.TEXT;
+ } else {
+ type = ExtensionType.JSON;
+ }
+ ExtensionState state;
+ if ( ext.getKind() == Kind.OPTIONAL ) {
+ state = ExtensionState.OPTIONAL;
+ } else if ( ext.getKind() == Kind.MANDATORY ) {
+ state = ExtensionState.REQUIRED;
+ } else {
+ state = ExtensionState.TRANSIENT;
+ }
+ final Extension e = new Extension(type, ext.getName(), state);
+ if ( ext.getType() == Type.ARTIFACTS ) {
+ for(final FeatureArtifact artifact : ext.getArtifacts()) {
+ final Artifact a = new Artifact(ArtifactId.parse(artifact.getID().toString()));
+ artifact.getMetadata().entrySet().stream().forEach(entry -> a.getMetadata().put(entry.getKey(), entry.getValue().toString()));
+ e.getArtifacts().add(a);
+ }
+ } else if ( ext.getType() == Type.TEXT ) {
+ e.setText(String.join("\n", ext.getText()));
+ } else {
+ e.setJSON(ext.getJSON());
+ }
+ f.getExtensions().add(e);
+ }
+ }
+ return f;
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/osgi/package-info.java b/src/main/java/org/apache/sling/feature/osgi/package-info.java
new file mode 100644
index 0000000..d756741
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/osgi/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.feature.osgi;
+
+
diff --git a/src/test/java/org/apache/sling/feature/osgi/ConvertersTest.java b/src/test/java/org/apache/sling/feature/osgi/ConvertersTest.java
new file mode 100644
index 0000000..5d538a3
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/osgi/ConvertersTest.java
@@ -0,0 +1,305 @@
+/*
+ * 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.
+ */
+package org.apache.sling.feature.osgi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.Hashtable;
+
+import org.apache.felix.cm.json.Configurations;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionState;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Feature;
+import org.junit.Test;
+
+public class ConvertersTest {
+
+ @Test public void testEmptyFeatureConversion() throws Exception {
+ final Feature feature = new Feature(ArtifactId.parse("g:a:1"));
+
+ // convert to OSGi feature
+ final org.osgi.service.feature.Feature osgiFeature = Converters.convert(feature);
+ assertEquals("g:a:1", osgiFeature.getID().toString());
+ assertFalse(osgiFeature.getDescription().isPresent());
+ assertFalse(osgiFeature.getDocURL().isPresent());
+ assertFalse(osgiFeature.getLicense().isPresent());
+ assertFalse(osgiFeature.getSCM().isPresent());
+ assertFalse(osgiFeature.getName().isPresent());
+ assertFalse(osgiFeature.getVendor().isPresent());
+ assertFalse(osgiFeature.isComplete());
+ assertTrue(osgiFeature.getCategories().isEmpty());
+ assertTrue(osgiFeature.getBundles().isEmpty());
+ assertTrue(osgiFeature.getConfigurations().isEmpty());
+ assertTrue(osgiFeature.getExtensions().isEmpty());
+ assertTrue(osgiFeature.getVariables().isEmpty());
+
+ // and back to Sling Feature
+ final Feature slingFeature = Converters.convert(osgiFeature);
+ assertEquals("g:a:1", slingFeature.getId().toMvnId());
+ assertNull(slingFeature.getDescription());
+ assertNull(slingFeature.getDocURL());
+ assertNull(slingFeature.getLicense());
+ assertNull(slingFeature.getSCMInfo());
+ assertNull(slingFeature.getTitle());
+ assertNull(slingFeature.getVendor());
+ assertFalse(slingFeature.isComplete());
+ assertTrue(slingFeature.getCategories().isEmpty());
+ assertTrue(slingFeature.getBundles().isEmpty());
+ assertTrue(slingFeature.getConfigurations().isEmpty());
+ assertTrue(slingFeature.getExtensions().isEmpty());
+ assertTrue(slingFeature.getVariables().isEmpty());
+ }
+
+ @Test public void testMetadataConversion() throws Exception {
+ final Feature feature = new Feature(ArtifactId.parse("g:a:1"));
+ feature.setComplete(true);
+ feature.setDescription("description");
+ feature.setDocURL("doc-url");
+ feature.setLicense("license");
+ feature.setSCMInfo("info");
+ feature.setTitle("title");
+ feature.setVendor("vendor");
+ feature.getCategories().add("c1");
+ feature.getCategories().add("c2");
+
+ // convert to OSGi feature
+ final org.osgi.service.feature.Feature osgiFeature = Converters.convert(feature);
+ assertEquals("g:a:1", osgiFeature.getID().toString());
+ assertEquals("description", osgiFeature.getDescription().get());
+ assertEquals("doc-url", osgiFeature.getDocURL().get());
+ assertEquals("license", osgiFeature.getLicense().get());
+ assertEquals("info", osgiFeature.getSCM().get());
+ assertEquals("title", osgiFeature.getName().get());
+ assertEquals("vendor", osgiFeature.getVendor().get());
+ assertTrue(osgiFeature.isComplete());
+ assertEquals(Arrays.asList("c1", "c2"), osgiFeature.getCategories());
+
+ // and back to Sling Feature
+ final Feature slingFeature = Converters.convert(osgiFeature);
+ assertEquals("g:a:1", slingFeature.getId().toMvnId());
+ assertEquals("description", slingFeature.getDescription());
+ assertEquals("doc-url", slingFeature.getDocURL());
+ assertEquals("license", slingFeature.getLicense());
+ assertEquals("info", slingFeature.getSCMInfo());
+ assertEquals("title", slingFeature.getTitle());
+ assertEquals("vendor", slingFeature.getVendor());
+ assertTrue(slingFeature.isComplete());
+ assertEquals(Arrays.asList("c1", "c2"), slingFeature.getCategories());
+ }
+
+ @Test public void testBundleConversion() throws Exception {
+ final Feature feature = new Feature(ArtifactId.parse("g:a:1"));
+ final Artifact bundle = new Artifact(ArtifactId.parse("g:b:2"));
+ bundle.getMetadata().put("key", "foo");
+ feature.getBundles().add(bundle);
+
+ // convert to OSGi feature
+ final org.osgi.service.feature.Feature osgiFeature = Converters.convert(feature);
+ assertEquals(1, osgiFeature.getBundles().size());
+ final org.osgi.service.feature.FeatureBundle ob = osgiFeature.getBundles().get(0);
+ assertEquals("g:b:2", ob.getID().toString());
+ assertEquals(1, ob.getMetadata().size());
+ assertEquals("foo", ob.getMetadata().get("key"));
+
+ // and back to Sling Feature
+ final Feature slingFeature = Converters.convert(osgiFeature);
+ assertEquals(1, slingFeature.getBundles().size());
+ final Artifact sb = slingFeature.getBundles().get(0);
+ assertEquals("g:b:2", sb.getId().toMvnId());
+ assertEquals(1, sb.getMetadata().size());
+ assertEquals("foo", sb.getMetadata().get("key"));
+ }
+
+ @Test public void testConfigurationConversion() throws Exception {
+ final Feature feature = new Feature(ArtifactId.parse("g:a:1"));
+ final Configuration c1 = new Configuration("org.sling.config");
+ c1.getProperties().put("key", "foo");
+ feature.getConfigurations().add(c1);
+ final Configuration c2 = new Configuration("org.sling.factory~name");
+ c2.getProperties().put("value", 5);
+ feature.getConfigurations().add(c2);
+
+ // convert to OSGi feature
+ final org.osgi.service.feature.Feature osgiFeature = Converters.convert(feature);
+ assertEquals(2, osgiFeature.getConfigurations().size());
+ final org.osgi.service.feature.FeatureConfiguration oc1 = osgiFeature.getConfigurations().get("org.sling.config");
+ assertEquals("org.sling.config", oc1.getPid());
+ assertFalse(oc1.getFactoryPid().isPresent());
+ assertEquals(1, oc1.getValues().size());
+ assertEquals("foo", oc1.getValues().get("key"));
+
+ final org.osgi.service.feature.FeatureConfiguration oc2 = osgiFeature.getConfigurations().get("org.sling.factory~name");
+ assertEquals("org.sling.factory~name", oc2.getPid());
+ assertEquals("org.sling.factory", oc2.getFactoryPid().get());
+ assertEquals(1, oc2.getValues().size());
+ assertEquals(5, oc2.getValues().get("value"));
+
+ // and back to Sling Feature
+ final Feature slingFeature = Converters.convert(osgiFeature);
+ assertEquals(2, slingFeature.getConfigurations().size());
+ final Configuration sc1 = slingFeature.getConfigurations().get(0);
+ assertEquals("org.sling.config", sc1.getPid());
+ assertNull(sc1.getFactoryPid());
+ assertNull(sc1.getName());
+ assertEquals(1, sc1.getProperties().size());
+ assertEquals("foo", sc1.getProperties().get("key"));
+
+ final Configuration sc2 = slingFeature.getConfigurations().get(1);
+ assertEquals("org.sling.factory~name", sc2.getPid());
+ assertEquals("org.sling.factory", sc2.getFactoryPid());
+ assertEquals("name", sc2.getName());
+ assertEquals(1, sc2.getProperties().size());
+ assertEquals(5, sc2.getProperties().get("value"));
+ }
+
+ @Test public void testVariablesConversion() throws Exception {
+ final Feature feature = new Feature(ArtifactId.parse("g:a:1"));
+ feature.getVariables().put("v1", "a");
+ feature.getVariableMetadata("v1").put("x", "y");
+
+ // convert to OSGi feature
+ final org.osgi.service.feature.Feature osgiFeature = Converters.convert(feature);
+ assertEquals(1, osgiFeature.getVariables().size());
+ assertEquals("a", osgiFeature.getVariables().get("v1"));
+ assertEquals(1, osgiFeature.getExtensions().size());
+ assertNotNull(osgiFeature.getExtensions().get(Extension.EXTENSION_NAME_INTERNAL_DATA));
+
+ // and back to Sling Feature
+ final Feature slingFeature = Converters.convert(osgiFeature);
+ assertEquals(1, slingFeature.getVariables().size());
+ assertEquals("a", slingFeature.getVariables().get("v1"));
+ assertEquals("y", slingFeature.getVariableMetadata("v1").get("x"));
+ assertTrue(slingFeature.getExtensions().isEmpty());
+ }
+
+ @Test public void testFrameworkPropertiesConversion() throws Exception {
+ final Feature feature = new Feature(ArtifactId.parse("g:a:1"));
+ feature.getFrameworkProperties().put("v1", "a");
+ feature.getFrameworkPropertyMetadata("v1").put("x", "y");
+
+ // convert to OSGi feature
+ final org.osgi.service.feature.Feature osgiFeature = Converters.convert(feature);
+ assertEquals(2, osgiFeature.getExtensions().size());
+ assertNotNull(osgiFeature.getExtensions().get(Extension.EXTENSION_NAME_INTERNAL_DATA));
+ final org.osgi.service.feature.FeatureExtension e = osgiFeature.getExtensions().get("framework-launching-properties");
+ assertNotNull(e);
+ assertEquals(org.osgi.service.feature.FeatureExtension.Type.JSON, e.getType());
+ try ( final StringReader r = new StringReader(e.getJSON()) ) {
+ final Hashtable<String, Object> p = Configurations.buildReader().verifyAsBundleResource(true).build(r).readConfiguration();
+ assertEquals(1, p.size());
+ assertEquals("a", p.get("v1"));
+ }
+
+ // and back to Sling Feature
+ final Feature slingFeature = Converters.convert(osgiFeature);
+ assertEquals(1, slingFeature.getFrameworkProperties().size());
+ assertEquals("a", slingFeature.getFrameworkProperties().get("v1"));
+ assertEquals("y", slingFeature.getFrameworkPropertyMetadata("v1").get("x"));
+ assertTrue(slingFeature.getExtensions().isEmpty());
+ }
+
+ @Test public void testJSONExtensionConversion() throws Exception {
+ final Feature feature = new Feature(ArtifactId.parse("g:a:1"));
+ final Extension e = new Extension(ExtensionType.JSON, "ext", ExtensionState.OPTIONAL);
+ e.setJSON("{\"a\":true}");
+ feature.getExtensions().add(e);
+
+ // convert to OSGi feature
+ final org.osgi.service.feature.Feature osgiFeature = Converters.convert(feature);
+ assertEquals(1, osgiFeature.getExtensions().size());
+ final org.osgi.service.feature.FeatureExtension oe = osgiFeature.getExtensions().get("ext");
+ assertNotNull(oe);
+ assertEquals(org.osgi.service.feature.FeatureExtension.Type.JSON, oe.getType());
+ assertEquals("{\"a\":true}", oe.getJSON());
+
+ // and back to Sling Feature
+ final Feature slingFeature = Converters.convert(osgiFeature);
+ assertEquals(1, slingFeature.getExtensions().size());
+ final Extension se = slingFeature.getExtensions().getByName("ext");
+ assertNotNull(se);
+ assertEquals(ExtensionType.JSON, se.getType());
+ assertEquals("{\"a\":true}", se.getJSON());
+ }
+
+ @Test public void testTextExtensionConversion() throws Exception {
+ final Feature feature = new Feature(ArtifactId.parse("g:a:1"));
+ final Extension e = new Extension(ExtensionType.TEXT, "ext", ExtensionState.OPTIONAL);
+ e.setText("Hello World");
+ feature.getExtensions().add(e);
+
+ // convert to OSGi feature
+ final org.osgi.service.feature.Feature osgiFeature = Converters.convert(feature);
+ assertEquals(1, osgiFeature.getExtensions().size());
+ final org.osgi.service.feature.FeatureExtension oe = osgiFeature.getExtensions().get("ext");
+ assertNotNull(oe);
+ assertEquals(org.osgi.service.feature.FeatureExtension.Type.TEXT, oe.getType());
+ assertEquals("Hello World", String.join("\n", oe.getText()));
+
+ // and back to Sling Feature
+ final Feature slingFeature = Converters.convert(osgiFeature);
+ assertEquals(1, slingFeature.getExtensions().size());
+ final Extension se = slingFeature.getExtensions().getByName("ext");
+ assertNotNull(se);
+ assertEquals(ExtensionType.TEXT, se.getType());
+ assertEquals("Hello World", se.getText());
+ }
+
+ @Test public void testArtifactExtensionConversion() throws Exception {
+ final Feature feature = new Feature(ArtifactId.parse("g:a:1"));
+ final Extension e = new Extension(ExtensionType.ARTIFACTS, "ext", ExtensionState.OPTIONAL);
+ final Artifact artifact = new Artifact(ArtifactId.parse("g:b:2"));
+ artifact.getMetadata().put("key", "foo");
+ e.getArtifacts().add(artifact);
+ feature.getExtensions().add(e);
+
+ // convert to OSGi feature
+ final org.osgi.service.feature.Feature osgiFeature = Converters.convert(feature);
+ assertEquals(1, osgiFeature.getExtensions().size());
+ final org.osgi.service.feature.FeatureExtension oe = osgiFeature.getExtensions().get("ext");
+ assertNotNull(oe);
+ assertEquals(org.osgi.service.feature.FeatureExtension.Type.ARTIFACTS, oe.getType());
+ assertEquals(1, oe.getArtifacts().size());
+ final org.osgi.service.feature.FeatureArtifact oa = oe.getArtifacts().get(0);
+ assertEquals("g:b:2", oa.getID().toString());
+ assertEquals(1, oa.getMetadata().size());
+ assertEquals("foo", oa.getMetadata().get("key"));
+
+ // and back to Sling Feature
+ final Feature slingFeature = Converters.convert(osgiFeature);
+ assertEquals(1, slingFeature.getExtensions().size());
+ final Extension se = slingFeature.getExtensions().getByName("ext");
+ assertNotNull(se);
+ assertEquals(ExtensionType.ARTIFACTS, se.getType());
+ assertEquals(1, se.getArtifacts().size());
+ final Artifact sa = se.getArtifacts().get(0);
+ assertEquals("g:b:2", sa.getId().toMvnId());
+ assertEquals(1, sa.getMetadata().size());
+ assertEquals("foo", sa.getMetadata().get("key"));
+ }
+}