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 2021/09/01 10:44:15 UTC

[sling-org-apache-sling-installer-factory-configuration] branch master updated: SLING-10771 : Support handling of metatype info when merging configurations

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-installer-factory-configuration.git


The following commit(s) were added to refs/heads/master by this push:
     new 06a94ca  SLING-10771 : Support handling of metatype info when merging configurations
06a94ca is described below

commit 06a94ca56663ffdd2b9f4c5e0b731b1e71f9f20f
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Sep 1 12:42:26 2021 +0200

    SLING-10771 : Support handling of metatype info when merging configurations
---
 .gitignore                                         |   1 +
 bnd.bnd                                            |   4 +
 pom.xml                                            |  24 +++++
 .../configuration/impl/ConfigTaskCreator.java      |  16 ++-
 .../configuration/impl/MetatypeHandler.java        | 115 +++++++++++++++++++++
 .../configuration/impl/ServicesListener.java       |  24 +++++
 .../impl/WebconsoleConfigurationHandler.java       |  77 ++++++++++++++
 .../configuration/impl/MetatypeHandlerTest.java    | 114 ++++++++++++++++++++
 8 files changed, 372 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index 5b783ed..964bd9e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ maven-eclipse.xml
 *.iws
 *.bak
 .vlt
+.vscode/
 .DS_Store
 jcr.log
 atlassian-ide-plugin.xml
diff --git a/bnd.bnd b/bnd.bnd
index e69de29..10de141 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -0,0 +1,4 @@
+Import-Package: !org.osgi.service.metatype, \
+  !org.apache.felix.webconsole.spi,*
+DynamicImport-Package: org.osgi.service.metatype, \
+  org.apache.felix.webconsole.spi
diff --git a/pom.xml b/pom.xml
index 3451869..d8d7888 100644
--- a/pom.xml
+++ b/pom.xml
@@ -94,6 +94,30 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.webconsole</artifactId>
+            <version>4.6.5-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.metatype</artifactId>
+            <version>1.4.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.util.converter</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.util.function</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
index 6b7559b..e4bd388 100644
--- a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
@@ -164,7 +164,7 @@ public class ConfigTaskCreator
                             attrs.put(ConfigurationAdmin.SERVICE_FACTORYPID, event.getFactoryPid());
                         }
 
-                        removeDefaultProperties(event.getPid(), dict);
+                        removeDefaultProperties(this.infoProvider, event.getPid(), dict);
                         this.changeListener.resourceAddedOrUpdated(InstallableResource.TYPE_CONFIG, event.getPid(), null, dict, attrs);
 
                     } else {
@@ -177,12 +177,12 @@ public class ConfigTaskCreator
         }
     }
 
