Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ProfileTypeBuilder to pass its three filters list to both implementations of ProfileType #4325

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
import org.openhab.core.thing.dto.ChannelTypeDTO;
import org.openhab.core.thing.profiles.ProfileType;
import org.openhab.core.thing.profiles.ProfileTypeRegistry;
import org.openhab.core.thing.profiles.TriggerProfileType;
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeRegistry;
Expand Down Expand Up @@ -174,10 +173,8 @@ public Response getLinkableItemTypes(

Set<String> result = new HashSet<>();
for (ProfileType profileType : profileTypeRegistry.getProfileTypes()) {
if (profileType instanceof TriggerProfileType type) {
if (type.getSupportedChannelTypeUIDs().contains(ctUID)) {
result.addAll(profileType.getSupportedItemTypes());
}
if (profileType.getSupportedChannelTypeUIDs().contains(ctUID)) {
result.addAll(profileType.getSupportedItemTypes());
}
}
if (result.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,24 @@
*/
package org.openhab.core.io.rest.core.internal.link;

import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Stream;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
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.QueryParam;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
Expand All @@ -35,6 +39,7 @@
import org.openhab.core.auth.Role;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.rest.JSONResponse;
import org.openhab.core.io.rest.LocaleService;
import org.openhab.core.io.rest.RESTConstants;
import org.openhab.core.io.rest.RESTResource;
import org.openhab.core.io.rest.Stream2JSONInputStream;
Expand All @@ -58,9 +63,10 @@
import org.openhab.core.thing.link.dto.ItemChannelLinkDTO;
import org.openhab.core.thing.profiles.ProfileType;
import org.openhab.core.thing.profiles.ProfileTypeRegistry;
import org.openhab.core.thing.profiles.TriggerProfileType;
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeRegistry;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
Expand All @@ -69,6 +75,8 @@
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand Down Expand Up @@ -103,6 +111,8 @@
@NonNullByDefault
public class ItemChannelLinkResource implements RESTResource {

private final Logger logger = LoggerFactory.getLogger(ItemChannelLinkResource.class);

/** The URI path to this resource */
public static final String PATH_LINKS = "links";

Expand All @@ -111,18 +121,23 @@ public class ItemChannelLinkResource implements RESTResource {
private final ThingRegistry thingRegistry;
private final ItemRegistry itemRegistry;
private final ProfileTypeRegistry profileTypeRegistry;
private final ChannelTypeRegistry channelTypeRegistry;
private final LocaleService localeService;

@Activate
public ItemChannelLinkResource(final @Reference ItemRegistry itemRegistry,
final @Reference ThingRegistry thingRegistry, final @Reference ChannelTypeRegistry channelTypeRegistry,
final @Reference ProfileTypeRegistry profileTypeRegistry,
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry,
final @Reference ManagedItemChannelLinkProvider managedItemChannelLinkProvider) {
final @Reference ManagedItemChannelLinkProvider managedItemChannelLinkProvider,
final @Reference LocaleService localeService) {
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
this.managedItemChannelLinkProvider = managedItemChannelLinkProvider;
this.thingRegistry = thingRegistry;
this.itemRegistry = itemRegistry;
this.profileTypeRegistry = profileTypeRegistry;
this.channelTypeRegistry = channelTypeRegistry;
this.localeService = localeService;
}

@GET
Expand Down Expand Up @@ -194,9 +209,14 @@ public Response getLink(@PathParam("itemName") @Parameter(description = "item na
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "400", description = "Content does not match the path"),
@ApiResponse(responseCode = "405", description = "Link is not editable") })
public Response link(@PathParam("itemName") @Parameter(description = "itemName") String itemName,
public Response link(
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
@PathParam("itemName") @Parameter(description = "itemName") String itemName,
@PathParam("channelUID") @Parameter(description = "channelUID") String channelUid,
@Parameter(description = "link data") @Nullable ItemChannelLinkDTO bean) {

Locale locale = localeService.getLocale(language);

Item item;
try {
item = itemRegistry.getItem(itemName);
Expand Down Expand Up @@ -230,18 +250,45 @@ public Response link(@PathParam("itemName") @Parameter(description = "itemName")
}
ProfileType profileType = profileTypeRegistry.getProfileTypes().stream()
.filter(p -> profileUid.equals(p.getUID().getAsString())).findFirst().orElse(null);
if (!(profileType instanceof TriggerProfileType)) {

Collection<String> supportedItemTypesOnProfileType = profileType.getSupportedItemTypes();
if (!supportedItemTypesOnProfileType.isEmpty() && !supportedItemTypesOnProfileType.contains(itemType)) {
// item type not matching
return Response.status(Status.BAD_REQUEST).build();
}

ChannelKind supportedChannelKind = profileType.getSupportedChannelKind();
if (supportedChannelKind != null && supportedChannelKind != channelKind) {
// only trigger profiles are allowed
return Response.status(Status.BAD_REQUEST).build();
}
if (!(profileType.getSupportedItemTypes().isEmpty()
|| profileType.getSupportedItemTypes().contains(itemType))
|| !(((TriggerProfileType) profileType).getSupportedChannelTypeUIDs().isEmpty()
|| ((TriggerProfileType) profileType).getSupportedChannelTypeUIDs()
.contains(channel.getChannelTypeUID()))) {
// item or channel type not matching

ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();

Collection<ChannelTypeUID> supportedChannelTypeUIDsOnProfileType = profileType
.getSupportedChannelTypeUIDs();
if (!supportedChannelTypeUIDsOnProfileType.isEmpty()
&& !supportedChannelTypeUIDsOnProfileType.contains(channelTypeUID)) {
// channel type not matching
return Response.status(Status.BAD_REQUEST).build();
}

Collection<String> supportedItemTypesOfChannelOnProfileType = profileType.getSupportedItemTypesOfChannel();
if (!supportedItemTypesOfChannelOnProfileType.isEmpty()) {
ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID, locale);
if (channelType == null) {
logger.error("Requested to filter against an unknown channel type: {} is not known to the registry",
channelTypeUID.toString());
return Response.status(Status.BAD_REQUEST).build();
}

String channelTypeItemType = channelType.getItemType();
if (channelTypeItemType == null || !supportedItemTypesOfChannelOnProfileType
.contains(ItemUtil.getMainItemType(channelTypeItemType))) {
// channel item type not allowed
return Response.status(Status.BAD_REQUEST).build();
}
}
}

ItemChannelLink link;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@
import org.openhab.core.items.ItemUtil;
import org.openhab.core.thing.profiles.ProfileType;
import org.openhab.core.thing.profiles.ProfileTypeRegistry;
import org.openhab.core.thing.profiles.StateProfileType;
import org.openhab.core.thing.profiles.TriggerProfileType;
import org.openhab.core.thing.profiles.dto.ProfileTypeDTO;
import org.openhab.core.thing.profiles.dto.ProfileTypeDTOMapper;
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeRegistry;
import org.openhab.core.thing.type.ChannelTypeUID;
Expand Down Expand Up @@ -128,13 +127,7 @@ private Predicate<ProfileType> matchesChannelUID(@Nullable String channelTypeUID
// requested to filter against an unknown channel type -> do not return a ProfileType
return t -> false;
}
switch (channelType.getKind()) {
case STATE:
return t -> stateProfileMatchesProfileType(t, channelType);
case TRIGGER:
return t -> triggerProfileMatchesProfileType(t, channelType);
}
return t -> false;
return t -> profileTypeMatchesChannelType(t, channelType);
J-N-K marked this conversation as resolved.
Show resolved Hide resolved
}

private Predicate<ProfileType> matchesItemType(@Nullable String itemType) {
Expand All @@ -151,31 +144,24 @@ private boolean profileTypeMatchesItemType(ProfileType pt, String itemType) {
|| supportedItemTypesOnProfileType.contains(itemType);
}

private boolean triggerProfileMatchesProfileType(ProfileType profileType, ChannelType channelType) {
if (profileType instanceof TriggerProfileType triggerProfileType) {
if (triggerProfileType.getSupportedChannelTypeUIDs().isEmpty()) {
return true;
}
private boolean profileTypeMatchesChannelType(ProfileType profileType, ChannelType channelType) {
ChannelKind supportedChannelKind = profileType.getSupportedChannelKind();
if (supportedChannelKind != null && supportedChannelKind != channelType.getKind())
return false;

if (triggerProfileType.getSupportedChannelTypeUIDs().contains(channelType.getUID())) {
return true;
}
Collection<ChannelTypeUID> supportedChannelTypeUIDsOnProfileType = profileType.getSupportedChannelTypeUIDs();
if (!supportedChannelTypeUIDsOnProfileType.isEmpty()
&& !supportedChannelTypeUIDsOnProfileType.contains(channelType.getUID())) {
return false;
}
return false;
}

private boolean stateProfileMatchesProfileType(ProfileType profileType, ChannelType channelType) {
if (profileType instanceof StateProfileType stateProfileType) {
if (stateProfileType.getSupportedItemTypesOfChannel().isEmpty()) {
return true;
}

Collection<String> supportedItemTypesOfChannelOnProfileType = stateProfileType
.getSupportedItemTypesOfChannel();
Collection<String> supportedItemTypesOfChannelOnProfileType = profileType.getSupportedItemTypesOfChannel();
if (supportedItemTypesOfChannelOnProfileType.isEmpty()) {
return true;
} else {
String itemType = channelType.getItemType();
return itemType != null
&& supportedItemTypesOfChannelOnProfileType.contains(ItemUtil.getMainItemType(itemType));
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.openhab.core.io.rest.LocaleService;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.ThingUID;
Expand All @@ -58,13 +59,14 @@ public class ItemChannelLinkResourceTest {
private @Mock @NonNullByDefault({}) ProfileTypeRegistry profileTypeRegistryMock;
private @Mock @NonNullByDefault({}) ItemChannelLinkRegistry itemChannelLinkRegistryMock;
private @Mock @NonNullByDefault({}) ManagedItemChannelLinkProvider managedItemChannelLinkProviderMock;
private @Mock @NonNullByDefault({}) LocaleService localeServiceMock;
private @NonNullByDefault({}) ItemChannelLinkResource itemChannelLinkResource;

@BeforeEach
public void setup() {
itemChannelLinkResource = new ItemChannelLinkResource(itemRegistryMock, thingRegistryMock,
channelTypeRegistryMock, profileTypeRegistryMock, itemChannelLinkRegistryMock,
managedItemChannelLinkProviderMock);
managedItemChannelLinkProviderMock, localeServiceMock);
when(itemChannelLinkRegistryMock.removeLinksForItem(any())).thenReturn(EXPECTED_REMOVED_LINKS);
when(itemChannelLinkRegistryMock.removeLinksForThing(any())).thenReturn(EXPECTED_REMOVED_LINKS);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,23 @@
import org.openhab.core.config.core.ParameterOption;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.ItemUtil;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.link.ItemChannelLink;
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
import org.openhab.core.thing.profiles.ProfileType;
import org.openhab.core.thing.profiles.ProfileTypeRegistry;
import org.openhab.core.thing.profiles.StateProfileType;
import org.openhab.core.thing.profiles.TriggerProfileType;
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeRegistry;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Provider for framework config parameters on {@link ItemChannelLink}s.
Expand All @@ -51,20 +56,25 @@
@NonNullByDefault
public class ItemChannelLinkConfigDescriptionProvider implements ConfigDescriptionProvider {

private final Logger logger = LoggerFactory.getLogger(ItemChannelLinkConfigDescriptionProvider.class);

private static final String SCHEME = "link";
public static final String PARAM_PROFILE = "profile";

private final ProfileTypeRegistry profileTypeRegistry;
private final ChannelTypeRegistry channelTypeRegistry;
private final ItemChannelLinkRegistry itemChannelLinkRegistry;
private final ItemRegistry itemRegistry;
private final ThingRegistry thingRegistry;

@Activate
public ItemChannelLinkConfigDescriptionProvider(final @Reference ProfileTypeRegistry profileTypeRegistry, //
final @Reference ChannelTypeRegistry channelTypeRegistry, //
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
final @Reference ItemRegistry itemRegistry, //
final @Reference ThingRegistry thingRegistry) {
this.profileTypeRegistry = profileTypeRegistry;
this.channelTypeRegistry = channelTypeRegistry;
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
this.itemRegistry = itemRegistry;
this.thingRegistry = thingRegistry;
Expand Down Expand Up @@ -106,15 +116,7 @@ private List<ParameterOption> getOptions(ItemChannelLink link, Item item, Channe
@Nullable Locale locale) {
Collection<ProfileType> profileTypes = profileTypeRegistry.getProfileTypes(locale);
return profileTypes.stream().filter(profileType -> {
switch (channel.getKind()) {
case STATE:
return profileType instanceof StateProfileType && isSupportedItemType(profileType, item);
case TRIGGER:
return profileType instanceof TriggerProfileType tpt && isSupportedItemType(profileType, item)
&& isSupportedChannelType(tpt, channel);
default:
throw new IllegalArgumentException("Unknown channel kind: " + channel.getKind());
}
return isSupportedItemType(profileType, item) && isSupportedChannelType(profileType, channel, locale);
}).map(profileType -> new ParameterOption(profileType.getUID().toString(), profileType.getLabel())).toList();
}

Expand All @@ -123,8 +125,33 @@ private boolean isSupportedItemType(ProfileType profileType, Item item) {
|| profileType.getSupportedItemTypes().contains(item.getType());
}

private boolean isSupportedChannelType(TriggerProfileType profileType, Channel channel) {
return profileType.getSupportedChannelTypeUIDs().isEmpty()
|| profileType.getSupportedChannelTypeUIDs().contains(channel.getChannelTypeUID());
private boolean isSupportedChannelType(ProfileType profileType, Channel channel, @Nullable Locale locale) {
ChannelKind supportedChannelKind = profileType.getSupportedChannelKind();
if (supportedChannelKind != null && supportedChannelKind != channel.getKind())
return false;

ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();

Collection<ChannelTypeUID> supportedChannelTypeUIDsOnProfileType = profileType.getSupportedChannelTypeUIDs();
if (!supportedChannelTypeUIDsOnProfileType.isEmpty()
&& !supportedChannelTypeUIDsOnProfileType.contains(channelTypeUID)) {
return false;
J-N-K marked this conversation as resolved.
Show resolved Hide resolved
}

Collection<String> supportedItemTypesOfChannelOnProfileType = profileType.getSupportedItemTypesOfChannel();
if (supportedItemTypesOfChannelOnProfileType.isEmpty()) {
return true;
} else {
ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID, locale);
if (channelType == null) {
logger.error("Requested to filter against an unknown channel type: {} is not known to the registry",
channelTypeUID.toString());
return false;
}

String itemType = channelType.getItemType();
return itemType != null
&& supportedItemTypesOfChannelOnProfileType.contains(ItemUtil.getMainItemType(itemType));
}
}
}
Loading