Skip to content

Commit

Permalink
Updated APIs for MainUI code tab (thing)
Browse files Browse the repository at this point in the history
Signed-off-by: Laurent Garnier <[email protected]>
  • Loading branch information
lolodomo committed Feb 8, 2025
1 parent d6b9a33 commit 9f78dad
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
* @author Wouter Born - Migrated to OpenAPI annotations
* @author Laurent Garnier - Added optional parameter newThingId to approve API
* @author Laurent Garnier - Added API to generate syntax
* @author Laurent Garnier - Added API to generate syntax for inbox entry
*/
@Component(service = { RESTResource.class, InboxResource.class })
@JaxrsResource
Expand Down Expand Up @@ -222,7 +222,7 @@ public Response unignore(@PathParam("thingUID") @Parameter(description = "thingU
@Produces(MediaType.TEXT_PLAIN)
@Operation(operationId = "generateSyntaxForDiscoveryResult", summary = "Generate syntax for the thing associated to the discovery result.", responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "400", description = "Unsupported syntax format."),
@ApiResponse(responseCode = "400", description = "Unsupported syntax generator."),
@ApiResponse(responseCode = "404", description = "Discovery result not found in the inbox or thing type not found.") })
public Response generateSyntaxForDiscoveryResult(
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
Expand All @@ -231,7 +231,7 @@ public Response generateSyntaxForDiscoveryResult(
@DefaultValue("true") @QueryParam("hideDefaultParameters") @Parameter(description = "hide the configuration parameters having the default value") boolean hideDefaultParameters) {
ThingSyntaxGenerator generator = thingSyntaxGenerators.get(format);
if (generator == null) {
String message = "No syntax available for format " + format + "!";
String message = "No syntax generator available for format " + format + "!";
return Response.status(Response.Status.BAD_REQUEST).entity(message).build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
* @author Stefan Triller - Added bulk item add method
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
* @author Wouter Born - Migrated to OpenAPI annotations
* @author Laurent Garnier - Added API to generate syntax
* @author Laurent Garnier - Added API to generate syntax for item
*/
@Component
@JaxrsResource
Expand Down Expand Up @@ -928,14 +928,14 @@ public Response getSemanticItem(final @Context UriInfo uriInfo, final @Context H
@Operation(operationId = "generateSyntaxForAllItems", summary = "Generate syntax for all items.", security = {
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "400", description = "Unsupported syntax format.") })
@ApiResponse(responseCode = "400", description = "Unsupported syntax generator.") })
public Response generateSyntaxForAllItems(
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
@DefaultValue("DSL") @QueryParam("format") @Parameter(description = "syntax format") String format,
@DefaultValue("true") @QueryParam("hideDefaultParameters") @Parameter(description = "hide the configuration parameters having the default value") boolean hideDefaultParameters) {
ItemSyntaxGenerator generator = itemSyntaxGenerators.get(format);
if (generator == null) {
String message = "No syntax available for format " + format + "!";
String message = "No syntax generator available for format " + format + "!";
return Response.status(Response.Status.BAD_REQUEST).entity(message).build();
}
return Response.ok(generator.generateSyntax(sortItems(itemRegistry.getAll()), itemChannelLinkRegistry.getAll(),
Expand All @@ -949,7 +949,7 @@ public Response generateSyntaxForAllItems(
@Operation(operationId = "generateSyntaxForItem", summary = "Generate syntax for an item.", security = {
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "400", description = "Unsupported syntax format."),
@ApiResponse(responseCode = "400", description = "Unsupported syntax generator."),
@ApiResponse(responseCode = "404", description = "Item not found.") })
public Response generateSyntaxForItem(
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
Expand All @@ -958,7 +958,7 @@ public Response generateSyntaxForItem(
@DefaultValue("true") @QueryParam("hideDefaultParameters") @Parameter(description = "hide the configuration parameters having the default value") boolean hideDefaultParameters) {
ItemSyntaxGenerator generator = itemSyntaxGenerators.get(format);
if (generator == null) {
String message = "No syntax available for format " + format + "!";
String message = "No syntax generator available for format " + format + "!";
return Response.status(Response.Status.BAD_REQUEST).entity(message).build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
import org.openhab.core.thing.link.ManagedItemChannelLinkProvider;
import org.openhab.core.thing.syntax.ThingSyntaxGenerator;
import org.openhab.core.thing.syntax.ThingSyntaxParser;
import org.openhab.core.thing.type.BridgeType;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeRegistry;
Expand Down Expand Up @@ -146,7 +147,7 @@
* @author Dimitar Ivanov - replaced Firmware UID with thing UID and firmware version
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
* @author Wouter Born - Migrated to OpenAPI annotations
* @author Laurent Garnier - Added API to generate syntax
* @author Laurent Garnier - Added API to parse and generate syntax for thing
*/
@Component
@JaxrsResource
Expand Down Expand Up @@ -179,6 +180,7 @@ public class ThingResource implements RESTResource {
private final RegistryChangedRunnableListener<Thing> resetLastModifiedChangeListener = new RegistryChangedRunnableListener<>(
() -> lastModified = null);
private final Map<String, ThingSyntaxGenerator> thingSyntaxGenerators = new ConcurrentHashMap<>();
private final Map<String, ThingSyntaxParser> thingSyntaxParsers = new ConcurrentHashMap<>();

private @Context @NonNullByDefault({}) UriInfo uriInfo;
private @Nullable Date lastModified = null;
Expand Down Expand Up @@ -232,6 +234,15 @@ protected void removeThingSyntaxGenerator(ThingSyntaxGenerator thingSyntaxGenera
thingSyntaxGenerators.remove(thingSyntaxGenerator.getGeneratorFormat());
}

@Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MULTIPLE)
protected void addThingSyntaxParser(ThingSyntaxParser thingSyntaxParser) {
thingSyntaxParsers.put(thingSyntaxParser.getParserFormat(), thingSyntaxParser);
}

protected void removeThingSyntaxParser(ThingSyntaxParser thingSyntaxParser) {
thingSyntaxParsers.remove(thingSyntaxParser.getParserFormat());
}

/**
* create a new Thing
*
Expand Down Expand Up @@ -744,36 +755,79 @@ public Response getFirmwares(@PathParam("thingUID") @Parameter(description = "th
@Operation(operationId = "generateSyntaxForAllThings", summary = "Generate syntax for all things.", security = {
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "400", description = "Unsupported syntax format.") })
@ApiResponse(responseCode = "400", description = "Unsupported syntax generator.") })
public Response generateSyntaxForAllThings(
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
@DefaultValue("DSL") @QueryParam("format") @Parameter(description = "syntax format") String format,
@DefaultValue("true") @QueryParam("hideDefaultParameters") @Parameter(description = "hide the configuration parameters having the default value") boolean hideDefaultParameters) {
ThingSyntaxGenerator generator = thingSyntaxGenerators.get(format);
if (generator == null) {
String message = "No syntax available for format " + format + "!";
String message = "No syntax generator available for format " + format + "!";
return Response.status(Response.Status.BAD_REQUEST).entity(message).build();
}
return Response.ok(generator.generateSyntax(sortThings(thingRegistry.getAll()), hideDefaultParameters)).build();
}

@GET
@POST
@RolesAllowed({ Role.ADMIN })
@Path("/{thingUID}/syntax/parse")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.APPLICATION_JSON)
@Operation(operationId = "parseSyntaxForThing", summary = "Parse syntax for a thing.", security = {
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = EnrichedThingDTO.class))),
@ApiResponse(responseCode = "400", description = "Unsupported syntax parser."),
@ApiResponse(responseCode = "400", description = "Invalid syntax.") })
public Response parseSyntaxForThing(
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
@PathParam("thingUID") @Parameter(description = "thingUID") String thingUID,
@DefaultValue("DSL") @QueryParam("format") @Parameter(description = "syntax format") String format,
@Parameter(description = "thing syntax", required = true) String syntax) {
final Locale locale = localeService.getLocale(language);

ThingSyntaxParser parser = thingSyntaxParsers.get(format);
if (parser == null) {
String message = "No syntax parser available for format " + format + "!";
return Response.status(Response.Status.BAD_REQUEST).entity(message).build();
}

Collection<Thing> things = parser.parseSyntax(syntax);
if (things.size() != 1) {
String message = things.size() == 0 ? "Invalid syntax!"
: "Only one thing expected while several are provided!";
return Response.status(Response.Status.BAD_REQUEST).entity(message).build();
}

Thing thing = things.iterator().next();
String uid = thing.getUID().getAsString();
if (!uid.equals(thingUID)) {
String message = "UID " + uid + " in the parsed syntax is not consistent with UID " + thingUID
+ " in the API URL!";
return Response.status(Response.Status.BAD_REQUEST).entity(message).build();
}

return getThingResponse(Status.OK, thing, locale, null);
}

@POST
@RolesAllowed({ Role.ADMIN })
@Path("/{thingUID}/syntax/generate")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.TEXT_PLAIN)
@Operation(operationId = "generateSyntaxForThing", summary = "Generate syntax for a thing.", security = {
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "400", description = "Unsupported syntax format."),
@ApiResponse(responseCode = "400", description = "Unsupported syntax generator."),
@ApiResponse(responseCode = "404", description = "Thing not found.") })
public Response generateSyntaxForThing(
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
@PathParam("thingUID") @Parameter(description = "thingUID") String thingUID,
@DefaultValue("DSL") @QueryParam("format") @Parameter(description = "syntax format") String format,
@DefaultValue("true") @QueryParam("hideDefaultParameters") @Parameter(description = "hide the configuration parameters having the default value") boolean hideDefaultParameters) {
@DefaultValue("true") @QueryParam("hideDefaultParameters") @Parameter(description = "hide the configuration parameters having the default value") boolean hideDefaultParameters,
@Parameter(description = "thing data", required = false) @Nullable ThingDTO thingBean) {
ThingSyntaxGenerator generator = thingSyntaxGenerators.get(format);
if (generator == null) {
String message = "No syntax available for format " + format + "!";
String message = "No syntax generator available for format " + format + "!";
return Response.status(Response.Status.BAD_REQUEST).entity(message).build();
}

Expand All @@ -784,6 +838,21 @@ public Response generateSyntaxForThing(
return Response.status(Response.Status.NOT_FOUND).entity(message).build();
}

if (thingBean != null) {
String uid = thingBean.UID;
if (uid != null && !uid.isEmpty() && !uid.equals(thingUID)) {
String message = "UID " + uid + " in the thing data is not consistent with UID " + thingUID
+ " in the API URL!";
return Response.status(Response.Status.BAD_REQUEST).entity(message).build();
}

thingBean.configuration = normalizeConfiguration(thingBean.configuration, thing.getThingTypeUID(),
thing.getUID());
normalizeChannels(thingBean, thing.getUID());

thing = ThingHelper.merge(thing, thingBean);
}

return Response.ok(generator.generateSyntax(List.of(thing), hideDefaultParameters)).build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* come from.
*
* @author Kai Kreuzer - Initial contribution
* @author Laurent Garnier - Added method generateSyntaxFromModelContent
* @author Laurent Garnier - Added methods addStandaloneModel, removeStandaloneModel and generateSyntaxFromModel
*/
@NonNullByDefault
public interface ModelRepository {
Expand Down Expand Up @@ -94,6 +94,25 @@ public interface ModelRepository {
*/
void removeModelRepositoryChangeListener(ModelRepositoryChangeListener listener);

/**
* Adds a standalone model to the repository
* A standalone model will be loaded without triggering any listener.
*
* @param modelType the model type
* @param inputStream an input stream with the model's content
* @return the created model name if it was successfully processed, null otherwise
*/
@Nullable
String addStandaloneModel(String modelType, InputStream inputStream);

/**
* Removes a standalone model from the repository
*
* @param name the name of the model to remove
* @return true, if model was removed, false, if it did not exist
*/
boolean removeStandaloneModel(String name);

/**
* Generate the syntax from a provided model content.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
* @author Kai Kreuzer - Initial contribution
* @author Oliver Libutzki - Added reloadAllModelsOfType method
* @author Simon Kaufmann - added validation of models before loading them
* @author Laurent Garnier - Added method generateSyntaxFromModelContent
* @author Laurent Garnier - Added methods addStandaloneModel, removeStandaloneModel and generateSyntaxFromModel
*/
@Component(immediate = true)
@NonNullByDefault
Expand All @@ -66,6 +66,8 @@ public class ModelRepositoryImpl implements ModelRepository {

private final SafeEMF safeEmf;

private int standaloneCounter;

@Activate
public ModelRepositoryImpl(final @Reference SafeEMF safeEmf) {
this.safeEmf = safeEmf;
Expand Down Expand Up @@ -98,6 +100,10 @@ public ModelRepositoryImpl(final @Reference SafeEMF safeEmf) {

@Override
public boolean addOrRefreshModel(String name, final InputStream originalInputStream) {
return addOrRefreshModel(name, originalInputStream, false);
}

public boolean addOrRefreshModel(String name, final InputStream originalInputStream, boolean standalone) {
logger.info("Loading model '{}'", name);
Resource resource = null;
byte[] bytes;
Expand Down Expand Up @@ -126,7 +132,9 @@ public boolean addOrRefreshModel(String name, final InputStream originalInputStr
resource = resourceSet.createResource(URI.createURI(name));
if (resource != null) {
resource.load(inputStream, resourceOptions);
notifyListeners(name, EventType.ADDED);
if (!standalone) {
notifyListeners(name, EventType.ADDED);
}
return true;
} else {
logger.warn("Ignoring file '{}' as we do not have a parser for it.", name);
Expand All @@ -137,7 +145,9 @@ public boolean addOrRefreshModel(String name, final InputStream originalInputStr
synchronized (resourceSet) {
resource.unload();
resource.load(inputStream, resourceOptions);
notifyListeners(name, EventType.MODIFIED);
if (!standalone) {
notifyListeners(name, EventType.MODIFIED);
}
return true;
}
}
Expand All @@ -152,11 +162,17 @@ public boolean addOrRefreshModel(String name, final InputStream originalInputStr

@Override
public boolean removeModel(String name) {
return removeModel(name, false);
}

private boolean removeModel(String name, boolean standalone) {
Resource resource = getResource(name);
if (resource != null) {
synchronized (resourceSet) {
// do not physically delete it, but remove it from the resource set
notifyListeners(name, EventType.REMOVED);
if (!standalone) {
notifyListeners(name, EventType.REMOVED);
}
resourceSet.getResources().remove(resource);
return true;
}
Expand Down Expand Up @@ -229,6 +245,17 @@ public void removeModelRepositoryChangeListener(ModelRepositoryChangeListener li
listeners.remove(listener);
}

@Override
public @Nullable String addStandaloneModel(String modelType, InputStream inputStream) {
String name = "standalone_%d.%s".formatted(++standaloneCounter, modelType);
return addOrRefreshModel(name, inputStream, true) ? name : null;
}

@Override
public boolean removeStandaloneModel(String name) {
return removeModel(name, true);
}

@Override
public String generateSyntaxFromModel(String modelType, EObject modelContent) {
String result = "";
Expand Down
Loading

0 comments on commit 9f78dad

Please sign in to comment.