-    private void removeDefaultProperties(final String pid, final Dictionary<String, Object> dict) {
+    public static Dictionary<String, Object> getDefaultProperties(final InfoProvider infoProvider, final String pid) {
         if ( Activator.MERGE_SCHEMES != null ) {
             final List<Dictionary<String, Object>> propertiesList = new ArrayList<>();
             final String entityId = InstallableResource.TYPE_CONFIG.concat(":").concat(pid);
             boolean done = false;
-            for(final ResourceGroup group : this.infoProvider.getInstallationState().getInstalledResources()) {
+            for(final ResourceGroup group : infoProvider.getInstallationState().getInstalledResources()) {
                 for(final Resource rsrc : group.getResources()) {
                     if ( rsrc.getEntityId().equals(entityId) ) {
                         done = true;
@@ -197,6 +197,16 @@ public class ConfigTaskCreator
             }
             if ( !propertiesList.isEmpty() ) {
                 final Dictionary<String, Object> defaultProps = ConfigUtil.mergeReverseOrder(propertiesList);
+                return defaultProps;
+            }
+        }
+        return null;
+    }
+
+    public static void removeDefaultProperties(final InfoProvider infoProvider, final String pid, final Dictionary<String, Object> dict) {
+        if ( Activator.MERGE_SCHEMES != null ) {
+            final Dictionary<String, Object> defaultProps = getDefaultProperties(infoProvider, pid);
+            if ( defaultProps != null ) {
                 final Enumeration<String> keyEnum = defaultProps.keys();
                 while ( keyEnum.hasMoreElements() ) {
                     final String key = keyEnum.nextElement();
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/MetatypeHandler.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/MetatypeHandler.java
new file mode 100644
index 0000000..883643c
--- /dev/null
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/MetatypeHandler.java
@@ -0,0 +1,115 @@
+/*
+ * 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.installer.factories.configuration.impl;
+
+import java.util.Arrays;
+import java.util.Dictionary;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.metatype.AttributeDefinition;
+import org.osgi.service.metatype.MetaTypeInformation;
+import org.osgi.service.metatype.MetaTypeService;
+import org.osgi.service.metatype.ObjectClassDefinition;
+import org.osgi.util.converter.Converters;
+
+public class MetatypeHandler {
+
+    private final MetaTypeService srv;
+
+    private final BundleContext bundleContext;
+
+    public MetatypeHandler(final Object mts, final BundleContext bundleContext) {
+        this.srv = (MetaTypeService)mts;
+        this.bundleContext = bundleContext;
+    }
+    
+    public void updateConfiguration(final String factoryPid,
+            final String pid,
+            final Dictionary<String, Object> props, 
+            final Dictionary<String, Object> defaultProps) {
+        // search metatype
+        final ObjectClassDefinition ocd;
+        if ( factoryPid != null ) {
+            ocd = this.getObjectClassDefinition( factoryPid );
+        } else {
+            ocd = this.getObjectClassDefinition(  pid );
+        }
+
+        if ( ocd != null ) {
+            for(final AttributeDefinition ad : ocd.getAttributeDefinitions(ObjectClassDefinition.ALL)) {
+                final String propName = ad.getID();
+                final Object newValue = props.get(propName);
+                if ( newValue != null 
+                        && (defaultProps == null || defaultProps.get(propName) == null) ) {
+                    if ( ad.getCardinality() == 0 ) {
+                        if ( !shouldSet(ad, newValue.toString())) {
+                            props.remove(propName);                            
+                        }
+                    } else {
+                        final String[] array = Converters.standardConverter().convert(newValue).to(String[].class);
+                        if ( !shouldSet(ad, array)) {
+                            props.remove(propName);
+                        }                        
+                    }        
+                }
+            }
+        }
+    }
+
+    private ObjectClassDefinition getObjectClassDefinition( final String pid ) {
+        for(final Bundle b : this.bundleContext.getBundles()) {
+            try {
+                final MetaTypeInformation mti = this.srv.getMetaTypeInformation( b );
+                if ( mti != null ) {
+                    final ObjectClassDefinition ocd = mti.getObjectClassDefinition( pid, null );;
+                    if ( ocd != null ) {
+                        return ocd;
+                    }
+                }
+            } catch ( final IllegalArgumentException iae ) {
+                // ignore
+            }
+        }
+        return null;
+    }
+
+    boolean shouldSet(final AttributeDefinition ad, final String value) {
+        if ( value.isEmpty() && ad.getDefaultValue() == null ) {
+            return false;
+        }
+        if ( ad.getDefaultValue() != null && value.equals(ad.getDefaultValue()[0]) ) {
+            return false;
+        }
+        return true;
+    }
+
+    boolean shouldSet(final AttributeDefinition ad, final String[] values) {
+        if ( ad.getDefaultValue() == null ) {
+            if ( values.length == 0 || (values.length == 1 && values[0].isEmpty() ) ) {
+                return false;
+            }
+        }
+        if ( ad.getDefaultValue() != null && Arrays.equals(ad.getDefaultValue(), values) ) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ServicesListener.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ServicesListener.java
index 41e37f9..139eb2f 100644
--- a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ServicesListener.java
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ServicesListener.java
@@ -22,10 +22,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.sling.installer.api.ResourceChangeListener;
 import org.apache.sling.installer.api.info.InfoProvider;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceFactory;
 import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
@@ -56,6 +58,9 @@ public class ServicesListener {
     /** Registration the service. */
     private volatile ServiceRegistration<?> configTaskCreatorRegistration;
 
+    /** Registration for the webconsole support. */
+    private volatile ServiceRegistration<?> webconsoleRegistration;
+
     private volatile ConfigTaskCreator configTaskCreator;
 
     private final AtomicBoolean active = new AtomicBoolean(false);
@@ -83,6 +88,21 @@ public class ServicesListener {
                 this.configTaskCreator = new ConfigTaskCreator(listener, configAdmin, infoProvider);
                 final ConfigUpdateHandler handler = new ConfigUpdateHandler(configAdmin, this);
                 configTaskCreatorRegistration = handler.register(this.bundleContext);
+                if ( Activator.MERGE_SCHEMES != null ) {
+                    this.webconsoleRegistration = this.bundleContext.registerService("org.apache.felix.webconsole.spi.ConfigurationHandler", new ServiceFactory<Object>(){
+
+                        @Override
+                        public Object getService(final Bundle bundle, final ServiceRegistration<Object> registration) {
+                            return new WebconsoleConfigurationHandler(bundleContext, infoProvider);
+                        }
+
+                        @Override
+                        public void ungetService(final Bundle bundle, final ServiceRegistration<Object> registration, final Object service) {
+                            ((WebconsoleConfigurationHandler)service).deactivate();
+                        }
+
+                    }, null);
+                }
             }
         } else {
             this.stop();
@@ -92,6 +112,10 @@ public class ServicesListener {
     private synchronized void stop() {
         active.set(false);
         // unregister
+        if ( this.webconsoleRegistration != null ) {
+            this.webconsoleRegistration.unregister();
+            this.webconsoleRegistration = null;
+        }
         if ( this.configTaskCreatorRegistration != null ) {
             this.configTaskCreatorRegistration.unregister();
             this.configTaskCreatorRegistration = null;
diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/WebconsoleConfigurationHandler.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/WebconsoleConfigurationHandler.java
new file mode 100644
index 0000000..3aa0161
--- /dev/null
+++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/WebconsoleConfigurationHandler.java
@@ -0,0 +1,77 @@
+/*
+ * 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.installer.factories.configuration.impl;
+
+import java.io.IOException;
+import java.util.Dictionary;
+
+import org.apache.felix.webconsole.spi.ConfigurationHandler;
+import org.apache.felix.webconsole.spi.ValidationException;
+import org.apache.sling.installer.api.info.InfoProvider;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class WebconsoleConfigurationHandler implements ConfigurationHandler {
+
+    static final String META_TYPE_NAME = "org.osgi.service.metatype.MetaTypeService"; 
+
+    private final InfoProvider infoProvider;
+
+    private final ServiceTracker<Object, Object> metatypeTracker;
+
+    private final BundleContext bundleContext;
+
+    public WebconsoleConfigurationHandler(final BundleContext context, final InfoProvider infoProvider) {
+        this.infoProvider = infoProvider;
+        this.bundleContext = context;
+        this.metatypeTracker = new ServiceTracker<>(context, META_TYPE_NAME, null);
+        this.metatypeTracker.open();    
+    }
+
+    public void deactivate() {
+        this.metatypeTracker.close();
+    }
+
+    @Override
+    public void createConfiguration(final String pid) throws ValidationException, IOException {
+        // nothing to do        
+    }
+
+    @Override
+    public void createFactoryConfiguration(final String factoryPid, String name) throws ValidationException, IOException {
+        // nothing to do        
+    }
+
+    @Override
+    public void deleteConfiguration(final String factoryPid, final String pid) throws ValidationException, IOException {
+        // nothing to do        
+    }
+
+    @Override
+    public void updateConfiguration(final String factoryPid, final String pid, final Dictionary<String, Object> props)
+            throws ValidationException, IOException {
+        final Object mts = this.metatypeTracker.getService();
+        if ( mts != null ) {
+            final Dictionary<String, Object> defaultProps = ConfigTaskCreator.getDefaultProperties(infoProvider, pid);
+            final MetatypeHandler mt = new MetatypeHandler(mts, this.bundleContext);
+            mt.updateConfiguration(factoryPid, pid, props, defaultProps);
+        }        
+    }
+    
+}
diff --git a/src/test/java/org/apache/sling/installer/factories/configuration/impl/MetatypeHandlerTest.java b/src/test/java/org/apache/sling/installer/factories/configuration/impl/MetatypeHandlerTest.java
new file mode 100644
index 0000000..51c906b
--- /dev/null
+++ b/src/test/java/org/apache/sling/installer/factories/configuration/impl/MetatypeHandlerTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.installer.factories.configuration.impl;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.metatype.AttributeDefinition;
+import org.osgi.service.metatype.MetaTypeInformation;
+import org.osgi.service.metatype.MetaTypeService;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+public class MetatypeHandlerTest {
+
+    @Test public void testUpdateConfiguration() throws Exception {
+         final BundleContext bundleContext = Mockito.mock(BundleContext.class);
+         final MetaTypeService mts = Mockito.mock(MetaTypeService.class);
+         final Bundle bundle = Mockito.mock(Bundle.class);
+         Mockito.when(bundleContext.getBundles()).thenReturn(new Bundle[] {bundle});
+         
+         final MetaTypeInformation info = Mockito.mock(MetaTypeInformation.class);
+         Mockito.when(mts.getMetaTypeInformation(bundle)).thenReturn(info);
+         final MetatypeHandler handler = new MetatypeHandler(mts, bundleContext);
+         
+         final ObjectClassDefinition ocd = Mockito.mock(ObjectClassDefinition.class);
+         Mockito.when(info.getObjectClassDefinition("my.pid", null)).thenReturn(ocd);
+
+         final AttributeDefinition ada = Mockito.mock(AttributeDefinition.class);
+         Mockito.when(ada.getID()).thenReturn("a");
+         Mockito.when(ada.getDefaultValue()).thenReturn(new String[] {"1"});
+         Mockito.when(ada.getCardinality()).thenReturn(1);
+         Mockito.when(ada.getType()).thenReturn(AttributeDefinition.STRING);
+
+         final AttributeDefinition adb = Mockito.mock(AttributeDefinition.class);
+         Mockito.when(adb.getID()).thenReturn("b");
+         Mockito.when(adb.getDefaultValue()).thenReturn(new String[] {"2"});
+         Mockito.when(adb.getCardinality()).thenReturn(1);
+         Mockito.when(adb.getType()).thenReturn(AttributeDefinition.STRING);
+
+         final AttributeDefinition adc = Mockito.mock(AttributeDefinition.class);
+         Mockito.when(adc.getID()).thenReturn("c");
+         Mockito.when(adc.getDefaultValue()).thenReturn(new String[] {"3"});
+         Mockito.when(adc.getCardinality()).thenReturn(1);
+         Mockito.when(adc.getType()).thenReturn(AttributeDefinition.STRING);
+
+         final AttributeDefinition add = Mockito.mock(AttributeDefinition.class);
+         Mockito.when(add.getID()).thenReturn("d");
+         Mockito.when(add.getDefaultValue()).thenReturn(new String[] {"4"});
+         Mockito.when(add.getCardinality()).thenReturn(1);
+         Mockito.when(add.getType()).thenReturn(AttributeDefinition.INTEGER);
+
+         final AttributeDefinition adE = Mockito.mock(AttributeDefinition.class);
+         Mockito.when(adE.getID()).thenReturn("e");
+         Mockito.when(adE.getDefaultValue()).thenReturn(new String[] {"5"});
+         Mockito.when(adE.getCardinality()).thenReturn(1);
+         Mockito.when(adE.getType()).thenReturn(AttributeDefinition.INTEGER);
+
+         final AttributeDefinition adF = Mockito.mock(AttributeDefinition.class);
+         Mockito.when(adF.getID()).thenReturn("f");
+         Mockito.when(adF.getDefaultValue()).thenReturn(new String[] {"/a", "/b"});
+         Mockito.when(adF.getCardinality()).thenReturn(-100);
+         Mockito.when(adF.getType()).thenReturn(AttributeDefinition.STRING);
+
+         final AttributeDefinition adG = Mockito.mock(AttributeDefinition.class);
+         Mockito.when(adG.getID()).thenReturn("g");
+         Mockito.when(adG.getDefaultValue()).thenReturn(new String[] {"/x", "/y"});
+         Mockito.when(adG.getCardinality()).thenReturn(-100);
+         Mockito.when(adG.getType()).thenReturn(AttributeDefinition.STRING);
+
+         Mockito.when(ocd.getAttributeDefinitions(ObjectClassDefinition.ALL)).thenReturn(new AttributeDefinition[] {ada,adb,adc,add,adE,adF,adG});
+
+         final Dictionary<String, Object> props = new Hashtable<>();
+         props.put("a", "2");
+         props.put("c", "3");
+         props.put("d", 4);
+         props.put("e", 5);
+         props.put("f", Arrays.asList("/a", "/b"));
+         props.put("g", Arrays.asList("/a", "/b"));
+
+         final Dictionary<String, Object> defaultProps = new Hashtable<>();
+         defaultProps.put("b", "5");
+         defaultProps.put("d", 7);
+
+         handler.updateConfiguration(null, "my.pid", props, defaultProps);
+
+         assertEquals(3, props.size());
+         assertEquals("2", props.get("a"));      
+         assertEquals(4, props.get("d"));
+         assertEquals(Arrays.asList("/a", "/b"), props.get("g"));  
+    }
+}