UNOMI-106 Infinite loop on startup
- Refactored ConfigSharingService to use a single one across the whole application. It also supports listeners if needed.
Signed-off-by: Serge Huber <shuber@apache.org>
diff --git a/api/src/main/java/org/apache/unomi/api/services/ConfigSharingService.java b/api/src/main/java/org/apache/unomi/api/services/ConfigSharingService.java
index 1bfb050..c1d91af 100644
--- a/api/src/main/java/org/apache/unomi/api/services/ConfigSharingService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/ConfigSharingService.java
@@ -16,17 +16,54 @@
*/
package org.apache.unomi.api.services;
+import java.util.Set;
+
/**
- * A service to share cfg properties with other bundles.
+ * A service to share configuration properties with other bundles. It also support listeners that will be called whenever
+ * a property is added/updated/removed. Simply register a service with the @link ConfigSharingServiceConfigChangeListener interface and it will
+ * be automatically picked up.
*/
public interface ConfigSharingService {
- String getOneshotImportUploadDir();
+ Object getProperty(String name);
+ Object setProperty(String name, Object value);
+ boolean hasProperty(String name);
+ Object removeProperty(String name);
+ Set<String> getPropertyNames();
- void setOneshotImportUploadDir(String oneshotImportUploadDir);
+ class ConfigChangeEvent {
+ public enum ConfigChangeEventType { ADDED, UPDATED, REMOVED };
+ private ConfigChangeEventType eventType;
+ private String name;
+ private Object oldValue;
+ private Object newValue;
- String getInternalServerPort();
+ public ConfigChangeEvent(ConfigChangeEventType eventType, String name, Object oldValue, Object newValue) {
+ this.eventType = eventType;
+ this.name = name;
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ }
- void setInternalServerPort(String internalServerPort);
+ public ConfigChangeEventType getEventType() {
+ return eventType;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Object getOldValue() {
+ return oldValue;
+ }
+
+ public Object getNewValue() {
+ return newValue;
+ }
+ }
+
+ interface ConfigChangeListener {
+ void configChanged(ConfigChangeEvent configChangeEvent);
+ }
}
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/config/ConfigSharingServiceImpl.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/config/ConfigSharingServiceImpl.java
deleted file mode 100644
index 6aa357e..0000000
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/config/ConfigSharingServiceImpl.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.unomi.router.core.config;
-
-import org.apache.unomi.api.services.ConfigSharingService;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.SynchronousBundleListener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Created by amidani on 27/06/2017.
- */
-public class ConfigSharingServiceImpl implements ConfigSharingService, SynchronousBundleListener {
-
- private static final Logger logger = LoggerFactory.getLogger(ConfigSharingServiceImpl.class);
-
- private String oneshotImportUploadDir;
- private BundleContext bundleContext;
-
- public void setBundleContext(BundleContext bundleContext) {
- this.bundleContext = bundleContext;
- }
-
- @Override
- public String getOneshotImportUploadDir() {
- return oneshotImportUploadDir;
- }
-
- @Override
- public void setOneshotImportUploadDir(String oneshotImportUploadDir) {
- this.oneshotImportUploadDir = oneshotImportUploadDir;
- }
-
- /** Methods below not used in router bundle implementation of the ConfigSharingService **/
-
- @Override
- public String getInternalServerPort() {
- return null;
- }
-
- @Override
- public void setInternalServerPort(String internalServerPort) { }
-
-
- public void preDestroy() throws Exception {
- bundleContext.removeBundleListener(this);
- logger.info("Config sharing service for Router is shutdown.");
- }
-
- private void processBundleStartup(BundleContext bundleContext) {
- if (bundleContext == null) {
- return;
- }
- }
-
- @Override
- public void bundleChanged(BundleEvent bundleEvent) {
-
- }
-
-}
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/context/RouterCamelContext.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/context/RouterCamelContext.java
index 5a26a15..32ceba8 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/context/RouterCamelContext.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/context/RouterCamelContext.java
@@ -21,6 +21,7 @@
import org.apache.camel.component.jackson.JacksonDataFormat;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.model.RouteDefinition;
+import org.apache.unomi.api.services.ConfigSharingService;
import org.apache.unomi.persistence.spi.PersistenceService;
import org.apache.unomi.router.api.ExportConfiguration;
import org.apache.unomi.router.api.ImportConfiguration;
@@ -63,13 +64,21 @@
private String configType;
private String allowedEndpoints;
private BundleContext bundleContext;
+ private ConfigSharingService configSharingService;
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
+ public void setConfigSharingService(ConfigSharingService configSharingService) {
+ this.configSharingService = configSharingService;
+ }
+
public void initCamelContext() throws Exception {
logger.info("Initialize Camel Context...");
+
+ configSharingService.setProperty("oneshotImportUploadDir", uploadDir);
+
camelContext = new DefaultCamelContext();
//--IMPORT ROUTES
diff --git a/extensions/router/router-core/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/extensions/router/router-core/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index aa39b15..5ae1e9c 100644
--- a/extensions/router/router-core/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/extensions/router/router-core/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -39,17 +39,6 @@
</cm:default-properties>
</cm:property-placeholder>
- <bean id="configSharingServiceRB" class="org.apache.unomi.router.core.config.ConfigSharingServiceImpl" destroy-method="preDestroy">
- <property name="oneshotImportUploadDir" value="${import.oneshot.uploadDir}"/>
- <property name="bundleContext" ref="blueprintBundleContext"/>
- </bean>
-
- <service id="configSharingServiceRBE" ref="configSharingServiceRB" auto-export="interfaces">
- <service-properties>
- <entry key="bundleDiscriminator" value="ROUTER"/>
- </service-properties>
- </service>
-
<bean id="unomiStorageProcessor" class="org.apache.unomi.router.core.processor.UnomiStorageProcessor">
<property name="profileImportService" ref="profileImportService"/>
</bean>
@@ -116,6 +105,7 @@
<property name="persistenceService" ref="persistenceService"/>
<property name="jacksonDataFormat" ref="jacksonDataFormat"/>
<property name="bundleContext" ref="blueprintBundleContext"/>
+ <property name="configSharingService" ref="configSharingService" />
</bean>
<camel:camelContext id="httpEndpoint" xmlns="http://camel.apache.org/schema/blueprint">
@@ -126,6 +116,7 @@
<property name="routerCamelContext" ref="camelContext"/>
</bean>
+ <reference id="configSharingService" interface="org.apache.unomi.api.services.ConfigSharingService" />
<reference id="httpService" interface="org.osgi.service.http.HttpService"/>
<reference id="profileImportService" interface="org.apache.unomi.router.api.services.ProfileImportService"/>
<reference id="persistenceService" interface="org.apache.unomi.persistence.spi.PersistenceService"/>
diff --git a/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/AbstractConfigurationServiceEndpoint.java b/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/AbstractConfigurationServiceEndpoint.java
index 195cd68..d7abbb2 100644
--- a/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/AbstractConfigurationServiceEndpoint.java
+++ b/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/AbstractConfigurationServiceEndpoint.java
@@ -30,17 +30,11 @@
public abstract class AbstractConfigurationServiceEndpoint<T> {
protected ImportExportConfigurationService<T> configurationService;
- protected ConfigSharingService routerConfigSharingService;
- protected ConfigSharingService clusterConfigSharingService;
+ protected ConfigSharingService configSharingService;
@WebMethod(exclude = true)
- public void setRouterConfigSharingService(ConfigSharingService routerConfigSharingService) {
- this.routerConfigSharingService = routerConfigSharingService;
- }
-
- @WebMethod(exclude = true)
- public void setClusterConfigSharingService(ConfigSharingService clusterConfigSharingService) {
- this.clusterConfigSharingService = clusterConfigSharingService;
+ public void setConfigSharingService(ConfigSharingService configSharingService) {
+ this.configSharingService = configSharingService;
}
/**
diff --git a/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ExportConfigurationServiceEndPoint.java b/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ExportConfigurationServiceEndPoint.java
index d5130ba..b3bcf65 100644
--- a/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ExportConfigurationServiceEndPoint.java
+++ b/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ExportConfigurationServiceEndPoint.java
@@ -65,7 +65,7 @@
if (RouterConstants.IMPORT_EXPORT_CONFIG_TYPE_RECURRENT.equals(exportConfigSaved.getConfigType())) {
CloseableHttpClient httpClient = HttpClients.createDefault();
try {
- HttpPut httpPut = new HttpPut("http://localhost:" + clusterConfigSharingService.getInternalServerPort() + "/configUpdate/exportConfigAdmin");
+ HttpPut httpPut = new HttpPut("http://localhost:" + configSharingService.getProperty("internalServerPort") + "/configUpdate/exportConfigAdmin");
StringEntity input = new StringEntity(new ObjectMapper().writeValueAsString(exportConfigSaved));
input.setContentType(MediaType.APPLICATION_JSON);
httpPut.setEntity(input);
diff --git a/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ImportConfigurationServiceEndPoint.java b/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ImportConfigurationServiceEndPoint.java
index e05167f..25ab70d 100644
--- a/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ImportConfigurationServiceEndPoint.java
+++ b/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ImportConfigurationServiceEndPoint.java
@@ -75,7 +75,7 @@
if (RouterConstants.IMPORT_EXPORT_CONFIG_TYPE_RECURRENT.equals(importConfigSaved.getConfigType())) {
CloseableHttpClient httpClient = HttpClients.createDefault();
try {
- HttpPut httpPut = new HttpPut("http://localhost:" + clusterConfigSharingService.getInternalServerPort() + "/configUpdate/importConfigAdmin");
+ HttpPut httpPut = new HttpPut("http://localhost:" + configSharingService.getProperty("internalServerPort") + "/configUpdate/importConfigAdmin");
StringEntity input = new StringEntity(new ObjectMapper().writeValueAsString(importConfigSaved));
input.setContentType(MediaType.APPLICATION_JSON);
httpPut.setEntity(input);
@@ -105,7 +105,7 @@
@Produces(MediaType.APPLICATION_JSON)
public Response processOneshotImportConfigurationCSV(@Multipart(value = "importConfigId") String importConfigId, @Multipart(value = "file") Attachment file) {
try {
- java.nio.file.Path path = Paths.get(routerConfigSharingService.getOneshotImportUploadDir() + importConfigId + ".csv");
+ java.nio.file.Path path = Paths.get(configSharingService.getProperty("oneshotImportUploadDir") + importConfigId + ".csv");
Files.deleteIfExists(path);
InputStream in = file.getObject(InputStream.class);
diff --git a/extensions/router/router-rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/extensions/router/router-rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 8cd4948..6c3c367 100644
--- a/extensions/router/router-rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/extensions/router/router-rest/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -17,13 +17,10 @@
-->
<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"
xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
- http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd
-
- http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.1.0.xsd">
+ http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd">
<bean id="cors-filter" class="org.apache.cxf.rs.security.cors.CrossOriginResourceSharingFilter"/>
<bean id="jacksonMapper" class="com.fasterxml.jackson.databind.ObjectMapper"/>
@@ -69,22 +66,16 @@
<reference id="exportConfigurationService" interface="org.apache.unomi.router.api.services.ImportExportConfigurationService"
filter="(configDiscriminator=EXPORT)"/>
- <reference id="configSharingServiceRouter" interface="org.apache.unomi.api.services.ConfigSharingService"
- filter="(bundleDiscriminator=ROUTER)"/>
-
- <reference id="configSharingServiceCluster" interface="org.apache.unomi.api.services.ConfigSharingService"
- filter="(bundleDiscriminator=SERVICES)"/>
+ <reference id="configSharingService" interface="org.apache.unomi.api.services.ConfigSharingService"/>
<bean id="importConfigurationServiceEndPoint" class="org.apache.unomi.router.rest.ImportConfigurationServiceEndPoint">
<property name="importConfigurationService" ref="importConfigurationService"/>
- <property name="routerConfigSharingService" ref="configSharingServiceRouter"/>
- <property name="clusterConfigSharingService" ref="configSharingServiceCluster"/>
+ <property name="configSharingService" ref="configSharingService"/>
</bean>
<bean id="exportConfigurationServiceEndPoint" class="org.apache.unomi.router.rest.ExportConfigurationServiceEndPoint">
<property name="exportConfigurationService" ref="exportConfigurationService"/>
- <property name="routerConfigSharingService" ref="configSharingServiceRouter"/>
- <property name="clusterConfigSharingService" ref="configSharingServiceCluster"/>
+ <property name="configSharingService" ref="configSharingService"/>
</bean>
</blueprint>
diff --git a/services/src/main/java/org/apache/unomi/services/services/ConfigSharingServiceImpl.java b/services/src/main/java/org/apache/unomi/services/services/ConfigSharingServiceImpl.java
index 2b7da8a..6583910 100644
--- a/services/src/main/java/org/apache/unomi/services/services/ConfigSharingServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/services/ConfigSharingServiceImpl.java
@@ -17,48 +17,76 @@
package org.apache.unomi.services.services;
import org.apache.unomi.api.services.ConfigSharingService;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.SynchronousBundleListener;
+import org.osgi.framework.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
/**
- * Created by amidani on 27/06/2017.
+ * An implementation of the ConfigSharingService that supports listeners that will be called when a property is added,
+ * updated or removed. The properties are stored in a ConcurrentHashMap so access should be thread-safe.
*/
public class ConfigSharingServiceImpl implements ConfigSharingService, SynchronousBundleListener {
private static final Logger logger = LoggerFactory.getLogger(ConfigSharingServiceImpl.class);
- private String internalServerPort;
-
private BundleContext bundleContext;
+ private Map<String,Object> configProperties = new ConcurrentHashMap<String,Object>();
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
- @Override
- public String getInternalServerPort() {
- return this.internalServerPort;
+ public void setConfigProperties(Map<String, Object> configProperties) {
+ this.configProperties = configProperties;
}
@Override
- public void setInternalServerPort(String internalServerPort) {
- this.internalServerPort = internalServerPort;
- }
-
- /**
- * Methods below not used in services bundle implementation of the ConfigSharingService
- **/
-
- @Override
- public String getOneshotImportUploadDir() {
- return null;
+ public Object getProperty(String name) {
+ return configProperties.get(name);
}
@Override
- public void setOneshotImportUploadDir(String oneshotImportUploadDir) {
+ public Object setProperty(String name, Object newValue) {
+ boolean existed = false;
+ if (configProperties.containsKey(name)) {
+ existed = true;
+ }
+ Object oldValue = configProperties.put(name, newValue);
+ if (existed) {
+ firePropertyUpdatedEvent(name, oldValue, newValue);
+ } else {
+ firePropertyAddedEvent(name, newValue);
+ }
+ return oldValue;
+ }
+
+ @Override
+ public boolean hasProperty(String name) {
+ return configProperties.containsKey(name);
+ }
+
+ @Override
+ public Object removeProperty(String name) {
+ boolean existed = false;
+ if (configProperties.containsKey(name)) {
+ existed = true;
+ }
+ Object oldValue = configProperties.remove(name);
+ if (existed) {
+ firePropertyRemovedEvent(name, oldValue);
+ }
+ return oldValue;
+ }
+
+ @Override
+ public Set<String> getPropertyNames() {
+ return configProperties.keySet();
}
public void preDestroy() throws Exception {
@@ -77,4 +105,47 @@
}
+ private void firePropertyAddedEvent(String name, Object newValue) {
+ fireConfigChangeEvent(new ConfigChangeEvent(ConfigChangeEvent.ConfigChangeEventType.ADDED, name, null, newValue));
+ }
+
+ private void firePropertyUpdatedEvent(String name, Object oldValue, Object newValue) {
+ fireConfigChangeEvent(new ConfigChangeEvent(ConfigChangeEvent.ConfigChangeEventType.UPDATED, name, oldValue, newValue));
+ }
+
+ private void firePropertyRemovedEvent(String name, Object oldValue) {
+ fireConfigChangeEvent(new ConfigChangeEvent(ConfigChangeEvent.ConfigChangeEventType.REMOVED, name, oldValue, null));
+ }
+
+ private void fireConfigChangeEvent(ConfigChangeEvent configChangeEvent) {
+ List<ConfigChangeListener> listeners = getListeners();
+ for (ConfigChangeListener configChangeListener : listeners) {
+ configChangeListener.configChanged(configChangeEvent);
+ }
+ }
+
+ /**
+ * This method is called a lot because this list may change at any time as listeners may come and go in OSGi
+ * @return a list of ConfigChangeListeners that will be used to listen to property changes
+ */
+ private List<ConfigChangeListener> getListeners() {
+ List<ConfigChangeListener> listeners = new ArrayList<>();
+ try {
+ ServiceReference<?>[] allListenerReferences = bundleContext.getAllServiceReferences(ConfigChangeListener.class.getName(), null);
+ if (allListenerReferences == null) {
+ return listeners;
+ }
+ for (ServiceReference<?> listenerReference : allListenerReferences) {
+ ConfigChangeListener configChangeListener = (ConfigChangeListener) bundleContext.getService(listenerReference);
+ if (configChangeListener != null) {
+ listeners.add(configChangeListener);
+ }
+ }
+ } catch (InvalidSyntaxException e) {
+ e.printStackTrace();
+ return listeners;
+ }
+ return listeners;
+ }
+
}
diff --git a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index b6c4f6c..8bbdcc6 100644
--- a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -235,15 +235,16 @@
</bean>
</service>
- <bean id="configSharingServiceCB" class="org.apache.unomi.services.services.ConfigSharingServiceImpl" destroy-method="preDestroy">
- <property name="internalServerPort" value="${cluster.contextserver.port}"/>
+ <bean id="configSharingServiceImpl" class="org.apache.unomi.services.services.ConfigSharingServiceImpl" destroy-method="preDestroy">
+ <property name="configProperties">
+ <map>
+ <entry key="internalServerPort" value="${cluster.contextserver.port}" />
+ </map>
+ </property>
<property name="bundleContext" ref="blueprintBundleContext"/>
</bean>
- <service id="configSharingServiceCBE" ref="configSharingServiceCB" auto-export="interfaces">
- <service-properties>
- <entry key="bundleDiscriminator" value="SERVICES"/>
- </service-properties>
+ <service id="configSharingService" ref="configSharingServiceImpl" auto-export="interfaces">
</service>
</blueprint>