Skip to content

[rest] Support sending Item command/state as JSON #4760

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

Merged
merged 2 commits into from
May 9, 2025
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.JsonObject;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand Down Expand Up @@ -172,6 +174,7 @@ private static void respectForwarded(final UriBuilder uriBuilder, final @Context
}

private final Logger logger = LoggerFactory.getLogger(ItemResource.class);
private final Gson gson = new Gson();

private final DTOMapper dtoMapper;
private final EventPublisher eventPublisher;
Expand Down Expand Up @@ -422,14 +425,29 @@ public Response getBinaryItemState(@HeaderParam("Accept") @Nullable String media
@RolesAllowed({ Role.USER, Role.ADMIN })
@Path("/{itemname: [a-zA-Z_0-9]+}/state")
@Consumes(MediaType.TEXT_PLAIN)
@Operation(operationId = "updateItemState", summary = "Updates the state of an item.", responses = {
@ApiResponse(responseCode = "202", description = "Accepted"),
@ApiResponse(responseCode = "404", description = "Item not found"),
@ApiResponse(responseCode = "400", description = "Item state null") })
public Response putItemState(
@Operation(operationId = "updateItemState", summary = "Updates the state of an item.", requestBody = @RequestBody(description = "Valid item state (e.g., ON, OFF) either as plain text or JSON", required = true, content = {
@Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", example = "ON")),
@Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(type = "string", example = "{ \"value\": \"ON\" }")) }), responses = {
@ApiResponse(responseCode = "202", description = "Accepted"),
@ApiResponse(responseCode = "404", description = "Item not found"),
@ApiResponse(responseCode = "400", description = "State cannot be parsed") })
public Response putItemStatePlain(
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
@PathParam("itemname") @Parameter(description = "item name") String itemname,
@Parameter(description = "valid item state (e.g. ON, OFF)", required = true) String value) {
return sendItemStateInternal(language, itemname, value);
}

@PUT
@RolesAllowed({ Role.USER, Role.ADMIN })
@Path("/{itemname: [a-zA-Z_0-9]+}/state")
@Consumes(MediaType.APPLICATION_JSON)
public Response putItemStateJson(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Nullable String language,
@PathParam("itemname") String itemname, ValueContainer valueContainer) {
return sendItemStateInternal(language, itemname, valueContainer.value());
}

private Response sendItemStateInternal(@Nullable String language, String itemname, String value) {
final Locale locale = localeService.getLocale(language);
final ZoneId zoneId = timeZoneProvider.getTimeZone();

Expand Down Expand Up @@ -459,12 +477,26 @@ public Response putItemState(
@RolesAllowed({ Role.USER, Role.ADMIN })
@Path("/{itemname: [a-zA-Z_0-9]+}")
@Consumes(MediaType.TEXT_PLAIN)
@Operation(operationId = "sendItemCommand", summary = "Sends a command to an item.", responses = {
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "Item not found"),
@ApiResponse(responseCode = "400", description = "Item command null") })
public Response postItemCommand(@PathParam("itemname") @Parameter(description = "item name") String itemname,
@Operation(operationId = "sendItemCommand", summary = "Sends a command to an item.", requestBody = @RequestBody(description = "Valid item command (e.g., ON, OFF) either as plain text or JSON", required = true, content = {
@Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", example = "ON")),
@Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(type = "string", example = "{ \"value\": \"ON\" }")) }), responses = {
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "Item not found"),
@ApiResponse(responseCode = "400", description = "Command cannot be parsed") })
public Response postItemCommandPlain(@PathParam("itemname") @Parameter(description = "item name") String itemname,
@Parameter(description = "valid item command (e.g. ON, OFF, UP, DOWN, REFRESH)", required = true) String value) {
return sendItemCommandInternal(itemname, value);
}

@POST
@RolesAllowed({ Role.USER, Role.ADMIN })
@Path("/{itemname: [a-zA-Z_0-9]+}")
@Consumes(MediaType.APPLICATION_JSON)
public Response postItemCommandJson(@PathParam("itemname") String itemname, ValueContainer valueContainer) {
return sendItemCommandInternal(itemname, valueContainer.value());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lolodomo this made me think of you. I know we decided not to do it, but isn't this what you were trying to do but having trouble to do it? i.e. trying to accept either plain text and json.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Professing both MIME types is relatively simple, the most difficult part is getting Swagger work.


private Response sendItemCommandInternal(String itemname, String value) {
Item item = getItem(itemname);
Command command = null;
if (item != null) {
Expand Down Expand Up @@ -1006,4 +1038,7 @@ private void addMetadata(EnrichedItemDTO dto, Set<String> namespaces, @Nullable
private boolean isEditable(String itemName) {
return managedItemProvider.get(itemName) != null;
}

private record ValueContainer(String value) {
}
}
Loading