mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-25 19:55:48 +01:00
UI component registries initial implementation (#1356)
* UI component registries initial implementation This is an initial implementation of #1355. It was simple enough to make to be proposed as a PR already without waiting for remarks on the RFC. The SitemapProvider for the `system:sitemap` namespace as described in #1355 is not part of this PR. Signed-off-by: Yannick Schaus <github@schaus.net>
This commit is contained in:
parent
11fa4fad4a
commit
eea31e536d
@ -12,19 +12,28 @@
|
||||
*/
|
||||
package org.openhab.core.io.rest.ui.internal;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
import org.openhab.core.io.rest.Stream2JSONInputStream;
|
||||
import org.openhab.core.io.rest.ui.TileDTO;
|
||||
import org.openhab.core.ui.components.RootUIComponent;
|
||||
import org.openhab.core.ui.components.UIComponentRegistry;
|
||||
import org.openhab.core.ui.components.UIComponentRegistryFactory;
|
||||
import org.openhab.core.ui.tiles.Tile;
|
||||
import org.openhab.core.ui.tiles.TileProvider;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
@ -58,6 +67,7 @@ public class UIResource implements RESTResource {
|
||||
private UriInfo uriInfo;
|
||||
|
||||
private TileProvider tileProvider;
|
||||
private UIComponentRegistryFactory componentRegistryFactory;
|
||||
|
||||
@GET
|
||||
@Path("/tiles")
|
||||
@ -69,9 +79,87 @@ public class UIResource implements RESTResource {
|
||||
return Response.ok(new Stream2JSONInputStream(tiles)).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/components/{namespace}")
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@ApiOperation(value = "Get all registered UI components in the specified namespace.")
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Tile.class) })
|
||||
public Response getAllComponents(@PathParam("namespace") String namespace) {
|
||||
UIComponentRegistry registry = componentRegistryFactory.getRegistry(namespace);
|
||||
Stream<RootUIComponent> components = registry.getAll().stream();
|
||||
return Response.ok(new Stream2JSONInputStream(components)).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/components/{namespace}/{componentUID}")
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@ApiOperation(value = "Get a specific UI component in the specified namespace.")
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Tile.class),
|
||||
@ApiResponse(code = 404, message = "Component not found", response = Tile.class) })
|
||||
public Response getComponentByUID(@PathParam("namespace") String namespace,
|
||||
@PathParam("componentUID") String componentUID) {
|
||||
UIComponentRegistry registry = componentRegistryFactory.getRegistry(namespace);
|
||||
RootUIComponent component = registry.get(componentUID);
|
||||
if (component == null) {
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
return Response.ok(component).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/components/{namespace}")
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@ApiOperation(value = "Add an UI component in the specified namespace.")
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Tile.class) })
|
||||
public Response addComponent(@PathParam("namespace") String namespace, RootUIComponent component) {
|
||||
UIComponentRegistry registry = componentRegistryFactory.getRegistry(namespace);
|
||||
component.updateTimestamp();
|
||||
RootUIComponent createdComponent = registry.add(component);
|
||||
return Response.ok(createdComponent).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/components/{namespace}/{componentUID}")
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@ApiOperation(value = "Update a specific UI component in the specified namespace.")
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Tile.class),
|
||||
@ApiResponse(code = 404, message = "Component not found", response = Tile.class) })
|
||||
public Response updateComponent(@PathParam("namespace") String namespace,
|
||||
@PathParam("componentUID") String componentUID, RootUIComponent component) {
|
||||
UIComponentRegistry registry = componentRegistryFactory.getRegistry(namespace);
|
||||
RootUIComponent existingComponent = registry.get(componentUID);
|
||||
if (existingComponent == null) {
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
if (!componentUID.equals(component.getUID())) {
|
||||
throw new InvalidParameterException(
|
||||
"The component UID in the body of the request should match the UID in the URL");
|
||||
}
|
||||
component.updateTimestamp();
|
||||
registry.update(component);
|
||||
return Response.ok(component).build();
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/components/{namespace}/{componentUID}")
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@ApiOperation(value = "Remove a specific UI component in the specified namespace.")
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Tile.class),
|
||||
@ApiResponse(code = 404, message = "Component not found", response = Tile.class) })
|
||||
public Response deleteComponent(@PathParam("namespace") String namespace,
|
||||
@PathParam("componentUID") String componentUID) {
|
||||
UIComponentRegistry registry = componentRegistryFactory.getRegistry(namespace);
|
||||
RootUIComponent component = registry.get(componentUID);
|
||||
if (component == null) {
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
registry.remove(componentUID);
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSatisfied() {
|
||||
return tileProvider != null;
|
||||
return tileProvider != null && componentRegistryFactory != null;
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
|
||||
@ -83,6 +171,15 @@ public class UIResource implements RESTResource {
|
||||
this.tileProvider = null;
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void setComponentRegistryFactory(UIComponentRegistryFactory componentRegistryFactory) {
|
||||
this.componentRegistryFactory = componentRegistryFactory;
|
||||
}
|
||||
|
||||
protected void unsetComponentRegistryFactory(UIComponentRegistryFactory componentRegistryFactory) {
|
||||
this.componentRegistryFactory = null;
|
||||
}
|
||||
|
||||
private TileDTO toTileDTO(Tile tile) {
|
||||
return new TileDTO(tile.getName(), tile.getUrl(), tile.getOverlay(), tile.getImageUrl());
|
||||
}
|
||||
|
@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.ui.components;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.common.registry.Identifiable;
|
||||
import org.openhab.core.config.core.dto.ConfigDescriptionDTO;
|
||||
|
||||
/**
|
||||
* A root component is a special type of {@link Component} at the root of the hierarchy.
|
||||
* It has a number of specific parameters, a set of tags, a timestamp, some configurable
|
||||
* parameters ("props") and is identifiable by its UID (generally a GUID).
|
||||
*
|
||||
* @author Yannick Schaus - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RootUIComponent extends UIComponent implements Identifiable<String> {
|
||||
String uid;
|
||||
|
||||
Set<String> tags = new HashSet<String>();
|
||||
|
||||
ConfigDescriptionDTO props;
|
||||
|
||||
@Nullable
|
||||
Date timestamp;
|
||||
|
||||
/**
|
||||
* Constructs a root component.
|
||||
*
|
||||
* @param name the name of the UI component to render the card on client frontends, ie. "HbCard"
|
||||
*/
|
||||
public RootUIComponent(String name) {
|
||||
super(name);
|
||||
this.uid = UUID.randomUUID().toString();
|
||||
this.props = new ConfigDescriptionDTO(null, new ArrayList<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a root component with a specific UID.
|
||||
*
|
||||
* @param uid the UID of the new card
|
||||
* @param name the name of the UI component to render the card on client frontends, ie. "HbCard"
|
||||
*/
|
||||
public RootUIComponent(String uid, String name) {
|
||||
super(name);
|
||||
this.uid = uid;
|
||||
this.props = new ConfigDescriptionDTO(null, new ArrayList<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUID() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the set of tags attached to the component
|
||||
*
|
||||
* @return the card tags
|
||||
*/
|
||||
public Set<String> getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timestamp of the component
|
||||
*
|
||||
* @return the timestamp
|
||||
*/
|
||||
public @Nullable Date getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specified timestamp of the component
|
||||
*
|
||||
* @param date the timestamp
|
||||
*/
|
||||
public void setTimestamp(Date date) {
|
||||
this.timestamp = date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the timestamp of the component to the current date & time.
|
||||
*/
|
||||
public void updateTimestamp() {
|
||||
this.timestamp = new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the component has a certain tag
|
||||
*
|
||||
* @param tag the tag to check
|
||||
* @return true if the component is tagged with the specified tag
|
||||
*/
|
||||
public boolean hasTag(String tag) {
|
||||
return (tags != null && tags.contains(tag));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a tag to the component
|
||||
*
|
||||
* @param tag the tag to add
|
||||
*/
|
||||
public void addTag(String tag) {
|
||||
this.tags.add(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds several tags to the component
|
||||
*
|
||||
* @param tags the tags to add
|
||||
*/
|
||||
public void addTags(Collection<String> tags) {
|
||||
this.tags.addAll(tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds several tags to the component
|
||||
*
|
||||
* @param tags the tags to add
|
||||
*/
|
||||
public void addTags(String... tags) {
|
||||
this.tags.addAll(Arrays.asList(tags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a tag on a component
|
||||
*
|
||||
* @param tag the tag to remove
|
||||
*/
|
||||
public void removeTag(String tag) {
|
||||
this.tags.remove(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all tags on the component
|
||||
*/
|
||||
public void removeAllTags() {
|
||||
this.tags.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configurable parameters ("props") of the component
|
||||
*
|
||||
* @return the configurable parameters
|
||||
*/
|
||||
public ConfigDescriptionDTO getProps() {
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the configurable parameters ("props") of the component
|
||||
*
|
||||
* @param props the configurable parameters
|
||||
*/
|
||||
public void setProps(ConfigDescriptionDTO props) {
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.ui.components;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An UIComponent represents a piece of UI element for a client frontend to render; it is kept very simple and delegates
|
||||
* the actual rendering and behavior to the frontend.
|
||||
*
|
||||
* It has a reference to a component's name as defined by the frontend, a map of configuration parameters, and several
|
||||
* named "slots", or placeholders, which may contain other sub-components, thus defining a tree.
|
||||
*
|
||||
* No checks are performed on the actual validity of configuration parameters and their values, the validity of a
|
||||
* particular slot for a certain component or the validity of certain types of sub-components within a particular slot:
|
||||
* that is the frontend's responsibility.
|
||||
*
|
||||
* @author Yannick Schaus - Initial contribution
|
||||
*/
|
||||
public class UIComponent {
|
||||
String component;
|
||||
|
||||
Map<String, Object> config;
|
||||
|
||||
Map<String, List<UIComponent>> slots = null;
|
||||
|
||||
/**
|
||||
* Constructs a component by its type name - component names are not arbitrary, they are defined by the target
|
||||
* frontend.
|
||||
*
|
||||
* @param componentType type of the component as known to the frontend
|
||||
*/
|
||||
public UIComponent(String componentType) {
|
||||
super();
|
||||
this.component = componentType;
|
||||
this.config = new HashMap<String, Object>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type of the component.
|
||||
*
|
||||
* @return the component type
|
||||
*/
|
||||
public String getType() {
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the configuration parameters of the component
|
||||
*
|
||||
* @return the map of configuration parameters
|
||||
*/
|
||||
public Map<String, Object> getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new configuration parameter to the component
|
||||
*
|
||||
* @param key the parameter key
|
||||
* @param value the parameter value
|
||||
*/
|
||||
public void addConfig(String key, Object value) {
|
||||
this.config.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the slots of the components including their sub-components
|
||||
*
|
||||
* @return the slots and their sub-components
|
||||
*/
|
||||
public Map<String, List<UIComponent>> getSlots() {
|
||||
return slots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new empty slot to the component
|
||||
*
|
||||
* @param slotName the name of the slot
|
||||
* @return the empty list of components in the newly created slot
|
||||
*/
|
||||
public List<UIComponent> addSlot(String slotName) {
|
||||
if (slots == null) {
|
||||
slots = new HashMap<String, List<UIComponent>>();
|
||||
}
|
||||
List<UIComponent> newSlot = new ArrayList<UIComponent>();
|
||||
this.slots.put(slotName, newSlot);
|
||||
|
||||
return newSlot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of sub-components in a slot
|
||||
*
|
||||
* @param slotName the name of the slot
|
||||
* @return the list of sub-components in the slot
|
||||
*/
|
||||
public List<UIComponent> getSlot(String slotName) {
|
||||
return this.slots.get(slotName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new sub-component to the specified slot. Creates the slot if necessary.
|
||||
*
|
||||
* @param slotName the slot to add the component to
|
||||
* @param subComponent the sub-component to add
|
||||
*/
|
||||
public void addComponent(String slotName, UIComponent subComponent) {
|
||||
List<UIComponent> slot;
|
||||
if (slots == null || !slots.containsKey(slotName)) {
|
||||
slot = addSlot(slotName);
|
||||
} else {
|
||||
slot = getSlot(slotName);
|
||||
}
|
||||
|
||||
slot.add(subComponent);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.ui.components;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.common.registry.Registry;
|
||||
|
||||
/**
|
||||
* A namespace-specific {@link Registry} for UI components.
|
||||
* It is normally instantiated for a specific namespace by the {@link UIComponentRegistryFactory}.
|
||||
*
|
||||
* @author Yannick Schaus - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface UIComponentRegistry extends Registry<RootUIComponent, String> {
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.ui.components;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* A factory for {@link UIComponentRegistry} instances based on the namespace.
|
||||
*
|
||||
* @author Yannick Schaus - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface UIComponentRegistryFactory {
|
||||
|
||||
/**
|
||||
* Gets the {@link UIComponentRegistry} for the specified namespace.
|
||||
*
|
||||
* @param namespace the namespace
|
||||
* @return a registry for UI elements in the namespace
|
||||
*/
|
||||
UIComponentRegistry getRegistry(String namespace);
|
||||
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.ui.internal.components;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
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.storage.Storage;
|
||||
import org.openhab.core.storage.StorageService;
|
||||
import org.openhab.core.ui.components.RootUIComponent;
|
||||
|
||||
/**
|
||||
* A namespace-specific {@link ManagedProvider} for UI components.
|
||||
*
|
||||
* @author Yannick Schaus - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UIComponentProvider extends AbstractProvider<RootUIComponent>
|
||||
implements ManagedProvider<RootUIComponent, String> {
|
||||
|
||||
private String namespace;
|
||||
private volatile 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) {
|
||||
this.namespace = namespace;
|
||||
this.storage = storageService.getStorage("uicomponents_" + namespace.replace(':', '_'),
|
||||
this.getClass().getClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<RootUIComponent> getAll() {
|
||||
List<RootUIComponent> components = new ArrayList<>();
|
||||
for (RootUIComponent component : storage.getValues()) {
|
||||
if (component != null) {
|
||||
components.add(component);
|
||||
}
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(@NonNull RootUIComponent element) {
|
||||
if (StringUtils.isEmpty(element.getUID())) {
|
||||
throw new IllegalArgumentException("Invalid UID");
|
||||
}
|
||||
|
||||
if (storage.get(element.getUID()) != null) {
|
||||
throw new IllegalArgumentException("Cannot add UI component to namespace " + namespace
|
||||
+ ", because a component with same UID (" + element.getUID() + ") already exists.");
|
||||
}
|
||||
storage.put(element.getUID(), element);
|
||||
notifyListenersAboutAddedElement(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable RootUIComponent remove(@NonNull String key) {
|
||||
RootUIComponent element = storage.remove(key);
|
||||
if (element != null) {
|
||||
notifyListenersAboutRemovedElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable RootUIComponent update(@NonNull RootUIComponent element) {
|
||||
if (storage.get(element.getUID()) != null) {
|
||||
RootUIComponent oldElement = storage.put(element.getUID(), element);
|
||||
if (oldElement != null) {
|
||||
notifyListenersAboutUpdatedElement(oldElement, element);
|
||||
return oldElement;
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot update UI component " + element.getUID() + " in namespace "
|
||||
+ namespace + " because it doesn't exist.");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable RootUIComponent get(String key) {
|
||||
if (StringUtils.isEmpty(key)) {
|
||||
throw new IllegalArgumentException("Invalid UID");
|
||||
}
|
||||
return storage.get(key);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.ui.internal.components;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.core.storage.StorageService;
|
||||
import org.openhab.core.ui.components.UIComponentRegistryFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Implementation for a {@link UIComponentRegistryFactory} using a {@link StorageService} and a
|
||||
* {@link UIComponentProvider}.
|
||||
*
|
||||
* @author Yannick Schaus - Initial contribution
|
||||
*/
|
||||
@Component(service = UIComponentRegistryFactory.class, immediate = true)
|
||||
public class UIComponentRegistryFactoryImpl implements UIComponentRegistryFactory {
|
||||
Map<String, UIComponentRegistryImpl> registries = new HashMap<>();
|
||||
|
||||
@Reference
|
||||
StorageService storageService;
|
||||
|
||||
@Override
|
||||
public UIComponentRegistryImpl getRegistry(String namespace) {
|
||||
if (registries.containsKey(namespace)) {
|
||||
return registries.get(namespace);
|
||||
} else {
|
||||
UIComponentRegistryImpl registry = new UIComponentRegistryImpl(namespace, storageService);
|
||||
registries.put(namespace, registry);
|
||||
return registry;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.ui.internal.components;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.common.registry.AbstractRegistry;
|
||||
import org.openhab.core.storage.StorageService;
|
||||
import org.openhab.core.ui.components.RootUIComponent;
|
||||
import org.openhab.core.ui.components.UIComponentRegistry;
|
||||
|
||||
/**
|
||||
* Implementation of a {@link UIComponentRegistry} using a {@link UIComponentProvider}.
|
||||
* It is instantiated by the {@link UIComponentRegistryFactoryImpl}.
|
||||
*
|
||||
* @author Yannick Schaus - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UIComponentRegistryImpl extends AbstractRegistry<RootUIComponent, String, UIComponentProvider>
|
||||
implements UIComponentRegistry {
|
||||
|
||||
String namespace;
|
||||
StorageService storageService;
|
||||
|
||||
/**
|
||||
* Constructs a UI component registry for the specified namespace.
|
||||
*
|
||||
* @param namespace UI components namespace of this registry
|
||||
* @param storageService supporting storage service
|
||||
*/
|
||||
public UIComponentRegistryImpl(String namespace, StorageService storageService) {
|
||||
super(null);
|
||||
this.namespace = namespace;
|
||||
this.storageService = storageService;
|
||||
UIComponentProvider provider = new UIComponentProvider(namespace, storageService);
|
||||
addProvider(provider);
|
||||
setManagedProvider(provider);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,337 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.ui.internal.components;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import org.eclipse.emf.common.util.EList;
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.common.registry.RegistryChangeListener;
|
||||
import org.openhab.core.config.core.ConfigUtil;
|
||||
import org.openhab.core.model.core.EventType;
|
||||
import org.openhab.core.model.core.ModelRepositoryChangeListener;
|
||||
import org.openhab.core.model.sitemap.SitemapProvider;
|
||||
import org.openhab.core.model.sitemap.sitemap.LinkableWidget;
|
||||
import org.openhab.core.model.sitemap.sitemap.Mapping;
|
||||
import org.openhab.core.model.sitemap.sitemap.Sitemap;
|
||||
import org.openhab.core.model.sitemap.sitemap.SitemapFactory;
|
||||
import org.openhab.core.model.sitemap.sitemap.SitemapPackage;
|
||||
import org.openhab.core.model.sitemap.sitemap.Widget;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.ChartImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.ColorpickerImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.DefaultImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.FrameImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.GroupImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.ImageImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.ListImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.MappingImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.MapviewImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.SelectionImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.SetpointImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.SitemapImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.SliderImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.SwitchImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.TextImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.VideoImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.WebviewImpl;
|
||||
import org.openhab.core.model.sitemap.sitemap.impl.WidgetImpl;
|
||||
import org.openhab.core.ui.components.RootUIComponent;
|
||||
import org.openhab.core.ui.components.UIComponent;
|
||||
import org.openhab.core.ui.components.UIComponentRegistry;
|
||||
import org.openhab.core.ui.components.UIComponentRegistryFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
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;
|
||||
|
||||
/**
|
||||
* This {@link SitemapProvider} provides sitemaps from all well-formed {@link RootUIComponent} found in a specific
|
||||
* "system:sitemap" namespace.
|
||||
*
|
||||
* @author Yannick Schaus - Initial contribution
|
||||
*/
|
||||
@Component(service = SitemapProvider.class)
|
||||
public class UIComponentSitemapProvider implements SitemapProvider, RegistryChangeListener<RootUIComponent> {
|
||||
private final Logger logger = LoggerFactory.getLogger(UIComponentSitemapProvider.class);
|
||||
|
||||
public static final String SITEMAP_NAMESPACE = "system:sitemap";
|
||||
|
||||
private static final String SITEMAP_PREFIX = "uicomponents_";
|
||||
private static final String SITEMAP_SUFFIX = ".sitemap";
|
||||
|
||||
private Map<String, Sitemap> sitemaps = new HashMap<>();
|
||||
private UIComponentRegistryFactory componentRegistryFactory;
|
||||
private UIComponentRegistry sitemapComponentRegistry;
|
||||
|
||||
private final Set<ModelRepositoryChangeListener> modelChangeListeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
@Override
|
||||
public @Nullable Sitemap getSitemap(@NonNull String sitemapName) {
|
||||
buildSitemap(sitemapName.replaceFirst(SITEMAP_PREFIX, ""));
|
||||
return sitemaps.get(sitemapName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Set<@NonNull String> getSitemapNames() {
|
||||
sitemaps.clear();
|
||||
Collection<RootUIComponent> rootComponents = sitemapComponentRegistry.getAll();
|
||||
// try building all sitemaps to leave the invalid ones out
|
||||
for (RootUIComponent rootComponent : rootComponents) {
|
||||
try {
|
||||
Sitemap sitemap = buildSitemap(rootComponent);
|
||||
sitemaps.put(sitemap.getName(), sitemap);
|
||||
} catch (Exception e) {
|
||||
logger.error("Cannot build sitemap {}", rootComponent.getUID(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return sitemaps.keySet();
|
||||
}
|
||||
|
||||
protected Sitemap buildSitemap(String sitemapName) {
|
||||
RootUIComponent rootComponent = sitemapComponentRegistry.get(sitemapName);
|
||||
if (rootComponent != null) {
|
||||
try {
|
||||
Sitemap sitemap = buildSitemap(rootComponent);
|
||||
sitemaps.put(sitemap.getName(), sitemap);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.error("Cannot build sitemap {}", rootComponent.getUID(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Sitemap buildSitemap(RootUIComponent rootComponent) {
|
||||
if (!"Sitemap".equals(rootComponent.getType())) {
|
||||
throw new IllegalArgumentException("Root component type is not Sitemap");
|
||||
}
|
||||
|
||||
SitemapImpl sitemap = (SitemapImpl) SitemapFactory.eINSTANCE.createSitemap();
|
||||
sitemap.setName(SITEMAP_PREFIX + rootComponent.getUID());
|
||||
sitemap.setLabel(rootComponent.getConfig().get("label").toString());
|
||||
|
||||
if (rootComponent.getSlots() != null && rootComponent.getSlots().containsKey("widgets")) {
|
||||
for (UIComponent component : rootComponent.getSlot("widgets")) {
|
||||
Widget widget = buildWidget(component);
|
||||
if (widget != null) {
|
||||
sitemap.getChildren().add(widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sitemap;
|
||||
}
|
||||
|
||||
protected Widget buildWidget(UIComponent component) {
|
||||
Widget widget = null;
|
||||
|
||||
switch (component.getType()) {
|
||||
case "Frame":
|
||||
FrameImpl frameWidget = (FrameImpl) SitemapFactory.eINSTANCE.createFrame();
|
||||
widget = frameWidget;
|
||||
break;
|
||||
case "Text":
|
||||
TextImpl textWidget = (TextImpl) SitemapFactory.eINSTANCE.createText();
|
||||
widget = textWidget;
|
||||
break;
|
||||
case "Group":
|
||||
GroupImpl groupWidget = (GroupImpl) SitemapFactory.eINSTANCE.createGroup();
|
||||
widget = groupWidget;
|
||||
break;
|
||||
case "Image":
|
||||
ImageImpl imageWidget = (ImageImpl) SitemapFactory.eINSTANCE.createImage();
|
||||
widget = imageWidget;
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "url", SitemapPackage.IMAGE__URL);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "refresh", SitemapPackage.IMAGE__REFRESH);
|
||||
break;
|
||||
case "Video":
|
||||
VideoImpl videoWidget = (VideoImpl) SitemapFactory.eINSTANCE.createVideo();
|
||||
widget = videoWidget;
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "url", SitemapPackage.IMAGE__URL);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "encoding", SitemapPackage.VIDEO__ENCODING);
|
||||
break;
|
||||
case "Chart":
|
||||
ChartImpl chartWidget = (ChartImpl) SitemapFactory.eINSTANCE.createChart();
|
||||
widget = chartWidget;
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "service", SitemapPackage.CHART__SERVICE);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "refresh", SitemapPackage.CHART__REFRESH);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "period", SitemapPackage.CHART__PERIOD);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "legend", SitemapPackage.CHART__LEGEND);
|
||||
break;
|
||||
case "Webview":
|
||||
WebviewImpl webviewWidget = (WebviewImpl) SitemapFactory.eINSTANCE.createWebview();
|
||||
widget = webviewWidget;
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "height", SitemapPackage.WEBVIEW__HEIGHT);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "url", SitemapPackage.WEBVIEW__URL);
|
||||
break;
|
||||
case "Switch":
|
||||
SwitchImpl switchWidget = (SwitchImpl) SitemapFactory.eINSTANCE.createSwitch();
|
||||
addWidgetMappings(switchWidget.getMappings(), component);
|
||||
widget = switchWidget;
|
||||
break;
|
||||
case "Mapview":
|
||||
MapviewImpl mapviewWidget = (MapviewImpl) SitemapFactory.eINSTANCE.createMapview();
|
||||
widget = mapviewWidget;
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "height", SitemapPackage.WEBVIEW__HEIGHT);
|
||||
break;
|
||||
case "Slider":
|
||||
SliderImpl sliderWidget = (SliderImpl) SitemapFactory.eINSTANCE.createSlider();
|
||||
widget = sliderWidget;
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "minValue", SitemapPackage.SLIDER__MIN_VALUE);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "maxValue", SitemapPackage.SLIDER__MAX_VALUE);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "step", SitemapPackage.SLIDER__STEP);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "switchEnabled",
|
||||
SitemapPackage.SLIDER__SWITCH_ENABLED);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "sendFrequency",
|
||||
SitemapPackage.SLIDER__FREQUENCY);
|
||||
break;
|
||||
case "Selection":
|
||||
SelectionImpl selectionWidget = (SelectionImpl) SitemapFactory.eINSTANCE.createSelection();
|
||||
addWidgetMappings(selectionWidget.getMappings(), component);
|
||||
widget = selectionWidget;
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "height", SitemapPackage.WEBVIEW__HEIGHT);
|
||||
break;
|
||||
case "List":
|
||||
ListImpl listWidget = (ListImpl) SitemapFactory.eINSTANCE.createList();
|
||||
widget = listWidget;
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "separator", SitemapPackage.LIST__SEPARATOR);
|
||||
break;
|
||||
case "Setpoint":
|
||||
SetpointImpl setpointWidget = (SetpointImpl) SitemapFactory.eINSTANCE.createSetpoint();
|
||||
widget = setpointWidget;
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "minValue", SitemapPackage.SETPOINT__MIN_VALUE);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "maxValue", SitemapPackage.SETPOINT__MAX_VALUE);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "step", SitemapPackage.SETPOINT__STEP);
|
||||
break;
|
||||
case "Colorpicker":
|
||||
ColorpickerImpl colorpickerWidget = (ColorpickerImpl) SitemapFactory.eINSTANCE.createColorpicker();
|
||||
widget = colorpickerWidget;
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "frequency",
|
||||
SitemapPackage.COLORPICKER__FREQUENCY);
|
||||
break;
|
||||
case "Default":
|
||||
DefaultImpl defaultWidget = (DefaultImpl) SitemapFactory.eINSTANCE.createDefault();
|
||||
widget = defaultWidget;
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "height", SitemapPackage.DEFAULT__HEIGHT);
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unknown sitemap component type {}", component.getType());
|
||||
break;
|
||||
}
|
||||
|
||||
if (widget != null) {
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "label", SitemapPackage.WIDGET__LABEL);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "icon", SitemapPackage.WIDGET__ICON);
|
||||
setWidgetPropertyFromComponentConfig(widget, component, "item", SitemapPackage.WIDGET__ITEM);
|
||||
|
||||
if (widget instanceof LinkableWidget) {
|
||||
LinkableWidget linkableWidget = (LinkableWidget) widget;
|
||||
if (component.getSlots() != null && component.getSlots().containsKey("widgets")) {
|
||||
for (UIComponent childComponent : component.getSlot("widgets")) {
|
||||
Widget childWidget = buildWidget(childComponent);
|
||||
if (childWidget != null) {
|
||||
linkableWidget.getChildren().add(childWidget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: process visibility & color rules
|
||||
}
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
private void setWidgetPropertyFromComponentConfig(Widget widget, UIComponent component, String configParamName,
|
||||
int feature) {
|
||||
if (component == null || component.getConfig() == null) {
|
||||
return;
|
||||
}
|
||||
Object value = component.getConfig().get(configParamName);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
WidgetImpl widgetImpl = (WidgetImpl) widget;
|
||||
widgetImpl.eSet(feature, ConfigUtil.normalizeType(value));
|
||||
}
|
||||
|
||||
private void addWidgetMappings(EList<Mapping> mappings, UIComponent component) {
|
||||
if (component.getConfig() != null && component.getConfig().containsKey("mappings")) {
|
||||
if (component.getConfig().get("mappings") instanceof Collection<?>) {
|
||||
for (Object sourceMapping : (Collection<?>) component.getConfig().get("mappings")) {
|
||||
if (sourceMapping instanceof String) {
|
||||
String cmd = sourceMapping.toString().split("=")[0].trim();
|
||||
String label = sourceMapping.toString().split("=")[1].trim();
|
||||
MappingImpl mapping = (MappingImpl) SitemapFactory.eINSTANCE.createMapping();
|
||||
mapping.setCmd(cmd);
|
||||
mapping.setLabel(label);
|
||||
mappings.add(mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addModelChangeListener(@NonNull ModelRepositoryChangeListener listener) {
|
||||
modelChangeListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeModelChangeListener(@NonNull ModelRepositoryChangeListener listener) {
|
||||
modelChangeListeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void added(RootUIComponent element) {
|
||||
for (ModelRepositoryChangeListener listener : modelChangeListeners) {
|
||||
listener.modelChanged(SITEMAP_PREFIX + element.getUID() + SITEMAP_SUFFIX, EventType.ADDED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(RootUIComponent element) {
|
||||
for (ModelRepositoryChangeListener listener : modelChangeListeners) {
|
||||
listener.modelChanged(SITEMAP_PREFIX + element.getUID() + SITEMAP_SUFFIX, EventType.REMOVED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(RootUIComponent oldElement, RootUIComponent element) {
|
||||
for (ModelRepositoryChangeListener listener : modelChangeListeners) {
|
||||
listener.modelChanged(SITEMAP_PREFIX + element.getUID() + SITEMAP_SUFFIX, EventType.MODIFIED);
|
||||
}
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void setComponentRegistryFactory(UIComponentRegistryFactory componentRegistryFactory) {
|
||||
this.componentRegistryFactory = componentRegistryFactory;
|
||||
this.sitemapComponentRegistry = this.componentRegistryFactory.getRegistry(SITEMAP_NAMESPACE);
|
||||
this.sitemapComponentRegistry.addRegistryChangeListener(this);
|
||||
}
|
||||
|
||||
protected void unsetComponentRegistryFactory(UIComponentRegistryFactory componentRegistryFactory) {
|
||||
this.componentRegistryFactory = null;
|
||||
this.sitemapComponentRegistry.removeRegistryChangeListener(this);
|
||||
this.sitemapComponentRegistry = null;
|
||||
}
|
||||
}
|
@ -63,7 +63,7 @@ public abstract class AbstractRegistry<E extends Identifiable<K>, K, P extends P
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractRegistry.class);
|
||||
|
||||
private final Class<P> providerClazz;
|
||||
private final @Nullable Class<P> providerClazz;
|
||||
private @Nullable ServiceTracker<P, P> providerTracker;
|
||||
|
||||
private final ReentrantReadWriteLock elementLock = new ReentrantReadWriteLock();
|
||||
@ -86,18 +86,19 @@ public abstract class AbstractRegistry<E extends Identifiable<K>, K, P extends P
|
||||
* @param providerClazz the class of the providers (see e.g. {@link AbstractRegistry#addProvider(Provider)}), null
|
||||
* if no providers should be tracked automatically after activation
|
||||
*/
|
||||
protected AbstractRegistry(final Class<P> providerClazz) {
|
||||
protected AbstractRegistry(final @Nullable Class<P> providerClazz) {
|
||||
this.providerClazz = providerClazz;
|
||||
}
|
||||
|
||||
protected void activate(final BundleContext context) {
|
||||
/*
|
||||
* The handlers for 'add' and 'remove' the services implementing the provider class (cardinality is
|
||||
* multiple) rely on an active component.
|
||||
* To grant that the add and remove functions are called only for an active component, we use a provider
|
||||
* tracker.
|
||||
*/
|
||||
if (providerClazz != null) {
|
||||
/*
|
||||
* The handlers for 'add' and 'remove' the services implementing the provider class (cardinality is
|
||||
* multiple) rely on an active component.
|
||||
* To grant that the add and remove functions are called only for an active component, we use a provider
|
||||
* tracker.
|
||||
*/
|
||||
Class<P> providerClazz = this.providerClazz;
|
||||
providerTracker = new ProviderTracker(context, providerClazz);
|
||||
providerTracker.open();
|
||||
}
|
||||
@ -327,7 +328,7 @@ public abstract class AbstractRegistry<E extends Identifiable<K>, K, P extends P
|
||||
}
|
||||
|
||||
@Override
|
||||
public E update(E element) {
|
||||
public @Nullable E update(E element) {
|
||||
return managedProvider.orElseThrow(() -> new IllegalStateException("ManagedProvider is not available"))
|
||||
.update(element);
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
package org.openhab.core.common.registry;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link ManagedProvider} is a specific {@link Provider} that enables to
|
||||
@ -41,6 +42,7 @@ public interface ManagedProvider<E extends Identifiable<K>, K> extends Provider<
|
||||
* @return element that was removed, or null if no element with the given
|
||||
* key exists
|
||||
*/
|
||||
@Nullable
|
||||
E remove(@NonNull K key);
|
||||
|
||||
/**
|
||||
@ -50,6 +52,7 @@ public interface ManagedProvider<E extends Identifiable<K>, K> extends Provider<
|
||||
* @return returns the old element or null if no element with the same key
|
||||
* exists
|
||||
*/
|
||||
@Nullable
|
||||
E update(@NonNull E element);
|
||||
|
||||
/**
|
||||
@ -59,6 +62,7 @@ public interface ManagedProvider<E extends Identifiable<K>, K> extends Provider<
|
||||
* @param key key
|
||||
* @return returns element or null, if no element for the given key exists
|
||||
*/
|
||||
@Nullable
|
||||
E get(K key);
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user