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

[rest] add semantics query endpoint to item resource #2985

Merged
merged 2 commits into from
Oct 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions bundles/org.openhab.core.io.rest.core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,13 @@
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.test</artifactId>
<artifactId>org.openhab.core.semantics</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.test</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.semantics.SemanticTags;
import org.openhab.core.semantics.SemanticsPredicates;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.TypeParser;
Expand Down Expand Up @@ -776,6 +778,34 @@ public Response createOrUpdateItems(
return JSONResponse.createResponse(Status.OK, responseList, null);
}

@GET
@RolesAllowed({ Role.USER, Role.ADMIN })
@Path("/{itemName: \\w+}/semantic/{semanticClass: \\w+}")
@Operation(operationId = "getSemanticItem", summary = "Gets the item which defines the requested semantics of an item.", responses = {
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "Item not found") })
public Response getSemanticItem(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders,
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
@PathParam("itemName") @Parameter(description = "item name") String itemName,
@PathParam("semanticClass") @Parameter(description = "semantic class") String semanticClassName) {
Locale locale = localeService.getLocale(language);

Class<? extends org.openhab.core.semantics.Tag> semanticClass = SemanticTags.getById(semanticClassName);
if (semanticClass == null) {
return Response.status(Status.NOT_FOUND).build();
}

Item foundItem = findParentByTag(getItem(itemName), SemanticsPredicates.isA(semanticClass));
if (foundItem == null) {
return Response.status(Status.NOT_FOUND).build();
}

EnrichedItemDTO dto = EnrichedItemDTOMapper.map(foundItem, false, null, uriBuilder(uriInfo, httpHeaders),
locale);
dto.editable = isEditable(dto.name);
return JSONResponse.createResponse(Status.OK, dto, null);
}

private JsonObject buildStatusObject(String itemName, String status, @Nullable String message) {
JsonObject jo = new JsonObject();
jo.addProperty("name", itemName);
Expand All @@ -784,6 +814,20 @@ private JsonObject buildStatusObject(String itemName, String status, @Nullable S
return jo;
}

private @Nullable Item findParentByTag(@Nullable Item item, Predicate<Item> predicate) {
if (item == null) {
return null;
}

if (predicate.test(item)) {
return item;
}

// check parents
return item.getGroupNames().stream().map(this::getItem).map(i -> findParentByTag(i, predicate))
.filter(Objects::nonNull).findAny().orElse(null);
}

/**
* helper: Response to be sent to client if a Thing cannot be found
*
Expand Down
5 changes: 4 additions & 1 deletion itests/org.openhab.core.io.rest.core.tests/itest.bndrun
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,7 @@ Fragment-Host: org.openhab.core.io.rest.core
org.openhab.core.persistence;version='[3.4.0,3.4.1)',\
org.openhab.core.test;version='[3.4.0,3.4.1)',\
org.openhab.core.thing;version='[3.4.0,3.4.1)',\
org.openhab.core.transform;version='[3.4.0,3.4.1)'
org.openhab.core.transform;version='[3.4.0,3.4.1)',\
junit-jupiter-params;version='[5.8.1,5.8.2)',\
org.openhab.core.semantics;version='[3.4.0,3.4.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)'
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
Expand All @@ -35,9 +36,14 @@
import javax.ws.rs.core.UriInfo;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
Expand Down Expand Up @@ -333,4 +339,36 @@ public void testRemoveMetadataUnmanagedMetadata() {
Response response = itemResource.removeMetadata(ITEM_NAME1, "namespace");
assertEquals(409, response.getStatus());
}

@SuppressWarnings("unused")
private static Stream<Arguments> findTagTestSource() {
return Stream.of( //
Arguments.of(ITEM_NAME3, "Location", hasItems(ITEM_NAME1)), // super-class of set tag
Arguments.of(ITEM_NAME3, "HVAC", hasItems(ITEM_NAME2)), // tag
Arguments.of(ITEM_NAME3, "Point", hasItems(ITEM_NAME3)), // self
Arguments.of(ITEM_NAME3, "NotATag", null), // invalid tag
Arguments.of(ITEM_NAME3, "Outdoor", null) // valid tag, not present
);
}

@ParameterizedTest
@MethodSource("findTagTestSource")
public void findTagTest(String itemName, String semanticClassName, @Nullable Matcher<Iterable> matcher)
throws IOException {
// setup test: item1 has the location, item2 the equipment, item3 is the point
item1.addTag("Office");
item2.addTag("HVAC");
item2.addGroupName(ITEM_NAME1);
item3.addTag("Point");
item3.addGroupName(ITEM_NAME2);

// do test
Response response = itemResource.getSemanticItem(uriInfoMock, httpHeadersMock, null, itemName,
semanticClassName);
if (matcher != null) {
assertThat(readItemNamesFromResponse(response), matcher);
} else {
assertThat(response.getStatus(), is(404));
}
}
}