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"));
+ }
+}