Skip to content

Commit

Permalink
Add support for automatic creation of ManagedProviders for UI compone…
Browse files Browse the repository at this point in the history
…nts (#2948)

* Add support for automatic creation of ManagedProviders for UI components

Signed-off-by: Jan N. Klug <github@klug.nrw>
  • Loading branch information
J-N-K authored May 5, 2022
1 parent b5673e1 commit ad3a084
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public String getComponent() {
/**
* Sets the type of the component.
*
* @return the component type
* @param component the component type
*/
public void setComponent(String component) {
this.component = component;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.ui.internal.components;
package org.openhab.core.ui.components;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.common.registry.Provider;
import org.openhab.core.ui.components.RootUIComponent;

/**
* Provides components (pages, widgets, etc.) at runtime.
*
* @author Łukasz Dywicki - Initial contribution
*/
@NonNullByDefault
public interface UIProvider extends Provider<RootUIComponent> {
public interface UIComponentProvider extends Provider<RootUIComponent> {
String CONFIG_NAMESPACE = "ui.namespace";

String getNamespace();
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,41 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.registry.AbstractProvider;
import org.openhab.core.common.registry.ManagedProvider;
import org.openhab.core.config.core.ConfigParser;
import org.openhab.core.storage.Storage;
import org.openhab.core.storage.StorageService;
import org.openhab.core.ui.components.RootUIComponent;
import org.openhab.core.ui.components.UIComponentProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

/**
* A namespace-specific {@link ManagedProvider} for UI components.
*
* @author Yannick Schaus - Initial contribution
* @author Jan N. Klug - Refactored to component factory
*/
@NonNullByDefault
public class UIComponentProvider extends AbstractProvider<RootUIComponent>
implements ManagedProvider<RootUIComponent, String>, UIProvider {
@Component(factory = "org.openhab.core.ui.component.provider.factory")
public class ManagedUIComponentProvider extends AbstractProvider<RootUIComponent>
implements ManagedProvider<RootUIComponent, String>, UIComponentProvider {

private String namespace;
private volatile Storage<RootUIComponent> storage;
private final String namespace;
private final Storage<RootUIComponent> storage;

/**
* Constructs a UI component provider for the specified namespace
*
* @param namespace UI components namespace of this provider
* @param storageService supporting storage service
*/
public UIComponentProvider(String namespace, StorageService storageService) {
@Activate
public ManagedUIComponentProvider(@Reference StorageService storageService, Map<String, Object> config) {
String namespace = ConfigParser.valueAs(config.get(CONFIG_NAMESPACE), String.class);
if (namespace == null) {
throw new IllegalStateException("'ui.namespace' must not be null in service configuration");
}
this.namespace = namespace;
this.storage = storageService.getStorage("uicomponents_" + namespace.replace(':', '_'),
this.getClass().getClassLoader());
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,33 @@
package org.openhab.core.ui.internal.components;

import java.util.Collections;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.common.registry.ManagedProvider;
import org.openhab.core.ui.components.RootUIComponent;
import org.openhab.core.ui.components.UIComponentProvider;
import org.openhab.core.ui.components.UIComponentRegistryFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.component.ComponentFactory;
import org.osgi.service.component.ComponentInstance;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Implementation for a {@link UIComponentRegistryFactory} using a set of {@link UIComponentProvider}.
* Implementation for a {@link UIComponentRegistryFactory} using a set of {@link ManagedUIComponentProvider}.
*
* @author Yannick Schaus - Initial contribution
* @author Łukasz Dywicki - Removed explicit dependency on storage providers.
Expand All @@ -37,63 +48,89 @@
@NonNullByDefault
@Component(service = UIComponentRegistryFactory.class, immediate = true)
public class UIComponentRegistryFactoryImpl implements UIComponentRegistryFactory {
private final Logger logger = LoggerFactory.getLogger(UIComponentRegistryFactoryImpl.class);

private final ComponentFactory<ManagedUIComponentProvider> providerFactory;
private final BundleContext bc;

Map<String, UIComponentRegistryImpl> registries = new ConcurrentHashMap<>();
Map<String, Set<UIProvider>> providers = new ConcurrentHashMap<>();
Set<ComponentInstance<ManagedUIComponentProvider>> createdProviders = new CopyOnWriteArraySet<>();
Map<String, Set<UIComponentProvider>> providers = new ConcurrentHashMap<>();

@Activate
public UIComponentRegistryFactoryImpl(
@Reference(target = "(component.factory=org.openhab.core.ui.component.provider.factory)") ComponentFactory<ManagedUIComponentProvider> factory,
BundleContext bc) {
this.providerFactory = factory;
this.bc = bc;
}

@Override
public UIComponentRegistryImpl getRegistry(String namespace) {
UIComponentRegistryImpl registry = registries.get(namespace);
if (registry == null) {
Set<UIProvider> namespaceProviders = this.providers.get(namespace);
ManagedProvider<RootUIComponent, String> managedProvider = null;
if (namespaceProviders != null) {
for (UIProvider provider : namespaceProviders) {
if (provider instanceof ManagedProvider) {
managedProvider = (ManagedProvider<RootUIComponent, String>) provider;
break;
}
}
if (!managedProviderAvailable(namespace)) {
logger.debug("Creating managed provider for '{}'", namespace);
Dictionary<String, Object> properties = new Hashtable<>();
properties.put(UIComponentProvider.CONFIG_NAMESPACE, namespace);
ComponentInstance<ManagedUIComponentProvider> instance = this.providerFactory.newInstance(properties);
createdProviders.add(instance);
}
registry = new UIComponentRegistryImpl(namespace, managedProvider, namespaceProviders);
Set<UIComponentProvider> namespaceProviders = this.providers.get(namespace);
registry = new UIComponentRegistryImpl(namespace, namespaceProviders);
registries.put(namespace, registry);
}
return registry;
}

@Deactivate
public void deactivate() {
createdProviders.forEach(ComponentInstance::dispose);
}

private boolean managedProviderAvailable(String namespace) {
try {
return bc.getServiceReferences(UIComponentProvider.class, null).stream().map(bc::getService)
.anyMatch(s -> namespace.equals(s.getNamespace()) && s instanceof ManagedProvider<?, ?>);
} catch (InvalidSyntaxException e) {
return false;
}
}

@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
void addProvider(UIProvider provider) {
void addProvider(UIComponentProvider provider) {
UIComponentRegistryImpl registry = registries.get(provider.getNamespace());
if (registry != null) {
registry.addProvider(provider);
}
registerProvider(provider);
}

void removeProvider(UIProvider provider) {
void removeProvider(UIComponentProvider provider) {
UIComponentRegistryImpl registry = registries.get(provider.getNamespace());
if (registry != null) {
registry.removeProvider(provider);
}
deregisterProvider(provider);
unregisterProvider(provider);
}

private void registerProvider(UIProvider provider) {
Set<UIProvider> existing = providers.get(provider.getNamespace());
private void registerProvider(UIComponentProvider provider) {
Set<UIComponentProvider> existing = providers.get(provider.getNamespace());

if (existing == null) {
existing = Collections.emptySet();
}

Set<UIProvider> updated = new HashSet<>(existing);
Set<UIComponentProvider> updated = new HashSet<>(existing);
updated.add(provider);
providers.put(provider.getNamespace(), Set.copyOf(updated));
}

private void deregisterProvider(UIProvider provider) {
Set<UIProvider> existing = providers.get(provider.getNamespace());
private void unregisterProvider(UIComponentProvider provider) {
Set<UIComponentProvider> existing = providers.get(provider.getNamespace());

if (existing != null && !existing.isEmpty()) {
Set<UIProvider> updated = new HashSet<>(existing);
Set<UIComponentProvider> updated = new HashSet<>(existing);
updated.remove(provider);
providers.put(provider.getNamespace(), Set.copyOf(updated));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,27 @@
import org.openhab.core.common.registry.ManagedProvider;
import org.openhab.core.common.registry.Provider;
import org.openhab.core.ui.components.RootUIComponent;
import org.openhab.core.ui.components.UIComponentProvider;
import org.openhab.core.ui.components.UIComponentRegistry;

/**
* Implementation of a {@link UIComponentRegistry} using a {@link UIComponentProvider}.
* Implementation of a {@link UIComponentRegistry} using a {@link ManagedUIComponentProvider}.
* It is instantiated by the {@link UIComponentRegistryFactoryImpl}.
*
* @author Yannick Schaus - Initial contribution
* @author Łukasz Dywicki - Support for dynamic registration of providers
*/
@NonNullByDefault
public class UIComponentRegistryImpl extends AbstractRegistry<RootUIComponent, String, UIComponentProvider>
public class UIComponentRegistryImpl extends AbstractRegistry<RootUIComponent, String, ManagedUIComponentProvider>
implements UIComponentRegistry {

/**
* Constructs a UI component registry for the specified namespace.
*
* @param namespace UI components namespace of this registry
*/
public UIComponentRegistryImpl(String namespace, @Nullable ManagedProvider<RootUIComponent, String> managedProvider,
@Nullable Set<UIProvider> providers) {
public UIComponentRegistryImpl(String namespace, @Nullable Set<UIComponentProvider> providers) {
super(null);
if (managedProvider != null) {
setManagedProvider(managedProvider);
}
if (providers != null && !providers.isEmpty()) {
for (Provider<RootUIComponent> provider : providers) {
addProvider(provider);
Expand All @@ -54,10 +51,16 @@ public UIComponentRegistryImpl(String namespace, @Nullable ManagedProvider<RootU
@Override
public void addProvider(Provider<RootUIComponent> provider) {
super.addProvider(provider);
if (getManagedProvider().isEmpty() && provider instanceof ManagedProvider) {
setManagedProvider((ManagedProvider<RootUIComponent, String>) provider);
}
}

@Override
public void removeProvider(Provider<RootUIComponent> provider) {
if (getManagedProvider().isPresent() && provider instanceof ManagedProvider) {
unsetManagedProvider((ManagedProvider<RootUIComponent, String>) provider);
}
super.removeProvider(provider);
}
}

0 comments on commit ad3a084

Please sign in to comment.