mirror of
https://github.com/danieldemus/openhab-core.git
synced 2026-07-29 12:34:22 +02:00
Migrate to JAX-RS Whiteboard (#1443)
* Migrate to JAX-RS Whiteboard * Upgrade Jackson and Swagger TP feature dependencies The upstream JAX-RS Whiteboard feature is not used because the version ranges currently do not work with Karaf on Windows. See: https://issues.apache.org/jira/browse/KARAF-6536 * Update Felix logback to 1.0.2 This version adds the jul-to-slf4j bridge (FELIX-6027). JUL is used by CXF and using the bridge we can use logback to reduce the verbosity of the info logging whenever org.apache.cxf.endpoint.ServerImpl creates endpoints. * Update Aries Whiteboard to 1.0.8 * Removes the hated default web application (ARIES-1931) * Improves performance * Uses CXF 3.2.12 * Don't reuse SSE event builders SSE event builders are not thread-safe. It also causes the wrong default values being used. Also-by: Markus Rathgeb <maggu2810@gmail.com> Signed-off-by: Wouter Born <github@maindrain.net>
This commit is contained in:
+13
-18
@@ -88,14 +88,6 @@
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EclipseSource JAX RS -->
|
||||
<dependency>
|
||||
<groupId>com.eclipsesource.jaxrs</groupId>
|
||||
<artifactId>publisher</artifactId>
|
||||
<version>5.3.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Gson -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.orbit.bundles</groupId>
|
||||
@@ -236,11 +228,17 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Servlet API -->
|
||||
<!-- Servlet and JAX-RS API -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>org.apache.felix.http.servlet-api</artifactId>
|
||||
<version>1.1.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.aries.spec</groupId>
|
||||
<artifactId>org.apache.aries.javax.jax.rs-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -251,13 +249,10 @@
|
||||
<version>1.5.5</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- WR RS API -->
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
<version>2.0.1</version>
|
||||
<scope>compile</scope>
|
||||
<groupId>de.maggu2810.jaxrswb.bundles</groupId>
|
||||
<artifactId>jaxrswb-swagger1-gen</artifactId>
|
||||
<version>0.0.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- XStream -->
|
||||
|
||||
@@ -238,12 +238,6 @@
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.rest.optimize</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.rest.sitemap</artifactId>
|
||||
@@ -256,6 +250,12 @@
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.rest.swagger</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.rest.ui</artifactId>
|
||||
|
||||
+126
-12
@@ -16,8 +16,10 @@
|
||||
<name>openHAB Core :: BOM :: Runtime</name>
|
||||
|
||||
<properties>
|
||||
<pax.web.version>7.2.11</pax.web.version>
|
||||
<jackson.version>2.10.3</jackson.version>
|
||||
<jetty.version>9.4.20.v20190813</jetty.version>
|
||||
<pax.web.version>7.2.11</pax.web.version>
|
||||
<swagger.version>1.6.1</swagger.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -113,8 +115,18 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.aries.jax.rs</groupId>
|
||||
<artifactId>org.apache.aries.jax.rs.whiteboard</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<version>1.0.8</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.geronimo.specs</groupId>
|
||||
<artifactId>geronimo-annotation_1.3_spec</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.geronimo.specs</groupId>
|
||||
<artifactId>geronimo-jaxrs_2.1_spec</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- JAX-RS 2.1 API with contract -->
|
||||
@@ -138,7 +150,7 @@
|
||||
<version>2.2.11_1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Java-Activation 1.1 API -->
|
||||
<!-- Activation 1.1 API -->
|
||||
<dependency>
|
||||
<groupId>org.apache.servicemix.specs</groupId>
|
||||
<artifactId>org.apache.servicemix.specs.activation-api-1.1</artifactId>
|
||||
@@ -146,6 +158,13 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Annotation API -->
|
||||
<dependency>
|
||||
<groupId>org.apache.servicemix.specs</groupId>
|
||||
<artifactId>org.apache.servicemix.specs.annotation-api-1.3</artifactId>
|
||||
<version>1.3_1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Stax 1.2 API -->
|
||||
<dependency>
|
||||
<groupId>org.apache.servicemix.specs</groupId>
|
||||
@@ -256,6 +275,13 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- OSGi ServiceLoader -->
|
||||
<dependency>
|
||||
<groupId>org.apache.aries.spifly</groupId>
|
||||
<artifactId>org.apache.aries.spifly.dynamic.bundle</artifactId>
|
||||
<version>1.2.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Several implementations need to log using SLF4J -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
@@ -342,7 +368,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>org.apache.felix.logback</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.0.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- END: logging -->
|
||||
@@ -868,14 +894,6 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- ... -->
|
||||
<dependency>
|
||||
<groupId>com.eclipsesource.jaxrs</groupId>
|
||||
<artifactId>publisher</artifactId>
|
||||
<version>5.3.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- jollyday -->
|
||||
<dependency>
|
||||
<groupId>de.jollyday</groupId>
|
||||
@@ -894,6 +912,102 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-joda</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.jaxrs</groupId>
|
||||
<artifactId>jackson-jaxrs-base</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.jaxrs</groupId>
|
||||
<artifactId>jackson-jaxrs-json-provider</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.module</groupId>
|
||||
<artifactId>jackson-module-jaxb-annotations</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Swagger -->
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-core</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-jaxrs</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-models</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JAX-RS Whiteboard Swagger 1 generator -->
|
||||
<dependency>
|
||||
<groupId>de.maggu2810.jaxrswb.bundles</groupId>
|
||||
<artifactId>jaxrswb-swagger1-gen</artifactId>
|
||||
<version>0.0.4</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.maggu2810.thirdparty.modified.org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
<version>0.9.10.v20160429-1435</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -41,6 +41,21 @@
|
||||
<artifactId>json-path</artifactId>
|
||||
<version>2.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.aries.jax.rs</groupId>
|
||||
<artifactId>org.apache.aries.jax.rs.whiteboard</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.geronimo.specs</groupId>
|
||||
<artifactId>geronimo-annotation_1.3_spec</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.geronimo.specs</groupId>
|
||||
<artifactId>geronimo-jaxrs_2.1_spec</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
+17
-3
@@ -43,11 +43,17 @@ import org.openhab.core.automation.type.ModuleType;
|
||||
import org.openhab.core.automation.type.ModuleTypeRegistry;
|
||||
import org.openhab.core.automation.type.TriggerType;
|
||||
import org.openhab.core.io.rest.LocaleService;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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 io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -61,13 +67,21 @@ import io.swagger.annotations.ApiResponses;
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Markus Rathgeb - Use DTOs
|
||||
* @author Ana Dimova - extends Module type DTOs with composites
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Path("module-types")
|
||||
@Api("module-types")
|
||||
@NonNullByDefault
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(ModuleTypeResource.PATH_MODULE_TYPES)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(ModuleTypeResource.PATH_MODULE_TYPES)
|
||||
@Api(ModuleTypeResource.PATH_MODULE_TYPES)
|
||||
@NonNullByDefault
|
||||
public class ModuleTypeResource implements RESTResource {
|
||||
|
||||
/** The URI path to this resource */
|
||||
public static final String PATH_MODULE_TYPES = "module-types";
|
||||
|
||||
private @NonNullByDefault({}) ModuleTypeRegistry moduleTypeRegistry;
|
||||
private @NonNullByDefault({}) LocaleService localeService;
|
||||
|
||||
|
||||
+16
-2
@@ -62,11 +62,17 @@ import org.openhab.core.automation.util.RuleBuilder;
|
||||
import org.openhab.core.config.core.ConfigUtil;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.io.rest.JSONResponse;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -82,13 +88,21 @@ import io.swagger.annotations.ResponseHeader;
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Markus Rathgeb - Use DTOs
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Path("rules")
|
||||
@Api("rules")
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(RuleResource.PATH_RULES)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(RuleResource.PATH_RULES)
|
||||
@Api(RuleResource.PATH_RULES)
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
public class RuleResource implements RESTResource {
|
||||
|
||||
/** The URI path to this resource */
|
||||
public static final String PATH_RULES = "rules";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RuleResource.class);
|
||||
|
||||
private RuleRegistry ruleRegistry;
|
||||
|
||||
+17
-3
@@ -35,11 +35,17 @@ import org.openhab.core.automation.template.RuleTemplate;
|
||||
import org.openhab.core.automation.template.Template;
|
||||
import org.openhab.core.automation.template.TemplateRegistry;
|
||||
import org.openhab.core.io.rest.LocaleService;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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 io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -51,13 +57,21 @@ import io.swagger.annotations.ApiResponses;
|
||||
* This class acts as a REST resource for templates and is registered with the Jersey servlet.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Path("templates")
|
||||
@Api("templates")
|
||||
@NonNullByDefault
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(TemplateResource.PATH_TEMPLATES)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(TemplateResource.PATH_TEMPLATES)
|
||||
@Api(TemplateResource.PATH_TEMPLATES)
|
||||
@NonNullByDefault
|
||||
public class TemplateResource implements RESTResource {
|
||||
|
||||
/** The URI path to this resource */
|
||||
public static final String PATH_TEMPLATES = "templates";
|
||||
|
||||
private @NonNullByDefault({}) TemplateRegistry<@NonNull RuleTemplate> templateRegistry;
|
||||
private @NonNullByDefault({}) LocaleService localeService;
|
||||
|
||||
|
||||
+10
-1
@@ -21,8 +21,13 @@ import javax.ws.rs.core.Response;
|
||||
|
||||
import org.openhab.core.auth.Role;
|
||||
import org.openhab.core.id.InstanceUUID;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
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 io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -33,10 +38,14 @@ import io.swagger.annotations.ApiResponses;
|
||||
* This class acts as a REST resource for accessing the UUID of the instance
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(UUIDResource.PATH_UUID)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@Path(UUIDResource.PATH_UUID)
|
||||
@Api(value = UUIDResource.PATH_UUID)
|
||||
@Api(UUIDResource.PATH_UUID)
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
public class UUIDResource implements RESTResource {
|
||||
|
||||
|
||||
+14
-3
@@ -35,11 +35,17 @@ import org.openhab.core.audio.AudioSource;
|
||||
import org.openhab.core.auth.Role;
|
||||
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.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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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 io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -51,15 +57,20 @@ import io.swagger.annotations.ApiResponses;
|
||||
* This class acts as a REST resource for audio features.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component
|
||||
@Path(AudioResource.PATH_SITEMAPS)
|
||||
@JaxrsResource
|
||||
@JaxrsName(AudioResource.PATH_AUDIO)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(AudioResource.PATH_AUDIO)
|
||||
@RolesAllowed({ Role.USER, Role.ADMIN })
|
||||
@Api(value = AudioResource.PATH_SITEMAPS)
|
||||
@Api(AudioResource.PATH_AUDIO)
|
||||
@NonNullByDefault
|
||||
public class AudioResource implements RESTResource {
|
||||
|
||||
static final String PATH_SITEMAPS = "audio";
|
||||
static final String PATH_AUDIO = "audio";
|
||||
|
||||
private @Nullable AudioManager audioManager;
|
||||
private @Nullable LocaleService localeService;
|
||||
|
||||
@@ -6,6 +6,13 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
@@ -16,12 +23,5 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ import org.osgi.framework.ServiceRegistration;
|
||||
public class Activator implements BundleActivator {
|
||||
private static Activator instance;
|
||||
|
||||
private ServiceRegistration rolesAllowedDynamicFeatureRegistration;
|
||||
private ServiceRegistration<?> rolesAllowedDynamicFeatureRegistration;
|
||||
|
||||
public static Activator getInstance() {
|
||||
return instance;
|
||||
|
||||
+2
-3
@@ -14,6 +14,7 @@ package org.openhab.core.io.rest.auth.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -31,8 +32,6 @@ import javax.ws.rs.container.ResourceInfo;
|
||||
import javax.ws.rs.core.FeatureContext;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
|
||||
import org.glassfish.jersey.server.model.AnnotatedMethod;
|
||||
import org.openhab.core.auth.Role;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -54,7 +53,7 @@ public class RolesAllowedDynamicFeatureImpl implements DynamicFeature {
|
||||
|
||||
@Override
|
||||
public void configure(ResourceInfo resourceInfo, FeatureContext configuration) {
|
||||
final AnnotatedMethod am = new AnnotatedMethod(resourceInfo.getResourceMethod());
|
||||
final Method am = resourceInfo.getResourceMethod();
|
||||
try {
|
||||
// DenyAll on the method take precedence over RolesAllowed and PermitAll
|
||||
if (am.isAnnotationPresent(DenyAll.class)) {
|
||||
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* 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.io.rest.auth.internal;
|
||||
|
||||
import java.util.Dictionary;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.http.HttpContext;
|
||||
import org.osgi.service.http.HttpService;
|
||||
|
||||
import com.eclipsesource.jaxrs.publisher.ServletConfiguration;
|
||||
|
||||
/**
|
||||
* Custom servlet configuration for jaxrs handler.
|
||||
*
|
||||
* This extension doesn't do much, just forces usage of http context created somewhere else.
|
||||
*
|
||||
* @author Łukasz Dywicki - Initial contribution
|
||||
*/
|
||||
@Component
|
||||
public class SmartHomeRestServletConfig implements ServletConfiguration {
|
||||
|
||||
private HttpContext httpContext;
|
||||
|
||||
@Override
|
||||
public HttpContext getHttpContext(HttpService httpService, String rootPath) {
|
||||
return httpContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dictionary<String, String> getInitParams(HttpService httpService, String rootPath) {
|
||||
return new Hashtable<>();
|
||||
}
|
||||
|
||||
@Reference
|
||||
public void setHttpContext(HttpContext httpContext) {
|
||||
this.httpContext = httpContext;
|
||||
}
|
||||
|
||||
public void unsetHttpContext(HttpContext httpContext) {
|
||||
this.httpContext = null;
|
||||
}
|
||||
}
|
||||
+13
-2
@@ -46,12 +46,18 @@ import org.openhab.core.auth.PendingToken;
|
||||
import org.openhab.core.auth.User;
|
||||
import org.openhab.core.auth.UserRegistry;
|
||||
import org.openhab.core.auth.UserSession;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
import org.openhab.core.io.rest.Stream2JSONInputStream;
|
||||
import org.openhab.core.io.rest.auth.internal.TokenEndpointException.ErrorType;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -64,10 +70,15 @@ import io.swagger.annotations.ApiResponses;
|
||||
* This class is used to issue JWT tokens to clients.
|
||||
*
|
||||
* @author Yannick Schaus - Initial contribution
|
||||
* @author Wouter Born - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Path(TokenResource.PATH_AUTH)
|
||||
@Api(value = TokenResource.PATH_AUTH)
|
||||
@Component(service = { RESTResource.class, TokenResource.class })
|
||||
@JaxrsResource
|
||||
@JaxrsName(TokenResource.PATH_AUTH)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(TokenResource.PATH_AUTH)
|
||||
@Api(TokenResource.PATH_AUTH)
|
||||
public class TokenResource implements RESTResource {
|
||||
private final Logger logger = LoggerFactory.getLogger(TokenResource.class);
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
Bundle-SymbolicName: ${project.artifactId}
|
||||
Bundle-Activator: org.openhab.core.io.rest.core.internal.RESTCoreActivator
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.io.rest.core.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.ext.MessageBodyReader;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* A message body reader for JSON using GSON.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
public class GsonMessageBodyReader<T> implements MessageBodyReader<T> {
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param gson the GSON object to use
|
||||
*/
|
||||
public GsonMessageBodyReader(final Gson gson) {
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T readFrom(final Class<T> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType, final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
|
||||
throws IOException, WebApplicationException {
|
||||
try (InputStreamReader reader = new InputStreamReader(entityStream, StandardCharsets.UTF_8)) {
|
||||
return gson.fromJson(reader, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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.io.rest.core.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||
import javax.ws.rs.ext.MessageBodyWriter;
|
||||
|
||||
import org.openhab.core.io.rest.JSONInputStream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* A message body writer for JSON using GSON.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
public class GsonMessageBodyWriter<T> implements MessageBodyWriter<T> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(GsonMessageBodyWriter.class);
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param gson the GSON object to use
|
||||
*/
|
||||
public GsonMessageBodyWriter(final Gson gson) {
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize(final T object, final Class<?> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(final T object, final Class<?> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
|
||||
final OutputStream entityStream) throws IOException, WebApplicationException {
|
||||
if (object == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Log a message if a response builder is received.
|
||||
if (object instanceof ResponseBuilder) {
|
||||
logger.error(
|
||||
"A REST endpoint returns a ResponseBuilder object. This is mostly wrong and the call to \"build()\" is missing. Please report or fix it. Got: {}",
|
||||
object);
|
||||
}
|
||||
|
||||
if (object instanceof InputStream && object instanceof JSONInputStream) {
|
||||
((InputStream) object).transferTo(entityStream);
|
||||
} else {
|
||||
entityStream.write(gson.toJson(object).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
// Flush the stream.
|
||||
// Keep this code as it has been present before,
|
||||
// but I don't think this needs to be done in the message body writer itself.
|
||||
entityStream.flush();
|
||||
}
|
||||
}
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
/**
|
||||
* 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.io.rest.core.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.ext.MessageBodyReader;
|
||||
import javax.ws.rs.ext.MessageBodyWriter;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* JSON reader/writer for Jersey using GSON.
|
||||
*
|
||||
* @author Simon Kaufmann - Initial contribution
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
@Provider
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Component(immediate = true)
|
||||
public class GsonProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
public GsonProvider() {
|
||||
gson = new GsonBuilder().create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize(T t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(T object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
|
||||
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
|
||||
throws IOException, WebApplicationException {
|
||||
try (OutputStream stream = entityStream) {
|
||||
entityStream.write(gson.toJson(object).getBytes(StandardCharsets.UTF_8));
|
||||
entityStream.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T readFrom(Class<T> type, Type genericType, Annotation[] annotations, MediaType mediaType,
|
||||
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
|
||||
throws IOException, WebApplicationException {
|
||||
try (InputStreamReader reader = new InputStreamReader(entityStream, StandardCharsets.UTF_8)) {
|
||||
return gson.fromJson(reader, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.io.rest.core.internal;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.io.rest.JSONResponse;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsExtension;
|
||||
|
||||
/**
|
||||
* Trap exceptions.
|
||||
*
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component
|
||||
@JaxrsExtension
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@NonNullByDefault
|
||||
public class JSONResponseExceptionMapper implements ExceptionMapper<@NonNull Exception> {
|
||||
|
||||
private final ExceptionMapper<Exception> delegate = new JSONResponse.ExceptionMapper();
|
||||
|
||||
@Override
|
||||
public Response toResponse(Exception e) {
|
||||
return delegate.toResponse(e);
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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.io.rest.core.internal;
|
||||
|
||||
import static org.osgi.service.component.annotations.ServiceScope.PROTOTYPE;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.InternalServerErrorException;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.ext.MessageBodyReader;
|
||||
import javax.ws.rs.ext.MessageBodyWriter;
|
||||
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsExtension;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsMediaType;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* A media type extension for all supported media types.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
@Component(scope = PROTOTYPE)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JaxrsExtension
|
||||
@JaxrsMediaType({ MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN })
|
||||
public class MediaTypeExtension<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {
|
||||
|
||||
private static String mediaTypeWithoutParams(final MediaType mediaType) {
|
||||
return mediaType.getType() + "/" + mediaType.getSubtype();
|
||||
}
|
||||
|
||||
private final Map<String, MessageBodyReader<T>> readers = new HashMap<>();
|
||||
private final Map<String, MessageBodyWriter<T>> writers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public MediaTypeExtension() {
|
||||
final Gson gson = new GsonBuilder().create();
|
||||
readers.put(mediaTypeWithoutParams(MediaType.APPLICATION_JSON_TYPE), new GsonMessageBodyReader<>(gson));
|
||||
readers.put(mediaTypeWithoutParams(MediaType.TEXT_PLAIN_TYPE), new PlainMessageBodyReader<>());
|
||||
writers.put(mediaTypeWithoutParams(MediaType.APPLICATION_JSON_TYPE), new GsonMessageBodyWriter<>(gson));
|
||||
writers.put(mediaTypeWithoutParams(MediaType.TEXT_PLAIN_TYPE), new PlainMessageBodyWriter<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType) {
|
||||
final MessageBodyWriter<T> writer = writers.get(mediaTypeWithoutParams(mediaType));
|
||||
return writer != null && writer.isWriteable(type, genericType, annotations, mediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(final T object, final Class<?> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
|
||||
final OutputStream entityStream) throws IOException, WebApplicationException {
|
||||
final MessageBodyWriter<T> writer = writers.get(mediaTypeWithoutParams(mediaType));
|
||||
if (writer != null) {
|
||||
writer.writeTo(object, type, genericType, annotations, mediaType, httpHeaders, entityStream);
|
||||
} else {
|
||||
throw new InternalServerErrorException("unsupported media type");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType) {
|
||||
final MessageBodyReader<T> reader = readers.get(mediaTypeWithoutParams(mediaType));
|
||||
return reader != null && reader.isReadable(type, genericType, annotations, mediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T readFrom(final Class<T> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType, final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
|
||||
throws IOException, WebApplicationException {
|
||||
final MessageBodyReader<T> reader = readers.get(mediaTypeWithoutParams(mediaType));
|
||||
if (reader != null) {
|
||||
return reader.readFrom(type, genericType, annotations, mediaType, httpHeaders, entityStream);
|
||||
} else {
|
||||
throw new InternalServerErrorException("unsupported media type");
|
||||
}
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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.io.rest.core.internal;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.ws.rs.InternalServerErrorException;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.ext.MessageBodyReader;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A message body reader for plain text.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
public class PlainMessageBodyReader<T> implements MessageBodyReader<T> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PlainMessageBodyReader.class);
|
||||
|
||||
@Override
|
||||
public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T readFrom(final Class<T> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType, final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
|
||||
throws IOException, WebApplicationException {
|
||||
final byte[] data = data(entityStream);
|
||||
logger.debug("Received: type={}, genericType={}, annotations={}, mediaType={}, httpHeaders={}", type,
|
||||
genericType, annotations, mediaType, httpHeaders);
|
||||
if (type.equals(String.class) || genericType.equals(String.class)) {
|
||||
return (T) new String(data, StandardCharsets.UTF_8);
|
||||
} else if (type.equals(byte[].class) || genericType.equals(byte[].class)) {
|
||||
return (T) data;
|
||||
} else if (type.equals(Byte[].class) || genericType.equals(Byte[].class)) {
|
||||
final Byte[] dataB = new Byte[data.length];
|
||||
for (int i = 0; i < data.length; ++i) {
|
||||
dataB[i] = data[i];
|
||||
}
|
||||
return (T) dataB;
|
||||
} else {
|
||||
throw new InternalServerErrorException(
|
||||
String.format("Cannot assign text plain to type \"%s\", generic type: \"%s\".", type, genericType));
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] data(final InputStream is) throws IOException {
|
||||
final ByteArrayOutputStream outputBytes = new ByteArrayOutputStream();
|
||||
int read;
|
||||
final byte[] buffer = new byte[1024];
|
||||
while ((read = is.read(buffer)) != -1) {
|
||||
outputBytes.write(buffer, 0, read);
|
||||
}
|
||||
return outputBytes.toByteArray();
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.io.rest.core.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.ext.MessageBodyWriter;
|
||||
|
||||
/**
|
||||
* A message body writer for plain text.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
public class PlainMessageBodyWriter<T> implements MessageBodyWriter<T> {
|
||||
|
||||
@Override
|
||||
public long getSize(final T object, final Class<?> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(final T object, final Class<?> type, final Type genericType, final Annotation[] annotations,
|
||||
final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
|
||||
final OutputStream entityStream) throws IOException, WebApplicationException {
|
||||
if (object == null) {
|
||||
return;
|
||||
}
|
||||
entityStream.write(object.toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* 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.io.rest.core.internal;
|
||||
|
||||
import org.openhab.core.io.rest.JSONResponse;
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
|
||||
/**
|
||||
* Extension of the default OSGi bundle activator
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Benedikt Niehues - made serviceRegistration of ExceptionMapper compatible with older OSGI versions
|
||||
*/
|
||||
public class RESTCoreActivator implements BundleActivator {
|
||||
|
||||
private static BundleContext context;
|
||||
@SuppressWarnings("rawtypes")
|
||||
private ServiceRegistration mExcMapper;
|
||||
|
||||
/**
|
||||
* Called whenever the OSGi framework starts our bundle
|
||||
*/
|
||||
@Override
|
||||
public void start(BundleContext bc) throws Exception {
|
||||
context = bc;
|
||||
mExcMapper = bc.registerService(JSONResponse.ExceptionMapper.class.getName(),
|
||||
new JSONResponse.ExceptionMapper(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the OSGi framework stops our bundle
|
||||
*/
|
||||
@Override
|
||||
public void stop(BundleContext context) throws Exception {
|
||||
RESTCoreActivator.context = null;
|
||||
mExcMapper.unregister();
|
||||
}
|
||||
|
||||
public static BundleContext getBundleContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
+13
-2
@@ -43,6 +43,7 @@ import org.openhab.core.config.core.ConfigDescriptionRegistry;
|
||||
import org.openhab.core.config.core.ConfigUtil;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
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;
|
||||
import org.openhab.core.io.rest.core.config.ConfigurationService;
|
||||
@@ -50,6 +51,11 @@ 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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -67,11 +73,16 @@ import io.swagger.annotations.ApiResponses;
|
||||
* @author Kai Kreuzer - refactored for using the OSGi JAX-RS connector
|
||||
* @author Yordan Zhelev - Added Swagger annotations
|
||||
* @author Franck Dechavanne - Added DTOs to ApiResponses
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(BindingResource.PATH_BINDINGS)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(BindingResource.PATH_BINDINGS)
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Api(value = BindingResource.PATH_BINDINGS)
|
||||
@Component
|
||||
@Api(BindingResource.PATH_BINDINGS)
|
||||
public class BindingResource implements RESTResource {
|
||||
|
||||
/** The URI path to this resource */
|
||||
|
||||
+13
-2
@@ -41,6 +41,7 @@ import org.openhab.core.config.core.dto.ConfigDescriptionDTOMapper;
|
||||
import org.openhab.core.config.core.dto.ConfigDescriptionParameterDTO;
|
||||
import org.openhab.core.config.core.dto.ConfigDescriptionParameterGroupDTO;
|
||||
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;
|
||||
import org.openhab.core.thing.dto.ChannelTypeDTO;
|
||||
@@ -55,6 +56,11 @@ 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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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 io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -68,11 +74,16 @@ import io.swagger.annotations.ApiResponses;
|
||||
* @author Chris Jackson - Initial contribution
|
||||
* @author Franck Dechavanne - Added DTOs to ApiResponses
|
||||
* @author Yannick Schaus - Added filter to getAll
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(ChannelTypeResource.PATH_CHANNEL_TYPES)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(ChannelTypeResource.PATH_CHANNEL_TYPES)
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Api(value = ChannelTypeResource.PATH_CHANNEL_TYPES)
|
||||
@Component
|
||||
@Api(ChannelTypeResource.PATH_CHANNEL_TYPES)
|
||||
public class ChannelTypeResource implements RESTResource {
|
||||
|
||||
/** The URI path to this resource */
|
||||
|
||||
+13
-2
@@ -36,6 +36,7 @@ import org.openhab.core.config.core.ConfigDescriptionRegistry;
|
||||
import org.openhab.core.config.core.dto.ConfigDescriptionDTO;
|
||||
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;
|
||||
import org.openhab.core.io.rest.core.config.EnrichedConfigDescriptionDTOMapper;
|
||||
@@ -43,6 +44,11 @@ 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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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 io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -56,11 +62,16 @@ import io.swagger.annotations.ApiResponses;
|
||||
* @author Dennis Nobel - Initial contribution
|
||||
* @author Chris Jackson - Modify response to use JSONResponse
|
||||
* @author Franck Dechavanne - Added DTOs to ApiResponses
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(ConfigDescriptionResource.PATH_CONFIG_DESCRIPTIONS)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(ConfigDescriptionResource.PATH_CONFIG_DESCRIPTIONS)
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Api(value = ConfigDescriptionResource.PATH_CONFIG_DESCRIPTIONS)
|
||||
@Component
|
||||
@Api(ConfigDescriptionResource.PATH_CONFIG_DESCRIPTIONS)
|
||||
@NonNullByDefault
|
||||
public class ConfigDescriptionResource implements RESTResource {
|
||||
|
||||
|
||||
+13
-2
@@ -29,11 +29,17 @@ import javax.ws.rs.core.UriInfo;
|
||||
import org.openhab.core.auth.Role;
|
||||
import org.openhab.core.config.discovery.DiscoveryServiceRegistry;
|
||||
import org.openhab.core.config.discovery.ScanListener;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -52,11 +58,16 @@ import io.swagger.annotations.ApiResponses;
|
||||
* @author Yordan Zhelev - Added Swagger annotations
|
||||
* @author Ivaylo Ivanov - Added payload to the response of <code>scan</code>
|
||||
* @author Franck Dechavanne - Added DTOs to ApiResponses
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component(service = { RESTResource.class, DiscoveryResource.class })
|
||||
@JaxrsResource
|
||||
@JaxrsName(DiscoveryResource.PATH_DISCOVERY)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(DiscoveryResource.PATH_DISCOVERY)
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Api(value = DiscoveryResource.PATH_DISCOVERY)
|
||||
@Component(service = { RESTResource.class, DiscoveryResource.class })
|
||||
@Api(DiscoveryResource.PATH_DISCOVERY)
|
||||
public class DiscoveryResource implements RESTResource {
|
||||
|
||||
/** The URI path to this resource */
|
||||
|
||||
+13
-2
@@ -36,6 +36,7 @@ import org.openhab.core.config.discovery.dto.DiscoveryResultDTO;
|
||||
import org.openhab.core.config.discovery.dto.DiscoveryResultDTOMapper;
|
||||
import org.openhab.core.config.discovery.inbox.Inbox;
|
||||
import org.openhab.core.io.rest.JSONResponse;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
import org.openhab.core.io.rest.Stream2JSONInputStream;
|
||||
import org.openhab.core.thing.Thing;
|
||||
@@ -44,6 +45,11 @@ 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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -62,11 +68,16 @@ import io.swagger.annotations.ApiResponses;
|
||||
* @author Yordan Zhelev - Added Swagger annotations
|
||||
* @author Chris Jackson - Updated to use JSONResponse. Fixed null response from approve. Improved error reporting.
|
||||
* @author Franck Dechavanne - Added DTOs to ApiResponses
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component(service = { RESTResource.class, InboxResource.class })
|
||||
@JaxrsResource
|
||||
@JaxrsName(InboxResource.PATH_INBOX)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(InboxResource.PATH_INBOX)
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Api(value = InboxResource.PATH_INBOX)
|
||||
@Component(service = { RESTResource.class, InboxResource.class })
|
||||
@Api(InboxResource.PATH_INBOX)
|
||||
public class InboxResource implements RESTResource {
|
||||
private final Logger logger = LoggerFactory.getLogger(InboxResource.class);
|
||||
|
||||
|
||||
+14
-2
@@ -45,12 +45,18 @@ import org.openhab.core.extension.ExtensionService;
|
||||
import org.openhab.core.extension.ExtensionType;
|
||||
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;
|
||||
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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -65,11 +71,16 @@ import io.swagger.annotations.ApiResponses;
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Franck Dechavanne - Added DTOs to ApiResponses
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(ExtensionResource.PATH_EXTENSIONS)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(ExtensionResource.PATH_EXTENSIONS)
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Api(value = ExtensionResource.PATH_EXTENSIONS)
|
||||
@Component
|
||||
@Api(ExtensionResource.PATH_EXTENSIONS)
|
||||
public class ExtensionResource implements RESTResource {
|
||||
|
||||
private static final String THREAD_POOL_NAME = "extensionService";
|
||||
@@ -194,6 +205,7 @@ public class ExtensionResource implements RESTResource {
|
||||
|
||||
@POST
|
||||
@Path("/{extensionId: [a-zA-Z_0-9-:]+}/uninstall")
|
||||
@ApiOperation(value = "Uninstalls the extension with the given ID.")
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK") })
|
||||
public Response uninstallExtension(
|
||||
final @PathParam("extensionId") @ApiParam(value = "extension ID", required = true) String extensionId) {
|
||||
|
||||
+59
-46
@@ -18,6 +18,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
@@ -41,6 +42,7 @@ import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@@ -50,6 +52,7 @@ import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.io.rest.DTOMapper;
|
||||
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;
|
||||
import org.openhab.core.io.rest.core.item.EnrichedGroupItemDTO;
|
||||
@@ -80,6 +83,11 @@ 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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -111,21 +119,39 @@ import io.swagger.annotations.ApiResponses;
|
||||
* @author Jörg Plewe - refactoring, error handling
|
||||
* @author Franck Dechavanne - Added DTOs to ApiResponses
|
||||
* @author Stefan Triller - Added bulk item add method
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Path(ItemResource.PATH_ITEMS)
|
||||
@Api(value = ItemResource.PATH_ITEMS)
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(ItemResource.PATH_ITEMS)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(ItemResource.PATH_ITEMS)
|
||||
@Api(ItemResource.PATH_ITEMS)
|
||||
@NonNullByDefault
|
||||
public class ItemResource implements RESTResource {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ItemResource.class);
|
||||
|
||||
/** The URI path to this resource */
|
||||
public static final String PATH_ITEMS = "items";
|
||||
|
||||
@Context
|
||||
@NonNullByDefault({})
|
||||
UriInfo uriInfo;
|
||||
/**
|
||||
* Replaces part of the URI builder by forwarded headers.
|
||||
*
|
||||
* @param uriBuilder the URI builder
|
||||
* @param httpHeaders the HTTP headers
|
||||
*/
|
||||
private static void respectForwarded(final UriBuilder uriBuilder, final @Context HttpHeaders httpHeaders) {
|
||||
Optional.ofNullable(httpHeaders.getHeaderString("X-Forwarded-Host")).ifPresent(host -> {
|
||||
final String[] parts = host.split(":");
|
||||
uriBuilder.host(parts[0]);
|
||||
if (parts.length > 1) {
|
||||
uriBuilder.port(Integer.parseInt(parts[1]));
|
||||
}
|
||||
});
|
||||
Optional.ofNullable(httpHeaders.getHeaderString("X-Forwarded-Proto")).ifPresent(uriBuilder::scheme);
|
||||
}
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ItemResource.class);
|
||||
|
||||
private @NonNullByDefault({}) ItemRegistry itemRegistry;
|
||||
private @NonNullByDefault({}) MetadataRegistry metadataRegistry;
|
||||
@@ -208,13 +234,20 @@ public class ItemResource implements RESTResource {
|
||||
this.itemBuilderFactory = null;
|
||||
}
|
||||
|
||||
private UriBuilder uriBuilder(final UriInfo uriInfo, final HttpHeaders httpHeaders) {
|
||||
final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
|
||||
respectForwarded(uriBuilder, httpHeaders);
|
||||
uriBuilder.path("{itemName}");
|
||||
return uriBuilder;
|
||||
}
|
||||
|
||||
@GET
|
||||
@RolesAllowed({ Role.USER, Role.ADMIN })
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@ApiOperation(value = "Get all available items.", response = EnrichedItemDTO.class, responseContainer = "List")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(code = 200, message = "OK", response = EnrichedItemDTO.class, responseContainer = "List") })
|
||||
public Response getItems(
|
||||
public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders,
|
||||
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language,
|
||||
@QueryParam("type") @ApiParam(value = "item type filter", required = false) @Nullable String type,
|
||||
@QueryParam("tags") @ApiParam(value = "item tag filter", required = false) @Nullable String tags,
|
||||
@@ -223,10 +256,11 @@ public class ItemResource implements RESTResource {
|
||||
@QueryParam("fields") @ApiParam(value = "limit output to the given fields (comma separated)", required = false) @Nullable String fields) {
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
final Set<String> namespaces = splitAndFilterNamespaces(namespaceSelector, locale);
|
||||
logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath());
|
||||
|
||||
final UriBuilder uriBuilder = uriBuilder(uriInfo, httpHeaders);
|
||||
|
||||
Stream<EnrichedItemDTO> itemStream = getItems(type, tags).stream() //
|
||||
.map(item -> EnrichedItemDTOMapper.map(item, recursive, null, uriInfo.getBaseUri(), locale)) //
|
||||
.map(item -> EnrichedItemDTOMapper.map(item, recursive, null, uriBuilder, locale)) //
|
||||
.peek(dto -> addMetadata(dto, namespaces, null)) //
|
||||
.peek(dto -> dto.editable = isEditable(dto.name));
|
||||
itemStream = dtoMapper.limitToFields(itemStream, fields);
|
||||
@@ -240,26 +274,23 @@ public class ItemResource implements RESTResource {
|
||||
@ApiOperation(value = "Gets a single item.", response = EnrichedItemDTO.class)
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = EnrichedItemDTO.class),
|
||||
@ApiResponse(code = 404, message = "Item not found") })
|
||||
public Response getItemData(
|
||||
public Response getItemData(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders,
|
||||
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language,
|
||||
@QueryParam("metadata") @ApiParam(value = "metadata selector", required = false) @Nullable String namespaceSelector,
|
||||
@PathParam("itemname") @ApiParam(value = "item name", required = true) String itemname) {
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
final Set<String> namespaces = splitAndFilterNamespaces(namespaceSelector, locale);
|
||||
logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath());
|
||||
|
||||
// get item
|
||||
Item item = getItem(itemname);
|
||||
|
||||
// if it exists
|
||||
if (item != null) {
|
||||
logger.debug("Received HTTP GET request at '{}'.", uriInfo.getPath());
|
||||
EnrichedItemDTO dto = EnrichedItemDTOMapper.map(item, true, null, uriInfo.getBaseUri(), locale);
|
||||
EnrichedItemDTO dto = EnrichedItemDTOMapper.map(item, true, null, uriBuilder(uriInfo, httpHeaders), locale);
|
||||
addMetadata(dto, namespaces, null);
|
||||
dto.editable = isEditable(dto.name);
|
||||
return JSONResponse.createResponse(Status.OK, dto, null);
|
||||
} else {
|
||||
logger.info("Received HTTP GET request at '{}' for the unknown item '{}'.", uriInfo.getPath(), itemname);
|
||||
return getItemNotFoundResponse(itemname);
|
||||
}
|
||||
}
|
||||
@@ -287,13 +318,10 @@ public class ItemResource implements RESTResource {
|
||||
|
||||
// if it exists
|
||||
if (item != null) {
|
||||
logger.debug("Received HTTP GET request at '{}'.", uriInfo.getPath());
|
||||
|
||||
// we cannot use JSONResponse.createResponse() bc. MediaType.TEXT_PLAIN
|
||||
// return JSONResponse.createResponse(Status.OK, item.getState().toString(), null);
|
||||
return Response.ok(item.getState().toFullString()).build();
|
||||
} else {
|
||||
logger.info("Received HTTP GET request at '{}' for the unknown item '{}'.", uriInfo.getPath(), itemname);
|
||||
return getItemNotFoundResponse(itemname);
|
||||
}
|
||||
}
|
||||
@@ -322,18 +350,14 @@ public class ItemResource implements RESTResource {
|
||||
|
||||
if (state != null) {
|
||||
// set State and report OK
|
||||
logger.debug("Received HTTP PUT request at '{}' with value '{}'.", uriInfo.getPath(), value);
|
||||
eventPublisher.post(ItemEventFactory.createStateEvent(itemname, state));
|
||||
return getItemResponse(Status.ACCEPTED, null, locale, null);
|
||||
return getItemResponse(null, Status.ACCEPTED, null, locale, null);
|
||||
} else {
|
||||
// State could not be parsed
|
||||
logger.warn("Received HTTP PUT request at '{}' with an invalid status value '{}'.", uriInfo.getPath(),
|
||||
value);
|
||||
return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "State could not be parsed: " + value);
|
||||
}
|
||||
} else {
|
||||
// Item does not exist
|
||||
logger.info("Received HTTP PUT request at '{}' for the unknown item '{}'.", uriInfo.getPath(), itemname);
|
||||
return getItemNotFoundResponse(itemname);
|
||||
}
|
||||
}
|
||||
@@ -369,18 +393,14 @@ public class ItemResource implements RESTResource {
|
||||
command = TypeParser.parseCommand(item.getAcceptedCommandTypes(), value);
|
||||
}
|
||||
if (command != null) {
|
||||
logger.debug("Received HTTP POST request at '{}' with value '{}'.", uriInfo.getPath(), value);
|
||||
eventPublisher.post(ItemEventFactory.createCommandEvent(itemname, command));
|
||||
ResponseBuilder resbuilder = Response.ok();
|
||||
resbuilder.type(MediaType.TEXT_PLAIN);
|
||||
return resbuilder.build();
|
||||
} else {
|
||||
logger.warn("Received HTTP POST request at '{}' with an invalid status value '{}'.", uriInfo.getPath(),
|
||||
value);
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
}
|
||||
} else {
|
||||
logger.info("Received HTTP POST request at '{}' for the unknown item '{}'.", uriInfo.getPath(), itemname);
|
||||
throw new WebApplicationException(404);
|
||||
}
|
||||
}
|
||||
@@ -469,7 +489,6 @@ public class ItemResource implements RESTResource {
|
||||
@ApiResponse(code = 404, message = "Item not found or item is not editable.") })
|
||||
public Response removeItem(@PathParam("itemname") @ApiParam(value = "item name", required = true) String itemname) {
|
||||
if (managedItemProvider.remove(itemname) == null) {
|
||||
logger.info("Received HTTP DELETE request at '{}' for the unknown item '{}'.", uriInfo.getPath(), itemname);
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
return Response.ok(null, MediaType.TEXT_PLAIN).build();
|
||||
@@ -487,7 +506,6 @@ public class ItemResource implements RESTResource {
|
||||
Item item = getItem(itemname);
|
||||
|
||||
if (item == null) {
|
||||
logger.info("Received HTTP PUT request at '{}' for the unknown item '{}'.", uriInfo.getPath(), itemname);
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
@@ -513,7 +531,6 @@ public class ItemResource implements RESTResource {
|
||||
Item item = getItem(itemname);
|
||||
|
||||
if (item == null) {
|
||||
logger.info("Received HTTP DELETE request at '{}' for the unknown item '{}'.", uriInfo.getPath(), itemname);
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
@@ -544,14 +561,11 @@ public class ItemResource implements RESTResource {
|
||||
Item item = getItem(itemname);
|
||||
|
||||
if (item == null) {
|
||||
logger.info("Received HTTP PUT request at '{}' for the unknown item '{}'.", uriInfo.getPath(), itemname);
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
String value = metadata.value;
|
||||
if (value == null || value.isEmpty()) {
|
||||
logger.info("Received HTTP PUT request at '{}' for item '{}' with empty metadata.", uriInfo.getPath(),
|
||||
itemname);
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
}
|
||||
|
||||
@@ -579,20 +593,15 @@ public class ItemResource implements RESTResource {
|
||||
Item item = getItem(itemname);
|
||||
|
||||
if (item == null) {
|
||||
logger.info("Received HTTP DELETE request at '{}' for the unknown item '{}'.", uriInfo.getPath(), itemname);
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
MetadataKey key = new MetadataKey(namespace, itemname);
|
||||
if (metadataRegistry.get(key) != null) {
|
||||
if (metadataRegistry.remove(key) == null) {
|
||||
logger.info("Received HTTP DELETE request at '{}' for unmanaged item meta-data '{}'.",
|
||||
uriInfo.getPath(), key);
|
||||
return Response.status(Status.CONFLICT).build();
|
||||
}
|
||||
} else {
|
||||
logger.info("Received HTTP DELETE request at '{}' for unknown item meta-data '{}'.", uriInfo.getPath(),
|
||||
key);
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
@@ -615,7 +624,7 @@ public class ItemResource implements RESTResource {
|
||||
@ApiResponse(code = 201, message = "Item created."), @ApiResponse(code = 400, message = "Item null."),
|
||||
@ApiResponse(code = 404, message = "Item not found."),
|
||||
@ApiResponse(code = 405, message = "Item not editable.") })
|
||||
public Response createOrUpdateItem(
|
||||
public Response createOrUpdateItem(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders,
|
||||
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language,
|
||||
@PathParam("itemname") @ApiParam(value = "item name", required = true) String itemname,
|
||||
@ApiParam(value = "item data", required = true) @Nullable GroupItemDTO item) {
|
||||
@@ -637,11 +646,13 @@ public class ItemResource implements RESTResource {
|
||||
if (getItem(itemname) == null) {
|
||||
// item does not yet exist, create it
|
||||
managedItemProvider.add(newItem);
|
||||
return getItemResponse(Status.CREATED, itemRegistry.get(itemname), locale, null);
|
||||
return getItemResponse(uriBuilder(uriInfo, httpHeaders), Status.CREATED, itemRegistry.get(itemname), locale,
|
||||
null);
|
||||
} else if (managedItemProvider.get(itemname) != null) {
|
||||
// item already exists as a managed item, update it
|
||||
managedItemProvider.update(newItem);
|
||||
return getItemResponse(Status.OK, itemRegistry.get(itemname), locale, null);
|
||||
return getItemResponse(uriBuilder(uriInfo, httpHeaders), Status.OK, itemRegistry.get(itemname), locale,
|
||||
null);
|
||||
} else {
|
||||
// Item exists but cannot be updated
|
||||
logger.warn("Cannot update existing item '{}', because is not managed.", itemname);
|
||||
@@ -708,8 +719,8 @@ public class ItemResource implements RESTResource {
|
||||
List<JsonObject> responseList = new ArrayList<>();
|
||||
|
||||
for (GroupItemDTO item : wrongTypes) {
|
||||
responseList.add(buildStatusObject(item.name, "error", "Received HTTP PUT request at '" + uriInfo.getPath()
|
||||
+ "' with an invalid item type '" + item.type + "'."));
|
||||
responseList.add(buildStatusObject(item.name, "error",
|
||||
"Received HTTP PUT request with an invalid item type '" + item.type + "'."));
|
||||
}
|
||||
for (Item item : failedItems) {
|
||||
responseList.add(buildStatusObject(item.getName(), "error", "Cannot update non-managed item"));
|
||||
@@ -746,14 +757,16 @@ public class ItemResource implements RESTResource {
|
||||
/**
|
||||
* Prepare a response representing the Item depending in the status.
|
||||
*
|
||||
* @param uriBuilder the URI builder
|
||||
* @param status
|
||||
* @param item can be null
|
||||
* @param locale the locale
|
||||
* @param errormessage optional message in case of error
|
||||
* @return Response configured to represent the Item in depending on the status
|
||||
*/
|
||||
private Response getItemResponse(Status status, @Nullable Item item, Locale locale, @Nullable String errormessage) {
|
||||
Object entity = null != item ? EnrichedItemDTOMapper.map(item, true, null, uriInfo.getBaseUri(), locale) : null;
|
||||
private Response getItemResponse(final @Nullable UriBuilder uriBuilder, Status status, @Nullable Item item,
|
||||
Locale locale, @Nullable String errormessage) {
|
||||
Object entity = null != item ? EnrichedItemDTOMapper.map(item, true, null, uriBuilder, locale) : null;
|
||||
return JSONResponse.createResponse(status, entity, errormessage);
|
||||
}
|
||||
|
||||
|
||||
+14
-2
@@ -34,6 +34,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
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.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
import org.openhab.core.io.rest.Stream2JSONInputStream;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@@ -45,6 +46,11 @@ 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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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 io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -60,10 +66,16 @@ import io.swagger.annotations.ApiResponses;
|
||||
* @author Kai Kreuzer - Removed Thing links and added auto link url
|
||||
* @author Franck Dechavanne - Added DTOs to ApiResponses
|
||||
* @author Yannick Schaus - Added filters to getAll
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Path(ItemChannelLinkResource.PATH_LINKS)
|
||||
@Api(value = ItemChannelLinkResource.PATH_LINKS)
|
||||
@Component(service = { RESTResource.class, ItemChannelLinkResource.class })
|
||||
@JaxrsResource
|
||||
@JaxrsName(ItemChannelLinkResource.PATH_LINKS)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(ItemChannelLinkResource.PATH_LINKS)
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Api(ItemChannelLinkResource.PATH_LINKS)
|
||||
public class ItemChannelLinkResource implements RESTResource {
|
||||
|
||||
/** The URI path to this resource */
|
||||
|
||||
+14
-3
@@ -39,6 +39,7 @@ import org.openhab.core.auth.Role;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
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.items.Item;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
@@ -61,6 +62,11 @@ 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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -79,10 +85,15 @@ import io.swagger.annotations.ApiResponses;
|
||||
* @author Franck Dechavanne - Added DTOs to ApiResponses
|
||||
* @author Erdoan Hadzhiyusein - Adapted the convertTime() method to work with the new DateTimeType
|
||||
* @author Lyubomir Papazov - Change java.util.Date references to be of type java.time.ZonedDateTime
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Path(PersistenceResource.PATH)
|
||||
@Api(value = PersistenceResource.PATH)
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(PersistenceResource.PATH_PERSISTENCE)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(PersistenceResource.PATH_PERSISTENCE)
|
||||
@Api(PersistenceResource.PATH_PERSISTENCE)
|
||||
public class PersistenceResource implements RESTResource {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PersistenceResource.class);
|
||||
@@ -92,7 +103,7 @@ public class PersistenceResource implements RESTResource {
|
||||
private static final String STANDARD = "Standard";
|
||||
|
||||
// The URI path to this resource
|
||||
public static final String PATH = "persistence";
|
||||
public static final String PATH_PERSISTENCE = "persistence";
|
||||
|
||||
private ItemRegistry itemRegistry;
|
||||
private PersistenceServiceRegistry persistenceServiceRegistry;
|
||||
|
||||
+13
-2
@@ -30,6 +30,7 @@ import javax.ws.rs.core.Response;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.auth.Role;
|
||||
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;
|
||||
import org.openhab.core.items.ItemUtil;
|
||||
@@ -46,6 +47,11 @@ 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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -59,11 +65,16 @@ import io.swagger.annotations.ApiResponses;
|
||||
* REST resource to obtain profile-types
|
||||
*
|
||||
* @author Stefan Triller - Initial contribution
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(ProfileTypeResource.PATH_PROFILE_TYPES)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(ProfileTypeResource.PATH_PROFILE_TYPES)
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Api(value = ProfileTypeResource.PATH_PROFILE_TYPES)
|
||||
@Component
|
||||
@Api(ProfileTypeResource.PATH_PROFILE_TYPES)
|
||||
public class ProfileTypeResource implements RESTResource {
|
||||
|
||||
/** The URI path to this resource */
|
||||
|
||||
+24
-6
@@ -42,19 +42,26 @@ import org.openhab.core.config.core.ConfigDescriptionRegistry;
|
||||
import org.openhab.core.config.core.ConfigUtil;
|
||||
import org.openhab.core.config.core.ConfigurableService;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
import org.openhab.core.io.rest.core.config.ConfigurationService;
|
||||
import org.openhab.core.io.rest.core.internal.RESTCoreActivator;
|
||||
import org.openhab.core.io.rest.core.service.ConfigurableServiceDTO;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.Constants;
|
||||
import org.osgi.framework.InvalidSyntaxException;
|
||||
import org.osgi.framework.ServiceReference;
|
||||
import org.osgi.service.cm.ConfigurationAdmin;
|
||||
import org.osgi.service.component.ComponentConstants;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -70,11 +77,16 @@ import io.swagger.annotations.ApiResponses;
|
||||
*
|
||||
* @author Dennis Nobel - Initial contribution
|
||||
* @author Franck Dechavanne - Added DTOs to ApiResponses
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component(service = { RESTResource.class, ConfigurableServiceResource.class })
|
||||
@JaxrsResource
|
||||
@JaxrsName(ConfigurableServiceResource.PATH_SERVICES)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(ConfigurableServiceResource.PATH_SERVICES)
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Api(value = ConfigurableServiceResource.PATH_SERVICES)
|
||||
@Component(service = { RESTResource.class, ConfigurableServiceResource.class })
|
||||
@Api(ConfigurableServiceResource.PATH_SERVICES)
|
||||
public class ConfigurableServiceResource implements RESTResource {
|
||||
|
||||
/** The URI path to this resource */
|
||||
@@ -91,9 +103,16 @@ public class ConfigurableServiceResource implements RESTResource {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ConfigurableServiceResource.class);
|
||||
|
||||
private final BundleContext bundleContext;
|
||||
|
||||
private ConfigurationService configurationService;
|
||||
private ConfigDescriptionRegistry configDescRegistry;
|
||||
|
||||
@Activate
|
||||
public ConfigurableServiceResource(final BundleContext bundleContext) {
|
||||
this.bundleContext = bundleContext;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@ApiOperation(value = "Get all configurable services.")
|
||||
@@ -250,7 +269,7 @@ public class ConfigurableServiceResource implements RESTResource {
|
||||
List<ConfigurableServiceDTO> services = new ArrayList<>();
|
||||
ServiceReference<?>[] serviceReferences = null;
|
||||
try {
|
||||
serviceReferences = RESTCoreActivator.getBundleContext().getServiceReferences((String) null, filter);
|
||||
serviceReferences = bundleContext.getServiceReferences((String) null, filter);
|
||||
} catch (InvalidSyntaxException ex) {
|
||||
logger.error("Cannot get service references because the syntax of the filter '{}' is invalid.", filter);
|
||||
}
|
||||
@@ -287,8 +306,7 @@ public class ConfigurableServiceResource implements RESTResource {
|
||||
String filter = "(" + Constants.SERVICE_PID + "=" + factoryPid + ")";
|
||||
|
||||
try {
|
||||
ServiceReference<?>[] refs = RESTCoreActivator.getBundleContext().getServiceReferences((String) null,
|
||||
filter);
|
||||
ServiceReference<?>[] refs = bundleContext.getServiceReferences((String) null, filter);
|
||||
|
||||
if (refs != null && refs.length > 0) {
|
||||
configDescriptionURI = (String) refs[0]
|
||||
|
||||
+13
-2
@@ -56,6 +56,7 @@ import org.openhab.core.config.core.status.ConfigStatusService;
|
||||
import org.openhab.core.config.core.validation.ConfigValidationException;
|
||||
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;
|
||||
import org.openhab.core.io.rest.core.thing.EnrichedThingDTO;
|
||||
@@ -97,6 +98,11 @@ 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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -121,10 +127,15 @@ import io.swagger.annotations.ApiResponses;
|
||||
* add editable flag to thing responses
|
||||
* @author Franck Dechavanne - Added DTOs to ApiResponses
|
||||
* @author Dimitar Ivanov - replaced Firmware UID with thing UID and firmware version
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Path(ThingResource.PATH_THINGS)
|
||||
@Api(value = ThingResource.PATH_THINGS)
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(ThingResource.PATH_THINGS)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(ThingResource.PATH_THINGS)
|
||||
@Api(ThingResource.PATH_THINGS)
|
||||
public class ThingResource implements RESTResource {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ThingResource.class);
|
||||
|
||||
+14
-3
@@ -37,6 +37,7 @@ import org.openhab.core.config.core.dto.ConfigDescriptionDTOMapper;
|
||||
import org.openhab.core.config.core.dto.ConfigDescriptionParameterDTO;
|
||||
import org.openhab.core.config.core.dto.ConfigDescriptionParameterGroupDTO;
|
||||
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;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
@@ -58,6 +59,11 @@ 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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -79,14 +85,19 @@ import io.swagger.annotations.ApiResponses;
|
||||
* @author Miki Jankov - Introducing StrippedThingTypeDTO
|
||||
* @author Franck Dechavanne - Added DTOs to ApiResponses
|
||||
* @author Yannick Schaus - Added filter to getAll
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Path(ThingTypeResource.PATH_THINGS_TYPES)
|
||||
@Api(value = ThingTypeResource.PATH_THINGS_TYPES)
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(ThingTypeResource.PATH_THING_TYPES)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(ThingTypeResource.PATH_THING_TYPES)
|
||||
@Api(ThingTypeResource.PATH_THING_TYPES)
|
||||
public class ThingTypeResource implements RESTResource {
|
||||
|
||||
/** The URI path to this resource */
|
||||
public static final String PATH_THINGS_TYPES = "thing-types";
|
||||
public static final String PATH_THING_TYPES = "thing-types";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ThingTypeResource.class);
|
||||
|
||||
|
||||
+18
-10
@@ -12,16 +12,15 @@
|
||||
*/
|
||||
package org.openhab.core.io.rest.core.item;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.rest.core.internal.RESTCoreActivator;
|
||||
import org.openhab.core.io.rest.core.internal.item.ItemResource;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.dto.ItemDTO;
|
||||
@@ -30,6 +29,7 @@ import org.openhab.core.transform.TransformationException;
|
||||
import org.openhab.core.transform.TransformationHelper;
|
||||
import org.openhab.core.types.StateDescription;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -51,17 +51,18 @@ public class EnrichedItemDTOMapper {
|
||||
* @param drillDown defines whether the whole tree should be traversed or only direct members are considered
|
||||
* @param itemFilter a predicate that filters items while traversing the tree (true means that an item is
|
||||
* considered, can be null)
|
||||
* @param uri the uri (can be null)
|
||||
* @param uriBuilder if present the URI builder contains one template that will be replaced by the specific item
|
||||
* name
|
||||
* @param locale locale (can be null)
|
||||
* @return item DTO object
|
||||
*/
|
||||
public static EnrichedItemDTO map(Item item, boolean drillDown, @Nullable Predicate<Item> itemFilter,
|
||||
@Nullable URI uri, @Nullable Locale locale) {
|
||||
@Nullable UriBuilder uriBuilder, @Nullable Locale locale) {
|
||||
ItemDTO itemDTO = ItemDTOMapper.map(item);
|
||||
return map(item, itemDTO, uri, drillDown, itemFilter, locale);
|
||||
return map(item, itemDTO, uriBuilder, drillDown, itemFilter, locale);
|
||||
}
|
||||
|
||||
private static EnrichedItemDTO map(Item item, ItemDTO itemDTO, @Nullable URI uri, boolean drillDown,
|
||||
private static EnrichedItemDTO map(Item item, ItemDTO itemDTO, @Nullable UriBuilder uriBuilder, boolean drillDown,
|
||||
@Nullable Predicate<Item> itemFilter, @Nullable Locale locale) {
|
||||
String state = item.getState().toFullString();
|
||||
String transformedState = considerTransformation(state, item, locale);
|
||||
@@ -69,7 +70,13 @@ public class EnrichedItemDTOMapper {
|
||||
transformedState = null;
|
||||
}
|
||||
StateDescription stateDescription = considerTransformation(item.getStateDescription(locale));
|
||||
String link = null != uri ? uri.toASCIIString() + ItemResource.PATH_ITEMS + "/" + itemDTO.name : null;
|
||||
|
||||
final String link;
|
||||
if (uriBuilder != null) {
|
||||
link = uriBuilder.build(itemDTO.name).toASCIIString();
|
||||
} else {
|
||||
link = null;
|
||||
}
|
||||
|
||||
EnrichedItemDTO enrichedItemDTO = null;
|
||||
|
||||
@@ -80,7 +87,7 @@ public class EnrichedItemDTOMapper {
|
||||
Collection<EnrichedItemDTO> members = new LinkedHashSet<>();
|
||||
for (Item member : groupItem.getMembers()) {
|
||||
if (itemFilter == null || itemFilter.test(member)) {
|
||||
members.add(map(member, drillDown, itemFilter, uri, locale));
|
||||
members.add(map(member, drillDown, itemFilter, uriBuilder, locale));
|
||||
}
|
||||
}
|
||||
memberDTOs = members.toArray(new EnrichedItemDTO[members.size()]);
|
||||
@@ -121,7 +128,8 @@ public class EnrichedItemDTOMapper {
|
||||
String pattern = stateDescription.getPattern();
|
||||
if (pattern != null) {
|
||||
try {
|
||||
return TransformationHelper.transform(RESTCoreActivator.getBundleContext(), pattern, state);
|
||||
return TransformationHelper.transform(
|
||||
FrameworkUtil.getBundle(EnrichedItemDTOMapper.class).getBundleContext(), pattern, state);
|
||||
} catch (NoClassDefFoundError ex) {
|
||||
// TransformationHelper is optional dependency, so ignore if class not found
|
||||
// return state as it is without transformation
|
||||
|
||||
+6
-9
@@ -15,8 +15,6 @@ package org.openhab.core.io.rest.core.item;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
@@ -51,32 +49,31 @@ public class EnrichedItemDTOMapperTest extends JavaTest {
|
||||
subGroup.addMember(stringItem);
|
||||
}
|
||||
|
||||
EnrichedGroupItemDTO dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, false, null, URI.create(""),
|
||||
null);
|
||||
EnrichedGroupItemDTO dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, false, null, null, null);
|
||||
assertThat(dto.members.length, is(0));
|
||||
|
||||
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true, null, URI.create(""), null);
|
||||
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true, null, null, null);
|
||||
assertThat(dto.members.length, is(3));
|
||||
assertThat(((EnrichedGroupItemDTO) dto.members[0]).members.length, is(1));
|
||||
|
||||
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true,
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()), URI.create(""), null);
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()), null, null);
|
||||
assertThat(dto.members.length, is(1));
|
||||
|
||||
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true,
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i instanceof GroupItem, URI.create(""), null);
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i instanceof GroupItem, null, null);
|
||||
assertThat(dto.members.length, is(2));
|
||||
assertThat(((EnrichedGroupItemDTO) dto.members[0]).members.length, is(0));
|
||||
|
||||
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true,
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i instanceof GroupItem, URI.create(""), null);
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i instanceof GroupItem, null, null);
|
||||
assertThat(dto.members.length, is(2));
|
||||
assertThat(((EnrichedGroupItemDTO) dto.members[0]).members.length, is(0));
|
||||
|
||||
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true,
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i.getType().equals(CoreItemFactory.STRING)
|
||||
|| i instanceof GroupItem,
|
||||
URI.create(""), null);
|
||||
null, null);
|
||||
assertThat(dto.members.length, is(2));
|
||||
assertThat(((EnrichedGroupItemDTO) dto.members[0]).members.length, is(1));
|
||||
}
|
||||
|
||||
+13
-2
@@ -32,8 +32,14 @@ import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -46,10 +52,15 @@ import io.swagger.annotations.ApiResponses;
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Janzen - Initial contribution
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component
|
||||
@Path("/log")
|
||||
@Api(value = LogHandler.PATH_LOG)
|
||||
@JaxrsResource
|
||||
@JaxrsName(LogHandler.PATH_LOG)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(LogHandler.PATH_LOG)
|
||||
@Api(LogHandler.PATH_LOG)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public class LogHandler implements RESTResource {
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-core
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
Bundle-SymbolicName: ${project.artifactId}
|
||||
Bundle-Activator: org.openhab.core.io.rest.optimize.internal.Activator
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* 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.io.rest.optimize.internal;
|
||||
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.InvalidSyntaxException;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
|
||||
import com.eclipsesource.jaxrs.publisher.ResourceFilter;
|
||||
|
||||
/**
|
||||
* Activator
|
||||
*
|
||||
* It registers a {@link ResourceFilter} in order to prevent the JAX-RS implementation to
|
||||
* enforce starting all services once they are registered.
|
||||
*
|
||||
* @author Simon Kaufmann - Initial contribution
|
||||
*/
|
||||
public class Activator implements BundleActivator {
|
||||
|
||||
private ServiceRegistration<?> resourceFilterRegistration;
|
||||
|
||||
@Override
|
||||
public void start(BundleContext context) throws Exception {
|
||||
registerResourceFilter(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(BundleContext context) throws Exception {
|
||||
unregisterResourceFilter();
|
||||
}
|
||||
|
||||
private void registerResourceFilter(BundleContext context) throws InvalidSyntaxException {
|
||||
resourceFilterRegistration = context.registerService(ResourceFilter.class.getName(), new ResourceFilterImpl(),
|
||||
null);
|
||||
}
|
||||
|
||||
private void unregisterResourceFilter() {
|
||||
if (resourceFilterRegistration != null) {
|
||||
resourceFilterRegistration.unregister();
|
||||
resourceFilterRegistration = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
/**
|
||||
* 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.io.rest.optimize.internal;
|
||||
|
||||
import static com.eclipsesource.jaxrs.publisher.ServiceProperties.PUBLISH;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.osgi.framework.Constants;
|
||||
import org.osgi.framework.Filter;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.osgi.framework.InvalidSyntaxException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.eclipsesource.jaxrs.publisher.ResourceFilter;
|
||||
|
||||
/**
|
||||
* Provides a filter for all classes/interfaces which are relevant in the context of JAX-RS.
|
||||
*
|
||||
* By default, this filter will allow every service outside of the org.openhab.core.**-Namespace to be parsed by
|
||||
* the JAXR-RS implementation. To further optimize this, install a fragment which adds a "/res/whitelist.txt" file,
|
||||
* containing one service interface or class per line like in the following example:
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* # My Custom Services
|
||||
* org.example.foo
|
||||
* org.example.bar
|
||||
* # Another one
|
||||
* org.example.test
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* If this file is present, no other services will be scanned and hence won't be available.
|
||||
*
|
||||
* @author Simon Kaufmann - Initial contribution
|
||||
*/
|
||||
public class ResourceFilterImpl implements ResourceFilter {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ResourceFilterImpl.class);
|
||||
|
||||
/**
|
||||
* All classes and interfaces which are considered to be relevant for JAX-RS.
|
||||
*/
|
||||
private static final String[] WHITELIST = new String[] {
|
||||
// JAX-RS
|
||||
"javax.ws.rs.ext.MessageBodyReader", "javax.ws.rs.ext.MessageBodyWriter",
|
||||
// openHAB
|
||||
"org.openhab.core.io.rest.internal.filter.ProxyFilter",
|
||||
"org.openhab.core.io.rest.internal.resources.RootResource",
|
||||
"org.openhab.core.io.rest.JSONResponse$ExceptionMapper", "org.openhab.core.io.rest.RESTResource",
|
||||
"org.openhab.core.io.rest.sse.internal.async.BlockingAsyncFeature",
|
||||
"org.openhab.core.io.rest.sse.SseResource",
|
||||
// SSE
|
||||
"org.glassfish.jersey.media.sse.SseFeature",
|
||||
"org.glassfish.jersey.server.monitoring.ApplicationEventListener" };
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
String filterString = createFilter(WHITELIST);
|
||||
try {
|
||||
return FrameworkUtil.createFilter(filterString);
|
||||
} catch (InvalidSyntaxException e) {
|
||||
logger.error("Error creating RESTResource filter", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param interfaces interface or class names
|
||||
* @return filter string which matches if the class implements one of the interfaces or the name of the class is
|
||||
* contained in interfaces
|
||||
*/
|
||||
private String createFilter(String[] interfaces) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("(&");
|
||||
builder.append("(|");
|
||||
List<String> whitelist = loadWhitelistExtension();
|
||||
if (whitelist == null) {
|
||||
logger.debug("No /res/whitelist.txt file found - scanning all unknown services");
|
||||
builder.append("(!(" + Constants.OBJECTCLASS + "=org.openhab.core.*))");
|
||||
} else {
|
||||
logger.debug("Whitelist /res/whitelist.txt file found - restricting scanning of services");
|
||||
whitelist.forEach(entry -> {
|
||||
builder.append("(" + Constants.OBJECTCLASS + "=" + entry + ")");
|
||||
});
|
||||
}
|
||||
for (String clazz : interfaces) {
|
||||
builder.append("(" + Constants.OBJECTCLASS + "=" + clazz + ")");
|
||||
}
|
||||
builder.append(")");
|
||||
builder.append("(!(" + PUBLISH + "=false)))");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private List<String> loadWhitelistExtension() {
|
||||
Enumeration<URL> entries = FrameworkUtil.getBundle(this.getClass()).findEntries("res", "whitelist.txt", false);
|
||||
if (entries != null && entries.hasMoreElements()) {
|
||||
URL url = entries.nextElement();
|
||||
try (InputStream is = url.openStream()) {
|
||||
return readWhitelistEntries(is);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error reading REST extension whitelist from {}", url, e);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> readWhitelistEntries(InputStream stream) throws IOException {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
|
||||
List<String> ret = new LinkedList<>();
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
String trimmed = line.trim();
|
||||
if (!trimmed.isEmpty() && !trimmed.startsWith("#")) {
|
||||
ret.add(trimmed);
|
||||
}
|
||||
line = reader.readLine();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 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.io.rest.sitemap.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.core.io.rest.sitemap.internal.JerseyResponseBuilderUtils.JerseyResponseBuilderDTO.ContextDTO;
|
||||
import org.openhab.core.io.rest.sitemap.internal.JerseyResponseBuilderUtils.JerseyResponseBuilderDTO.ContextDTO.StreamInfoDTO;
|
||||
import org.osgi.dto.DTO;
|
||||
|
||||
/**
|
||||
* Data transfer object until UIs are fixed.
|
||||
*
|
||||
* <p>
|
||||
* {@link https://github.com/openhab/openhab-core/issues/1216}
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
public class JerseyResponseBuilderUtils extends DTO {
|
||||
|
||||
public static class JerseyResponseBuilderDTO extends DTO {
|
||||
|
||||
public static class ContextDTO extends DTO {
|
||||
|
||||
public static class StreamInfoDTO extends DTO {
|
||||
int bufferSize;
|
||||
boolean directWrite;
|
||||
boolean isCommitted;
|
||||
boolean isClosed;
|
||||
}
|
||||
|
||||
public Map<String, List<Object>> headers;
|
||||
public StreamInfoDTO committingOutputStream;
|
||||
public List<String> entityAnnotations;
|
||||
public StreamInfoDTO entityStream;
|
||||
}
|
||||
|
||||
public String status;
|
||||
public ContextDTO context;
|
||||
}
|
||||
|
||||
public static DTO created(final String location) {
|
||||
JerseyResponseBuilderDTO jrbDTO = new JerseyResponseBuilderDTO();
|
||||
jrbDTO.status = "CREATED";
|
||||
jrbDTO.context = new ContextDTO();
|
||||
jrbDTO.context.headers = new HashMap<>();
|
||||
jrbDTO.context.headers.put("Location", Arrays.asList(location));
|
||||
jrbDTO.context.committingOutputStream = new StreamInfoDTO();
|
||||
jrbDTO.context.committingOutputStream.bufferSize = 0;
|
||||
jrbDTO.context.committingOutputStream.directWrite = true;
|
||||
jrbDTO.context.committingOutputStream.isCommitted = false;
|
||||
jrbDTO.context.committingOutputStream.isClosed = false;
|
||||
jrbDTO.context.entityAnnotations = new ArrayList<>(0);
|
||||
jrbDTO.context.entityStream = new StreamInfoDTO();
|
||||
jrbDTO.context.entityStream.bufferSize = 0;
|
||||
jrbDTO.context.entityStream.directWrite = true;
|
||||
jrbDTO.context.entityStream.isCommitted = false;
|
||||
jrbDTO.context.entityStream.isClosed = false;
|
||||
return jrbDTO;
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* 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.io.rest.sitemap.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.glassfish.jersey.media.sse.EventOutput;
|
||||
import org.glassfish.jersey.media.sse.OutboundEvent;
|
||||
import org.openhab.core.io.rest.sitemap.SitemapSubscriptionService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link EventOutput} implementation that takes a subscription id parameter and only writes out events that match the
|
||||
* page of this subscription.
|
||||
* Should only be used when the {@link OutboundEvent}s sent through this {@link EventOutput} contain a data object of
|
||||
* type {@link SitemapEvent}
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
public class SitemapEventOutput extends EventOutput {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SitemapEventOutput.class);
|
||||
|
||||
private final String subscriptionId;
|
||||
private final SitemapSubscriptionService subscriptions;
|
||||
|
||||
public SitemapEventOutput(SitemapSubscriptionService subscriptions, String subscriptionId) {
|
||||
super();
|
||||
this.subscriptions = subscriptions;
|
||||
this.subscriptionId = subscriptionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutboundEvent chunk) throws IOException {
|
||||
if (chunk.getName().equals("subscriptionId") && chunk.getData().equals(subscriptionId)) {
|
||||
super.write(chunk);
|
||||
} else {
|
||||
SitemapEvent event = (SitemapEvent) chunk.getData();
|
||||
String sitemapName = event.sitemapName;
|
||||
String pageId = event.pageId;
|
||||
if (sitemapName != null && sitemapName.equals(subscriptions.getSitemapName(subscriptionId))
|
||||
&& pageId != null && pageId.equals(subscriptions.getPageId(subscriptionId))) {
|
||||
super.write(chunk);
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (event instanceof SitemapWidgetEvent) {
|
||||
logger.debug("Sent sitemap event for widget {} to subscription {}.",
|
||||
((SitemapWidgetEvent) event).widgetId, subscriptionId);
|
||||
} else if (event instanceof ServerAliveEvent) {
|
||||
logger.debug("Sent alive event to subscription {}.", subscriptionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getSubscriptionId() {
|
||||
return subscriptionId;
|
||||
}
|
||||
}
|
||||
+88
-58
@@ -45,20 +45,21 @@ import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.sse.OutboundSseEvent;
|
||||
import javax.ws.rs.sse.Sse;
|
||||
import javax.ws.rs.sse.SseEventSink;
|
||||
|
||||
import org.eclipse.emf.common.util.EList;
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.glassfish.jersey.media.sse.EventOutput;
|
||||
import org.glassfish.jersey.media.sse.OutboundEvent;
|
||||
import org.glassfish.jersey.media.sse.SseBroadcaster;
|
||||
import org.glassfish.jersey.media.sse.SseFeature;
|
||||
import org.glassfish.jersey.server.BroadcasterListener;
|
||||
import org.glassfish.jersey.server.ChunkedOutput;
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.auth.Role;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
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.SseBroadcaster;
|
||||
import org.openhab.core.io.rest.core.item.EnrichedItemDTOMapper;
|
||||
import org.openhab.core.io.rest.sitemap.SitemapSubscriptionService;
|
||||
import org.openhab.core.io.rest.sitemap.SitemapSubscriptionService.SitemapSubscriptionCallback;
|
||||
@@ -93,6 +94,11 @@ import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.component.annotations.ReferenceCardinality;
|
||||
import org.osgi.service.component.annotations.ReferencePolicy;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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;
|
||||
|
||||
@@ -112,12 +118,18 @@ import io.swagger.annotations.ApiResponses;
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Chris Jackson - Initial contribution
|
||||
* @author Yordan Zhelev - Added Swagger annotations
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component(service = RESTResource.class)
|
||||
@JaxrsResource
|
||||
@JaxrsName(SitemapResource.PATH_SITEMAPS)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(SitemapResource.PATH_SITEMAPS)
|
||||
@RolesAllowed({ Role.USER, Role.ADMIN })
|
||||
@Api(value = SitemapResource.PATH_SITEMAPS)
|
||||
public class SitemapResource implements RESTResource, SitemapSubscriptionCallback, BroadcasterListener<OutboundEvent> {
|
||||
@Api(SitemapResource.PATH_SITEMAPS)
|
||||
public class SitemapResource
|
||||
implements RESTResource, SitemapSubscriptionCallback, SseBroadcaster.Listener<SseSinkInfo> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SitemapResource.class);
|
||||
|
||||
@@ -127,7 +139,9 @@ public class SitemapResource implements RESTResource, SitemapSubscriptionCallbac
|
||||
|
||||
private static final long TIMEOUT_IN_MS = 30000;
|
||||
|
||||
private SseBroadcaster broadcaster;
|
||||
private @NonNullByDefault({}) Sse sse;
|
||||
|
||||
private SseBroadcaster<@NonNull SseSinkInfo> broadcaster;
|
||||
|
||||
@Context
|
||||
UriInfo uriInfo;
|
||||
@@ -146,7 +160,7 @@ public class SitemapResource implements RESTResource, SitemapSubscriptionCallbac
|
||||
|
||||
private final java.util.List<SitemapProvider> sitemapProviders = new ArrayList<>();
|
||||
|
||||
private final Map<String, EventOutput> eventOutputs = new MapMaker().weakValues().makeMap();
|
||||
private final Map<String, SseSinkInfo> knownSubscriptions = new MapMaker().weakValues().makeMap();
|
||||
|
||||
private final ScheduledExecutorService scheduler = ThreadPoolManager
|
||||
.getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON);
|
||||
@@ -155,8 +169,7 @@ public class SitemapResource implements RESTResource, SitemapSubscriptionCallbac
|
||||
|
||||
@Activate
|
||||
protected void activate() {
|
||||
broadcaster = new SseBroadcaster();
|
||||
broadcaster.add(this);
|
||||
broadcaster = new SseBroadcaster<>();
|
||||
|
||||
// The clean SSE subscriptions job sends an ALIVE event to all subscribers. This will trigger
|
||||
// an exception when the subscriber is dead, leading to the release of the SSE subscription
|
||||
@@ -179,8 +192,12 @@ public class SitemapResource implements RESTResource, SitemapSubscriptionCallbac
|
||||
cleanSubscriptionsJob.cancel(true);
|
||||
cleanSubscriptionsJob = null;
|
||||
}
|
||||
broadcaster.remove(this);
|
||||
broadcaster = null;
|
||||
broadcaster.close();
|
||||
}
|
||||
|
||||
@Context
|
||||
public void setSse(final Sse sse) {
|
||||
this.sse = sse;
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
|
||||
@@ -292,6 +309,7 @@ public class SitemapResource implements RESTResource, SitemapSubscriptionCallbac
|
||||
*/
|
||||
@POST
|
||||
@Path(SEGMENT_EVENTS + "/subscribe")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@ApiOperation(value = "Creates a sitemap event subscription.")
|
||||
@ApiResponses(value = { @ApiResponse(code = 201, message = "Subscription created."),
|
||||
@ApiResponse(code = 503, message = "Subscriptions limit reached.") })
|
||||
@@ -301,13 +319,15 @@ public class SitemapResource implements RESTResource, SitemapSubscriptionCallbac
|
||||
return JSONResponse.createResponse(Status.SERVICE_UNAVAILABLE, null,
|
||||
"Max number of subscriptions is reached.");
|
||||
}
|
||||
final EventOutput eventOutput = new SitemapEventOutput(subscriptions, subscriptionId);
|
||||
broadcaster.add(eventOutput);
|
||||
eventOutputs.put(subscriptionId, eventOutput);
|
||||
final SseSinkInfo sinkInfo = new SseSinkInfo(subscriptionId, subscriptions);
|
||||
knownSubscriptions.put(subscriptionId, sinkInfo);
|
||||
URI uri = uriInfo.getBaseUriBuilder().path(PATH_SITEMAPS).path(SEGMENT_EVENTS).path(subscriptionId).build();
|
||||
logger.debug("Client from IP {} requested new subscription => got id {}.", request.getRemoteAddr(),
|
||||
subscriptionId);
|
||||
return Response.created(uri);
|
||||
|
||||
// See https://github.com/openhab/openhab-core/issues/1216
|
||||
// return Response.created(uri).build();
|
||||
return JerseyResponseBuilderUtils.created(uri.toASCIIString());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,28 +337,31 @@ public class SitemapResource implements RESTResource, SitemapSubscriptionCallbac
|
||||
* connection.
|
||||
*/
|
||||
@GET
|
||||
@Path(SEGMENT_EVENTS + "/{subscriptionid: [a-zA-Z_0-9-]+}/")
|
||||
@Produces(SseFeature.SERVER_SENT_EVENTS)
|
||||
@ApiOperation(value = "Get sitemap events.", response = EventOutput.class)
|
||||
@Path(SEGMENT_EVENTS + "/{subscriptionid: [a-zA-Z_0-9-]+}")
|
||||
@Produces(MediaType.SERVER_SENT_EVENTS)
|
||||
@ApiOperation(value = "Get sitemap events.")
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK"),
|
||||
@ApiResponse(code = 400, message = "Page not linked to the subscription."),
|
||||
@ApiResponse(code = 404, message = "Subscription not found.") })
|
||||
public Object getSitemapEvents(
|
||||
public void getSitemapEvents(@Context final SseEventSink sseEventSink, @Context final HttpServletResponse response,
|
||||
@PathParam("subscriptionid") @ApiParam(value = "subscription id") String subscriptionId,
|
||||
@QueryParam("sitemap") @ApiParam(value = "sitemap name", required = false) String sitemapname,
|
||||
@QueryParam("pageid") @ApiParam(value = "page id", required = false) String pageId) {
|
||||
EventOutput eventOutput = eventOutputs.get(subscriptionId);
|
||||
if (!subscriptions.exists(subscriptionId) || eventOutput == null) {
|
||||
return JSONResponse.createResponse(Status.NOT_FOUND, null,
|
||||
"Subscription id " + subscriptionId + " does not exist.");
|
||||
final SseSinkInfo sinkInfo = knownSubscriptions.get(subscriptionId);
|
||||
if (sinkInfo == null) {
|
||||
logger.debug("Subscription id {} does not exist.", subscriptionId);
|
||||
response.setStatus(Status.NOT_FOUND.getStatusCode());
|
||||
return;
|
||||
}
|
||||
if (sitemapname != null && pageId != null) {
|
||||
subscriptions.setPageId(subscriptionId, sitemapname, pageId);
|
||||
}
|
||||
if (subscriptions.getSitemapName(subscriptionId) == null || subscriptions.getPageId(subscriptionId) == null) {
|
||||
return JSONResponse.createResponse(Status.BAD_REQUEST, null,
|
||||
"Subscription id " + subscriptionId + " is not yet linked to a sitemap/page.");
|
||||
logger.debug("Subscription id {} is not yet linked to a sitemap/page.", subscriptionId);
|
||||
response.setStatus(Status.BAD_REQUEST.getStatusCode());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Client from IP {} requested sitemap event stream for subscription {}.", request.getRemoteAddr(),
|
||||
subscriptionId);
|
||||
|
||||
@@ -346,7 +369,7 @@ public class SitemapResource implements RESTResource, SitemapSubscriptionCallbac
|
||||
// This allows you to not disable proxy buffering in nginx and still have working sse
|
||||
response.addHeader(X_ACCEL_BUFFERING_HEADER, "no");
|
||||
|
||||
return eventOutput;
|
||||
broadcaster.add(sseEventSink, sinkInfo);
|
||||
}
|
||||
|
||||
private PageDTO getPageBean(String sitemapName, String pageId, URI uri, Locale locale, boolean timeout,
|
||||
@@ -491,8 +514,8 @@ public class SitemapResource implements RESTResource, SitemapSubscriptionCallbac
|
||||
.substring(widget.eClass().getInstanceTypeName().lastIndexOf(".") + 1);
|
||||
boolean isMapview = "mapview".equalsIgnoreCase(widgetTypeName);
|
||||
Predicate<Item> itemFilter = (i -> CoreItemFactory.LOCATION.equals(i.getType()));
|
||||
bean.item = EnrichedItemDTOMapper.map(item, isMapview, itemFilter, UriBuilder.fromUri(uri).build(),
|
||||
locale);
|
||||
bean.item = EnrichedItemDTOMapper.map(item, isMapview, itemFilter,
|
||||
UriBuilder.fromUri(uri).path("{itemName}"), locale);
|
||||
bean.state = itemUIRegistry.getState(widget).toFullString();
|
||||
// In case the widget state is identical to the item state, its value is set to null.
|
||||
if (bean.state != null && bean.state.equals(bean.item.state)) {
|
||||
@@ -801,42 +824,49 @@ public class SitemapResource implements RESTResource, SitemapSubscriptionCallbac
|
||||
|
||||
@Override
|
||||
public void onEvent(SitemapEvent event) {
|
||||
OutboundEvent.Builder eventBuilder = new OutboundEvent.Builder();
|
||||
OutboundEvent outboundEvent = eventBuilder.name("event").mediaType(MediaType.APPLICATION_JSON_TYPE).data(event)
|
||||
.build();
|
||||
broadcaster.broadcast(outboundEvent);
|
||||
final Sse sse = this.sse;
|
||||
if (sse == null) {
|
||||
logger.trace("broadcast skipped (no one listened since activation)");
|
||||
return;
|
||||
}
|
||||
|
||||
final OutboundSseEvent outboundSseEvent = sse.newEventBuilder().name("event")
|
||||
.mediaType(MediaType.APPLICATION_JSON_TYPE).data(event).build();
|
||||
broadcaster.sendIf(outboundSseEvent, info -> {
|
||||
String sitemapName = event.sitemapName;
|
||||
String pageId = event.pageId;
|
||||
if (sitemapName != null && sitemapName.equals(subscriptions.getSitemapName(info.subscriptionId))
|
||||
&& pageId != null && pageId.equals(subscriptions.getPageId(info.subscriptionId))) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (event instanceof SitemapWidgetEvent) {
|
||||
logger.debug("Sent sitemap event for widget {} to subscription {}.",
|
||||
((SitemapWidgetEvent) event).widgetId, info.subscriptionId);
|
||||
} else if (event instanceof ServerAliveEvent) {
|
||||
logger.debug("Sent alive event to subscription {}.", info.subscriptionId);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelease(String subscriptionId) {
|
||||
logger.debug("SSE connection for subscription {} has been released.", subscriptionId);
|
||||
EventOutput eventOutput = eventOutputs.remove(subscriptionId);
|
||||
if (eventOutput != null) {
|
||||
broadcaster.remove(eventOutput);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(ChunkedOutput<OutboundEvent> event) {
|
||||
if (event instanceof SitemapEventOutput) {
|
||||
SitemapEventOutput sitemapEvent = (SitemapEventOutput) event;
|
||||
logger.debug("SSE connection for subscription {} has been closed.", sitemapEvent.getSubscriptionId());
|
||||
subscriptions.removeSubscription(sitemapEvent.getSubscriptionId());
|
||||
EventOutput eventOutput = eventOutputs.remove(sitemapEvent.getSubscriptionId());
|
||||
if (eventOutput != null) {
|
||||
broadcaster.remove(eventOutput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(ChunkedOutput<OutboundEvent> event, Exception e) {
|
||||
// the exception is usually "null" and onClose() is automatically called afterwards
|
||||
// - so let's don't do anything in this method.
|
||||
broadcaster.closeAndRemoveIf(info -> info.subscriptionId.equals(subscriptionId));
|
||||
knownSubscriptions.remove(subscriptionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSatisfied() {
|
||||
return itemUIRegistry != null && subscriptions != null && localeService != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sseEventSinkRemoved(SseEventSink sink, SseSinkInfo info) {
|
||||
logger.debug("SSE connection for subscription {} has been closed.", info.subscriptionId);
|
||||
subscriptions.removeSubscription(info.subscriptionId);
|
||||
knownSubscriptions.remove(info.subscriptionId);
|
||||
}
|
||||
}
|
||||
|
||||
+33
@@ -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.io.rest.sitemap.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.io.rest.sitemap.SitemapSubscriptionService;
|
||||
|
||||
/**
|
||||
* The specific information we need to hold for a SSE sink.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SseSinkInfo {
|
||||
|
||||
public final String subscriptionId;
|
||||
public final SitemapSubscriptionService subscriptions;
|
||||
|
||||
public SseSinkInfo(String subscriptionId, SitemapSubscriptionService subscriptions) {
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.subscriptions = subscriptions;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
Bundle-SymbolicName: ${project.artifactId}
|
||||
Bundle-Activator: org.openhab.core.io.rest.sse.internal.SseActivator
|
||||
@@ -17,7 +17,7 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core</artifactId>
|
||||
<artifactId>org.openhab.core.io.rest</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
+118
-96
@@ -12,14 +12,17 @@
|
||||
*/
|
||||
package org.openhab.core.io.rest.sse;
|
||||
|
||||
import static org.openhab.core.io.rest.sse.internal.SseSinkItemInfo.*;
|
||||
import static org.openhab.core.io.rest.sse.internal.SseSinkTopicInfo.matchesTopic;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
import javax.inject.Singleton;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
@@ -29,29 +32,37 @@ import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
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;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.sse.OutboundSseEvent;
|
||||
import javax.ws.rs.sse.Sse;
|
||||
import javax.ws.rs.sse.SseEventSink;
|
||||
|
||||
import org.glassfish.jersey.media.sse.EventOutput;
|
||||
import org.glassfish.jersey.media.sse.OutboundEvent;
|
||||
import org.glassfish.jersey.media.sse.SseBroadcaster;
|
||||
import org.glassfish.jersey.media.sse.SseFeature;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.auth.Role;
|
||||
import org.openhab.core.common.NamedThreadFactory;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.io.rest.sse.internal.ItemStatesSseBroadcaster;
|
||||
import org.openhab.core.io.rest.sse.internal.SseEventOutput;
|
||||
import org.openhab.core.io.rest.sse.internal.SseStateEventOutput;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.SseBroadcaster;
|
||||
import org.openhab.core.io.rest.sse.internal.SseItemStatesEventBuilder;
|
||||
import org.openhab.core.io.rest.sse.internal.SsePublisher;
|
||||
import org.openhab.core.io.rest.sse.internal.SseSinkItemInfo;
|
||||
import org.openhab.core.io.rest.sse.internal.SseSinkTopicInfo;
|
||||
import org.openhab.core.io.rest.sse.internal.dto.EventDTO;
|
||||
import org.openhab.core.io.rest.sse.internal.util.SseUtil;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.events.ItemStateChangedEvent;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import io.swagger.annotations.ApiResponse;
|
||||
@@ -63,75 +74,69 @@ import io.swagger.annotations.ApiResponses;
|
||||
* @author Ivan Iliev - Initial contribution
|
||||
* @author Yordan Zhelev - Added Swagger annotations
|
||||
* @author Yannick Schaus - Add endpoints to track item state updates
|
||||
* @author Markus Rathgeb - Drop Glassfish dependency and use API only
|
||||
* @author Wouter Born - Rework SSE item state sinks for dropping Glassfish
|
||||
*/
|
||||
@Component(immediate = true, service = SseResource.class)
|
||||
@Component(service = SsePublisher.class)
|
||||
@JaxrsResource
|
||||
@JaxrsName(SseResource.PATH_EVENTS)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(SseResource.PATH_EVENTS)
|
||||
@RolesAllowed({ Role.USER })
|
||||
@Singleton
|
||||
@Api(value = SseResource.PATH_EVENTS, hidden = true)
|
||||
public class SseResource {
|
||||
@NonNullByDefault
|
||||
public class SseResource implements SsePublisher {
|
||||
|
||||
// The URI path to this resource
|
||||
public static final String PATH_EVENTS = "events";
|
||||
|
||||
private static final String X_ACCEL_BUFFERING_HEADER = "X-Accel-Buffering";
|
||||
|
||||
private SseBroadcaster broadcaster;
|
||||
private ItemStatesSseBroadcaster statesBroadcaster;
|
||||
private final Logger logger = LoggerFactory.getLogger(SseResource.class);
|
||||
|
||||
private @NonNullByDefault({}) Sse sse;
|
||||
|
||||
private final SseBroadcaster<SseSinkItemInfo> itemStatesBroadcaster = new SseBroadcaster<>();
|
||||
private final SseItemStatesEventBuilder itemStatesEventBuilder;
|
||||
private final SseBroadcaster<SseSinkTopicInfo> topicBroadcaster = new SseBroadcaster<>();
|
||||
|
||||
private ExecutorService executorService;
|
||||
|
||||
@Reference
|
||||
private ItemRegistry itemRegistry;
|
||||
|
||||
@Context
|
||||
private UriInfo uriInfo;
|
||||
|
||||
@Context
|
||||
private HttpServletResponse response;
|
||||
|
||||
@Context
|
||||
private HttpServletRequest request;
|
||||
public void setSse(final Sse sse) {
|
||||
this.sse = sse;
|
||||
}
|
||||
|
||||
@Activate
|
||||
public void activate() {
|
||||
this.executorService = Executors.newSingleThreadExecutor(new NamedThreadFactory("SseResource"));
|
||||
this.broadcaster = new SseBroadcaster();
|
||||
this.statesBroadcaster = new ItemStatesSseBroadcaster(itemRegistry);
|
||||
public SseResource(@Reference SseItemStatesEventBuilder itemStatesEventBuilder) {
|
||||
this.executorService = Executors.newSingleThreadExecutor();
|
||||
this.itemStatesEventBuilder = itemStatesEventBuilder;
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
this.executorService.shutdown();
|
||||
this.broadcaster = null;
|
||||
this.statesBroadcaster = null;
|
||||
itemStatesBroadcaster.close();
|
||||
topicBroadcaster.close();
|
||||
executorService.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes the connecting client to the stream of events filtered by the
|
||||
* given eventFilter.
|
||||
*
|
||||
* @param eventFilter
|
||||
* @return {@link EventOutput} object associated with the incoming
|
||||
* connection.
|
||||
* @throws IOException
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
@GET
|
||||
@Produces(SseFeature.SERVER_SENT_EVENTS)
|
||||
@ApiOperation(value = "Get all events.", response = EventOutput.class)
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK"),
|
||||
@ApiResponse(code = 400, message = "Topic is empty or contains invalid characters") })
|
||||
public Object getEvents(@QueryParam("topics") @ApiParam(value = "topics") String eventFilter)
|
||||
throws IOException, InterruptedException {
|
||||
if (!SseUtil.isValidTopicFilter(eventFilter)) {
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
@Override
|
||||
public void broadcast(Event event) {
|
||||
if (sse == null) {
|
||||
logger.trace("broadcast skipped (no one listened since activation)");
|
||||
return;
|
||||
}
|
||||
|
||||
// construct an EventOutput that will only write out events that match
|
||||
// the given filter
|
||||
final EventOutput eventOutput = new SseEventOutput(eventFilter);
|
||||
broadcaster.add(eventOutput);
|
||||
executorService.execute(() -> {
|
||||
handleEventBroadcastTopic(event);
|
||||
if (event instanceof ItemStateChangedEvent) {
|
||||
handleEventBroadcastItemState((ItemStateChangedEvent) event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addCommonResponseHeaders(final HttpServletResponse response) {
|
||||
// Disables proxy buffering when using an nginx http server proxy for this response.
|
||||
// This allows you to not disable proxy buffering in nginx and still have working sse
|
||||
response.addHeader(X_ACCEL_BUFFERING_HEADER, "no");
|
||||
@@ -140,36 +145,57 @@ public class SseResource {
|
||||
// events at the moment of sending them.
|
||||
response.addHeader(HttpHeaders.CONTENT_ENCODING, "identity");
|
||||
|
||||
return eventOutput;
|
||||
try {
|
||||
response.flushBuffer();
|
||||
} catch (final IOException ex) {
|
||||
logger.trace("flush buffer failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.SERVER_SENT_EVENTS)
|
||||
@ApiOperation(value = "Get all events.")
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK"),
|
||||
@ApiResponse(code = 400, message = "Topic is empty or contains invalid characters") })
|
||||
public void listen(@Context final SseEventSink sseEventSink, @Context final HttpServletResponse response,
|
||||
@QueryParam("topics") @ApiParam(value = "topics") String eventFilter) {
|
||||
if (!SseUtil.isValidTopicFilter(eventFilter)) {
|
||||
response.setStatus(Status.BAD_REQUEST.getStatusCode());
|
||||
return;
|
||||
}
|
||||
|
||||
topicBroadcaster.add(sseEventSink, new SseSinkTopicInfo(eventFilter));
|
||||
|
||||
addCommonResponseHeaders(response);
|
||||
}
|
||||
|
||||
private void handleEventBroadcastTopic(Event event) {
|
||||
final EventDTO eventDTO = SseUtil.buildDTO(event);
|
||||
final OutboundSseEvent sseEvent = SseUtil.buildEvent(sse.newEventBuilder(), eventDTO);
|
||||
|
||||
topicBroadcaster.sendIf(sseEvent, matchesTopic(eventDTO.topic));
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes the connecting client for state updates. It will initially only send a "ready" event with an unique
|
||||
* connectionId that the client can use to dynamically alter the list of tracked items.
|
||||
*
|
||||
* @return {@link EventOutput} object associated with the incoming
|
||||
* connection.
|
||||
* @throws IOException
|
||||
* @throws InterruptedException
|
||||
* @return {@link EventOutput} object associated with the incoming connection.
|
||||
*/
|
||||
@GET
|
||||
@Path("/states")
|
||||
@Produces(SseFeature.SERVER_SENT_EVENTS)
|
||||
@ApiOperation(value = "Initiates a new item state tracker connection", response = EventOutput.class)
|
||||
@Produces(MediaType.SERVER_SENT_EVENTS)
|
||||
@ApiOperation(value = "Initiates a new item state tracker connection")
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK") })
|
||||
public Object getStateEvents() throws IOException, InterruptedException {
|
||||
final SseStateEventOutput eventOutput = new SseStateEventOutput();
|
||||
statesBroadcaster.add(eventOutput);
|
||||
public void getStateEvents(@Context final SseEventSink sseEventSink, @Context final HttpServletResponse response) {
|
||||
final SseSinkItemInfo sinkItemInfo = new SseSinkItemInfo();
|
||||
itemStatesBroadcaster.add(sseEventSink, sinkItemInfo);
|
||||
|
||||
// Disables proxy buffering when using an nginx http server proxy for this response.
|
||||
// This allows you to not disable proxy buffering in nginx and still have working sse
|
||||
response.addHeader(X_ACCEL_BUFFERING_HEADER, "no");
|
||||
addCommonResponseHeaders(response);
|
||||
|
||||
// We want to make sure that the response is not compressed and buffered so that the client receives server sent
|
||||
// events at the moment of sending them.
|
||||
response.addHeader(HttpHeaders.CONTENT_ENCODING, "identity");
|
||||
|
||||
return eventOutput;
|
||||
String connectionId = sinkItemInfo.getConnectionId();
|
||||
OutboundSseEvent readyEvent = sse.newEventBuilder().id("0").name("ready").data(connectionId).build();
|
||||
itemStatesBroadcaster.sendIf(readyEvent, hasConnectionId(connectionId));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,26 +211,20 @@ public class SseResource {
|
||||
@ApiResponse(code = 404, message = "Unknown connectionId") })
|
||||
public Object updateTrackedItems(@PathParam("connectionId") String connectionId,
|
||||
@ApiParam("items") Set<String> itemNames) {
|
||||
try {
|
||||
statesBroadcaster.updateTrackedItems(connectionId, itemNames);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Optional<SseSinkItemInfo> itemStateInfo = itemStatesBroadcaster.getInfoIf(hasConnectionId(connectionId))
|
||||
.findFirst();
|
||||
if (!itemStateInfo.isPresent()) {
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
return Response.ok().build();
|
||||
}
|
||||
itemStateInfo.get().updateTrackedItems(itemNames);
|
||||
|
||||
/**
|
||||
* Broadcasts an event described by the given parameter to all currently
|
||||
* listening clients.
|
||||
*
|
||||
* @param sseEventType the SSE event type
|
||||
* @param event the event
|
||||
*/
|
||||
public void broadcastEvent(final Event event) {
|
||||
executorService.execute(() -> {
|
||||
broadcaster.broadcast(SseUtil.buildEvent(event));
|
||||
});
|
||||
OutboundSseEvent itemStateEvent = itemStatesEventBuilder.buildEvent(sse.newEventBuilder(), itemNames);
|
||||
if (itemStateEvent != null) {
|
||||
itemStatesBroadcaster.sendIf(itemStateEvent, hasConnectionId(connectionId));
|
||||
}
|
||||
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,12 +232,14 @@ public class SseResource {
|
||||
*
|
||||
* @param stateChangeEvent the {@link ItemStateChangedEvent} containing the new state
|
||||
*/
|
||||
public void broadcastStateEvent(final ItemStateChangedEvent stateChangeEvent) {
|
||||
executorService.execute(() -> {
|
||||
OutboundEvent event = statesBroadcaster.buildStateEvent(Set.of(stateChangeEvent.getItemName()));
|
||||
public void handleEventBroadcastItemState(final ItemStateChangedEvent stateChangeEvent) {
|
||||
String itemName = stateChangeEvent.getItemName();
|
||||
boolean isTracked = itemStatesBroadcaster.getInfoIf(info -> true).anyMatch(tracksItem(itemName));
|
||||
if (isTracked) {
|
||||
OutboundSseEvent event = itemStatesEventBuilder.buildEvent(sse.newEventBuilder(), Set.of(itemName));
|
||||
if (event != null) {
|
||||
statesBroadcaster.broadcast(event);
|
||||
itemStatesBroadcaster.sendIf(event, tracksItem(itemName));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-110
@@ -1,110 +0,0 @@
|
||||
/**
|
||||
* 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.io.rest.sse.internal;
|
||||
|
||||
import javax.ws.rs.ext.RuntimeDelegate;
|
||||
|
||||
import org.glassfish.jersey.media.sse.SseFeature;
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Bundle activator for openHAB SSE bundle.
|
||||
*
|
||||
* @author Ivan Iliev - Initial contribution
|
||||
*/
|
||||
public class SseActivator implements BundleActivator {
|
||||
|
||||
// See: https://github.com/openhab/openhab-core/issues/597
|
||||
private static class WorkAroundIssue597 {
|
||||
public static RuntimeDelegate getRuntimeDelegate(final long millisMax, final long millisSleep)
|
||||
throws InterruptedException {
|
||||
final Logger logger = LoggerFactory.getLogger(WorkAroundIssue597.class);
|
||||
|
||||
logger.trace("get runtime delegate");
|
||||
final long begMillis = System.currentTimeMillis();
|
||||
do {
|
||||
try {
|
||||
final RuntimeDelegate runtimeDelegate = RuntimeDelegate.getInstance();
|
||||
logger.trace("succeeded");
|
||||
return runtimeDelegate;
|
||||
} catch (final LinkageError ex) {
|
||||
logger.trace("linkage error");
|
||||
if (System.currentTimeMillis() - begMillis <= millisMax) {
|
||||
Thread.sleep(millisSleep);
|
||||
}
|
||||
}
|
||||
} while (System.currentTimeMillis() - begMillis <= millisMax);
|
||||
logger.trace("give up");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SseActivator.class);
|
||||
|
||||
private static BundleContext context;
|
||||
|
||||
private ServiceRegistration<?> sseFeatureRegistration;
|
||||
|
||||
private ServiceRegistration<?> blockingAsyncFeatureRegistration;
|
||||
|
||||
/**
|
||||
* Called whenever the OSGi framework starts our bundle
|
||||
*/
|
||||
@Override
|
||||
public void start(BundleContext bc) throws Exception {
|
||||
context = bc;
|
||||
|
||||
WorkAroundIssue597.getRuntimeDelegate(5000, 200);
|
||||
String featureName = SseFeature.class.getName();
|
||||
if (bc.getServiceReference(featureName) == null) {
|
||||
sseFeatureRegistration = bc.registerService(featureName, new SseFeature(), null);
|
||||
|
||||
logger.debug("SSE API - SseFeature registered.");
|
||||
}
|
||||
|
||||
logger.debug("SSE API has been started.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the OSGi framework stops our bundle
|
||||
*/
|
||||
@Override
|
||||
public void stop(BundleContext bc) throws Exception {
|
||||
context = null;
|
||||
|
||||
if (sseFeatureRegistration != null) {
|
||||
sseFeatureRegistration.unregister();
|
||||
logger.debug("SseFeature unregistered.");
|
||||
}
|
||||
|
||||
if (blockingAsyncFeatureRegistration != null) {
|
||||
blockingAsyncFeatureRegistration.unregister();
|
||||
logger.debug("BlockingAsyncFeature unregistered.");
|
||||
}
|
||||
|
||||
logger.debug("SSE API has been stopped.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bundle context of this bundle
|
||||
*
|
||||
* @return the bundle context
|
||||
*/
|
||||
public static BundleContext getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
* 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.io.rest.sse.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.glassfish.jersey.media.sse.EventOutput;
|
||||
import org.glassfish.jersey.media.sse.OutboundEvent;
|
||||
import org.openhab.core.io.rest.sse.beans.EventBean;
|
||||
import org.openhab.core.io.rest.sse.internal.util.SseUtil;
|
||||
|
||||
/**
|
||||
* {@link EventOutput} implementation that takes a filter parameter and only writes out events that match this filter.
|
||||
* Should only be used when the {@link OutboundEvent}s sent through this {@link EventOutput} contain a data object of
|
||||
* type {@link EventBean}
|
||||
*
|
||||
* @author Ivan Iliev - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class SseEventOutput extends EventOutput {
|
||||
|
||||
private List<String> regexFilters;
|
||||
|
||||
public SseEventOutput(String topicFilter) {
|
||||
super();
|
||||
this.regexFilters = SseUtil.convertToRegex(topicFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutboundEvent chunk) throws IOException {
|
||||
EventBean event = (EventBean) chunk.getData();
|
||||
|
||||
for (String filter : regexFilters) {
|
||||
if (event.topic.matches(filter)) {
|
||||
super.write(chunk);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
-97
@@ -12,7 +12,6 @@
|
||||
*/
|
||||
package org.openhab.core.io.rest.sse.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.DateTimeException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
@@ -21,10 +20,12 @@ import java.util.Set;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.sse.OutboundSseEvent;
|
||||
import javax.ws.rs.sse.OutboundSseEvent.Builder;
|
||||
|
||||
import org.glassfish.jersey.media.sse.OutboundEvent;
|
||||
import org.glassfish.jersey.media.sse.SseBroadcaster;
|
||||
import org.glassfish.jersey.server.ChunkedOutput;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.rest.sse.internal.dto.StateDTO;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
@@ -37,123 +38,61 @@ import org.openhab.core.types.StateDescription;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.openhab.core.types.util.UnitUtils;
|
||||
import org.osgi.framework.BundleContext;
|
||||
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;
|
||||
|
||||
/**
|
||||
* This {@link SseBroadcaster} keeps track of the {@link SseStateEventOutput} listeners to state changes and tracks them
|
||||
* by their connectionId.
|
||||
* The {@link SseItemStatesEventBuilder} builds {@link OutboundSseEvent}s for connections that listen to item state
|
||||
* changes.
|
||||
*
|
||||
* @author Yannick Schaus - initial contribution
|
||||
* @author Yannick Schaus - Initial contribution
|
||||
* @author Wouter Born - Rework SSE item state sinks for dropping Glassfish
|
||||
*/
|
||||
public class ItemStatesSseBroadcaster extends SseBroadcaster {
|
||||
@Component(service = SseItemStatesEventBuilder.class)
|
||||
@NonNullByDefault
|
||||
public class SseItemStatesEventBuilder {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ItemStatesSseBroadcaster.class);
|
||||
private final Logger logger = LoggerFactory.getLogger(SseItemStatesEventBuilder.class);
|
||||
|
||||
private Map<String, SseStateEventOutput> eventOutputs = new HashMap<>();
|
||||
private final BundleContext bundleContext;
|
||||
private final ItemRegistry itemRegistry;
|
||||
|
||||
private ItemRegistry itemRegistry;
|
||||
|
||||
public ItemStatesSseBroadcaster(ItemRegistry itemRegistry) {
|
||||
super();
|
||||
@Activate
|
||||
public SseItemStatesEventBuilder(final BundleContext bundleContext, final @Reference ItemRegistry itemRegistry) {
|
||||
this.bundleContext = bundleContext;
|
||||
this.itemRegistry = itemRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <OUT extends ChunkedOutput<OutboundEvent>> boolean add(OUT chunkedOutput) {
|
||||
if (chunkedOutput instanceof SseStateEventOutput) {
|
||||
SseStateEventOutput eventOutput = (SseStateEventOutput) chunkedOutput;
|
||||
OutboundEvent.Builder builder = new OutboundEvent.Builder();
|
||||
String connectionId = eventOutput.getConnectionId();
|
||||
try {
|
||||
eventOutputs.put(connectionId, eventOutput);
|
||||
eventOutput.writeDirect(builder.id("0").name("ready").data(connectionId).build());
|
||||
} catch (IOException e) {
|
||||
logger.error("Cannot write initial ready event to {}, discarding connection", connectionId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return super.add(chunkedOutput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <OUT extends ChunkedOutput<OutboundEvent>> boolean remove(OUT chunkedOutput) {
|
||||
eventOutputs.values().remove(chunkedOutput);
|
||||
return super.remove(chunkedOutput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(ChunkedOutput<OutboundEvent> chunkedOutput) {
|
||||
remove(chunkedOutput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(ChunkedOutput<OutboundEvent> chunkedOutput, Exception exception) {
|
||||
remove(chunkedOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the list of tracked items for a connection
|
||||
*
|
||||
* @param connectionId the connection id
|
||||
* @param newTrackedItems the list of items and their current state to send to the client
|
||||
*/
|
||||
public void updateTrackedItems(String connectionId, Set<String> newTrackedItems) {
|
||||
SseStateEventOutput eventOutput = eventOutputs.get(connectionId);
|
||||
|
||||
if (eventOutput == null) {
|
||||
throw new IllegalArgumentException("ConnectionId not found");
|
||||
}
|
||||
|
||||
eventOutput.setTrackedItems(newTrackedItems);
|
||||
|
||||
try {
|
||||
if (!eventOutput.isClosed()) {
|
||||
OutboundEvent event = buildStateEvent(newTrackedItems);
|
||||
if (event != null) {
|
||||
eventOutput.writeDirect(event);
|
||||
}
|
||||
}
|
||||
if (eventOutput.isClosed()) {
|
||||
onClose(eventOutput);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
onException(eventOutput, e);
|
||||
}
|
||||
}
|
||||
|
||||
public OutboundEvent buildStateEvent(Set<String> itemNames) {
|
||||
Map<String, StateDTO> payload = new HashMap<>();
|
||||
public @Nullable OutboundSseEvent buildEvent(Builder eventBuilder, Set<String> itemNames) {
|
||||
Map<String, StateDTO> payload = new HashMap<>(itemNames.size());
|
||||
for (String itemName : itemNames) {
|
||||
try {
|
||||
// Check that the item is tracked by at least one connection
|
||||
if (eventOutputs.values().stream().anyMatch(c -> c.getTrackedItems().contains(itemName))) {
|
||||
Item item = itemRegistry.getItem(itemName);
|
||||
StateDTO stateDto = new StateDTO();
|
||||
stateDto.state = item.getState().toString();
|
||||
String displayState = getDisplayState(item, Locale.getDefault());
|
||||
// Only include the display state if it's different than the raw state
|
||||
if (stateDto.state != null && !stateDto.state.equals(displayState)) {
|
||||
stateDto.displayState = displayState;
|
||||
}
|
||||
payload.put(itemName, stateDto);
|
||||
Item item = itemRegistry.getItem(itemName);
|
||||
StateDTO stateDto = new StateDTO();
|
||||
stateDto.state = item.getState().toString();
|
||||
String displayState = getDisplayState(item, Locale.getDefault());
|
||||
// Only include the display state if it's different than the raw state
|
||||
if (stateDto.state != null && !stateDto.state.equals(displayState)) {
|
||||
stateDto.displayState = displayState;
|
||||
}
|
||||
payload.put(itemName, stateDto);
|
||||
} catch (ItemNotFoundException e) {
|
||||
logger.warn("Attempting to send a state update of an item which doesn't exist: {}", itemName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!payload.isEmpty()) {
|
||||
OutboundEvent.Builder builder = new OutboundEvent.Builder();
|
||||
OutboundEvent event = builder.mediaType(MediaType.APPLICATION_JSON_TYPE).data(payload).build();
|
||||
return event;
|
||||
return eventBuilder.mediaType(MediaType.APPLICATION_JSON_TYPE).data(payload).build();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getDisplayState(Item item, Locale locale) {
|
||||
private @Nullable String getDisplayState(Item item, Locale locale) {
|
||||
StateDescription stateDescription = item.getStateDescription(locale);
|
||||
State state = item.getState();
|
||||
String displayState = state.toString();
|
||||
@@ -174,8 +113,7 @@ public class ItemStatesSseBroadcaster extends SseBroadcaster {
|
||||
if (pattern != null) {
|
||||
if (TransformationHelper.isTransform(pattern)) {
|
||||
try {
|
||||
displayState = TransformationHelper.transform(SseActivator.getContext(), pattern,
|
||||
state.toString());
|
||||
displayState = TransformationHelper.transform(bundleContext, pattern, state.toString());
|
||||
} catch (NoClassDefFoundError ex) {
|
||||
// TransformationHelper is optional dependency, so ignore if class not found
|
||||
// return state as it is without transformation
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.io.rest.sse.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.events.Event;
|
||||
|
||||
/**
|
||||
* Marker interface for a publisher of events using SSE.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SsePublisher {
|
||||
void broadcast(final Event event);
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.io.rest.sse.internal;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The specific information we need to hold for a SSE sink which tracks item state updates.
|
||||
*
|
||||
* @author Wouter Born - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SseSinkItemInfo {
|
||||
|
||||
private final String connectionId = UUID.randomUUID().toString();
|
||||
private final Set<String> trackedItems = new CopyOnWriteArraySet<>();
|
||||
|
||||
/**
|
||||
* Gets the connection identifier of this {@link SseSinkItemInfo}
|
||||
*
|
||||
* @return the connection id
|
||||
*/
|
||||
public String getConnectionId() {
|
||||
return connectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the list of tracked items for a connection
|
||||
*
|
||||
* @param itemNames the item names to track
|
||||
*/
|
||||
public void updateTrackedItems(Set<String> itemNames) {
|
||||
trackedItems.clear();
|
||||
trackedItems.addAll(itemNames);
|
||||
}
|
||||
|
||||
public static Predicate<SseSinkItemInfo> hasConnectionId(String connectionId) {
|
||||
return info -> info.connectionId.equals(connectionId);
|
||||
}
|
||||
|
||||
public static Predicate<SseSinkItemInfo> tracksItem(String itemName) {
|
||||
return info -> info.trackedItems.contains(itemName);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.io.rest.sse.internal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.io.rest.sse.internal.util.SseUtil;
|
||||
|
||||
/**
|
||||
* The specific information we need to hold for a SSE sink which subscribes to event topics.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SseSinkTopicInfo {
|
||||
|
||||
private final List<String> regexFilters;
|
||||
|
||||
public SseSinkTopicInfo(String topicFilter) {
|
||||
this.regexFilters = SseUtil.convertToRegex(topicFilter);
|
||||
}
|
||||
|
||||
public static Predicate<SseSinkTopicInfo> matchesTopic(final String topic) {
|
||||
return info -> info.regexFilters.stream().anyMatch(topic::matches);
|
||||
}
|
||||
}
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
* 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.io.rest.sse.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.glassfish.jersey.media.sse.EventOutput;
|
||||
import org.glassfish.jersey.media.sse.OutboundEvent;
|
||||
|
||||
/**
|
||||
* {@link EventOutput} implementation that takes a list of item names writes out state change events that match this
|
||||
* list. Should only be used when the {@link OutboundEvent}s sent through this {@link EventOutput} contain a map of item
|
||||
* names and states.
|
||||
*
|
||||
* It also uniquely identifies the connection with a random identifier.
|
||||
*
|
||||
* @author Yannick Schaus - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class SseStateEventOutput extends EventOutput {
|
||||
|
||||
private String connectionId;
|
||||
private Collection<String> trackedItems = Collections.emptySet();
|
||||
|
||||
public SseStateEventOutput() {
|
||||
super();
|
||||
this.connectionId = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connection identifier of this {@link SseStateEventOutput}
|
||||
*
|
||||
* @return the connection id
|
||||
*/
|
||||
public String getConnectionId() {
|
||||
return connectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the list of tracked items for this connection
|
||||
*
|
||||
* @param itemNames the item names to track
|
||||
*/
|
||||
public void setTrackedItems(Collection<String> itemNames) {
|
||||
this.trackedItems = itemNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of tracked items for this connection
|
||||
*
|
||||
* @return a list of tracked item names
|
||||
*/
|
||||
protected Collection<String> getTrackedItems() {
|
||||
return trackedItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutboundEvent chunk) throws IOException {
|
||||
if (chunk.getData() instanceof Map<?, ?>) {
|
||||
Map<?, ?> event = (Map<?, ?>) chunk.getData();
|
||||
String itemName = event.keySet().iterator().next().toString();
|
||||
|
||||
if (trackedItems != null && trackedItems.contains(itemName)) {
|
||||
super.write(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an event without filtering
|
||||
*
|
||||
* @param chunk
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeDirect(OutboundEvent chunk) throws IOException {
|
||||
super.write(chunk);
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -10,15 +10,18 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.rest.sse.beans;
|
||||
package org.openhab.core.io.rest.sse.internal.dto;
|
||||
|
||||
import org.osgi.dto.DTO;
|
||||
|
||||
/**
|
||||
* Event bean for broadcasted events.
|
||||
*
|
||||
* @author Ivan Iliev - Initial contribution
|
||||
* @author Dennis Nobel - Added event type and renamed object to payload
|
||||
* @author Markus Rathgeb - Follow the Data Transfer Objects Specification
|
||||
*/
|
||||
public class EventBean {
|
||||
public class EventDTO extends DTO {
|
||||
|
||||
public String topic;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.rest.sse.internal;
|
||||
package org.openhab.core.io.rest.sse.internal.dto;
|
||||
|
||||
/**
|
||||
* A DTO class holding the state of an item, and its version suitable for display.
|
||||
+6
-11
@@ -20,9 +20,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventFilter;
|
||||
import org.openhab.core.events.EventSubscriber;
|
||||
import org.openhab.core.io.rest.sse.SseResource;
|
||||
import org.openhab.core.io.rest.sse.internal.ItemStatesSseBroadcaster;
|
||||
import org.openhab.core.items.events.ItemStateChangedEvent;
|
||||
import org.openhab.core.io.rest.sse.internal.SsePublisher;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
@@ -32,7 +30,7 @@ import org.osgi.service.component.annotations.Reference;
|
||||
* to currently listening SSE clients.
|
||||
*
|
||||
* @author Stefan Bußweiler - Initial contribution
|
||||
* @author Yannick Schaus - Broadcast state events to the specialized {@link ItemStatesSseBroadcaster}
|
||||
* @author Yannick Schaus - Broadcast state events to the specialized ItemStatesSseBroadcaster
|
||||
*/
|
||||
@Component
|
||||
@NonNullByDefault
|
||||
@@ -40,11 +38,11 @@ public class SseEventSubscriber implements EventSubscriber {
|
||||
|
||||
private final Set<String> subscribedEventTypes = Collections.singleton(EventSubscriber.ALL_EVENT_TYPES);
|
||||
|
||||
private final SseResource sseResource;
|
||||
private final SsePublisher ssePublisher;
|
||||
|
||||
@Activate
|
||||
public SseEventSubscriber(final @Reference SseResource sseResource) {
|
||||
this.sseResource = sseResource;
|
||||
public SseEventSubscriber(final @Reference SsePublisher ssePublisher) {
|
||||
this.ssePublisher = ssePublisher;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,9 +57,6 @@ public class SseEventSubscriber implements EventSubscriber {
|
||||
|
||||
@Override
|
||||
public void receive(Event event) {
|
||||
sseResource.broadcastEvent(event);
|
||||
if (event instanceof ItemStateChangedEvent) {
|
||||
sseResource.broadcastStateEvent((ItemStateChangedEvent) event);
|
||||
}
|
||||
ssePublisher.broadcast(event);
|
||||
}
|
||||
}
|
||||
|
||||
+25
-17
@@ -17,39 +17,47 @@ import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.sse.OutboundSseEvent;
|
||||
|
||||
import org.glassfish.jersey.media.sse.OutboundEvent;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.io.rest.sse.beans.EventBean;
|
||||
import org.openhab.core.io.rest.sse.internal.dto.EventDTO;
|
||||
|
||||
/**
|
||||
* Utility class containing helper methods for the SSE implementation.
|
||||
*
|
||||
* @author Ivan Iliev - Initial contribution
|
||||
* @author Dennis Nobel - Changed EventBean
|
||||
* @author Markus Rathgeb - Don't depend on specific application but use APIs if possible
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SseUtil {
|
||||
static final String TOPIC_VALIDATE_PATTERN = "(\\w*\\*?\\/?,?:?-?\\s*)*";
|
||||
|
||||
public static EventDTO buildDTO(final Event event) {
|
||||
EventDTO dto = new EventDTO();
|
||||
dto.topic = event.getTopic();
|
||||
dto.type = event.getType();
|
||||
dto.payload = event.getPayload();
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link OutboundEvent} object containing an {@link EventBean} created for the given Eclipse
|
||||
* Creates a new {@link OutboundSseEvent} object containing an {@link EventDTO} created for the given Eclipse
|
||||
* SmartHome {@link Event}.
|
||||
*
|
||||
* @param event the event
|
||||
*
|
||||
* @param eventBuilder the builder that should be used
|
||||
* @param event the event data transfer object
|
||||
* @return a new OutboundEvent
|
||||
*/
|
||||
public static OutboundEvent buildEvent(Event event) {
|
||||
EventBean eventBean = new EventBean();
|
||||
eventBean.topic = event.getTopic();
|
||||
eventBean.type = event.getType();
|
||||
eventBean.payload = event.getPayload();
|
||||
public static OutboundSseEvent buildEvent(OutboundSseEvent.Builder eventBuilder, EventDTO event) {
|
||||
final OutboundSseEvent sseEvent = eventBuilder.name("message") //
|
||||
.mediaType(MediaType.APPLICATION_JSON_TYPE) //
|
||||
.data(event) //
|
||||
.build();
|
||||
|
||||
OutboundEvent.Builder eventBuilder = new OutboundEvent.Builder();
|
||||
OutboundEvent outboundEvent = eventBuilder.name("message").mediaType(MediaType.APPLICATION_JSON_TYPE)
|
||||
.data(eventBean).build();
|
||||
|
||||
return outboundEvent;
|
||||
return sseEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +67,7 @@ public class SseUtil {
|
||||
* @return true if the given input filter is empty or a valid topic filter string
|
||||
*
|
||||
*/
|
||||
public static boolean isValidTopicFilter(String topicFilter) {
|
||||
public static boolean isValidTopicFilter(@Nullable String topicFilter) {
|
||||
return topicFilter == null || topicFilter.isEmpty() || topicFilter.matches(TOPIC_VALIDATE_PATTERN);
|
||||
}
|
||||
|
||||
@@ -70,7 +78,7 @@ public class SseUtil {
|
||||
* @param topicFilter
|
||||
* @return
|
||||
*/
|
||||
public static List<String> convertToRegex(String topicFilter) {
|
||||
public static List<String> convertToRegex(@Nullable String topicFilter) {
|
||||
List<String> filters = new ArrayList<>();
|
||||
|
||||
if (topicFilter == null || topicFilter.isEmpty()) {
|
||||
|
||||
+7
-7
@@ -6,6 +6,13 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
@@ -16,12 +23,5 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.io.rest.optimize</name>
|
||||
<name>org.openhab.core.io.rest.swagger</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
+11
-3
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
@@ -10,8 +10,16 @@
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.io.rest.optimize</artifactId>
|
||||
<artifactId>org.openhab.core.io.rest.swagger</artifactId>
|
||||
|
||||
<name>openHAB Core :: Bundles :: REST JAX-RS Optimizations</name>
|
||||
<name>openHAB Core :: Bundles :: REST Interface :: Swagger 1</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.rest</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.io.rest.swagger.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.maggu2810.jaxrswb.swagger1.gen.JaxRsWhiteboardSwaggerGenerator;
|
||||
|
||||
/**
|
||||
* An endpoint to generate and provide a Swagger 1 description.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
@Component(service = RESTResource.class)
|
||||
@JaxrsResource
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path("/swagger.json")
|
||||
@NonNullByDefault
|
||||
public class RESTResource {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RESTResource.class);
|
||||
private final JaxRsWhiteboardSwaggerGenerator generator;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param generator the generator
|
||||
*/
|
||||
@Activate
|
||||
public RESTResource(final @Reference JaxRsWhiteboardSwaggerGenerator generator) {
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current JAX-RS Whiteboard provided endpoint information by Swagger 1.
|
||||
*
|
||||
* @return a Swagger 1 description of the endpoints
|
||||
*/
|
||||
@GET
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public Object getSwagger() {
|
||||
final Map<String, Object> map;
|
||||
try {
|
||||
map = generator.generateMap();
|
||||
} catch (final IOException ex) {
|
||||
logger.warn("Error on Swagger DTO generation.", ex);
|
||||
return Response.serverError().build();
|
||||
}
|
||||
return Response.status(Response.Status.OK).entity(map).build();
|
||||
}
|
||||
}
|
||||
+12
-5
@@ -30,6 +30,7 @@ import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import org.openhab.core.auth.Role;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
import org.openhab.core.io.rest.Stream2JSONInputStream;
|
||||
import org.openhab.core.io.rest.ui.TileDTO;
|
||||
@@ -42,8 +43,11 @@ 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;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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 io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -56,11 +60,14 @@ import io.swagger.annotations.ApiResponses;
|
||||
*
|
||||
* @author Yannick Schaus - Initial contribution
|
||||
*/
|
||||
@Path(UIResource.PATH_UI)
|
||||
@Api(value = UIResource.PATH_UI)
|
||||
@Component(service = { RESTResource.class, UIResource.class })
|
||||
@JaxrsResource
|
||||
@JaxrsName(UIResource.PATH_UI)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(UIResource.PATH_UI)
|
||||
@Api(UIResource.PATH_UI)
|
||||
public class UIResource implements RESTResource {
|
||||
private final Logger logger = LoggerFactory.getLogger(UIResource.class);
|
||||
|
||||
/** The URI path to this resource */
|
||||
public static final String PATH_UI = "ui";
|
||||
|
||||
+14
-3
@@ -36,6 +36,7 @@ import javax.ws.rs.core.UriInfo;
|
||||
import org.openhab.core.auth.Role;
|
||||
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.voice.Voice;
|
||||
import org.openhab.core.voice.VoiceManager;
|
||||
@@ -45,6 +46,11 @@ 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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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 io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -57,14 +63,19 @@ import io.swagger.annotations.ApiResponses;
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Laurent Garnier - add TTS feature to the REST API
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Component
|
||||
@Path(VoiceResource.PATH_SITEMAPS)
|
||||
@JaxrsResource
|
||||
@JaxrsName(VoiceResource.PATH_VOICE)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(VoiceResource.PATH_VOICE)
|
||||
@RolesAllowed({ Role.USER, Role.ADMIN })
|
||||
@Api(value = VoiceResource.PATH_SITEMAPS)
|
||||
@Api(VoiceResource.PATH_VOICE)
|
||||
public class VoiceResource implements RESTResource {
|
||||
|
||||
static final String PATH_SITEMAPS = "voice";
|
||||
static final String PATH_VOICE = "voice";
|
||||
|
||||
@Context
|
||||
UriInfo uriInfo;
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.io.rest;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Marker interface for an input stream that provides a JSON string.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface JSONInputStream {
|
||||
|
||||
}
|
||||
+13
-3
@@ -23,7 +23,6 @@ import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.slf4j.Logger;
|
||||
@@ -162,7 +161,7 @@ public class JSONResponse {
|
||||
try {
|
||||
// we will not actively close the PipedInputStream since it is read by the receiving end
|
||||
// and will be GC'ed once the response is consumed.
|
||||
PipedInputStream in = new PipedInputStream(out);
|
||||
PipedJSONInputStream in = new PipedJSONInputStream(out);
|
||||
rp.entity(in);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
@@ -183,12 +182,23 @@ public class JSONResponse {
|
||||
return rp.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* An piped input stream that is marked to produce JSON string.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
private static class PipedJSONInputStream extends PipedInputStream implements JSONInputStream {
|
||||
|
||||
public PipedJSONInputStream(PipedOutputStream src) throws IOException {
|
||||
super(src);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* trap exceptions
|
||||
*
|
||||
* @author Joerg Plewe
|
||||
*/
|
||||
@Provider
|
||||
public static class ExceptionMapper implements javax.ws.rs.ext.ExceptionMapper<Exception> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ExceptionMapper.class);
|
||||
|
||||
+3
-1
@@ -19,6 +19,8 @@ package org.openhab.core.io.rest;
|
||||
*/
|
||||
public class RESTConstants {
|
||||
|
||||
// URI path used for the REST API
|
||||
@Deprecated
|
||||
public static final String REST_URI = "/rest";
|
||||
|
||||
public static final String JAX_RS_NAME = "openhab";
|
||||
}
|
||||
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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.io.rest;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.ws.rs.sse.OutboundSseEvent;
|
||||
import javax.ws.rs.sse.SseEventSink;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* We do not use the SseBroadcaster as it seems registered SseEventSinks are not removed if the peer terminates the
|
||||
* connection.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*
|
||||
* @param <I> the type of the SSE event sink specific information
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SseBroadcaster<@NonNull I> implements Closeable {
|
||||
|
||||
public interface Listener<I> {
|
||||
void sseEventSinkRemoved(final SseEventSink sink, I info);
|
||||
}
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SseBroadcaster.class);
|
||||
|
||||
private final List<Listener<I>> listeners = new CopyOnWriteArrayList<>();
|
||||
private final Map<SseEventSink, I> sinks = new ConcurrentHashMap<>();
|
||||
|
||||
public void addListener(final Listener<I> listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(final Listener<I> listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
public @Nullable I add(final SseEventSink sink, final I info) {
|
||||
return sinks.put(sink, info);
|
||||
}
|
||||
|
||||
public @Nullable I remove(final SseEventSink sink) {
|
||||
return sinks.remove(sink);
|
||||
}
|
||||
|
||||
public @Nullable I getInfo(final SseEventSink sink) {
|
||||
return sinks.get(sink);
|
||||
}
|
||||
|
||||
public Stream<I> getInfoIf(Predicate<I> predicate) {
|
||||
return sinks.values().stream().filter(predicate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
final Iterator<Entry<SseEventSink, I>> it = sinks.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
final Entry<SseEventSink, I> entry = it.next();
|
||||
it.remove();
|
||||
close(entry.getKey());
|
||||
notifyAboutRemoval(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void send(final OutboundSseEvent event) {
|
||||
sendIf(event, info -> true);
|
||||
}
|
||||
|
||||
public void sendIf(final OutboundSseEvent event, Predicate<I> predicate) {
|
||||
logger.trace("broadcast to potential {} sinks", sinks.size());
|
||||
sinks.forEach((sink, info) -> {
|
||||
if (sink.isClosed()) {
|
||||
// We are using a concurrent collection, so we are allowed to modify the collection asynchronous (we
|
||||
// don't know if there is currently an iteration in progress or not, but it does not matter).
|
||||
handleRemoval(sink);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we should send at all.
|
||||
if (!predicate.test(info)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sink.send(event).exceptionally(th -> {
|
||||
close(sink);
|
||||
|
||||
// We are using a concurrent collection, so we are allowed to modify the collection asynchronous (we
|
||||
// don't know if there is currently an iteration in progress or not, but it does not matter).
|
||||
handleRemoval(sink);
|
||||
|
||||
final String thClass = th.getClass().toString();
|
||||
final String message = th.getMessage();
|
||||
|
||||
if (thClass.equals("class org.eclipse.jetty.io.EofException")) {
|
||||
// The peer terminates the connection.
|
||||
} else if (th instanceof IllegalStateException && message != null
|
||||
&& (message.equals("The sink is already closed, unable to queue SSE event for send")
|
||||
|| message.equals("AsyncContext completed and/or Request lifecycle recycled"))) {
|
||||
// java.lang.IllegalStateException: The sink is already closed, unable to queue SSE event for
|
||||
// send
|
||||
// java.lang.IllegalStateException: AsyncContext completed and/or Request lifecycle recycled
|
||||
} else {
|
||||
logger.warn("failure", th);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void closeAndRemoveIf(Predicate<I> predicate) {
|
||||
sinks.forEach((sink, info) -> {
|
||||
if (predicate.test(info)) {
|
||||
close(sink);
|
||||
|
||||
// We are using a concurrent collection, so we are allowed to modify the collection asynchronous (we
|
||||
// don't know if there is currently an iteration in progress or not, but it does not matter).
|
||||
handleRemoval(sink);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void close(final SseEventSink sink) {
|
||||
try {
|
||||
sink.close();
|
||||
} catch (final RuntimeException ex) {
|
||||
logger.debug("Closing a SSE event sink failed. Nothing we can do here...", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRemoval(final SseEventSink sink) {
|
||||
final I info = sinks.remove(sink);
|
||||
notifyAboutRemoval(sink, info);
|
||||
}
|
||||
|
||||
private void notifyAboutRemoval(final SseEventSink sink, I info) {
|
||||
listeners.forEach(listener -> {
|
||||
listener.sseEventSinkRemoved(sink, info);
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -31,7 +31,7 @@ import com.google.gson.GsonBuilder;
|
||||
*
|
||||
* @author Henning Treu - Initial contribution
|
||||
*/
|
||||
public class Stream2JSONInputStream extends InputStream {
|
||||
public class Stream2JSONInputStream extends InputStream implements JSONInputStream {
|
||||
|
||||
private final Iterator<String> iterator;
|
||||
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.io.rest.internal;
|
||||
|
||||
import javax.ws.rs.core.Application;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationBase;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName;
|
||||
|
||||
/**
|
||||
* The JAX-RS application for the openHAB JAX-RS resources.
|
||||
*
|
||||
* @author Markus Rathgeb - Initial contribution
|
||||
*/
|
||||
@Component(service = Application.class, property = {
|
||||
// https://lists.apache.org/thread.html/
|
||||
// r1379789bd90c6b7e3971d5ffeedb2e0d1e1c9103fd2392cb95458596%40%3Cuser.aries.apache.org%3E
|
||||
"servlet.init.hide-service-list-page=true" })
|
||||
@JaxrsName(RESTConstants.JAX_RS_NAME)
|
||||
@JaxrsApplicationBase("rest")
|
||||
@NonNullByDefault
|
||||
public class RESTApplicationImpl extends Application {
|
||||
}
|
||||
+8
-3
@@ -23,12 +23,15 @@ import javax.ws.rs.container.ContainerResponseContext;
|
||||
import javax.ws.rs.container.ContainerResponseFilter;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.internal.Constants;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.ConfigurationPolicy;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsExtension;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -41,10 +44,12 @@ import org.slf4j.LoggerFactory;
|
||||
* This implementation does not allow specific request/response headers nor cookies (allowCredentials).
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Provider
|
||||
@Component(immediate = true, property = {
|
||||
@Component(property = {
|
||||
"service.pid=org.openhab.core.cors" }, configurationPid = "org.openhab.cors", configurationPolicy = ConfigurationPolicy.REQUIRE)
|
||||
@JaxrsExtension
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
public class CorsFilter implements ContainerResponseFilter {
|
||||
|
||||
static final String HTTP_HEAD_METHOD = "HEAD";
|
||||
|
||||
+8
-3
@@ -23,9 +23,12 @@ import javax.ws.rs.container.PreMatching;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsExtension;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -34,10 +37,12 @@ import org.slf4j.LoggerFactory;
|
||||
* context if proxy headers are detected.
|
||||
*
|
||||
* @author Ivan Iliev - Initial contribution
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Provider
|
||||
@Component(configurationPid = "org.openhab.proxyfilter")
|
||||
@JaxrsExtension
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@PreMatching
|
||||
@Component(configurationPid = "org.openhab.proxyfilter", immediate = true, service = ProxyFilter.class)
|
||||
public class ProxyFilter implements ContainerRequestFilter {
|
||||
|
||||
static final String PROTO_PROXY_HEADER = "x-forwarded-proto";
|
||||
|
||||
+8
-3
@@ -20,10 +20,13 @@ import javax.ws.rs.container.ContainerRequestFilter;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsExtension;
|
||||
|
||||
/**
|
||||
* A filter that only affects resources implementing the {@link SatisfiableRESTResource} interface.
|
||||
@@ -32,9 +35,11 @@ import org.osgi.service.component.annotations.Component;
|
||||
* 503 - Service Unavailable.
|
||||
*
|
||||
* @author Ivan Iliev - Initial contribution
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Provider
|
||||
@Component(immediate = true, service = SatisfiableResourceFilter.class)
|
||||
@Component
|
||||
@JaxrsExtension
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
public class SatisfiableResourceFilter implements ContainerRequestFilter {
|
||||
|
||||
@Override
|
||||
|
||||
+62
-77
@@ -12,35 +12,41 @@
|
||||
*/
|
||||
package org.openhab.core.io.rest.internal.resources;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Dictionary;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
import org.openhab.core.io.rest.internal.Constants;
|
||||
import org.openhab.core.io.rest.internal.resources.beans.RootBean;
|
||||
import org.osgi.service.cm.Configuration;
|
||||
import org.osgi.service.cm.ConfigurationAdmin;
|
||||
import org.openhab.core.io.rest.internal.resources.beans.RootBean.Links;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
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.osgi.service.jaxrs.runtime.JaxrsServiceRuntime;
|
||||
import org.osgi.service.jaxrs.runtime.dto.ApplicationDTO;
|
||||
import org.osgi.service.jaxrs.runtime.dto.ResourceDTO;
|
||||
import org.osgi.service.jaxrs.runtime.dto.RuntimeDTO;
|
||||
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiResponse;
|
||||
import io.swagger.annotations.ApiResponses;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This class acts as an entry point / root resource for the REST API.
|
||||
@@ -52,82 +58,61 @@ import org.slf4j.LoggerFactory;
|
||||
* The result is returned as JSON
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Path("/")
|
||||
@Component(service = RootResource.class, configurationPid = "org.openhab.restroot")
|
||||
@JaxrsResource
|
||||
@JaxrsName(RootResource.RESOURCE_NAME)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NonNullByDefault
|
||||
@Path("/")
|
||||
@Api(RootResource.RESOURCE_NAME)
|
||||
public class RootResource {
|
||||
|
||||
private final transient Logger logger = LoggerFactory.getLogger(RootResource.class);
|
||||
public static final String RESOURCE_NAME = "root";
|
||||
|
||||
private final List<RESTResource> restResources = new ArrayList<>();
|
||||
private final Logger logger = LoggerFactory.getLogger(RootResource.class);
|
||||
private final JaxrsServiceRuntime runtime;
|
||||
|
||||
private ConfigurationAdmin configurationAdmin;
|
||||
|
||||
@Context
|
||||
UriInfo uriInfo;
|
||||
@Activate
|
||||
public RootResource(final @Reference JaxrsServiceRuntime runtime) {
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response getRoot(@Context HttpHeaders headers) {
|
||||
return Response.ok(getRootBean()).build();
|
||||
}
|
||||
@ApiOperation(value = "Gets the API version and links to resources.")
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK") })
|
||||
public Object getRoot(@Context UriInfo uriInfo) {
|
||||
// key: path, value: name (this way we could ensure that ever path is added only once).
|
||||
final Map<String, String> collectedLinks = new HashMap<>();
|
||||
|
||||
private RootBean getRootBean() {
|
||||
RootBean bean = new RootBean();
|
||||
final RuntimeDTO runtimeDTO = runtime.getRuntimeDTO();
|
||||
final RootBean bean = new RootBean();
|
||||
for (final ApplicationDTO applicationDTO : runtimeDTO.applicationDTOs) {
|
||||
for (final ResourceDTO resourceDTO : applicationDTO.resourceDTOs) {
|
||||
// We are using the JAX-RS name per convention for the link type.
|
||||
// Let's skip names that begin with a dot (e.g. the generated ones) and empty ones.
|
||||
final String name = resourceDTO.name;
|
||||
if (name == null || name.isEmpty() || name.startsWith(".") || RESOURCE_NAME.equals(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (RESTResource resource : restResources) {
|
||||
// we will include all RESTResources that are currently satisfied
|
||||
if (resource.isSatisfied()) {
|
||||
String path = resource.getClass().getAnnotation(Path.class).value();
|
||||
bean.links
|
||||
.add(new RootBean.Links(path, uriInfo.getBaseUriBuilder().path(path).build().toASCIIString()));
|
||||
// The path is provided for every resource method by the respective info DTO.
|
||||
// We don't want to add every REST endpoint but just the "parent" one.
|
||||
// Per convention the name is similar to the path (without the leading "/") for openHAB REST
|
||||
// implementations.
|
||||
|
||||
final URI uri = uriInfo.getBaseUriBuilder().path("/" + name).build();
|
||||
if (collectedLinks.put(uri.toASCIIString(), name) != null) {
|
||||
logger.warn("Duplicate entry: {}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collectedLinks.forEach((path, name) -> {
|
||||
bean.links.add(new Links(name, path));
|
||||
});
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
public void addRESTResource(RESTResource resource) {
|
||||
restResources.add(resource);
|
||||
}
|
||||
|
||||
public void removeRESTResource(RESTResource resource) {
|
||||
restResources.remove(resource);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Activate
|
||||
public void activate() {
|
||||
Configuration configuration;
|
||||
try {
|
||||
configuration = configurationAdmin.getConfiguration(Constants.JAXRS_CONNECTOR_CONFIG, null);
|
||||
|
||||
if (configuration != null) {
|
||||
Dictionary properties = configuration.getProperties();
|
||||
|
||||
if (properties == null) {
|
||||
properties = new Properties();
|
||||
}
|
||||
|
||||
String rootAlias = (String) properties.get(Constants.JAXRS_CONNECTOR_ROOT_PROPERTY);
|
||||
if (!RESTConstants.REST_URI.equals(rootAlias)) {
|
||||
properties.put(Constants.JAXRS_CONNECTOR_ROOT_PROPERTY, RESTConstants.REST_URI);
|
||||
|
||||
configuration.update(properties);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not set REST configuration properties!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
|
||||
this.configurationAdmin = configurationAdmin;
|
||||
}
|
||||
|
||||
protected void unsetConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
|
||||
this.configurationAdmin = null;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -23,9 +23,9 @@ import java.util.Collections;
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.core.MultivaluedHashMap;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import org.glassfish.jersey.uri.internal.JerseyUriBuilder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@@ -156,9 +156,9 @@ public class ProxyFilterTest {
|
||||
private void setupContextURIs(String baseURI, String requestURI) {
|
||||
try {
|
||||
when(uriInfo.getBaseUri()).thenReturn(new URI(baseURI));
|
||||
when(uriInfo.getBaseUriBuilder()).thenReturn(new JerseyUriBuilder().uri(baseURI));
|
||||
when(uriInfo.getBaseUriBuilder()).thenReturn(UriBuilder.fromUri(baseURI));
|
||||
when(uriInfo.getRequestUri()).thenReturn(new URI(requestURI));
|
||||
when(uriInfo.getRequestUriBuilder()).thenReturn(new JerseyUriBuilder().uri(requestURI));
|
||||
when(uriInfo.getRequestUriBuilder()).thenReturn(UriBuilder.fromUri(requestURI));
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("Error while setting up context mock", e);
|
||||
}
|
||||
|
||||
+23
-1
@@ -26,6 +26,7 @@ import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
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.ui.icon.IconProvider;
|
||||
import org.openhab.core.ui.icon.IconSet;
|
||||
@@ -33,16 +34,35 @@ 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.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
|
||||
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
|
||||
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 io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiResponse;
|
||||
import io.swagger.annotations.ApiResponses;
|
||||
|
||||
/**
|
||||
* This is a REST resource that provides information about available icon sets.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
|
||||
*/
|
||||
@Path("iconsets")
|
||||
@Component
|
||||
@JaxrsResource
|
||||
@JaxrsName(IconSetResource.PATH_ICONSETS)
|
||||
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
|
||||
@JSONRequired
|
||||
@Path(IconSetResource.PATH_ICONSETS)
|
||||
@Api(IconSetResource.PATH_ICONSETS)
|
||||
public class IconSetResource implements RESTResource {
|
||||
|
||||
/** The URI path to this resource */
|
||||
public static final String PATH_ICONSETS = "iconsets";
|
||||
|
||||
private List<IconProvider> iconProviders = new ArrayList<>(5);
|
||||
|
||||
private LocaleService localeService;
|
||||
@@ -70,6 +90,8 @@ public class IconSetResource implements RESTResource {
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@ApiOperation(value = "Gets all icon sets.")
|
||||
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK") })
|
||||
public Response getAll(@HeaderParam("Accept-Language") String language) {
|
||||
Locale locale = localeService.getLocale(language);
|
||||
|
||||
|
||||
+1
-1
@@ -64,9 +64,9 @@
|
||||
<module>org.openhab.core.io.rest.core</module>
|
||||
<module>org.openhab.core.io.rest.log</module>
|
||||
<module>org.openhab.core.io.rest.mdns</module>
|
||||
<module>org.openhab.core.io.rest.optimize</module>
|
||||
<module>org.openhab.core.io.rest.sitemap</module>
|
||||
<module>org.openhab.core.io.rest.sse</module>
|
||||
<module>org.openhab.core.io.rest.swagger</module>
|
||||
<module>org.openhab.core.io.rest.ui</module>
|
||||
<module>org.openhab.core.io.rest.voice</module>
|
||||
<module>org.openhab.core.io.transport.dbus</module>
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
<requirement>openhab.tp;filter:="(&(feature=xtext)(version>=2.19.0)(!(version>=2.20.0)))"</requirement>
|
||||
<feature dependency="true">openhab.tp-xtext</feature>
|
||||
|
||||
<requirement>openhab.tp;filter:="(feature=jax-rs)"</requirement>
|
||||
<feature dependency="true">openhab.tp-jax-rs</feature>
|
||||
<requirement>openhab.tp;filter:="(feature=jax-rs-whiteboard)"</requirement>
|
||||
<feature dependency="true">openhab.tp-jax-rs-whiteboard</feature>
|
||||
|
||||
<requirement>openhab.tp;filter:="(feature=httpclient)"</requirement>
|
||||
<feature dependency="true">openhab.tp-httpclient</feature>
|
||||
@@ -56,7 +56,6 @@
|
||||
<feature dependency="true">pax-http-whiteboard</feature>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.http/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest.optimize/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest.core/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest.sse/${project.version}</bundle>
|
||||
</feature>
|
||||
@@ -171,6 +170,13 @@
|
||||
<feature dependency="true">openhab-core-model-sitemap</feature>
|
||||
</feature>
|
||||
|
||||
<feature name="openhab-core-io-rest-swagger" version="${project.version}">
|
||||
<feature>openhab-core-base</feature>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest.swagger/${project.version}</bundle>
|
||||
<requirement>openhab.tp;filter:="(feature=jaxrswb-swagger-gen)"</requirement>
|
||||
<feature dependency="true">openhab.tp-jaxrswb-swagger-gen</feature>
|
||||
</feature>
|
||||
|
||||
<feature name="openhab-core-io-rest-audio" version="${project.version}">
|
||||
<feature>openhab-core-base</feature>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest.audio/${project.version}</bundle>
|
||||
|
||||
@@ -78,31 +78,37 @@
|
||||
</feature>
|
||||
|
||||
<feature name="openhab.tp-jackson" description="FasterXML Jackson bundles" version="${project.version}">
|
||||
<capability>openhab.tp;feature=jackson;version=2.9.10</capability>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-annotations/2.9.10</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-core/2.9.10</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-databind/2.9.10</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.9.10</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.9.10</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.datatype/jackson-datatype-joda/2.9.10</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/2.9.10</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/2.9.10</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.9.10</bundle>
|
||||
<bundle dependency="true">mvn:org.yaml/snakeyaml/1.23</bundle>
|
||||
<capability>openhab.tp;feature=jackson;version=2.10.3</capability>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-annotations/2.10.3</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-core/2.10.3</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-databind/2.10.3</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.10.3</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.10.3</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.datatype/jackson-datatype-joda/2.10.3</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/2.10.3</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/2.10.3</bundle>
|
||||
<bundle dependency="true">mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.10.3</bundle>
|
||||
<bundle dependency="true">mvn:org.yaml/snakeyaml/1.26</bundle>
|
||||
</feature>
|
||||
|
||||
<feature name="openhab.tp-jax-rs" version="${project.version}">
|
||||
<capability>openhab.tp;feature=jax-rs;version=5.3.1</capability>
|
||||
<feature>openhab.tp-jax-rs-min</feature>
|
||||
</feature>
|
||||
<feature name="openhab.tp-jax-rs-whiteboard" version="${project.version}">
|
||||
<capability>openhab.tp;feature=jax-rs-whiteboard;version=1.0.8</capability>
|
||||
|
||||
<feature name="openhab.tp-jax-rs-min" version="${project.version}">
|
||||
<capability>openhab.tp;feature=jax-rs-min;version=5.3.1</capability>
|
||||
<feature dependency="true">http</feature>
|
||||
<feature dependency="true">openhab.tp-kat.cpy-jersey-min-2.22.2</feature>
|
||||
<bundle>mvn:com.eclipsesource.jaxrs/publisher/5.3.1</bundle>
|
||||
<config name="com.eclipsesource.jaxrs.connector">
|
||||
root=/rest
|
||||
<!-- BEG: https://issues.apache.org/jira/browse/KARAF-6536 -->
|
||||
<!-- Add it here, so we do not use to rely on a feature that is using version ranges. -->
|
||||
<feature>http-whiteboard</feature>
|
||||
<feature dependency="true">openhab.tp-jaxws</feature>
|
||||
<bundle dependency="true">mvn:org.apache.aries.spec/org.apache.aries.javax.jax.rs-api/1.0.1</bundle>
|
||||
<bundle dependency="true">mvn:org.osgi/org.osgi.util.function/1.1.0</bundle>
|
||||
<bundle dependency="true">mvn:org.osgi/org.osgi.util.promise/1.1.0</bundle>
|
||||
<bundle dependency="true">mvn:org.osgi/org.osgi.service.jaxrs/1.0.0</bundle>
|
||||
<bundle dependency="true">mvn:org.apache.servicemix.specs/org.apache.servicemix.specs.annotation-api-1.3/1.3_1</bundle>
|
||||
<bundle dependency="true">mvn:org.apache.servicemix.specs/org.apache.servicemix.specs.jaxws-api-2.2/2.9.0</bundle>
|
||||
<bundle>mvn:org.apache.aries.jax.rs/org.apache.aries.jax.rs.whiteboard/1.0.8</bundle>
|
||||
<!-- END: https://issues.apache.org/jira/browse/KARAF-6536 -->
|
||||
|
||||
<config name="org.apache.aries.jax.rs.whiteboard.default">
|
||||
replace.loopback.address.with.localhost=false
|
||||
</config>
|
||||
</feature>
|
||||
|
||||
@@ -111,30 +117,6 @@
|
||||
<bundle dependency="true">mvn:org.openhab.osgiify/com.igormaznitsa.jbbp/1.4.1</bundle>
|
||||
</feature>
|
||||
|
||||
<feature name="openhab.tp-kat.cpy-jersey-min-2.22.2" version="${project.version}">
|
||||
<feature>http</feature>
|
||||
<bundle start-level="36">mvn:org.glassfish.jersey.containers/jersey-container-servlet/2.22.2</bundle>
|
||||
<bundle start-level="36">mvn:org.glassfish.jersey.media/jersey-media-sse/2.22.2</bundle>
|
||||
<bundle start-level="36">mvn:org.glassfish.jersey.media/jersey-media-multipart/2.22.2</bundle>
|
||||
<bundle start-level="36" dependency="true">mvn:org.glassfish.jersey.containers/jersey-container-servlet-core/2.22.2</bundle>
|
||||
<bundle start-level="36" dependency="true">mvn:org.glassfish.jersey.core/jersey-common/2.22.2</bundle>
|
||||
<bundle start-level="36" dependency="true">mvn:org.glassfish.jersey.bundles.repackaged/jersey-guava/2.22.2</bundle>
|
||||
<bundle start-level="36" dependency="true">mvn:org.glassfish.jersey.core/jersey-server/2.22.2</bundle>
|
||||
<bundle start-level="36" dependency="true">mvn:org.glassfish.jersey.core/jersey-client/2.22.2</bundle>
|
||||
<bundle start-level="36" dependency="true">mvn:org.glassfish.jersey.media/jersey-media-jaxb/2.22.2</bundle>
|
||||
<bundle start-level="33" dependency="true">mvn:org.glassfish.hk2/hk2-api/2.4.0-b34</bundle>
|
||||
<bundle start-level="33" dependency="true">mvn:org.glassfish.hk2/hk2-locator/2.4.0-b34</bundle>
|
||||
<bundle start-level="33" dependency="true">mvn:org.glassfish.hk2/hk2-utils/2.4.0-b34</bundle>
|
||||
<bundle start-level="33" dependency="true">mvn:org.glassfish.hk2/osgi-resource-locator/1.0.1</bundle>
|
||||
<bundle start-level="33" dependency="true">mvn:org.glassfish.hk2.external/javax.inject/2.4.0-b34</bundle>
|
||||
<bundle start-level="33" dependency="true">mvn:org.glassfish.hk2.external/aopalliance-repackaged/2.4.0-b34</bundle>
|
||||
<bundle dependency="true">mvn:javax.annotation/javax.annotation-api/1.2</bundle>
|
||||
<bundle dependency="true">mvn:javax.validation/validation-api/1.1.0.Final</bundle>
|
||||
<bundle dependency="true">mvn:javax.ws.rs/javax.ws.rs-api/2.0.1</bundle>
|
||||
<bundle dependency="true">mvn:org.javassist/javassist/3.18.1-GA</bundle>
|
||||
<bundle dependency="true">mvn:org.jvnet.mimepull/mimepull/1.9.6</bundle>
|
||||
</feature>
|
||||
|
||||
<feature name="openhab.tp-netty" description="Netty bundles" version="${project.version}">
|
||||
<capability>openhab.tp;feature=netty;version=4.1.42.Final</capability>
|
||||
<bundle dependency="true">mvn:io.netty/netty-buffer/4.1.42.Final</bundle>
|
||||
@@ -267,37 +249,25 @@
|
||||
<bundle dependency="true">mvn:org.eclipse.xtext/org.eclipse.xtext.xbase.ide/2.19.0</bundle>
|
||||
</feature>
|
||||
|
||||
<!-- <feature name="openhab.tp-swagger-annotations" description="Swagger Annotations" version="${project.version}"> <bundle dependency="true">mvn:io.swagger/swagger-annotations/1.5.5</bundle> </feature> -->
|
||||
|
||||
<feature name="openhab.tp-swagger-jax-rs-provider" description="JAX-RS provider for Swagger integration" version="${project.version}">
|
||||
<feature name="openhab.tp-jaxrswb-swagger-gen" description="JAX-RS Whiteboard Swagger 1 generator" version="${project.version}">
|
||||
<details>
|
||||
This feature is currently only for external usage by products and not used by openHAB Core itself.
|
||||
</details>
|
||||
<capability>openhab.tp;feature=jax-rs-provider-swagger;version=1.1.1</capability>
|
||||
<feature dependency="true">openhab.tp-jax-rs-min</feature>
|
||||
<capability>openhab.tp;feature=jaxrswb-swagger-gen;version=0.0.4</capability>
|
||||
<bundle>mvn:de.maggu2810.jaxrswb.bundles/jaxrswb-swagger1-gen/0.0.4</bundle>
|
||||
<feature dependency="true">openhab.tp-jax-rs-whiteboard</feature>
|
||||
<feature dependency="true">openhab.tp-jackson</feature>
|
||||
<!-- BEG: swagger-jersey2-1.5.8 -->
|
||||
<feature dependency="true">openhab.tp-kat.cpy-jersey-min-2.22.2</feature>
|
||||
<bundle>mvn:de.maggu2810.thirdparty.modified.io.swagger/swagger-jersey2-jaxrs/1.5.8.v20160511-1038</bundle>
|
||||
<bundle dependency="true">mvn:io.swagger/swagger-annotations/1.5.8</bundle>
|
||||
<bundle dependency="true">mvn:io.swagger/swagger-core/1.5.8</bundle>
|
||||
<bundle dependency="true">mvn:io.swagger/swagger-jaxrs/1.5.8</bundle>
|
||||
<bundle dependency="true">mvn:io.swagger/swagger-models/1.5.8</bundle>
|
||||
<bundle dependency="true">mvn:de.maggu2810.thirdparty.modified.org.reflections/reflections/0.9.10.v20160429-1435</bundle>
|
||||
<bundle dependency="true">mvn:com.google.code.findbugs/annotations/2.0.1</bundle>
|
||||
<bundle dependency="true">mvn:com.google.guava/guava/18.0</bundle>
|
||||
<bundle dependency="true">mvn:io.swagger/swagger-jaxrs/1.6.1</bundle>
|
||||
<bundle dependency="true">mvn:io.swagger/swagger-models/1.6.1</bundle>
|
||||
<bundle dependency="true">mvn:io.swagger/swagger-annotations/1.6.1</bundle>
|
||||
<bundle dependency="true">mvn:io.swagger/swagger-core/1.6.1</bundle>
|
||||
<bundle dependency="true">mvn:com.google.guava/guava/27.1-jre</bundle>
|
||||
<bundle dependency="true">mvn:com.google.guava/failureaccess/1.0.1</bundle>
|
||||
<bundle dependency="true">mvn:javax.validation/validation-api/1.1.0.Final</bundle>
|
||||
<bundle dependency="true">mvn:joda-time/joda-time/2.2</bundle>
|
||||
<bundle dependency="true">mvn:org.apache.commons/commons-lang3/3.2.1</bundle>
|
||||
<bundle dependency="true">mvn:org.codehaus.woodstox/stax2-api/3.1.4</bundle>
|
||||
<bundle dependency="true">mvn:org.javassist/javassist/3.19.0-GA</bundle>
|
||||
<bundle dependency="true">mvn:org.slf4j/slf4j-api/1.7.7</bundle>
|
||||
<!-- END: swagger-jersey2-1.5.8 -->
|
||||
|
||||
<bundle>mvn:de.maggu2810.thirdparty.modified.com.eclipsesource.jaxrs/provider-swagger/1.1.1.201605111122</bundle>
|
||||
<config name="com.eclipsesource.jaxrs.swagger.config">
|
||||
swagger.basePath=/rest
|
||||
swagger.info.title=openHAB REST API
|
||||
<bundle dependency="true">mvn:de.maggu2810.thirdparty.modified.org.reflections/reflections/0.9.10.v20160429-1435</bundle>
|
||||
<config name="de.maggu2810.jaxrswb.swagger1.gen">
|
||||
info.title=openHAB REST API
|
||||
</config>
|
||||
</feature>
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ feature.openhab-config: \
|
||||
org.apache.servicemix.bundles.xstream;version='[1.4.7,1.4.8)',\
|
||||
org.apache.felix.configadmin;version='[1.9.8,1.9.9)',\
|
||||
org.apache.felix.log;version='[1.2.0,1.2.1)',\
|
||||
org.apache.felix.logback;version='[1.0.0,1.0.1)',\
|
||||
tec.uom.lib.uom-lib-common;version='[1.0.3,1.0.4)',\
|
||||
tec.uom.se;version='[1.0.10,1.0.11)',\
|
||||
jollyday;version='[0.5.8,0.5.9)',\
|
||||
@@ -55,4 +54,5 @@ feature.openhab-config: \
|
||||
org.openhab.core.config.xml;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.ephemeris;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.ephemeris.tests;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.test;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.test;version='[3.0.0,3.0.1)',\
|
||||
org.apache.felix.logback;version='[1.0.2,1.0.3)'
|
||||
|
||||
@@ -4,14 +4,12 @@ Bundle-SymbolicName: ${project.artifactId}
|
||||
Fragment-Host: org.openhab.core.io.rest.core
|
||||
|
||||
-runrequires: \
|
||||
bnd.identity;id='org.openhab.core.io.rest.core.tests',\
|
||||
bnd.identity;id='com.eclipsesource.jaxrs.jersey-all'
|
||||
bnd.identity;id='org.openhab.core.io.rest.core.tests'
|
||||
|
||||
#
|
||||
# done
|
||||
#
|
||||
-runbundles: \
|
||||
com.eclipsesource.jaxrs.jersey-all;version='[2.22.2,2.22.3)',\
|
||||
com.jayway.jsonpath.json-path;version='[2.4.0,2.4.1)',\
|
||||
javax.measure.unit-api;version='[1.0.0,1.0.1)',\
|
||||
net.minidev.accessors-smart;version='[1.2.0,1.2.1)',\
|
||||
@@ -45,6 +43,22 @@ Fragment-Host: org.openhab.core.io.rest.core
|
||||
org.eclipse.jetty.server;version='[9.4.20,9.4.21)',\
|
||||
org.eclipse.jetty.servlet;version='[9.4.20,9.4.21)',\
|
||||
org.eclipse.jetty.util;version='[9.4.20,9.4.21)',\
|
||||
org.apache.aries.javax.jax.rs-api;version='[1.0.0,1.0.1)',\
|
||||
org.apache.aries.jax.rs.whiteboard;version='[1.0.8,1.0.9)',\
|
||||
org.apache.xbean.bundleutils;version='[4.12.0,4.12.1)',\
|
||||
org.apache.xbean.finder;version='[4.12.0,4.12.1)',\
|
||||
org.eclipse.jetty.xml;version='[9.4.20,9.4.21)',\
|
||||
org.objectweb.asm;version='[7.1.0,7.1.1)',\
|
||||
org.objectweb.asm.commons;version='[7.1.0,7.1.1)',\
|
||||
org.objectweb.asm.tree;version='[7.1.0,7.1.1)',\
|
||||
org.ops4j.pax.swissbox.optional.jcl;version='[1.8.3,1.8.4)',\
|
||||
org.ops4j.pax.web.pax-web-api;version='[7.2.11,7.2.12)',\
|
||||
org.ops4j.pax.web.pax-web-jetty;version='[7.2.11,7.2.12)',\
|
||||
org.ops4j.pax.web.pax-web-runtime;version='[7.2.11,7.2.12)',\
|
||||
org.ops4j.pax.web.pax-web-spi;version='[7.2.11,7.2.12)',\
|
||||
org.osgi.service.jaxrs;version='[1.0.0,1.0.1)',\
|
||||
org.osgi.util.function;version='[1.1.0,1.1.1)',\
|
||||
org.osgi.util.promise;version='[1.1.0,1.1.1)',\
|
||||
org.openhab.core;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.config.core;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.config.discovery;version='[3.0.0,3.0.1)',\
|
||||
@@ -55,4 +69,6 @@ Fragment-Host: org.openhab.core.io.rest.core
|
||||
org.openhab.core.persistence;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.test;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
javax.xml.soap-api;version='[1.3.5,1.3.6)',\
|
||||
org.apache.servicemix.specs.jaxws-api-2.2;version='[2.9.0,2.9.1)'
|
||||
|
||||
+2
-2
@@ -58,7 +58,7 @@ public class InboxResourceOSGITest extends JavaOSGiTest {
|
||||
|
||||
resource.setInbox(inbox);
|
||||
Response reponse = resource.approve(null, testThing.getUID().toString(), testThingLabel);
|
||||
assertTrue(reponse.getStatusInfo() == Status.OK);
|
||||
assertTrue(reponse.getStatusInfo().getStatusCode() == Status.OK.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -68,6 +68,6 @@ public class InboxResourceOSGITest extends JavaOSGiTest {
|
||||
|
||||
resource.setInbox(inbox);
|
||||
Response reponse = resource.approve(null, testThing.getUID().toString(), testThingLabel);
|
||||
assertTrue(reponse.getStatusInfo() == Status.NOT_FOUND);
|
||||
assertTrue(reponse.getStatusInfo().getStatusCode() == Status.NOT_FOUND.getStatusCode());
|
||||
}
|
||||
}
|
||||
|
||||
+27
-14
@@ -16,18 +16,22 @@ import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
|
||||
import static org.hamcrest.core.IsCollectionContaining.hasItems;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
@@ -71,6 +75,9 @@ public class ItemResourceOSGiTest extends JavaOSGiTest {
|
||||
@Mock
|
||||
private ItemProvider itemProvider;
|
||||
|
||||
private UriInfo uriInfo;
|
||||
private HttpHeaders httpHeaders;
|
||||
|
||||
private ItemResource itemResource;
|
||||
private ManagedItemProvider managedItemProvider;
|
||||
|
||||
@@ -81,8 +88,6 @@ public class ItemResourceOSGiTest extends JavaOSGiTest {
|
||||
itemResource = getService(RESTResource.class, ItemResource.class);
|
||||
assertNotNull(itemResource);
|
||||
|
||||
itemResource.uriInfo = mock(UriInfo.class);
|
||||
|
||||
registerVolatileStorageService();
|
||||
managedItemProvider = getService(ManagedItemProvider.class);
|
||||
|
||||
@@ -93,13 +98,21 @@ public class ItemResourceOSGiTest extends JavaOSGiTest {
|
||||
|
||||
when(itemProvider.getAll()).thenReturn(Arrays.asList(item1, item2, item3, item4));
|
||||
registerService(itemProvider);
|
||||
|
||||
UriBuilder uriBuilder = mock(UriBuilder.class);
|
||||
when(uriBuilder.build(any())).thenReturn(URI.create(""));
|
||||
uriInfo = mock(UriInfo.class);
|
||||
when(uriInfo.getAbsolutePathBuilder()).thenReturn(uriBuilder);
|
||||
when(uriInfo.getPath()).thenReturn("");
|
||||
httpHeaders = mock(HttpHeaders.class);
|
||||
when(httpHeaders.getHeaderString(anyString())).thenReturn(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnUnicodeItems() throws IOException {
|
||||
item4.setLabel(ITEM_LABEL4);
|
||||
|
||||
Response response = itemResource.getItems(null, null, null, null, false, null);
|
||||
Response response = itemResource.getItems(uriInfo, httpHeaders, null, null, null, null, false, null);
|
||||
assertThat(readItemLabelsFromResponse(response), hasItems(ITEM_LABEL4));
|
||||
}
|
||||
|
||||
@@ -107,7 +120,7 @@ public class ItemResourceOSGiTest extends JavaOSGiTest {
|
||||
public void shouldReturnUnicodeItem() throws IOException {
|
||||
item4.setLabel(ITEM_LABEL4);
|
||||
|
||||
Response response = itemResource.getItemData(null, null, ITEM_NAME4);
|
||||
Response response = itemResource.getItemData(uriInfo, httpHeaders, null, null, ITEM_NAME4);
|
||||
assertThat(readItemLabelsFromResponse(response), hasItems(ITEM_LABEL4));
|
||||
}
|
||||
|
||||
@@ -119,25 +132,25 @@ public class ItemResourceOSGiTest extends JavaOSGiTest {
|
||||
item3.addTag("Tag2");
|
||||
item4.addTag("Tag4");
|
||||
|
||||
Response response = itemResource.getItems(null, null, "Tag1", null, false, null);
|
||||
Response response = itemResource.getItems(uriInfo, httpHeaders, null, null, "Tag1", null, false, null);
|
||||
assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME1, ITEM_NAME2));
|
||||
|
||||
response = itemResource.getItems(null, null, "Tag2", null, false, null);
|
||||
response = itemResource.getItems(uriInfo, httpHeaders, null, null, "Tag2", null, false, null);
|
||||
assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME2, ITEM_NAME3));
|
||||
|
||||
response = itemResource.getItems(null, null, "NotExistingTag", null, false, null);
|
||||
response = itemResource.getItems(uriInfo, httpHeaders, null, null, "NotExistingTag", null, false, null);
|
||||
assertThat(readItemNamesFromResponse(response), hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFilterItemsByType() throws Exception {
|
||||
Response response = itemResource.getItems(null, "Switch", null, null, false, null);
|
||||
Response response = itemResource.getItems(uriInfo, httpHeaders, null, "Switch", null, null, false, null);
|
||||
assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME1, ITEM_NAME2));
|
||||
|
||||
response = itemResource.getItems(null, "Dimmer", null, null, false, null);
|
||||
response = itemResource.getItems(uriInfo, httpHeaders, null, "Dimmer", null, null, false, null);
|
||||
assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME3));
|
||||
|
||||
response = itemResource.getItems(null, "Color", null, null, false, null);
|
||||
response = itemResource.getItems(uriInfo, httpHeaders, null, "Color", null, null, false, null);
|
||||
assertThat(readItemNamesFromResponse(response), hasSize(0));
|
||||
}
|
||||
|
||||
@@ -145,15 +158,15 @@ public class ItemResourceOSGiTest extends JavaOSGiTest {
|
||||
public void shouldAddAndRemoveTags() throws Exception {
|
||||
managedItemProvider.add(new SwitchItem("Switch"));
|
||||
|
||||
Response response = itemResource.getItems(null, null, "MyTag", null, false, null);
|
||||
Response response = itemResource.getItems(uriInfo, httpHeaders, null, null, "MyTag", null, false, null);
|
||||
assertThat(readItemNamesFromResponse(response), hasSize(0));
|
||||
|
||||
itemResource.addTag("Switch", "MyTag");
|
||||
response = itemResource.getItems(null, null, "MyTag", null, false, null);
|
||||
response = itemResource.getItems(uriInfo, httpHeaders, null, null, "MyTag", null, false, null);
|
||||
assertThat(readItemNamesFromResponse(response), hasSize(1));
|
||||
|
||||
itemResource.removeTag("Switch", "MyTag");
|
||||
response = itemResource.getItems(null, null, "MyTag", null, false, null);
|
||||
response = itemResource.getItems(uriInfo, httpHeaders, null, null, "MyTag", null, false, null);
|
||||
assertThat(readItemNamesFromResponse(response), hasSize(0));
|
||||
}
|
||||
|
||||
@@ -162,7 +175,7 @@ public class ItemResourceOSGiTest extends JavaOSGiTest {
|
||||
JsonParser parser = new JsonParser();
|
||||
managedItemProvider.add(new SwitchItem("Switch"));
|
||||
itemResource.addTag("Switch", "MyTag");
|
||||
Response response = itemResource.getItems(null, null, "MyTag", null, false, "type,name");
|
||||
Response response = itemResource.getItems(uriInfo, httpHeaders, null, null, "MyTag", null, false, "type,name");
|
||||
|
||||
JsonElement result = parser.parse(IOUtils.toString((InputStream) response.getEntity()));
|
||||
JsonElement expected = parser.parse("[{type: \"Switch\", name: \"Switch\"}]");
|
||||
|
||||
@@ -96,4 +96,4 @@ Fragment-Host: org.openhab.core.model.core
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.rule.runtime;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.model.item.runtime;version='[3.0.0,3.0.1)'
|
||||
|
||||
@@ -94,5 +94,4 @@ Fragment-Host: org.openhab.core.model.item
|
||||
org.openhab.core.test;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.rule.runtime;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)'
|
||||
|
||||
@@ -111,4 +111,4 @@ Fragment-Host: org.openhab.core.model.persistence
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.rule.runtime;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.model.item.runtime;version='[3.0.0,3.0.1)'
|
||||
|
||||
@@ -94,4 +94,5 @@ Fragment-Host: org.openhab.core.model.rule.runtime
|
||||
org.openhab.core.test;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.item.runtime;version='[3.0.0,3.0.1)'
|
||||
|
||||
@@ -94,5 +94,4 @@ Fragment-Host: org.openhab.core.model.script
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.rule.runtime;version='[3.0.0,3.0.1)'
|
||||
|
||||
org.openhab.core.model.item.runtime;version='[3.0.0,3.0.1)'
|
||||
|
||||
@@ -105,6 +105,5 @@ Fragment-Host: org.openhab.core.model.thing
|
||||
org.openhab.core.thing;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.thing.xml;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.transform;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)',\
|
||||
org.openhab.core.model.sitemap.runtime;version='[3.0.0,3.0.1)'
|
||||
org.openhab.core.voice;version='[3.0.0,3.0.1)'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user