mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 05:11:53 +01:00
mavenize openHAB and integrate mavenized ESH repository (#467)
* mavenize openHAB and integrate mavenized ESH repository Signed-off-by: Markus Rathgeb <maggu2810@gmail.com>
This commit is contained in:
parent
45a7ce27cb
commit
a37cceab67
25
.gitignore
vendored
25
.gitignore
vendored
@ -1,11 +1,22 @@
|
||||
**/target/
|
||||
target/
|
||||
src-gen/
|
||||
xtend-gen/
|
||||
bin/
|
||||
.antlr*
|
||||
.metadata/
|
||||
*/plugin.xml_gen
|
||||
.DS_Store
|
||||
*.iml
|
||||
.idea/
|
||||
|
||||
/*features*/*/src/main/history/
|
||||
maven-metadata-local.xml
|
||||
dependency-reduced-pom.xml
|
||||
npm-debug.log
|
||||
bundles/antlr-generator-3.2.0-patch.jar
|
||||
bundles/org.openhab.core.model.*/META-INF/
|
||||
features/karaf/*/src/main/history
|
||||
|
||||
bundles/org.openhab.ui.homebuilder/web/dist
|
||||
bundles/org.openhab.ui.homebuilder/npm_cache
|
||||
bundles/org.openhab.ui.homebuilder/web/node
|
||||
Californium.properties
|
||||
|
||||
*.orig
|
||||
|
||||
generated/
|
||||
userdata/
|
||||
|
12
.travis.yml
12
.travis.yml
@ -3,10 +3,20 @@ dist: trusty
|
||||
|
||||
language: java
|
||||
jdk: oraclejdk8
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
- $HOME/.p2
|
||||
- extensions/ui/org.eclipse.smarthome.ui.paper/npm_cache
|
||||
|
||||
before_install: echo "MAVEN_OPTS='-Xms1g -Xmx2g -XX:PermSize=512m -XX:MaxPermSize=1g'" > ~/.mavenrc
|
||||
|
||||
#$ mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
|
||||
#$ mvn test -B
|
||||
|
||||
install:
|
||||
- echo 'mvn clean install -B -V 1> .build.stdout 2> .build.stderr' > .build.sh
|
||||
- echo 'mvn clean verify -B -V -Dspotbugs.skip=true 1> .build.stdout 2> .build.stderr' > .build.sh
|
||||
- chmod 0755 .build.sh
|
||||
script:
|
||||
- travis_wait 60 ./.build.sh
|
||||
|
17
bom/compile-model/.project
Normal file
17
bom/compile-model/.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.bom.compile-model</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
4
bom/compile-model/.settings/org.eclipse.m2e.core.prefs
Normal file
4
bom/compile-model/.settings/org.eclipse.m2e.core.prefs
Normal file
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
134
bom/compile-model/pom.xml
Normal file
134
bom/compile-model/pom.xml
Normal file
@ -0,0 +1,134 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bom</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bom</artifactId>
|
||||
<version>2.5.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.bom.compile-model</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>openHAB Core :: BOM :: Compile Model</name>
|
||||
|
||||
<properties>
|
||||
<emf.1.version>2.12.0</emf.1.version>
|
||||
<emf.3.version>2.11.0</emf.3.version>
|
||||
<emf.mwe.version>1.3.21.201705291010</emf.mwe.version>
|
||||
<emf.mwe2.version>2.9.1.201705291010</emf.mwe2.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext.generator</artifactId>
|
||||
<version>${xtext.version}</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.platform</groupId>
|
||||
<artifactId>org.eclipse.osgi</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- EMF -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.emf</groupId>
|
||||
<artifactId>org.eclipse.emf.common</artifactId>
|
||||
<version>${emf.1.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.emf</groupId>
|
||||
<artifactId>org.eclipse.emf.ecore</artifactId>
|
||||
<version>${emf.1.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.emf</groupId>
|
||||
<artifactId>org.eclipse.emf.ecore.change</artifactId>
|
||||
<version>${emf.3.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.emf</groupId>
|
||||
<artifactId>org.eclipse.emf.ecore.xmi</artifactId>
|
||||
<version>${emf.1.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.emf</groupId>
|
||||
<artifactId>org.eclipse.emf.codegen</artifactId>
|
||||
<version>${emf.3.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.emf</groupId>
|
||||
<artifactId>org.eclipse.emf.codegen.ecore</artifactId>
|
||||
<version>${emf.1.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- EMF MWE2 -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.emf</groupId>
|
||||
<artifactId>org.eclipse.emf.mwe2.launch</artifactId>
|
||||
<version>${emf.mwe2.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext.common.types</artifactId>
|
||||
<version>${xtext.version}</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.platform</groupId>
|
||||
<artifactId>org.eclipse.osgi</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!--Xbase -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext.xbase</artifactId>
|
||||
<version>${xtext.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext.xbase.lib</artifactId>
|
||||
<version>${xtext.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- for the model.*.ide bundles -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext.xbase.ide</artifactId>
|
||||
<version>${xtext.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext.ide</artifactId>
|
||||
<version>${xtext.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- TEST -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext</artifactId>
|
||||
<version>${xtext.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
17
bom/compile/.project
Normal file
17
bom/compile/.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.bom.compile</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
2
bom/compile/.settings/org.eclipse.core.resources.prefs
Normal file
2
bom/compile/.settings/org.eclipse.core.resources.prefs
Normal file
@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
4
bom/compile/.settings/org.eclipse.m2e.core.prefs
Normal file
4
bom/compile/.settings/org.eclipse.m2e.core.prefs
Normal file
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
271
bom/compile/pom.xml
Normal file
271
bom/compile/pom.xml
Normal file
@ -0,0 +1,271 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bom</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bom</artifactId>
|
||||
<version>2.5.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.bom.compile</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>openHAB Core :: BOM :: Compile</name>
|
||||
<description>The dependencies that are used to compile the core bundles</description>
|
||||
|
||||
<properties>
|
||||
<jetty.version>9.3.25.v20180904</jetty.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>osgi.core</artifactId>
|
||||
<version>6.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>osgi.cmpn</artifactId>
|
||||
<version>6.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>osgi.annotation</artifactId>
|
||||
<version>6.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jdt</groupId>
|
||||
<artifactId>org.eclipse.jdt.annotation</artifactId>
|
||||
<version>2.2.100</version>
|
||||
<!--<scope>compile</scope> -->
|
||||
</dependency>
|
||||
|
||||
<!-- Annotation API -->
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>1.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons -->
|
||||
<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-exec</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.servicemix.bundles</groupId>
|
||||
<artifactId>org.apache.servicemix.bundles.commons-httpclient</artifactId>
|
||||
<version>3.1_7</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
<scope>compile</scope>
|
||||
</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>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.3.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson -->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
<version>1.9.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<version>1.9.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Jetty -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-proxy</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-client</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JmDNS -->
|
||||
<dependency>
|
||||
<groupId>org.jmdns</groupId>
|
||||
<artifactId>jmdns</artifactId>
|
||||
<version>3.5.5</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Joda Time -->
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<version>2.9.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- jUPnP -->
|
||||
<dependency>
|
||||
<groupId>org.jupnp</groupId>
|
||||
<artifactId>org.jupnp</artifactId>
|
||||
<version>2.5.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- MapDB -->
|
||||
<dependency>
|
||||
<groupId>org.mapdb</groupId>
|
||||
<artifactId>mapdb</artifactId>
|
||||
<version>1.0.9</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Measurement -->
|
||||
<dependency>
|
||||
<groupId>javax.measure</groupId>
|
||||
<artifactId>unit-api</artifactId>
|
||||
<version>1.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tec.uom</groupId>
|
||||
<artifactId>uom-se</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tec.uom.lib</groupId>
|
||||
<artifactId>uom-lib-common</artifactId>
|
||||
<version>1.0.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Paho MQTT -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.paho</groupId>
|
||||
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Quartz -->
|
||||
<dependency>
|
||||
<groupId>org.quartz-scheduler</groupId>
|
||||
<artifactId>quartz</artifactId>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.quartz-scheduler</groupId>
|
||||
<artifactId>quartz-jobs</artifactId>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Serial Communication -->
|
||||
<!-- <dependency> <groupId>org.eclipse.kura</groupId> <artifactId>org.eclipse.soda.dk.comm</artifactId> <version>1.2.201</version>
|
||||
<scope>compile</scope> </dependency> -->
|
||||
<dependency>
|
||||
<groupId>org.vesalainen.comm</groupId>
|
||||
<artifactId>javaxcomm</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.neuronrobotics</groupId>
|
||||
<artifactId>nrjavaserial</artifactId>
|
||||
<version>3.14.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Servlet API -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Swagger -->
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<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>
|
||||
</dependency>
|
||||
|
||||
<!-- XStream -->
|
||||
<dependency>
|
||||
<groupId>org.apache.servicemix.bundles</groupId>
|
||||
<artifactId>org.apache.servicemix.bundles.xstream</artifactId>
|
||||
<version>1.4.7_1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency> <groupId></groupId> <artifactId></artifactId> <version></version> <scope>compile</scope> </dependency> -->
|
||||
</dependencies>
|
||||
|
||||
</project>
|
17
bom/openhab-core/.project
Normal file
17
bom/openhab-core/.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.bom.esh-core</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
4
bom/openhab-core/.settings/org.eclipse.m2e.core.prefs
Normal file
4
bom/openhab-core/.settings/org.eclipse.m2e.core.prefs
Normal file
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
531
bom/openhab-core/pom.xml
Normal file
531
bom/openhab-core/pom.xml
Normal file
@ -0,0 +1,531 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bom</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bom</artifactId>
|
||||
<version>2.5.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.bom.openhab-core</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>openHAB Core :: BOM :: openHAB Core</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.test</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.test.magic</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.boot</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.compat1x</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.karaf</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.thing</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.audio</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.voice</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.binding.xml</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.extension.sample</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.id</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.persistence</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.scheduler</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.semantics</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.thing.xml</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.transform</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.auth.jaas</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.auth.oauth2client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.net</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.console</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.http</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.rest</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.transport.mdns</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.transport.serial</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.console.eclipse</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.console.rfc147</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.console.karaf</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.http.auth</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.http.auth.basic</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.monitor</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.rest.auth</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.rest.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.rest.log</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.rest.mdns</artifactId>
|
||||
<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>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.rest.sse</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.rest.voice</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.transport.dbus</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.transport.mqtt</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.transport.serial.javacomm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.transport.serial.rxtx</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.transport.serial.rxtx.rfc2217</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.transport.upnp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.jetty.certificate</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.discovery</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.discovery.mdns</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.discovery.usbserial</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.discovery.usbserial.linuxsysfs</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.discovery.upnp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.dispatch</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.serial</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.xml</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.automation</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.automation.module.script</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.automation.module.media</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.automation.module.script.rulesupport</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.automation.rest</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.sitemap</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.item</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.item.ide</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.item.runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.persistence</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.persistence.ide</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.script</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.rule</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.rule.ide</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.script.ide</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.sitemap.ide</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.thing</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.thing.ide</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.lsp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.persistence.runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.rule.runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.script.runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.sitemap.runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.thing.runtime</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.ui</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.ui.dashboard</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.ui.icon</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.storage.json</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.storage.mapdb</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
28
bom/pom.xml
Normal file
28
bom/pom.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core</groupId>
|
||||
<artifactId>org.openhab.core.reactor</artifactId>
|
||||
<version>2.5.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.openhab.core.bom</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bom</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>openHAB Core :: BOM</name>
|
||||
|
||||
<modules>
|
||||
<module>compile</module>
|
||||
<module>compile-model</module>
|
||||
<module>runtime</module>
|
||||
<module>runtime-index</module>
|
||||
<module>test</module>
|
||||
<module>test-index</module>
|
||||
<module>openhab-core</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
27
bom/runtime-index/.classpath
Normal file
27
bom/runtime-index/.classpath
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<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-1.8">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
23
bom/runtime-index/.project
Normal file
23
bom/runtime-index/.project
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.bom.runtime-index</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
6
bom/runtime-index/.settings/org.eclipse.jdt.core.prefs
Normal file
6
bom/runtime-index/.settings/org.eclipse.jdt.core.prefs
Normal file
@ -0,0 +1,6 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
4
bom/runtime-index/.settings/org.eclipse.m2e.core.prefs
Normal file
4
bom/runtime-index/.settings/org.eclipse.m2e.core.prefs
Normal file
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
40
bom/runtime-index/pom.xml
Normal file
40
bom/runtime-index/pom.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bom</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bom</artifactId>
|
||||
<version>2.5.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.bom.runtime-index</artifactId>
|
||||
|
||||
<name>openHAB Core :: BOM :: Runtime Index</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bom</groupId>
|
||||
<artifactId>org.openhab.core.bom.runtime</artifactId>
|
||||
<type>pom</type>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>biz.aQute.bnd</groupId>
|
||||
<artifactId>bnd-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>biz.aQute.bnd</groupId>
|
||||
<artifactId>bnd-indexer-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
17
bom/runtime/.project
Normal file
17
bom/runtime/.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.bom.runtime</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
2
bom/runtime/.settings/org.eclipse.core.resources.prefs
Normal file
2
bom/runtime/.settings/org.eclipse.core.resources.prefs
Normal file
@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
4
bom/runtime/.settings/org.eclipse.m2e.core.prefs
Normal file
4
bom/runtime/.settings/org.eclipse.m2e.core.prefs
Normal file
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
590
bom/runtime/pom.xml
Normal file
590
bom/runtime/pom.xml
Normal file
@ -0,0 +1,590 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bom</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bom</artifactId>
|
||||
<version>2.5.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.bom.runtime</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>openHAB Core :: BOM :: Runtime</name>
|
||||
|
||||
<properties>
|
||||
<pax.web.version>7.2.3</pax.web.version>
|
||||
<jetty.version>9.4.11.v20180605</jetty.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.osgi.enroute</groupId>
|
||||
<artifactId>impl-index</artifactId>
|
||||
<version>7.0.0</version>
|
||||
<type>pom</type>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>org.apache.felix.http.jetty</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.osgi.enroute</groupId>
|
||||
<artifactId>debug-bundles</artifactId>
|
||||
<version>7.0.0</version>
|
||||
<type>pom</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- BEG: logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>org.apache.felix.log</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>osgi.core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>org.apache.felix.logback</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- END: logging -->
|
||||
|
||||
<!-- Apache Commons -->
|
||||
<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-exec</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
<version>1.3.3</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.servicemix.bundles</groupId>
|
||||
<artifactId>org.apache.servicemix.bundles.commons-httpclient</artifactId>
|
||||
<version>3.1_7</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Gson -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.3.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.orbit.bundles</groupId>
|
||||
<artifactId>com.google.gson</artifactId>
|
||||
<version>2.7.0.v20170129-0911</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JmDNS -->
|
||||
<dependency>
|
||||
<groupId>org.jmdns</groupId>
|
||||
<artifactId>jmdns</artifactId>
|
||||
<version>3.5.5</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- jUPnP -->
|
||||
<dependency>
|
||||
<groupId>org.jupnp</groupId>
|
||||
<artifactId>org.jupnp</artifactId>
|
||||
<version>2.5.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- MapDB -->
|
||||
<dependency>
|
||||
<groupId>org.mapdb</groupId>
|
||||
<artifactId>mapdb</artifactId>
|
||||
<version>1.0.9</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Measurement -->
|
||||
<dependency>
|
||||
<groupId>javax.measure</groupId>
|
||||
<artifactId>unit-api</artifactId>
|
||||
<version>1.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tec.uom</groupId>
|
||||
<artifactId>uom-se</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tec.uom.lib</groupId>
|
||||
<artifactId>uom-lib-common</artifactId>
|
||||
<version>1.0.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- XStream -->
|
||||
<dependency>
|
||||
<groupId>org.apache.servicemix.bundles</groupId>
|
||||
<artifactId>org.apache.servicemix.bundles.xstream</artifactId>
|
||||
<version>1.4.7_1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Pax Web -->
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-api</artifactId>
|
||||
<version>${pax.web.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-extender-whiteboard</artifactId>
|
||||
<version>${pax.web.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-jetty</artifactId>
|
||||
<version>${pax.web.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-jsp</artifactId>
|
||||
<version>${pax.web.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-runtime</artifactId>
|
||||
<version>${pax.web.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-spi</artifactId>
|
||||
<version>${pax.web.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Persistence -->
|
||||
<!-- <dependency> -->
|
||||
<!-- <groupId>org.hibernate</groupId> -->
|
||||
<!-- <artifactId>hibernate-osgi</artifactId> -->
|
||||
<!-- <version>5.2.12.Final</version> -->
|
||||
<!-- </dependency> -->
|
||||
<!-- BEG: Needed by Hibernate, but not present as OSGi bundles in Hibernate's dependencies -->
|
||||
<!-- <dependency> -->
|
||||
<!-- <groupId>org.apache.servicemix.bundles</groupId> -->
|
||||
<!-- <artifactId>org.apache.servicemix.bundles.antlr</artifactId> -->
|
||||
<!-- <version>2.7.7_5</version> -->
|
||||
<!-- </dependency> -->
|
||||
<!-- <dependency> -->
|
||||
<!-- <groupId>org.apache.servicemix.bundles</groupId> -->
|
||||
<!-- <artifactId>org.apache.servicemix.bundles.dom4j</artifactId> -->
|
||||
<!-- <version>1.6.1_5</version> -->
|
||||
<!-- </dependency> -->
|
||||
<!-- END: Needed by Hibernate, but not present as OSGi bundles in Hibernate's dependencies -->
|
||||
|
||||
<!-- Jetty -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-continuation</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-deploy</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-http</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-io</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-jaas</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-jmx</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-jndi</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-plus</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-proxy</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-rewrite</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-jaspi</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlets</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util-ajax</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-api</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-client</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-common</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>javax-websocket-client-impl</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>javax-websocket-server-impl</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-server</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-servlet</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-xml</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Xbean -->
|
||||
<dependency>
|
||||
<groupId>org.apache.xbean</groupId>
|
||||
<artifactId>xbean-bundleutils</artifactId>
|
||||
<version>4.6</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.xbean</groupId>
|
||||
<artifactId>xbean-finder</artifactId>
|
||||
<version>4.6</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Google Commons / Guava -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>15.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- EMF, Xtext -->
|
||||
<dependency>
|
||||
<groupId>de.maggu2810.p2redist</groupId>
|
||||
<artifactId>org.antlr.runtime</artifactId>
|
||||
<version>3.2.0.v201101311130</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.emf</groupId>
|
||||
<artifactId>org.eclipse.emf.common</artifactId>
|
||||
<version>2.12.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.emf</groupId>
|
||||
<artifactId>org.eclipse.emf.ecore</artifactId>
|
||||
<version>2.12.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.emf</groupId>
|
||||
<artifactId>org.eclipse.emf.ecore.change</artifactId>
|
||||
<version>2.11.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.emf</groupId>
|
||||
<artifactId>org.eclipse.emf.ecore.xmi</artifactId>
|
||||
<version>2.12.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtend</groupId>
|
||||
<artifactId>org.eclipse.xtend.lib</artifactId>
|
||||
<version>2.14.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtend</groupId>
|
||||
<artifactId>org.eclipse.xtend.lib.macro</artifactId>
|
||||
<version>2.14.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext</artifactId>
|
||||
<version>2.14.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext.common.types</artifactId>
|
||||
<version>2.14.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext.ide</artifactId>
|
||||
<version>2.14.0</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext.util</artifactId>
|
||||
<version>2.14.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext.xbase</artifactId>
|
||||
<version>2.14.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext.xbase.ide</artifactId>
|
||||
<version>2.14.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.xtext</groupId>
|
||||
<artifactId>org.eclipse.xtext.xbase.lib</artifactId>
|
||||
<version>2.14.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.hk2</groupId>
|
||||
<artifactId>hk2-api</artifactId>
|
||||
<version>2.4.0-b34</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.hk2.external</groupId>
|
||||
<artifactId>aopalliance-repackaged</artifactId>
|
||||
<version>2.4.0-b34</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.hk2.external</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
<version>2.4.0-b34</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.hk2</groupId>
|
||||
<artifactId>hk2-locator</artifactId>
|
||||
<version>2.4.0-b34</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.hk2</groupId>
|
||||
<artifactId>osgi-resource-locator</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.hk2</groupId>
|
||||
<artifactId>hk2-utils</artifactId>
|
||||
<version>2.4.0-b34</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
<version>1.2.17</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
<version>6.1.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Joda Time -->
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<version>2.9.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- ... -->
|
||||
<dependency>
|
||||
<groupId>com.eclipsesource.jaxrs</groupId>
|
||||
<artifactId>publisher</artifactId>
|
||||
<version>5.3.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
27
bom/test-index/.classpath
Normal file
27
bom/test-index/.classpath
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<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-1.8">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
23
bom/test-index/.project
Normal file
23
bom/test-index/.project
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.bom.test-index</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
6
bom/test-index/.settings/org.eclipse.jdt.core.prefs
Normal file
6
bom/test-index/.settings/org.eclipse.jdt.core.prefs
Normal file
@ -0,0 +1,6 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
4
bom/test-index/.settings/org.eclipse.m2e.core.prefs
Normal file
4
bom/test-index/.settings/org.eclipse.m2e.core.prefs
Normal file
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
40
bom/test-index/pom.xml
Normal file
40
bom/test-index/pom.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bom</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bom</artifactId>
|
||||
<version>2.5.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.bom.test-index</artifactId>
|
||||
|
||||
<name>openHAB Core :: BOM :: Test Index</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bom</groupId>
|
||||
<artifactId>org.openhab.core.bom.test</artifactId>
|
||||
<type>pom</type>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>biz.aQute.bnd</groupId>
|
||||
<artifactId>bnd-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>biz.aQute.bnd</groupId>
|
||||
<artifactId>bnd-indexer-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
17
bom/test/.project
Normal file
17
bom/test/.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.bom.test</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
2
bom/test/.settings/org.eclipse.core.resources.prefs
Normal file
2
bom/test/.settings/org.eclipse.core.resources.prefs
Normal file
@ -0,0 +1,2 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
4
bom/test/.settings/org.eclipse.m2e.core.prefs
Normal file
4
bom/test/.settings/org.eclipse.m2e.core.prefs
Normal file
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
59
bom/test/pom.xml
Normal file
59
bom/test/pom.xml
Normal file
@ -0,0 +1,59 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bom</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bom</artifactId>
|
||||
<version>2.5.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.bom.test</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>openHAB Core :: BOM :: Test</name>
|
||||
|
||||
<properties>
|
||||
<jetty.version>9.3.25.v20180904</jetty.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>osgi.enroute.junit.wrapper</artifactId>
|
||||
<version>4.12.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>osgi.enroute.hamcrest.wrapper</artifactId>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>2.13.0</version>
|
||||
</dependency>
|
||||
<!-- <dependency> -->
|
||||
<!-- <groupId>org.codehaus.groovy</groupId> -->
|
||||
<!-- <artifactId>groovy-all</artifactId> -->
|
||||
<!-- <version>2.5.5</version> -->
|
||||
<!-- <type>pom</type> -->
|
||||
<!-- </dependency> -->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
<version>2.5.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jetty -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
32
bundles/org.openhab.core.audio/.classpath
Normal file
32
bundles/org.openhab.core.audio/.classpath
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<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>
|
23
bundles/org.openhab.core.audio/.project
Normal file
23
bundles/org.openhab.core.audio/.project
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.audio</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -0,0 +1,6 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding//src/main/resources=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
encoding/ESH-INF=UTF-8
|
||||
encoding/src=UTF-8
|
@ -0,0 +1,6 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
19
bundles/org.openhab.core.audio/NOTICE
Normal file
19
bundles/org.openhab.core.audio/NOTICE
Normal file
@ -0,0 +1,19 @@
|
||||
This content is produced and maintained by the Eclipse SmartHome project.
|
||||
|
||||
* Project home: https://eclipse.org/smarthome/
|
||||
|
||||
== 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/eclipse/smarthome
|
||||
|
||||
== Copyright Holders
|
||||
|
||||
See the NOTICE file distributed with the source code at
|
||||
https://github.com/eclipse/smarthome/blob/master/NOTICE
|
||||
for detailed information regarding copyright ownership.
|
34
bundles/org.openhab.core.audio/pom.xml
Normal file
34
bundles/org.openhab.core.audio/pom.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bundles</artifactId>
|
||||
<version>2.5.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.audio</artifactId>
|
||||
|
||||
<name>openHAB Core :: Bundles :: Audio</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.console</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.http</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
/**
|
||||
* General purpose audio exception
|
||||
*
|
||||
* @author Harald Kuhn - Initial API
|
||||
* @author Kelly Davis - Modified to match discussion in #584
|
||||
*/
|
||||
public class AudioException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs a new exception with null as its detail message.
|
||||
*/
|
||||
public AudioException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message and cause.
|
||||
*
|
||||
* @param message Detail message
|
||||
* @param cause The cause
|
||||
*/
|
||||
public AudioException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message.
|
||||
*
|
||||
* @param message Detail message
|
||||
*/
|
||||
public AudioException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified cause.
|
||||
*
|
||||
* @param cause The cause
|
||||
*/
|
||||
public AudioException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,440 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An audio format definition
|
||||
*
|
||||
* @author Harald Kuhn - Initial API
|
||||
* @author Kelly Davis - Modified to match discussion in #584
|
||||
* @author Kai Kreuzer - Moved class, included constants, added toString
|
||||
*/
|
||||
public class AudioFormat {
|
||||
|
||||
// generic mp3 format without any further constraints
|
||||
public static final AudioFormat MP3 = new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, null, null,
|
||||
null, null);
|
||||
|
||||
// generic wav format without any further constraints
|
||||
public static final AudioFormat WAV = new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED,
|
||||
null, null, null, null);
|
||||
|
||||
// generic OGG format without any further constraints
|
||||
public static final AudioFormat OGG = new AudioFormat(AudioFormat.CONTAINER_OGG, AudioFormat.CODEC_VORBIS, null,
|
||||
null, null, null);
|
||||
|
||||
// generic AAC format without any further constraints
|
||||
public static final AudioFormat AAC = new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_AAC, null, null,
|
||||
null, null);
|
||||
|
||||
/**
|
||||
* {@link AudioCodec} encoded data without any container header or footer,
|
||||
* e.g. MP3 is a non-container format
|
||||
*/
|
||||
public static final String CONTAINER_NONE = "NONE";
|
||||
|
||||
/**
|
||||
* Microsofts wave container format
|
||||
*
|
||||
* @see <a href="http://bit.ly/1TUW93t">WAV Format</a>
|
||||
* @see <a href="http://bit.ly/1oRMKOt">Supported codecs</a>
|
||||
* @see <a href="http://bit.ly/1TUWSlk">RIFF container format</a>
|
||||
*/
|
||||
public static final String CONTAINER_WAVE = "WAVE";
|
||||
|
||||
/**
|
||||
* OGG container format
|
||||
*
|
||||
* @see <a href="http://bit.ly/1oRMWNE">OGG</a>
|
||||
*/
|
||||
public static final String CONTAINER_OGG = "OGG";
|
||||
|
||||
/**
|
||||
* PCM Signed
|
||||
*
|
||||
* @see <a href="http://wiki.multimedia.cx/?title=PCM#PCM_Types">PCM Types</a>
|
||||
*/
|
||||
public static final String CODEC_PCM_SIGNED = "PCM_SIGNED";
|
||||
|
||||
/**
|
||||
* PCM Unsigned
|
||||
*
|
||||
* @see <a href="http://wiki.multimedia.cx/?title=PCM#PCM_Types">PCM Types</a>
|
||||
*/
|
||||
public static final String CODEC_PCM_UNSIGNED = "PCM_UNSIGNED";
|
||||
|
||||
/**
|
||||
* PCM A-law
|
||||
*
|
||||
* @see <a href="http://wiki.multimedia.cx/?title=PCM#PCM_Types">PCM Types</a>
|
||||
*/
|
||||
public static final String CODEC_PCM_ALAW = "ALAW";
|
||||
|
||||
/**
|
||||
* PCM u-law
|
||||
*
|
||||
* @see <a href="http://wiki.multimedia.cx/?title=PCM#PCM_Types">PCM Types</a>
|
||||
*/
|
||||
public static final String CODEC_PCM_ULAW = "ULAW";
|
||||
|
||||
/**
|
||||
* MP3 Codec
|
||||
*
|
||||
* @see <a href="http://wiki.multimedia.cx/index.php?title=MP3">MP3 Codec</a>
|
||||
*/
|
||||
public static final String CODEC_MP3 = "MP3";
|
||||
|
||||
/**
|
||||
* Vorbis Codec
|
||||
*
|
||||
* @see <a href="http://xiph.org/vorbis/doc/">Vorbis</a>
|
||||
*/
|
||||
public static final String CODEC_VORBIS = "VORBIS";
|
||||
|
||||
/**
|
||||
* AAC Codec
|
||||
*/
|
||||
public static final String CODEC_AAC = "AAC";
|
||||
|
||||
/**
|
||||
* Codec
|
||||
*/
|
||||
private final String codec;
|
||||
|
||||
/**
|
||||
* Container
|
||||
*/
|
||||
private final String container;
|
||||
|
||||
/**
|
||||
* Big endian or little endian
|
||||
*/
|
||||
private final Boolean bigEndian;
|
||||
|
||||
/**
|
||||
* Bit depth
|
||||
*
|
||||
* @see <a href="http://bit.ly/1OTydad">Bit Depth</a>
|
||||
*/
|
||||
private final Integer bitDepth;
|
||||
|
||||
/**
|
||||
* Bit rate
|
||||
*
|
||||
* @see <a href="http://bit.ly/1OTy5rk">Bit Rate</a>
|
||||
*/
|
||||
private final Integer bitRate;
|
||||
|
||||
/**
|
||||
* Sample frequency
|
||||
*/
|
||||
private final Long frequency;
|
||||
|
||||
/**
|
||||
* Constructs an instance with the specified properties.
|
||||
*
|
||||
* Note that any properties that are null indicate that
|
||||
* the corresponding AudioFormat allows any value for
|
||||
* the property.
|
||||
*
|
||||
* Concretely this implies that if, for example, one
|
||||
* passed null for the value of frequency, this would
|
||||
* mean the created AudioFormat allowed for any valid
|
||||
* frequency.
|
||||
*
|
||||
* @param container The container for the audio
|
||||
* @param codec The audio codec
|
||||
* @param bigEndian If the audo data is big endian
|
||||
* @param bitDepth The bit depth of the audo data
|
||||
* @param bitRate The bit rate of the audio
|
||||
* @param frequency The frequency at which the audio was sampled
|
||||
*/
|
||||
public AudioFormat(String container, String codec, Boolean bigEndian, Integer bitDepth, Integer bitRate,
|
||||
Long frequency) {
|
||||
this.container = container;
|
||||
this.codec = codec;
|
||||
this.bigEndian = bigEndian;
|
||||
this.bitDepth = bitDepth;
|
||||
this.bitRate = bitRate;
|
||||
this.frequency = frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets codec
|
||||
*
|
||||
* @return The codec
|
||||
*/
|
||||
public String getCodec() {
|
||||
return codec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets container
|
||||
*
|
||||
* @return The container
|
||||
*/
|
||||
public String getContainer() {
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is big endian?
|
||||
*
|
||||
* @return If format is big endian
|
||||
*/
|
||||
public Boolean isBigEndian() {
|
||||
return bigEndian;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets bit depth
|
||||
*
|
||||
* @see <a href="http://bit.ly/1OTydad">Bit Depth</a>
|
||||
* @return Bit depth
|
||||
*/
|
||||
public Integer getBitDepth() {
|
||||
return bitDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets bit rate
|
||||
*
|
||||
* @see <a href="http://bit.ly/1OTy5rk">Bit Rate</a>
|
||||
* @return Bit rate
|
||||
*/
|
||||
public Integer getBitRate() {
|
||||
return bitRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets frequency
|
||||
*
|
||||
* @return The frequency
|
||||
*/
|
||||
public Long getFrequency() {
|
||||
return frequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the passed AudioFormat is compatible with this AudioFormat.
|
||||
*
|
||||
* This AudioFormat is compatible with the passed AudioFormat if both have
|
||||
* the same value for all non-null members of this instance.
|
||||
*/
|
||||
public boolean isCompatible(AudioFormat audioFormat) {
|
||||
if (audioFormat == null) {
|
||||
return false;
|
||||
}
|
||||
if ((null != getContainer()) && (!getContainer().equals(audioFormat.getContainer()))) {
|
||||
return false;
|
||||
}
|
||||
if ((null != getCodec()) && (!getCodec().equals(audioFormat.getCodec()))) {
|
||||
return false;
|
||||
}
|
||||
if ((null != isBigEndian()) && (!isBigEndian().equals(audioFormat.isBigEndian()))) {
|
||||
return false;
|
||||
}
|
||||
if ((null != getBitDepth()) && (!getBitDepth().equals(audioFormat.getBitDepth()))) {
|
||||
return false;
|
||||
}
|
||||
if ((null != getBitRate()) && (!getBitRate().equals(audioFormat.getBitRate()))) {
|
||||
return false;
|
||||
}
|
||||
if ((null != getFrequency()) && (!getFrequency().equals(audioFormat.getFrequency()))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the best match between a list of audio formats supported by a source and a sink.
|
||||
*
|
||||
* @param inputs the supported audio formats of an audio source
|
||||
* @param outputs the supported audio formats of an audio sink
|
||||
* @return the best matching format or null, if source and sink are incompatible
|
||||
*/
|
||||
public static AudioFormat getBestMatch(Set<AudioFormat> inputs, Set<AudioFormat> outputs) {
|
||||
AudioFormat preferredFormat = getPreferredFormat(inputs);
|
||||
if (preferredFormat != null) {
|
||||
for (AudioFormat output : outputs) {
|
||||
if (output.isCompatible(preferredFormat)) {
|
||||
return preferredFormat;
|
||||
} else {
|
||||
for (AudioFormat input : inputs) {
|
||||
if (output.isCompatible(input)) {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first concrete AudioFormat in the passed set or a preferred one
|
||||
* based on 16bit, 16KHz, big endian default
|
||||
*
|
||||
* @param audioFormats The AudioFormats from which to choose
|
||||
* @return The preferred AudioFormat or null if none could be determined. A passed concrete format is preferred
|
||||
* adding default values to an abstract AudioFormat in the passed set.
|
||||
*/
|
||||
public static AudioFormat getPreferredFormat(Set<AudioFormat> audioFormats) {
|
||||
// Return the first concrete AudioFormat found
|
||||
for (AudioFormat currentAudioFormat : audioFormats) {
|
||||
// Check if currentAudioFormat is abstract
|
||||
if (null == currentAudioFormat.getCodec()) {
|
||||
continue;
|
||||
}
|
||||
if (null == currentAudioFormat.getContainer()) {
|
||||
continue;
|
||||
}
|
||||
if (null == currentAudioFormat.isBigEndian()) {
|
||||
continue;
|
||||
}
|
||||
if (null == currentAudioFormat.getBitDepth()) {
|
||||
continue;
|
||||
}
|
||||
if (null == currentAudioFormat.getBitRate()) {
|
||||
continue;
|
||||
}
|
||||
if (null == currentAudioFormat.getFrequency()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prefer WAVE container
|
||||
if (!currentAudioFormat.getContainer().equals("WAVE")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// As currentAudioFormat is concrete, use it
|
||||
return currentAudioFormat;
|
||||
}
|
||||
|
||||
// There's no concrete AudioFormat so we must create one
|
||||
for (AudioFormat currentAudioFormat : audioFormats) {
|
||||
// Define AudioFormat to return
|
||||
AudioFormat format = currentAudioFormat;
|
||||
|
||||
// Not all Codecs and containers can be supported
|
||||
if (null == format.getCodec()) {
|
||||
continue;
|
||||
}
|
||||
if (null == format.getContainer()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prefer WAVE container
|
||||
if (!format.getContainer().equals(AudioFormat.CONTAINER_WAVE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If required set BigEndian, BitDepth, BitRate, and Frequency to default values
|
||||
if (null == format.isBigEndian()) {
|
||||
format = new AudioFormat(format.getContainer(), format.getCodec(), new Boolean(true),
|
||||
format.getBitDepth(), format.getBitRate(), format.getFrequency());
|
||||
}
|
||||
if (null == format.getBitDepth() || null == format.getBitRate() || null == format.getFrequency()) {
|
||||
// Define default values
|
||||
int defaultBitDepth = 16;
|
||||
long defaultFrequency = 16384;
|
||||
|
||||
// Obtain current values
|
||||
Integer bitRate = format.getBitRate();
|
||||
Long frequency = format.getFrequency();
|
||||
Integer bitDepth = format.getBitDepth();
|
||||
|
||||
// These values must be interdependent (bitRate = bitDepth * frequency)
|
||||
if (null == bitRate) {
|
||||
if (null == bitDepth) {
|
||||
bitDepth = new Integer(defaultBitDepth);
|
||||
}
|
||||
if (null == frequency) {
|
||||
frequency = new Long(defaultFrequency);
|
||||
}
|
||||
bitRate = new Integer(bitDepth.intValue() * frequency.intValue());
|
||||
} else if (null == bitDepth) {
|
||||
if (null == frequency) {
|
||||
frequency = new Long(defaultFrequency);
|
||||
}
|
||||
bitDepth = new Integer(bitRate.intValue() / frequency.intValue());
|
||||
} else if (null == frequency) {
|
||||
frequency = new Long(bitRate.longValue() / bitDepth.longValue());
|
||||
}
|
||||
|
||||
format = new AudioFormat(format.getContainer(), format.getCodec(), format.isBigEndian(), bitDepth,
|
||||
bitRate, frequency);
|
||||
}
|
||||
|
||||
// Return preferred AudioFormat
|
||||
return format;
|
||||
}
|
||||
|
||||
// Return null indicating failure
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof AudioFormat) {
|
||||
AudioFormat format = (AudioFormat) obj;
|
||||
if (!(null == getCodec() ? null == format.getCodec() : getCodec().equals(format.getCodec()))) {
|
||||
return false;
|
||||
}
|
||||
if (!(null == getContainer() ? null == format.getContainer()
|
||||
: getContainer().equals(format.getContainer()))) {
|
||||
return false;
|
||||
}
|
||||
if (!(null == isBigEndian() ? null == format.isBigEndian() : isBigEndian().equals(format.isBigEndian()))) {
|
||||
return false;
|
||||
}
|
||||
if (!(null == getBitDepth() ? null == format.getBitDepth() : getBitDepth().equals(format.getBitDepth()))) {
|
||||
return false;
|
||||
}
|
||||
if (!(null == getBitRate() ? null == format.getBitRate() : getBitRate().equals(format.getBitRate()))) {
|
||||
return false;
|
||||
}
|
||||
if (!(null == getFrequency() ? null == format.getFrequency()
|
||||
: getFrequency().equals(format.getFrequency()))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((bigEndian == null) ? 0 : bigEndian.hashCode());
|
||||
result = prime * result + ((bitDepth == null) ? 0 : bitDepth.hashCode());
|
||||
result = prime * result + ((bitRate == null) ? 0 : bitRate.hashCode());
|
||||
result = prime * result + ((codec == null) ? 0 : codec.hashCode());
|
||||
result = prime * result + ((container == null) ? 0 : container.hashCode());
|
||||
result = prime * result + ((frequency == null) ? 0 : frequency.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AudioFormat [" + (codec != null ? "codec=" + codec + ", " : "")
|
||||
+ (container != null ? "container=" + container + ", " : "")
|
||||
+ (bigEndian != null ? "bigEndian=" + bigEndian + ", " : "")
|
||||
+ (bitDepth != null ? "bitDepth=" + bitDepth + ", " : "")
|
||||
+ (bitRate != null ? "bitRate=" + bitRate + ", " : "")
|
||||
+ (frequency != null ? "frequency=" + frequency : "") + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
import org.eclipse.smarthome.core.audio.internal.AudioServlet;
|
||||
|
||||
/**
|
||||
* This is an interface that is implemented by {@link AudioServlet} and which allows exposing audio streams through
|
||||
* HTTP.
|
||||
* Streams are only served a single time and then discarded.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
public interface AudioHTTPServer {
|
||||
|
||||
/**
|
||||
* Creates a relative url for a given {@link AudioStream} where it can be requested a single time.
|
||||
* Note that the HTTP header only contains "Content-length", if the passed stream is an instance of
|
||||
* {@link FixedLengthAudioStream}.
|
||||
* If the client that requests the url expects this header field to be present, make sure to pass such an instance.
|
||||
* Streams are closed after having been served.
|
||||
*
|
||||
* @param stream the stream to serve on HTTP
|
||||
* @return the relative URL to access the stream starting with a '/'
|
||||
*/
|
||||
String serve(AudioStream stream);
|
||||
|
||||
/**
|
||||
* Creates a relative url for a given {@link AudioStream} where it can be requested multiple times within the given
|
||||
* time frame.
|
||||
* This method only accepts {@link FixedLengthAudioStream}s, since it needs to be able to create multiple concurrent
|
||||
* streams from it, which isn't possible with a regular {@link AudioStream}.
|
||||
* Streams are closed, once they expire.
|
||||
*
|
||||
* @param stream the stream to serve on HTTP
|
||||
* @param seconds number of seconds for which the stream is available through HTTP
|
||||
* @return the relative URL to access the stream starting with a '/'
|
||||
*/
|
||||
String serve(FixedLengthAudioStream stream, int seconds);
|
||||
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.smarthome.core.library.types.PercentType;
|
||||
|
||||
/**
|
||||
* This service provides functionality around audio services and is the central service to be used directly by others.
|
||||
*
|
||||
* @author Karel Goderis - Initial contribution and API
|
||||
* @author Kai Kreuzer - removed unwanted dependencies
|
||||
* @author Christoph Weitkamp - Added parameter to adjust the volume
|
||||
* @author Wouter Born - Added methods for getting all sinks and sources
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface AudioManager {
|
||||
|
||||
/**
|
||||
* Name of the sub-directory of the config folder, holding sound files.
|
||||
*/
|
||||
static final String SOUND_DIR = "sounds";
|
||||
|
||||
/**
|
||||
* Plays the passed audio stream using the default audio sink.
|
||||
*
|
||||
* @param audioStream The audio stream to play or null if streaming should be stopped
|
||||
*/
|
||||
void play(@Nullable AudioStream audioStream);
|
||||
|
||||
/**
|
||||
* Plays the passed audio stream on the given sink.
|
||||
*
|
||||
* @param audioStream The audio stream to play or null if streaming should be stopped
|
||||
* @param sinkId The id of the audio sink to use or null for the default
|
||||
*/
|
||||
void play(@Nullable AudioStream audioStream, @Nullable String sinkId);
|
||||
|
||||
/**
|
||||
* Plays the passed audio stream on the given sink.
|
||||
*
|
||||
* @param audioStream The audio stream to play or null if streaming should be stopped
|
||||
* @param sinkId The id of the audio sink to use or null for the default
|
||||
* @param volume The volume to be used or null if the default notification volume should be used
|
||||
*/
|
||||
void play(@Nullable AudioStream audioStream, @Nullable String sinkId, @Nullable PercentType volume);
|
||||
|
||||
/**
|
||||
* Plays an audio file from the "sounds" folder using the default audio sink.
|
||||
*
|
||||
* @param fileName The file from the "sounds" folder
|
||||
* @throws AudioException in case the file does not exist or cannot be opened
|
||||
*/
|
||||
void playFile(String fileName) throws AudioException;
|
||||
|
||||
/**
|
||||
* Plays an audio file with the given volume from the "sounds" folder using the default audio sink.
|
||||
*
|
||||
* @param fileName The file from the "sounds" folder
|
||||
* @param volume The volume to be used or null if the default notification volume should be used
|
||||
* @throws AudioException in case the file does not exist or cannot be opened
|
||||
*/
|
||||
void playFile(String fileName, @Nullable PercentType volume) throws AudioException;
|
||||
|
||||
/**
|
||||
* Plays an audio file from the "sounds" folder using the given audio sink.
|
||||
*
|
||||
* @param fileName The file from the "sounds" folder
|
||||
* @param sinkId The id of the audio sink to use or null for the default
|
||||
* @throws AudioException in case the file does not exist or cannot be opened
|
||||
*/
|
||||
void playFile(String fileName, @Nullable String sinkId) throws AudioException;
|
||||
|
||||
/**
|
||||
* Plays an audio file with the given volume from the "sounds" folder using the given audio sink.
|
||||
*
|
||||
* @param fileName The file from the "sounds" folder
|
||||
* @param sinkId The id of the audio sink to use or null for the default
|
||||
* @param volume The volume to be used or null if the default notification volume should be used
|
||||
* @throws AudioException in case the file does not exist or cannot be opened
|
||||
*/
|
||||
void playFile(String fileName, @Nullable String sinkId, @Nullable PercentType volume) throws AudioException;
|
||||
|
||||
/**
|
||||
* Stream audio from the passed url using the default audio sink.
|
||||
*
|
||||
* @param url The url to stream from or null if streaming should be stopped
|
||||
* @throws AudioException in case the url stream cannot be opened
|
||||
*/
|
||||
void stream(@Nullable String url) throws AudioException;
|
||||
|
||||
/**
|
||||
* Stream audio from the passed url to the given sink
|
||||
*
|
||||
* @param url The url to stream from or null if streaming should be stopped
|
||||
* @param sinkId The id of the audio sink to use or null for the default
|
||||
* @throws AudioException in case the url stream cannot be opened
|
||||
*/
|
||||
void stream(@Nullable String url, @Nullable String sinkId) throws AudioException;
|
||||
|
||||
/**
|
||||
* Retrieves the current volume of a sink
|
||||
*
|
||||
* @param sinkId the sink to get the volume for or null for the default
|
||||
* @return the volume as a value between 0 and 100
|
||||
* @throws IOException if the sink is not able to determine the volume
|
||||
*/
|
||||
PercentType getVolume(@Nullable String sinkId) throws IOException;
|
||||
|
||||
/**
|
||||
* Sets the volume for a sink.
|
||||
*
|
||||
* @param volume the volume to set as a value between 0 and 100
|
||||
* @param sinkId the sink to set the volume for or null for the default
|
||||
* @throws IOException if the sink is not able to set the volume
|
||||
*/
|
||||
void setVolume(PercentType volume, @Nullable String sinkId) throws IOException;
|
||||
|
||||
/**
|
||||
* Retrieves an AudioSource.
|
||||
* If a default name is configured and the service available, this is returned. If no default name is configured,
|
||||
* the first available service is returned, if one exists. If no service with the default name is found, null is
|
||||
* returned.
|
||||
*
|
||||
* @return an AudioSource or null, if no service is available or if a default is configured, but no according
|
||||
* service is found
|
||||
*/
|
||||
@Nullable
|
||||
AudioSource getSource();
|
||||
|
||||
/**
|
||||
* Retrieves all audio sources
|
||||
*
|
||||
* @return all audio sources
|
||||
*/
|
||||
Set<AudioSource> getAllSources();
|
||||
|
||||
/**
|
||||
* Retrieves an AudioSink.
|
||||
* If a default name is configured and the service available, this is returned. If no default name is configured,
|
||||
* the first available service is returned, if one exists. If no service with the default name is found, null is
|
||||
* returned.
|
||||
*
|
||||
* @return an AudioSink or null, if no service is available or if a default is configured, but no according service
|
||||
* is found
|
||||
*/
|
||||
@Nullable
|
||||
AudioSink getSink();
|
||||
|
||||
/**
|
||||
* Retrieves all audio sinks
|
||||
*
|
||||
* @return all audio sinks
|
||||
*/
|
||||
Set<AudioSink> getAllSinks();
|
||||
|
||||
/**
|
||||
* Get a list of source ids that match a given pattern
|
||||
*
|
||||
* @param pattern pattern to search, can include `*` and `?` placeholders
|
||||
* @return ids of matching sources
|
||||
*/
|
||||
Set<String> getSourceIds(String pattern);
|
||||
|
||||
/**
|
||||
* Retrieves the sink for a given id
|
||||
*
|
||||
* @param sinkId the id of the sink or null for the default
|
||||
* @return the sink instance for the id or the default sink
|
||||
*/
|
||||
@Nullable
|
||||
AudioSink getSink(@Nullable String sinkId);
|
||||
|
||||
/**
|
||||
* Get a list of sink ids that match a given pattern
|
||||
*
|
||||
* @param pattern pattern to search, can include `*` and `?` placeholders
|
||||
* @return ids of matching sinks
|
||||
*/
|
||||
Set<String> getSinkIds(String pattern);
|
||||
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.smarthome.core.library.types.PercentType;
|
||||
|
||||
/**
|
||||
* Definition of an audio output like headphones, a speaker or for writing to
|
||||
* a file / clip.
|
||||
*
|
||||
* @author Harald Kuhn - Initial API
|
||||
* @author Kelly Davis - Modified to match discussion in #584
|
||||
* @author Christoph Weitkamp - Added getSupportedStreams() and UnsupportedAudioStreamException
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface AudioSink {
|
||||
|
||||
/**
|
||||
* Returns a simple string that uniquely identifies this service
|
||||
*
|
||||
* @return an id that identifies this service
|
||||
*/
|
||||
public String getId();
|
||||
|
||||
/**
|
||||
* Returns a localized human readable label that can be used within UIs.
|
||||
*
|
||||
* @param locale the locale to provide the label for
|
||||
* @return a localized string to be used in UIs
|
||||
*/
|
||||
@Nullable
|
||||
public String getLabel(Locale locale);
|
||||
|
||||
/**
|
||||
* Processes the passed {@link AudioStream}
|
||||
*
|
||||
* If the passed {@link AudioStream} is not supported by this instance, an {@link UnsupportedAudioStreamException}
|
||||
* is thrown.
|
||||
*
|
||||
* If the passed {@link AudioStream} has a {@link AudioFormat} not supported by this instance,
|
||||
* an {@link UnsupportedAudioFormatException} is thrown.
|
||||
*
|
||||
* In case the audioStream is null, this should be interpreted as a request to end any currently playing stream.
|
||||
*
|
||||
* @param audioStream the audio stream to play or null to keep quiet
|
||||
* @throws UnsupportedAudioFormatException If audioStream format is not supported
|
||||
* @throws UnsupportedAudioStreamException If audioStream is not supported
|
||||
*/
|
||||
void process(@Nullable AudioStream audioStream)
|
||||
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException;
|
||||
|
||||
/**
|
||||
* Gets a set containing all supported audio formats
|
||||
*
|
||||
* @return A Set containing all supported audio formats
|
||||
*/
|
||||
public Set<AudioFormat> getSupportedFormats();
|
||||
|
||||
/**
|
||||
* Gets a set containing all supported audio stream formats
|
||||
*
|
||||
* @return A Set containing all supported audio stream formats
|
||||
*/
|
||||
public Set<Class<? extends AudioStream>> getSupportedStreams();
|
||||
|
||||
/**
|
||||
* Gets the volume
|
||||
*
|
||||
* @return a PercentType value between 0 and 100 representing the actual volume
|
||||
* @throws IOException if the volume can not be determined
|
||||
*/
|
||||
public PercentType getVolume() throws IOException;
|
||||
|
||||
/**
|
||||
* Sets the volume
|
||||
*
|
||||
* @param volume a PercentType value between 0 and 100 representing the desired volume
|
||||
* @throws IOException if the volume can not be set
|
||||
*/
|
||||
public void setVolume(PercentType volume) throws IOException;
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This is an audio source, which can provide a continuous live stream of audio.
|
||||
* Its main use is for microphones and other "line-in" sources and it can be registered as a service in order to make
|
||||
* it available throughout the system.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution and API
|
||||
*/
|
||||
public interface AudioSource {
|
||||
|
||||
/**
|
||||
* Returns a simple string that uniquely identifies this service
|
||||
*
|
||||
* @return an id that identifies this service
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Returns a localized human readable label that can be used within UIs.
|
||||
*
|
||||
* @param locale the locale to provide the label for
|
||||
* @return a localized string to be used in UIs
|
||||
*/
|
||||
String getLabel(Locale locale);
|
||||
|
||||
/**
|
||||
* Obtain the audio formats supported by this AudioSource
|
||||
*
|
||||
* @return The audio formats supported by this service
|
||||
*/
|
||||
Set<AudioFormat> getSupportedFormats();
|
||||
|
||||
/**
|
||||
* Gets an AudioStream for reading audio data in supported audio format
|
||||
*
|
||||
* @param format the expected audio format of the stream
|
||||
* @return AudioStream for reading audio data
|
||||
* @throws AudioException If problem occurs obtaining the stream
|
||||
*/
|
||||
AudioStream getInputStream(AudioFormat format) throws AudioException;
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Wrapper for a source of audio data.
|
||||
*
|
||||
* In contrast to {@link AudioSource}, this is often a "one time use" instance for passing some audio data,
|
||||
* but it is not meant to be registered as a service.
|
||||
*
|
||||
* The stream needs to be closed by the client that uses it.
|
||||
*
|
||||
* @author Harald Kuhn - Initial API
|
||||
* @author Kelly Davis - Modified to match discussion in #584
|
||||
* @author Kai Kreuzer - Refactored to be only a temporary instance for the stream
|
||||
*/
|
||||
public abstract class AudioStream extends InputStream {
|
||||
|
||||
/**
|
||||
* Gets the supported audio format
|
||||
*
|
||||
* @return The supported audio format
|
||||
*/
|
||||
public abstract AudioFormat getFormat();
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* This is an implementation of a {@link FixedLengthAudioStream}, which is based on a simple byte array.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
public class ByteArrayAudioStream extends FixedLengthAudioStream {
|
||||
|
||||
private byte[] bytes;
|
||||
private AudioFormat format;
|
||||
private ByteArrayInputStream stream;
|
||||
|
||||
public ByteArrayAudioStream(byte[] bytes, AudioFormat format) {
|
||||
this.bytes = bytes;
|
||||
this.format = format;
|
||||
this.stream = new ByteArrayInputStream(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioFormat getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return stream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
stream.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return bytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getClonedStream() {
|
||||
return new ByteArrayAudioStream(bytes, format);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.smarthome.core.audio.utils.AudioStreamUtils;
|
||||
|
||||
/**
|
||||
* This is an AudioStream from an audio file
|
||||
*
|
||||
* @author Karel Goderis - Initial contribution and API
|
||||
* @author Kai Kreuzer - Refactored to take a file as input
|
||||
* @author Christoph Weitkamp - Refactored use of filename extension
|
||||
*
|
||||
*/
|
||||
public class FileAudioStream extends FixedLengthAudioStream {
|
||||
|
||||
public static final String WAV_EXTENSION = "wav";
|
||||
public static final String MP3_EXTENSION = "mp3";
|
||||
public static final String OGG_EXTENSION = "ogg";
|
||||
public static final String AAC_EXTENSION = "aac";
|
||||
|
||||
private final File file;
|
||||
private final AudioFormat audioFormat;
|
||||
private InputStream inputStream;
|
||||
private final long length;
|
||||
|
||||
public FileAudioStream(File file) throws AudioException {
|
||||
this(file, getAudioFormat(file));
|
||||
}
|
||||
|
||||
public FileAudioStream(File file, AudioFormat format) throws AudioException {
|
||||
this.file = file;
|
||||
this.inputStream = getInputStream(file);
|
||||
this.audioFormat = format;
|
||||
this.length = file.length();
|
||||
}
|
||||
|
||||
private static AudioFormat getAudioFormat(File file) throws AudioException {
|
||||
final String filename = file.getName().toLowerCase();
|
||||
final String extension = AudioStreamUtils.getExtension(filename);
|
||||
switch (extension) {
|
||||
case WAV_EXTENSION:
|
||||
return new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, false, 16, 705600,
|
||||
44100L);
|
||||
case MP3_EXTENSION:
|
||||
return AudioFormat.MP3;
|
||||
case OGG_EXTENSION:
|
||||
return AudioFormat.OGG;
|
||||
case AAC_EXTENSION:
|
||||
return AudioFormat.AAC;
|
||||
default:
|
||||
throw new AudioException("Unsupported file extension!");
|
||||
}
|
||||
}
|
||||
|
||||
private static InputStream getInputStream(File file) throws AudioException {
|
||||
try {
|
||||
return new FileInputStream(file);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new AudioException("File '" + file.getAbsolutePath() + "' not found!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioFormat getFormat() {
|
||||
return audioFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return inputStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
inputStream.close();
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
IOUtils.closeQuietly(inputStream);
|
||||
try {
|
||||
inputStream = getInputStream(file);
|
||||
} catch (AudioException e) {
|
||||
throw new IOException("Cannot reset file input stream: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getClonedStream() throws AudioException {
|
||||
return getInputStream(file);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* This is an {@link AudioStream}, which can provide information about its absolute length and is able to provide
|
||||
* cloned streams.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
public abstract class FixedLengthAudioStream extends AudioStream {
|
||||
|
||||
/**
|
||||
* Provides the length of the stream in bytes.
|
||||
*
|
||||
* @return absolute length in bytes
|
||||
*/
|
||||
public abstract long length();
|
||||
|
||||
/**
|
||||
* Returns a new, fully independent stream instance, which can be read and closed without impacting the original
|
||||
* instance.
|
||||
*
|
||||
* @return a new input stream that can be consumed by the caller
|
||||
* @throws AudioException if stream cannot be created
|
||||
*/
|
||||
public abstract InputStream getClonedStream() throws AudioException;
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.smarthome.core.audio.utils.AudioStreamUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is an AudioStream from an URL. Note that some sinks, like Sonos, can directly handle URL
|
||||
* based streams, and therefore can/should call getURL() to get an direct reference to the URL.
|
||||
*
|
||||
* @author Karel Goderis - Initial contribution and API
|
||||
* @author Kai Kreuzer - Refactored to not require a source
|
||||
* @author Christoph Weitkamp - Refactored use of filename extension
|
||||
*
|
||||
*/
|
||||
public class URLAudioStream extends AudioStream {
|
||||
|
||||
private static final Pattern PLS_STREAM_PATTERN = Pattern.compile("^File[0-9]=(.+)$");
|
||||
|
||||
public static final String M3U_EXTENSION = "m3u";
|
||||
public static final String PLS_EXTENSION = "pls";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(URLAudioStream.class);
|
||||
|
||||
private final AudioFormat audioFormat;
|
||||
private final InputStream inputStream;
|
||||
private String url;
|
||||
|
||||
private Socket shoutCastSocket;
|
||||
|
||||
public URLAudioStream(String url) throws AudioException {
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("url must not be null!");
|
||||
}
|
||||
this.url = url;
|
||||
this.audioFormat = new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, false, 16, null, null);
|
||||
this.inputStream = createInputStream();
|
||||
}
|
||||
|
||||
private InputStream createInputStream() throws AudioException {
|
||||
final String filename = url.toLowerCase();
|
||||
final String extension = AudioStreamUtils.getExtension(filename);
|
||||
try {
|
||||
switch (extension) {
|
||||
case M3U_EXTENSION:
|
||||
try (final InputStream isM3U = new URL(url).openStream()) {
|
||||
for (final String line : IOUtils.readLines(isM3U)) {
|
||||
if (!line.isEmpty() && !line.startsWith("#")) {
|
||||
url = line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PLS_EXTENSION:
|
||||
try (final InputStream isPLS = new URL(url).openStream()) {
|
||||
for (final String line : IOUtils.readLines(isPLS)) {
|
||||
if (!line.isEmpty() && line.startsWith("File")) {
|
||||
final Matcher matcher = PLS_STREAM_PATTERN.matcher(line);
|
||||
if (matcher.find()) {
|
||||
url = matcher.group(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
URL streamUrl = new URL(url);
|
||||
URLConnection connection = streamUrl.openConnection();
|
||||
if (connection.getContentType().equals("unknown/unknown")) {
|
||||
// Java does not parse non-standard headers used by SHOUTCast
|
||||
int port = streamUrl.getPort() > 0 ? streamUrl.getPort() : 80;
|
||||
// Manipulate User-Agent to receive a stream
|
||||
shoutCastSocket = new Socket(streamUrl.getHost(), port);
|
||||
|
||||
OutputStream os = shoutCastSocket.getOutputStream();
|
||||
String userAgent = "WinampMPEG/5.09";
|
||||
String req = "GET / HTTP/1.0\r\nuser-agent: " + userAgent
|
||||
+ "\r\nIcy-MetaData: 1\r\nConnection: keep-alive\r\n\r\n";
|
||||
os.write(req.getBytes());
|
||||
return shoutCastSocket.getInputStream();
|
||||
} else {
|
||||
// getInputStream() method is more error-proof than openStream(),
|
||||
// because openStream() does openConnection().getInputStream(),
|
||||
// which opens a new connection and does not reuse the old one.
|
||||
return connection.getInputStream();
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
logger.error("URL '{}' is not a valid url: {}", url, e.getMessage(), e);
|
||||
throw new AudioException("URL not valid");
|
||||
} catch (IOException e) {
|
||||
logger.error("Cannot set up stream '{}': {}", url, e.getMessage(), e);
|
||||
throw new AudioException("IO Error");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioFormat getFormat() {
|
||||
return audioFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return inputStream.read();
|
||||
}
|
||||
|
||||
public String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
if (shoutCastSocket != null) {
|
||||
shoutCastSocket.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return url;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
/**
|
||||
* Thrown when a requested format is not supported by an {@link AudioSource}
|
||||
* or {@link AudioSink} implementation
|
||||
*
|
||||
* @author Harald Kuhn - Initial API
|
||||
* @author Kelly Davis - Modified to match discussion in #584
|
||||
*/
|
||||
public class UnsupportedAudioFormatException extends AudioException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Unsupported {@link AudioFormat}
|
||||
*/
|
||||
private AudioFormat unsupportedFormat;
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message, unsupported format, and cause.
|
||||
*
|
||||
* @param message Detail message
|
||||
* @param unsupportedFormat Unsupported format
|
||||
* @param cause The cause
|
||||
*/
|
||||
public UnsupportedAudioFormatException(String message, AudioFormat unsupportedFormat, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.unsupportedFormat = unsupportedFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message and unsupported format.
|
||||
*
|
||||
* @param message Detail message
|
||||
* @param unsupportedFormat Unsupported format
|
||||
*/
|
||||
public UnsupportedAudioFormatException(String message, AudioFormat unsupportedFormat) {
|
||||
this(message, unsupportedFormat, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unsupported format
|
||||
*
|
||||
* @return The unsupported format
|
||||
*/
|
||||
public AudioFormat getUnsupportedFormat() {
|
||||
return unsupportedFormat;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio;
|
||||
|
||||
/**
|
||||
* Thrown when a requested {@link AudioStream} is not supported by an {@link AudioSource} or {@link AudioSink}
|
||||
* implementation
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
public class UnsupportedAudioStreamException extends AudioException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Unsupported {@link AudioStream}
|
||||
*/
|
||||
private Class<? extends AudioStream> unsupportedAudioStreamClass;
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message, unsupported format, and cause.
|
||||
*
|
||||
* @param message The message
|
||||
* @param unsupportedAudioStreamClass The unsupported audio stream class
|
||||
* @param cause The cause
|
||||
*/
|
||||
public UnsupportedAudioStreamException(String message, Class<? extends AudioStream> unsupportedAudioStreamClass,
|
||||
Throwable cause) {
|
||||
super(message, cause);
|
||||
this.unsupportedAudioStreamClass = unsupportedAudioStreamClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message and unsupported format.
|
||||
*
|
||||
* @param message The message
|
||||
* @param unsupportedAudioStreamClass The unsupported audio stream class
|
||||
*/
|
||||
public UnsupportedAudioStreamException(String message, Class<? extends AudioStream> unsupportedAudioStreamClass) {
|
||||
this(message, unsupportedAudioStreamClass, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unsupported audio stream class.
|
||||
*
|
||||
* @return The unsupported audio stream class
|
||||
*/
|
||||
public Class<? extends AudioStream> getUnsupportedAudioStreamClass() {
|
||||
return unsupportedAudioStreamClass;
|
||||
}
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio.internal;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.eclipse.smarthome.core.audio.AudioException;
|
||||
import org.eclipse.smarthome.core.audio.AudioManager;
|
||||
import org.eclipse.smarthome.core.audio.AudioSink;
|
||||
import org.eclipse.smarthome.core.audio.AudioSource;
|
||||
import org.eclipse.smarthome.core.i18n.LocaleProvider;
|
||||
import org.eclipse.smarthome.core.library.types.PercentType;
|
||||
import org.eclipse.smarthome.io.console.Console;
|
||||
import org.eclipse.smarthome.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.eclipse.smarthome.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Console command extension for all audio features.
|
||||
*
|
||||
* @author Karel Goderis - Initial contribution and API
|
||||
* @author Kai Kreuzer - refactored to match AudioManager implementation
|
||||
* @author Christoph Weitkamp - Added parameter to adjust the volume
|
||||
* @author Wouter Born - Sort audio sink and source options
|
||||
*/
|
||||
@Component(service = ConsoleCommandExtension.class)
|
||||
public class AudioConsoleCommandExtension extends AbstractConsoleCommandExtension {
|
||||
|
||||
static final String SUBCMD_PLAY = "play";
|
||||
static final String SUBCMD_STREAM = "stream";
|
||||
static final String SUBCMD_SOURCES = "sources";
|
||||
static final String SUBCMD_SINKS = "sinks";
|
||||
|
||||
private AudioManager audioManager;
|
||||
private LocaleProvider localeProvider;
|
||||
|
||||
public AudioConsoleCommandExtension() {
|
||||
super("audio", "Commands around audio enablement features.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getUsages() {
|
||||
return Arrays.asList(new String[] {
|
||||
buildCommandUsage(SUBCMD_PLAY + " [<sink>] <filename>",
|
||||
"plays a sound file from the sounds folder through the optionally specified audio sink(s)"),
|
||||
buildCommandUsage(SUBCMD_PLAY + " <sink> <filename> <volume>",
|
||||
"plays a sound file from the sounds folder through the specified audio sink(s) with the specified volume"),
|
||||
buildCommandUsage(SUBCMD_STREAM + " [<sink>] <url>",
|
||||
"streams the sound from the url through the optionally specified audio sink(s)"),
|
||||
buildCommandUsage(SUBCMD_SOURCES, "lists the audio sources"),
|
||||
buildCommandUsage(SUBCMD_SINKS, "lists the audio sinks") });
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String[] args, Console console) {
|
||||
if (args.length > 0) {
|
||||
String subCommand = args[0];
|
||||
switch (subCommand) {
|
||||
case SUBCMD_PLAY:
|
||||
if (args.length > 1) {
|
||||
play((String[]) ArrayUtils.subarray(args, 1, args.length), console);
|
||||
} else {
|
||||
console.println(
|
||||
"Specify file to play, and optionally the sink(s) to use (e.g. 'play javasound hello.mp3')");
|
||||
}
|
||||
return;
|
||||
case SUBCMD_STREAM:
|
||||
if (args.length > 1) {
|
||||
stream((String[]) ArrayUtils.subarray(args, 1, args.length), console);
|
||||
} else {
|
||||
console.println("Specify url to stream from, and optionally the sink(s) to use");
|
||||
}
|
||||
return;
|
||||
case SUBCMD_SOURCES:
|
||||
listSources(console);
|
||||
return;
|
||||
case SUBCMD_SINKS:
|
||||
listSinks(console);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
printUsage(console);
|
||||
}
|
||||
}
|
||||
|
||||
private void listSources(Console console) {
|
||||
Set<AudioSource> sources = audioManager.getAllSources();
|
||||
if (sources.size() > 0) {
|
||||
AudioSource defaultSource = audioManager.getSource();
|
||||
Locale locale = localeProvider.getLocale();
|
||||
sources.stream().sorted(comparing(s -> s.getLabel(locale))).forEach(source -> {
|
||||
console.println(String.format("%s %s (%s)", source.equals(defaultSource) ? "*" : " ",
|
||||
source.getLabel(locale), source.getId()));
|
||||
});
|
||||
} else {
|
||||
console.println("No audio sources found.");
|
||||
}
|
||||
}
|
||||
|
||||
private void listSinks(Console console) {
|
||||
Set<AudioSink> sinks = audioManager.getAllSinks();
|
||||
if (sinks.size() > 0) {
|
||||
AudioSink defaultSink = audioManager.getSink();
|
||||
Locale locale = localeProvider.getLocale();
|
||||
sinks.stream().sorted(comparing(s -> s.getLabel(locale))).forEach(sink -> {
|
||||
console.println(String.format("%s %s (%s)", sink.equals(defaultSink) ? "*" : " ", sink.getLabel(locale),
|
||||
sink.getId()));
|
||||
});
|
||||
} else {
|
||||
console.println("No audio sinks found.");
|
||||
}
|
||||
}
|
||||
|
||||
private void play(String[] args, Console console) {
|
||||
switch (args.length) {
|
||||
case 1:
|
||||
playOnSink(null, args[0], null, console);
|
||||
break;
|
||||
case 2:
|
||||
playOnSinks(args[0], args[1], null, console);
|
||||
break;
|
||||
case 3:
|
||||
PercentType volume = null;
|
||||
try {
|
||||
volume = PercentType.valueOf(args[2]);
|
||||
} catch (Exception e) {
|
||||
console.println(e.getMessage());
|
||||
break;
|
||||
}
|
||||
playOnSinks(args[0], args[1], volume, console);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void playOnSinks(String pattern, String fileName, PercentType volume, Console console) {
|
||||
for (String sinkId : audioManager.getSinkIds(pattern)) {
|
||||
playOnSink(sinkId, fileName, volume, console);
|
||||
}
|
||||
}
|
||||
|
||||
private void playOnSink(String sinkId, String fileName, PercentType volume, Console console) {
|
||||
try {
|
||||
audioManager.playFile(fileName, sinkId, volume);
|
||||
} catch (AudioException e) {
|
||||
console.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void stream(String[] args, Console console) {
|
||||
switch (args.length) {
|
||||
case 1:
|
||||
streamOnSink(null, args[0], console);
|
||||
break;
|
||||
case 2:
|
||||
streamOnSinks(args[0], args[1], console);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void streamOnSinks(String pattern, String url, Console console) {
|
||||
for (String sinkId : audioManager.getSinkIds(pattern)) {
|
||||
streamOnSink(sinkId, url, console);
|
||||
}
|
||||
}
|
||||
|
||||
private void streamOnSink(String sinkId, String url, Console console) {
|
||||
try {
|
||||
audioManager.stream(url, sinkId);
|
||||
} catch (AudioException e) {
|
||||
console.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setAudioManager(AudioManager audioManager) {
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
protected void unsetAudioManager(AudioManager audioManager) {
|
||||
this.audioManager = null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setLocaleProvider(LocaleProvider localeProvider) {
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
protected void unsetLocaleProvider(LocaleProvider localeProvider) {
|
||||
this.localeProvider = null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,322 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio.internal;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.smarthome.config.core.ConfigConstants;
|
||||
import org.eclipse.smarthome.config.core.ConfigOptionProvider;
|
||||
import org.eclipse.smarthome.config.core.ConfigurableService;
|
||||
import org.eclipse.smarthome.config.core.ParameterOption;
|
||||
import org.eclipse.smarthome.core.audio.AudioException;
|
||||
import org.eclipse.smarthome.core.audio.AudioManager;
|
||||
import org.eclipse.smarthome.core.audio.AudioSink;
|
||||
import org.eclipse.smarthome.core.audio.AudioSource;
|
||||
import org.eclipse.smarthome.core.audio.AudioStream;
|
||||
import org.eclipse.smarthome.core.audio.FileAudioStream;
|
||||
import org.eclipse.smarthome.core.audio.URLAudioStream;
|
||||
import org.eclipse.smarthome.core.audio.UnsupportedAudioFormatException;
|
||||
import org.eclipse.smarthome.core.audio.UnsupportedAudioStreamException;
|
||||
import org.eclipse.smarthome.core.library.types.PercentType;
|
||||
import org.osgi.framework.Constants;
|
||||
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.Modified;
|
||||
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;
|
||||
|
||||
/**
|
||||
* This service provides functionality around audio services and is the central service to be used directly by others.
|
||||
*
|
||||
* @author Karel Goderis - Initial contribution and API
|
||||
* @author Kai Kreuzer - removed unwanted dependencies
|
||||
* @author Christoph Weitkamp - Added getSupportedStreams() and UnsupportedAudioStreamException
|
||||
* @author Christoph Weitkamp - Added parameter to adjust the volume
|
||||
* @author Wouter Born - Sort audio sink and source options
|
||||
*/
|
||||
@Component(immediate = true, configurationPid = "org.eclipse.smarthome.audio", property = { //
|
||||
Constants.SERVICE_PID + "=org.eclipse.smarthome.audio", //
|
||||
ConfigurableService.SERVICE_PROPERTY_CATEGORY + "=system", //
|
||||
ConfigurableService.SERVICE_PROPERTY_DESCRIPTION_URI + "=" + AudioManagerImpl.CONFIG_URI, //
|
||||
ConfigurableService.SERVICE_PROPERTY_LABEL + "=Audio" //
|
||||
})
|
||||
public class AudioManagerImpl implements AudioManager, ConfigOptionProvider {
|
||||
|
||||
// constants for the configuration properties
|
||||
static final String CONFIG_URI = "system:audio";
|
||||
static final String CONFIG_DEFAULT_SINK = "defaultSink";
|
||||
static final String CONFIG_DEFAULT_SOURCE = "defaultSource";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AudioManagerImpl.class);
|
||||
|
||||
// service maps
|
||||
private final Map<String, AudioSource> audioSources = new ConcurrentHashMap<>();
|
||||
private final Map<String, AudioSink> audioSinks = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* default settings filled through the service configuration
|
||||
*/
|
||||
private String defaultSource;
|
||||
private String defaultSink;
|
||||
|
||||
@Activate
|
||||
protected void activate(Map<String, Object> config) {
|
||||
modified(config);
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
}
|
||||
|
||||
@Modified
|
||||
void modified(Map<String, Object> config) {
|
||||
if (config != null) {
|
||||
this.defaultSource = config.containsKey(CONFIG_DEFAULT_SOURCE)
|
||||
? config.get(CONFIG_DEFAULT_SOURCE).toString()
|
||||
: null;
|
||||
this.defaultSink = config.containsKey(CONFIG_DEFAULT_SINK) ? config.get(CONFIG_DEFAULT_SINK).toString()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play(AudioStream audioStream) {
|
||||
play(audioStream, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play(AudioStream audioStream, String sinkId) {
|
||||
play(audioStream, sinkId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play(AudioStream audioStream, String sinkId, PercentType volume) {
|
||||
AudioSink sink = getSink(sinkId);
|
||||
if (sink != null) {
|
||||
PercentType oldVolume = null;
|
||||
try {
|
||||
// get current volume
|
||||
oldVolume = getVolume(sinkId);
|
||||
} catch (IOException e) {
|
||||
logger.debug("An exception occurred while getting the volume of sink '{}' : {}", sink.getId(),
|
||||
e.getMessage(), e);
|
||||
}
|
||||
// set notification sound volume
|
||||
if (volume != null) {
|
||||
try {
|
||||
setVolume(volume, sinkId);
|
||||
} catch (IOException e) {
|
||||
logger.debug("An exception occurred while setting the volume of sink '{}' : {}", sink.getId(),
|
||||
e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
sink.process(audioStream);
|
||||
} catch (UnsupportedAudioFormatException | UnsupportedAudioStreamException e) {
|
||||
logger.warn("Error playing '{}': {}", audioStream, e.getMessage(), e);
|
||||
} finally {
|
||||
if (volume != null && oldVolume != null) {
|
||||
// restore volume only if it was set before
|
||||
try {
|
||||
setVolume(oldVolume, sinkId);
|
||||
} catch (IOException e) {
|
||||
logger.debug("An exception occurred while setting the volume of sink '{}' : {}", sink.getId(),
|
||||
e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn("Failed playing audio stream '{}' as no audio sink was found.", audioStream);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playFile(String fileName) throws AudioException {
|
||||
playFile(fileName, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playFile(String fileName, PercentType volume) throws AudioException {
|
||||
playFile(fileName, null, volume);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playFile(String fileName, String sinkId) throws AudioException {
|
||||
playFile(fileName, sinkId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playFile(String fileName, String sinkId, PercentType volume) throws AudioException {
|
||||
Objects.requireNonNull(fileName, "File cannot be played as fileName is null.");
|
||||
|
||||
File file = new File(
|
||||
ConfigConstants.getConfigFolder() + File.separator + SOUND_DIR + File.separator + fileName);
|
||||
FileAudioStream is = new FileAudioStream(file);
|
||||
play(is, sinkId, volume);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stream(String url) throws AudioException {
|
||||
stream(url, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stream(String url, String sinkId) throws AudioException {
|
||||
AudioStream audioStream = url != null ? new URLAudioStream(url) : null;
|
||||
play(audioStream, sinkId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PercentType getVolume(String sinkId) throws IOException {
|
||||
AudioSink sink = getSink(sinkId);
|
||||
|
||||
if (sink != null) {
|
||||
return sink.getVolume();
|
||||
}
|
||||
return PercentType.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVolume(PercentType volume, String sinkId) throws IOException {
|
||||
AudioSink sink = getSink(sinkId);
|
||||
|
||||
if (sink != null) {
|
||||
sink.setVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioSource getSource() {
|
||||
AudioSource source = null;
|
||||
if (defaultSource != null) {
|
||||
source = audioSources.get(defaultSource);
|
||||
if (source == null) {
|
||||
logger.warn("Default AudioSource service '{}' not available!", defaultSource);
|
||||
}
|
||||
} else if (!audioSources.isEmpty()) {
|
||||
source = audioSources.values().iterator().next();
|
||||
} else {
|
||||
logger.debug("No AudioSource service available!");
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<AudioSource> getAllSources() {
|
||||
return new HashSet<>(audioSources.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioSink getSink() {
|
||||
AudioSink sink = null;
|
||||
if (defaultSink != null) {
|
||||
sink = audioSinks.get(defaultSink);
|
||||
if (sink == null) {
|
||||
logger.warn("Default AudioSink service '{}' not available!", defaultSink);
|
||||
}
|
||||
} else if (!audioSinks.isEmpty()) {
|
||||
sink = audioSinks.values().iterator().next();
|
||||
} else {
|
||||
logger.debug("No AudioSink service available!");
|
||||
}
|
||||
return sink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<AudioSink> getAllSinks() {
|
||||
return new HashSet<>(audioSinks.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSourceIds(String pattern) {
|
||||
String regex = pattern.replace("?", ".?").replace("*", ".*?");
|
||||
Set<String> matchedSources = new HashSet<>();
|
||||
|
||||
for (String aSource : audioSources.keySet()) {
|
||||
if (aSource.matches(regex)) {
|
||||
matchedSources.add(aSource);
|
||||
}
|
||||
}
|
||||
|
||||
return matchedSources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioSink getSink(String sinkId) {
|
||||
return (sinkId == null) ? getSink() : audioSinks.get(sinkId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSinkIds(String pattern) {
|
||||
String regex = pattern.replace("?", ".?").replace("*", ".*?");
|
||||
Set<String> matchedSinkIds = new HashSet<>();
|
||||
|
||||
for (String sinkId : audioSinks.keySet()) {
|
||||
if (sinkId.matches(regex)) {
|
||||
matchedSinkIds.add(sinkId);
|
||||
}
|
||||
}
|
||||
|
||||
return matchedSinkIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ParameterOption> getParameterOptions(URI uri, String param, Locale locale) {
|
||||
if (uri.toString().equals(CONFIG_URI)) {
|
||||
final Locale safeLocale = locale != null ? locale : Locale.getDefault();
|
||||
if (CONFIG_DEFAULT_SOURCE.equals(param)) {
|
||||
return audioSources.values().stream().sorted(comparing(s -> s.getLabel(safeLocale)))
|
||||
.map(s -> new ParameterOption(s.getId(), s.getLabel(safeLocale))).collect(toList());
|
||||
} else if (CONFIG_DEFAULT_SINK.equals(param)) {
|
||||
return audioSinks.values().stream().sorted(comparing(s -> s.getLabel(safeLocale)))
|
||||
.map(s -> new ParameterOption(s.getId(), s.getLabel(safeLocale))).collect(toList());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void addAudioSource(AudioSource audioSource) {
|
||||
this.audioSources.put(audioSource.getId(), audioSource);
|
||||
}
|
||||
|
||||
protected void removeAudioSource(AudioSource audioSource) {
|
||||
this.audioSources.remove(audioSource.getId());
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void addAudioSink(AudioSink audioSink) {
|
||||
this.audioSinks.put(audioSink.getId(), audioSink);
|
||||
}
|
||||
|
||||
protected void removeAudioSink(AudioSink audioSink) {
|
||||
this.audioSinks.remove(audioSink.getId());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.smarthome.core.audio.AudioException;
|
||||
import org.eclipse.smarthome.core.audio.AudioFormat;
|
||||
import org.eclipse.smarthome.core.audio.AudioHTTPServer;
|
||||
import org.eclipse.smarthome.core.audio.AudioStream;
|
||||
import org.eclipse.smarthome.core.audio.FixedLengthAudioStream;
|
||||
import org.eclipse.smarthome.io.http.servlet.SmartHomeServlet;
|
||||
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.http.HttpService;
|
||||
|
||||
/**
|
||||
* A servlet that serves audio streams via HTTP.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
public class AudioServlet extends SmartHomeServlet implements AudioHTTPServer {
|
||||
|
||||
private static final long serialVersionUID = -3364664035854567854L;
|
||||
|
||||
private static final String SERVLET_NAME = "/audio";
|
||||
|
||||
private final Map<String, AudioStream> oneTimeStreams = new ConcurrentHashMap<>();
|
||||
private final Map<String, FixedLengthAudioStream> multiTimeStreams = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, Long> streamTimeouts = new ConcurrentHashMap<>();
|
||||
|
||||
@Activate
|
||||
protected void activate() {
|
||||
super.activate(SERVLET_NAME);
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
super.deactivate(SERVLET_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Reference
|
||||
protected void setHttpService(HttpService httpService) {
|
||||
super.setHttpService(httpService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsetHttpService(HttpService httpService) {
|
||||
super.unsetHttpService(httpService);
|
||||
}
|
||||
|
||||
private InputStream prepareInputStream(final String streamId, final HttpServletResponse resp)
|
||||
throws AudioException {
|
||||
final AudioStream stream;
|
||||
final boolean multiAccess;
|
||||
if (oneTimeStreams.containsKey(streamId)) {
|
||||
stream = oneTimeStreams.remove(streamId);
|
||||
multiAccess = false;
|
||||
} else if (multiTimeStreams.containsKey(streamId)) {
|
||||
stream = multiTimeStreams.get(streamId);
|
||||
multiAccess = true;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.debug("Stream to serve is {}", streamId);
|
||||
|
||||
// try to set the content-type, if possible
|
||||
final String mimeType;
|
||||
if (stream.getFormat().getCodec() == AudioFormat.CODEC_MP3) {
|
||||
mimeType = "audio/mpeg";
|
||||
} else if (stream.getFormat().getContainer() == AudioFormat.CONTAINER_WAVE) {
|
||||
mimeType = "audio/wav";
|
||||
} else if (stream.getFormat().getContainer() == AudioFormat.CONTAINER_OGG) {
|
||||
mimeType = "audio/ogg";
|
||||
} else {
|
||||
mimeType = null;
|
||||
}
|
||||
if (mimeType != null) {
|
||||
resp.setContentType(mimeType);
|
||||
}
|
||||
|
||||
// try to set the content-length, if possible
|
||||
if (stream instanceof FixedLengthAudioStream) {
|
||||
final Long size = ((FixedLengthAudioStream) stream).length();
|
||||
resp.setContentLength(size.intValue());
|
||||
}
|
||||
|
||||
if (multiAccess) {
|
||||
// we need to care about concurrent access and have a separate stream for each thread
|
||||
return ((FixedLengthAudioStream) stream).getClonedStream();
|
||||
} else {
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
removeTimedOutStreams();
|
||||
|
||||
final String streamId = StringUtils.substringBefore(StringUtils.substringAfterLast(req.getRequestURI(), "/"),
|
||||
".");
|
||||
|
||||
try (final InputStream stream = prepareInputStream(streamId, resp)) {
|
||||
if (stream == null) {
|
||||
logger.debug("Received request for invalid stream id at {}", req.getRequestURI());
|
||||
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
} else {
|
||||
IOUtils.copy(stream, resp.getOutputStream());
|
||||
resp.flushBuffer();
|
||||
}
|
||||
} catch (final AudioException ex) {
|
||||
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void removeTimedOutStreams() {
|
||||
for (String streamId : multiTimeStreams.keySet()) {
|
||||
if (streamTimeouts.get(streamId) < System.nanoTime()) {
|
||||
// the stream has expired, we need to remove it!
|
||||
FixedLengthAudioStream stream = multiTimeStreams.remove(streamId);
|
||||
streamTimeouts.remove(streamId);
|
||||
IOUtils.closeQuietly(stream);
|
||||
stream = null;
|
||||
logger.debug("Removed timed out stream {}", streamId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serve(AudioStream stream) {
|
||||
String streamId = UUID.randomUUID().toString();
|
||||
oneTimeStreams.put(streamId, stream);
|
||||
return getRelativeURL(streamId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serve(FixedLengthAudioStream stream, int seconds) {
|
||||
String streamId = UUID.randomUUID().toString();
|
||||
multiTimeStreams.put(streamId, stream);
|
||||
streamTimeouts.put(streamId, System.nanoTime() + TimeUnit.SECONDS.toNanos(seconds));
|
||||
return getRelativeURL(streamId);
|
||||
}
|
||||
|
||||
Map<String, FixedLengthAudioStream> getMultiTimeStreams() {
|
||||
return Collections.unmodifiableMap(multiTimeStreams);
|
||||
}
|
||||
|
||||
private String getRelativeURL(String streamId) {
|
||||
return SERVLET_NAME + "/" + streamId;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.core.audio.utils;
|
||||
|
||||
/**
|
||||
* Some general filename and extension utilities.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
public class AudioStreamUtils {
|
||||
|
||||
public static final String EXTENSION_SEPARATOR = ".";
|
||||
|
||||
/**
|
||||
* Gets the base name of a filename.
|
||||
*
|
||||
* @param filename the filename to query
|
||||
* @return the base name of the file or an empty string if none exists or {@code null} if the filename is
|
||||
* {@code null}
|
||||
*/
|
||||
public static String getBaseName(String filename) {
|
||||
if (filename == null) {
|
||||
return null;
|
||||
}
|
||||
final int index = filename.lastIndexOf(EXTENSION_SEPARATOR);
|
||||
if (index == -1) {
|
||||
return filename;
|
||||
} else {
|
||||
return filename.substring(0, index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of a filename.
|
||||
*
|
||||
* @param filename the filename to retrieve the extension of
|
||||
* @return the extension of the file or an empty string if none exists or {@code null} if the filename is
|
||||
* {@code null}
|
||||
*/
|
||||
public static String getExtension(String filename) {
|
||||
if (filename == null) {
|
||||
return null;
|
||||
}
|
||||
final int index = filename.lastIndexOf(EXTENSION_SEPARATOR);
|
||||
if (index == -1) {
|
||||
return "";
|
||||
} else {
|
||||
return filename.substring(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the extension of a filename matches the given.
|
||||
*
|
||||
* @param filename the filename to check the extension of
|
||||
* @param extension the extension to check for
|
||||
* @return {@code true} if the filename has the specified extension
|
||||
*/
|
||||
public static boolean isExtension(String filename, String extension) {
|
||||
if (filename == null) {
|
||||
return false;
|
||||
}
|
||||
if (extension == null || extension.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return getExtension(filename).equals(extension);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="http://eclipse.org/smarthome/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="http://eclipse.org/smarthome/schemas/config-description/v1.0.0
|
||||
http://eclipse.org/smarthome/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="system:audio">
|
||||
<parameter name="defaultSource" type="text" required="false">
|
||||
<label>Default Source</label>
|
||||
<description>The default audio source to use if no other is specified.</description>
|
||||
</parameter>
|
||||
<parameter name="defaultSink" type="text" required="false">
|
||||
<label>Default Sink</label>
|
||||
<description>The default audio sink to use if no other is specified.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
27
bundles/org.openhab.core.auth.jaas/.classpath
Normal file
27
bundles/org.openhab.core.auth.jaas/.classpath
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<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>
|
23
bundles/org.openhab.core.auth.jaas/.project
Normal file
23
bundles/org.openhab.core.auth.jaas/.project
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.auth.jaas</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -0,0 +1,4 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
encoding/src=UTF-8
|
@ -0,0 +1,6 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
19
bundles/org.openhab.core.auth.jaas/NOTICE
Normal file
19
bundles/org.openhab.core.auth.jaas/NOTICE
Normal file
@ -0,0 +1,19 @@
|
||||
This content is produced and maintained by the Eclipse SmartHome project.
|
||||
|
||||
* Project home: https://eclipse.org/smarthome/
|
||||
|
||||
== 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/eclipse/smarthome
|
||||
|
||||
== Copyright Holders
|
||||
|
||||
See the NOTICE file distributed with the source code at
|
||||
https://github.com/eclipse/smarthome/blob/master/NOTICE
|
||||
for detailed information regarding copyright ownership.
|
24
bundles/org.openhab.core.auth.jaas/pom.xml
Normal file
24
bundles/org.openhab.core.auth.jaas/pom.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bundles</artifactId>
|
||||
<version>2.5.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.auth.jaas</artifactId>
|
||||
|
||||
<name>openHAB Core :: Bundles :: JAAS Authentication</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.auth.jaas.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.NameCallback;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
import javax.security.auth.login.LoginContext;
|
||||
import javax.security.auth.login.LoginException;
|
||||
|
||||
import org.eclipse.smarthome.core.auth.Authentication;
|
||||
import org.eclipse.smarthome.core.auth.AuthenticationException;
|
||||
import org.eclipse.smarthome.core.auth.AuthenticationProvider;
|
||||
import org.eclipse.smarthome.core.auth.Credentials;
|
||||
import org.eclipse.smarthome.core.auth.UsernamePasswordCredentials;
|
||||
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.Modified;
|
||||
|
||||
/**
|
||||
* Implementation of authentication provider which is backed by JAAS realm.
|
||||
*
|
||||
* Real authentication logic is embedded in login modules implemented by 3rd party, this code is just for bridging it to
|
||||
* smarthome platform.
|
||||
*
|
||||
* @author Łukasz Dywicki - Initial contribution and API
|
||||
* @author Kai Kreuzer - Removed ManagedService and used DS configuration instead
|
||||
*/
|
||||
@Component(configurationPid = "org.eclipse.smarthome.jaas")
|
||||
public class JaasAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private String realmName;
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(final Credentials credentials) throws AuthenticationException {
|
||||
if (realmName == null) { // configuration is not yet ready or set
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(credentials instanceof UsernamePasswordCredentials)) {
|
||||
throw new AuthenticationException("Unsupported credentials passed to provider.");
|
||||
}
|
||||
|
||||
UsernamePasswordCredentials userCredentials = (UsernamePasswordCredentials) credentials;
|
||||
final String name = userCredentials.getUsername();
|
||||
final char[] password = userCredentials.getPassword().toCharArray();
|
||||
|
||||
try {
|
||||
LoginContext loginContext = new LoginContext(realmName, new CallbackHandler() {
|
||||
@Override
|
||||
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||
for (Callback callback : callbacks) {
|
||||
if (callback instanceof PasswordCallback) {
|
||||
((PasswordCallback) callback).setPassword(password);
|
||||
} else if (callback instanceof NameCallback) {
|
||||
((NameCallback) callback).setName(name);
|
||||
} else {
|
||||
throw new UnsupportedCallbackException(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
loginContext.login();
|
||||
|
||||
return getAuthentication(name, loginContext.getSubject());
|
||||
} catch (LoginException e) {
|
||||
throw new AuthenticationException("Could not obtain authentication over login context", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Authentication getAuthentication(String name, Subject subject) {
|
||||
return new Authentication(name, getRoles(subject.getPrincipals()));
|
||||
}
|
||||
|
||||
private String[] getRoles(Set<Principal> principals) {
|
||||
String[] roles = new String[principals.size()];
|
||||
int i = 0;
|
||||
for (Principal principal : principals) {
|
||||
roles[i++] = principal.getName();
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Activate
|
||||
protected void activate(Map<String, Object> properties) {
|
||||
modified(properties);
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate(Map<String, Object> properties) {
|
||||
}
|
||||
|
||||
@Modified
|
||||
protected void modified(Map<String, Object> properties) {
|
||||
if (properties == null) {
|
||||
realmName = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Object propertyValue = properties.get("realmName");
|
||||
if (propertyValue != null) {
|
||||
if (propertyValue instanceof String) {
|
||||
realmName = (String) propertyValue;
|
||||
} else {
|
||||
realmName = propertyValue.toString();
|
||||
}
|
||||
} else {
|
||||
// value could be unset, we should reset it value
|
||||
realmName = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<? extends Credentials> type) {
|
||||
return UsernamePasswordCredentials.class.isAssignableFrom(type);
|
||||
}
|
||||
}
|
27
bundles/org.openhab.core.auth.oauth2client/.classpath
Normal file
27
bundles/org.openhab.core.auth.oauth2client/.classpath
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<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>
|
23
bundles/org.openhab.core.auth.oauth2client/.project
Normal file
23
bundles/org.openhab.core.auth.oauth2client/.project
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.auth.oauth2client</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -0,0 +1,4 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
encoding/src=UTF-8
|
@ -0,0 +1,6 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
29
bundles/org.openhab.core.auth.oauth2client/pom.xml
Normal file
29
bundles/org.openhab.core.auth.oauth2client/pom.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bundles</artifactId>
|
||||
<version>2.5.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.auth.oauth2client</artifactId>
|
||||
|
||||
<name>openHAB Core :: Bundles :: OAuth2Client</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.io.net</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.auth.oauth2client.internal;
|
||||
|
||||
/**
|
||||
* Just a place to store all the important, reused keywords.
|
||||
*
|
||||
* @author Gary Tse - Initial contribution
|
||||
*
|
||||
*/
|
||||
public interface Keyword {
|
||||
|
||||
String CLIENT_ID = "client_id";
|
||||
String CLIENT_SECRET = "client_secret";
|
||||
|
||||
String GRANT_TYPE = "grant_type";
|
||||
String USERNAME = "username";
|
||||
String PASSWORD = "password";
|
||||
String CLIENT_CREDENTIALS = "client_credentials";
|
||||
String AUTHORIZATION_CODE = "authorization_code";
|
||||
|
||||
String SCOPE = "scope";
|
||||
|
||||
String REFRESH_TOKEN = "refresh_token";
|
||||
String REDIRECT_URI = "redirect_uri";
|
||||
|
||||
String CODE = "code"; // https://tools.ietf.org/html/rfc6749#section-4.1
|
||||
|
||||
String STATE = "state"; // https://tools.ietf.org/html/rfc6749#section-4.1
|
||||
|
||||
}
|
@ -0,0 +1,407 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.auth.oauth2client.internal;
|
||||
|
||||
import static org.eclipse.smarthome.auth.oauth2client.internal.Keyword.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.AccessTokenRefreshListener;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.AccessTokenResponse;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.OAuthClientService;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.OAuthException;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.OAuthResponseException;
|
||||
import org.eclipse.smarthome.io.net.http.HttpClientFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Implementation of OAuthClientService.
|
||||
*
|
||||
* It requires the following services:
|
||||
*
|
||||
* org.eclipse.smarthome.core.storage.Storage (mandatory; for storing grant tokens, access tokens and refresh tokens)
|
||||
*
|
||||
* HttpClientFactory for http connections with Jetty
|
||||
*
|
||||
* The OAuthTokens, request parameters are stored and persisted using the "Storage" service.
|
||||
* This allows the token to be automatically refreshed when needed.
|
||||
*
|
||||
* @author Michael Bock - Initial contribution
|
||||
* @author Gary Tse - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OAuthClientServiceImpl implements OAuthClientService {
|
||||
|
||||
public static final int DEFAULT_TOKEN_EXPIRES_IN_BUFFER_SECOND = 10;
|
||||
|
||||
private static final String EXCEPTION_MESSAGE_CLOSED = "Client service is closed";
|
||||
|
||||
private transient final Logger logger = LoggerFactory.getLogger(OAuthClientServiceImpl.class);
|
||||
|
||||
private @NonNullByDefault({}) OAuthStoreHandler storeHandler;
|
||||
|
||||
// Constructor params - static
|
||||
private final String handle;
|
||||
private final int tokenExpiresInSeconds;
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
private final List<AccessTokenRefreshListener> accessTokenRefreshListeners = new ArrayList<>();
|
||||
|
||||
private PersistedParams persistedParams = new PersistedParams();
|
||||
|
||||
private volatile boolean closed = false;
|
||||
|
||||
private OAuthClientServiceImpl(String handle, int tokenExpiresInSeconds, HttpClientFactory httpClientFactory) {
|
||||
this.handle = handle;
|
||||
this.tokenExpiresInSeconds = tokenExpiresInSeconds;
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* It should only be used internally, thus the access is package level
|
||||
*
|
||||
* @param bundleContext Bundle Context
|
||||
* @param handle The handle produced previously from
|
||||
* {@link org.eclipse.smarthome.core.auth.client.oauth2.OAuthFactory#createOAuthClientService}
|
||||
* @param storeHandler Storage handler
|
||||
* @param tokenExpiresInSeconds Positive integer; a small time buffer in seconds. It is used to calculate the expiry
|
||||
* of the access tokens. This allows the access token to expire earlier than the
|
||||
* official stated expiry time; thus prevents the caller obtaining a valid token at the time of invoke,
|
||||
* only to find the token immediately expired.
|
||||
* @param httpClientFactory Http client factory
|
||||
* @return new instance of OAuthClientServiceImpl or null if it doesn't exist
|
||||
* @throws IllegalStateException if store is not available.
|
||||
*/
|
||||
static @Nullable OAuthClientServiceImpl getInstance(String handle, OAuthStoreHandler storeHandler,
|
||||
int tokenExpiresInSeconds, HttpClientFactory httpClientFactory) {
|
||||
// Load parameters from Store
|
||||
PersistedParams persistedParamsFromStore = storeHandler.loadPersistedParams(handle);
|
||||
if (persistedParamsFromStore == null) {
|
||||
return null;
|
||||
}
|
||||
OAuthClientServiceImpl clientService = new OAuthClientServiceImpl(handle, tokenExpiresInSeconds,
|
||||
httpClientFactory);
|
||||
clientService.storeHandler = storeHandler;
|
||||
clientService.persistedParams = persistedParamsFromStore;
|
||||
|
||||
return clientService;
|
||||
}
|
||||
|
||||
/**
|
||||
* It should only be used internally, thus the access is package level
|
||||
*
|
||||
* @param bundleContext Bundle Context*
|
||||
* @param handle The handle produced previously from
|
||||
* {@link org.eclipse.smarthome.core.auth.client.oauth2.OAuthFactory#createOAuthClientService}*
|
||||
* @param storeHandler Storage handler
|
||||
* @param httpClientFactory Http client factory
|
||||
* @param persistedParams These parameters are static with respect to the oauth provider and thus can be persisted.
|
||||
* @return OAuthClientServiceImpl an instance
|
||||
*/
|
||||
static OAuthClientServiceImpl createInstance(String handle, OAuthStoreHandler storeHandler,
|
||||
HttpClientFactory httpClientFactory, PersistedParams params) {
|
||||
OAuthClientServiceImpl clientService = new OAuthClientServiceImpl(handle, params.tokenExpiresInSeconds,
|
||||
httpClientFactory);
|
||||
|
||||
clientService.storeHandler = storeHandler;
|
||||
clientService.persistedParams = params;
|
||||
storeHandler.savePersistedParams(handle, clientService.persistedParams);
|
||||
|
||||
return clientService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthorizationUrl(@Nullable String redirectURI, @Nullable String scope, @Nullable String state)
|
||||
throws OAuthException {
|
||||
if (state == null) {
|
||||
persistedParams.state = createNewState();
|
||||
} else {
|
||||
persistedParams.state = state;
|
||||
}
|
||||
String scopeToUse = scope == null ? persistedParams.scope : scope;
|
||||
// keep it to check against redirectUri in #getAccessTokenResponseByAuthorizationCode
|
||||
persistedParams.redirectUri = redirectURI;
|
||||
String authorizationUrl = persistedParams.authorizationUrl;
|
||||
if (authorizationUrl == null) {
|
||||
throw new OAuthException("Missing authorization url");
|
||||
}
|
||||
String clientId = persistedParams.clientId;
|
||||
if (clientId == null) {
|
||||
throw new OAuthException("Missing client ID");
|
||||
}
|
||||
|
||||
OAuthConnector connector = new OAuthConnector(httpClientFactory);
|
||||
return connector.getAuthorizationUrl(authorizationUrl, clientId, redirectURI, persistedParams.state,
|
||||
scopeToUse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String extractAuthCodeFromAuthResponse(@NonNull String redirectURLwithParams) throws OAuthException {
|
||||
// parse the redirectURL
|
||||
try {
|
||||
URL redirectURLObject = new URL(redirectURLwithParams);
|
||||
UrlEncoded urlEncoded = new UrlEncoded(redirectURLObject.getQuery());
|
||||
|
||||
String stateFromRedirectURL = urlEncoded.getValue(STATE, 0); // may contain multiple...
|
||||
if (stateFromRedirectURL == null) {
|
||||
if (persistedParams.state == null) {
|
||||
// This should not happen as the state is usually set
|
||||
return urlEncoded.getValue(CODE, 0);
|
||||
} // else
|
||||
throw new OAuthException(String.format("state from redirectURL is incorrect. Expected: %s Found: %s",
|
||||
persistedParams.state, stateFromRedirectURL));
|
||||
} else {
|
||||
if (stateFromRedirectURL.equals(persistedParams.state)) {
|
||||
return urlEncoded.getValue(CODE, 0);
|
||||
} // else
|
||||
throw new OAuthException(String.format("state from redirectURL is incorrect. Expected: %s Found: %s",
|
||||
persistedParams.state, stateFromRedirectURL));
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
throw new OAuthException("Redirect URL is malformed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessTokenResponse getAccessTokenResponseByAuthorizationCode(String authorizationCode, String redirectURI)
|
||||
throws OAuthException, IOException, OAuthResponseException {
|
||||
|
||||
if (isClosed()) {
|
||||
throw new OAuthException(EXCEPTION_MESSAGE_CLOSED);
|
||||
}
|
||||
|
||||
if (persistedParams.redirectUri != null && !persistedParams.redirectUri.equals(redirectURI)) {
|
||||
// check parameter redirectURI in #getAuthorizationUrl are the same as given
|
||||
throw new OAuthException(String.format(
|
||||
"redirectURI should be the same from previous call #getAuthorizationUrl. Expected: %s Found: %s",
|
||||
persistedParams.redirectUri, redirectURI));
|
||||
}
|
||||
String tokenUrl = persistedParams.tokenUrl;
|
||||
if (tokenUrl == null) {
|
||||
throw new OAuthException("Missing token url");
|
||||
}
|
||||
String clientId = persistedParams.clientId;
|
||||
if (clientId == null) {
|
||||
throw new OAuthException("Missing client ID");
|
||||
}
|
||||
|
||||
OAuthConnector connector = new OAuthConnector(httpClientFactory);
|
||||
AccessTokenResponse accessTokenResponse = connector.grantTypeAuthorizationCode(tokenUrl, authorizationCode,
|
||||
clientId, persistedParams.clientSecret, redirectURI,
|
||||
Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
|
||||
|
||||
// store it
|
||||
storeHandler.saveAccessTokenResponse(handle, accessTokenResponse);
|
||||
return accessTokenResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implicit Grant (RFC 6749 section 4.2) is not implemented. It is directly interacting with user-agent
|
||||
* The implicit grant is not implemented. It usually involves browser/javascript redirection flows
|
||||
* and is out of Eclipse SmartHome scope.
|
||||
*/
|
||||
@Override
|
||||
public AccessTokenResponse getAccessTokenByImplicit(@Nullable String redirectURI, @Nullable String scope,
|
||||
@Nullable String state) throws OAuthException, IOException, OAuthResponseException {
|
||||
throw new UnsupportedOperationException("Implicit Grant is not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessTokenResponse getAccessTokenByResourceOwnerPasswordCredentials(String username, String password,
|
||||
@Nullable String scope) throws OAuthException, IOException, OAuthResponseException {
|
||||
|
||||
if (isClosed()) {
|
||||
throw new OAuthException(EXCEPTION_MESSAGE_CLOSED);
|
||||
}
|
||||
String tokenUrl = persistedParams.tokenUrl;
|
||||
if (tokenUrl == null) {
|
||||
throw new OAuthException("Missing token url");
|
||||
}
|
||||
|
||||
OAuthConnector connector = new OAuthConnector(httpClientFactory);
|
||||
AccessTokenResponse accessTokenResponse = connector.grantTypePassword(tokenUrl, username, password,
|
||||
persistedParams.clientId, persistedParams.clientSecret, scope,
|
||||
Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
|
||||
|
||||
// store it
|
||||
storeHandler.saveAccessTokenResponse(handle, accessTokenResponse);
|
||||
return accessTokenResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessTokenResponse getAccessTokenByClientCredentials(@Nullable String scope)
|
||||
throws OAuthException, IOException, OAuthResponseException {
|
||||
|
||||
if (isClosed()) {
|
||||
throw new OAuthException(EXCEPTION_MESSAGE_CLOSED);
|
||||
}
|
||||
String tokenUrl = persistedParams.tokenUrl;
|
||||
if (tokenUrl == null) {
|
||||
throw new OAuthException("Missing token url");
|
||||
}
|
||||
String clientId = persistedParams.clientId;
|
||||
if (clientId == null) {
|
||||
throw new OAuthException("Missing client ID");
|
||||
}
|
||||
|
||||
OAuthConnector connector = new OAuthConnector(httpClientFactory);
|
||||
// depending on usage, cannot guarantee every parameter is not null at the beginning
|
||||
AccessTokenResponse accessTokenResponse = connector.grantTypeClientCredentials(tokenUrl, clientId,
|
||||
persistedParams.clientSecret, scope, Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
|
||||
|
||||
// store it
|
||||
storeHandler.saveAccessTokenResponse(handle, accessTokenResponse);
|
||||
return accessTokenResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessTokenResponse refreshToken() throws OAuthException, IOException, OAuthResponseException {
|
||||
|
||||
if (isClosed()) {
|
||||
throw new OAuthException(EXCEPTION_MESSAGE_CLOSED);
|
||||
}
|
||||
|
||||
AccessTokenResponse lastAccessToken;
|
||||
try {
|
||||
lastAccessToken = storeHandler.loadAccessTokenResponse(handle);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new OAuthException("Cannot decrypt access token from store", e);
|
||||
}
|
||||
if (lastAccessToken == null) {
|
||||
throw new OAuthException(
|
||||
"Cannot refresh token because last access token is not available from handle: " + handle);
|
||||
}
|
||||
if (lastAccessToken.getRefreshToken() == null) {
|
||||
throw new OAuthException("Cannot refresh token because last access token did not have a refresh token");
|
||||
}
|
||||
String tokenUrl = persistedParams.tokenUrl;
|
||||
if (tokenUrl == null) {
|
||||
throw new OAuthException("tokenUrl is required but null");
|
||||
}
|
||||
|
||||
OAuthConnector connector = new OAuthConnector(httpClientFactory);
|
||||
AccessTokenResponse accessTokenResponse = connector.grantTypeRefreshToken(tokenUrl,
|
||||
lastAccessToken.getRefreshToken(), persistedParams.clientId, persistedParams.clientSecret,
|
||||
persistedParams.scope, Boolean.TRUE.equals(persistedParams.supportsBasicAuth));
|
||||
|
||||
// The service may not return the refresh token so use the last refresh token otherwise it's not stored.
|
||||
if (StringUtil.isBlank(accessTokenResponse.getRefreshToken())) {
|
||||
accessTokenResponse.setRefreshToken(lastAccessToken.getRefreshToken());
|
||||
}
|
||||
// store it
|
||||
storeHandler.saveAccessTokenResponse(handle, accessTokenResponse);
|
||||
accessTokenRefreshListeners.forEach(l -> l.onAccessTokenResponse(accessTokenResponse));
|
||||
return accessTokenResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AccessTokenResponse getAccessTokenResponse()
|
||||
throws OAuthException, IOException, OAuthResponseException {
|
||||
|
||||
if (isClosed()) {
|
||||
throw new OAuthException(EXCEPTION_MESSAGE_CLOSED);
|
||||
}
|
||||
|
||||
AccessTokenResponse lastAccessToken;
|
||||
try {
|
||||
lastAccessToken = storeHandler.loadAccessTokenResponse(handle);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new OAuthException("Cannot decrypt access token from store", e);
|
||||
}
|
||||
if (lastAccessToken == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lastAccessToken.isExpired(LocalDateTime.now(), tokenExpiresInSeconds)
|
||||
&& lastAccessToken.getRefreshToken() != null) {
|
||||
return refreshToken();
|
||||
}
|
||||
return lastAccessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importAccessTokenResponse(AccessTokenResponse accessTokenResponse) throws OAuthException {
|
||||
if (isClosed()) {
|
||||
throw new OAuthException(EXCEPTION_MESSAGE_CLOSED);
|
||||
}
|
||||
storeHandler.saveAccessTokenResponse(handle, accessTokenResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access tokens have expiry times. They are given by authorization server.
|
||||
* This parameter introduces a buffer in seconds that deduct from the expires-in.
|
||||
* For example, if the expires-in = 3600 seconds ( 1hour ), then setExpiresInBuffer(60)
|
||||
* will remove 60 seconds from the expiry time. In other words, the expires-in
|
||||
* becomes 3540 ( 59 mins ) effectively.
|
||||
*
|
||||
* Calls to protected resources can reasonably assume that the token is not expired.
|
||||
*
|
||||
* @param tokenExpiresInBuffer The number of seconds to remove the expires-in. Default 0 seconds.
|
||||
*/
|
||||
public void setTokenExpiresInBuffer(int tokenExpiresInBuffer) {
|
||||
this.persistedParams.tokenExpiresInSeconds = tokenExpiresInBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() throws OAuthException {
|
||||
|
||||
if (isClosed()) {
|
||||
throw new OAuthException(EXCEPTION_MESSAGE_CLOSED);
|
||||
}
|
||||
|
||||
logger.debug("removing handle: {}", handle);
|
||||
storeHandler.remove(handle);
|
||||
close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
closed = true;
|
||||
storeHandler = null;
|
||||
|
||||
logger.debug("closing oauth client, handle: {}", handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAccessTokenRefreshListener(AccessTokenRefreshListener listener) {
|
||||
accessTokenRefreshListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAccessTokenRefreshListener(AccessTokenRefreshListener listener) {
|
||||
return accessTokenRefreshListeners.remove(listener);
|
||||
}
|
||||
|
||||
private String createNewState() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,383 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.auth.oauth2client.internal;
|
||||
|
||||
import static org.eclipse.smarthome.auth.oauth2client.internal.Keyword.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.FormContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.AccessTokenResponse;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.OAuthException;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.OAuthResponseException;
|
||||
import org.eclipse.smarthome.io.net.http.HttpClientFactory;
|
||||
import org.eclipse.smarthome.io.net.http.TrustManagerProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* Implementation of the OAuthConnector. It directly deals with the underlying http connections (using Jetty).
|
||||
* This is meant for internal use. OAuth2client's clients should look into {@code OAuthClientService} or
|
||||
* {@code OAuthFactory}
|
||||
*
|
||||
* @author Michael Bock - Initial contribution
|
||||
* @author Gary Tse - ESH adaptation
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OAuthConnector {
|
||||
|
||||
private static final String HTTP_CLIENT_CONSUMER_NAME = "OAuthConnector";
|
||||
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OAuthConnector.class);
|
||||
private final Gson gson;
|
||||
|
||||
public OAuthConnector(HttpClientFactory httpClientFactory) {
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization Code Grant
|
||||
*
|
||||
* @param authorizationEndpoint The end point of the authorization provider that performs authorization of the
|
||||
* resource owner
|
||||
* @param clientId Client identifier (will be URL-encoded)
|
||||
* @param redirectURI RFC 6749 section 3.1.2 (will be URL-encoded)
|
||||
* @param state Recommended to enhance security (will be URL-encoded)
|
||||
* @param scope Optional space separated list of scope (will be URL-encoded)
|
||||
*
|
||||
* @return A URL based on the authorizationEndpoint, with query parameters added.
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.1.1">rfc6749 section-4.1.1</a>
|
||||
*/
|
||||
public String getAuthorizationUrl(String authorizationEndpoint, String clientId, @Nullable String redirectURI,
|
||||
@Nullable String state, @Nullable String scope) {
|
||||
StringBuilder authorizationUrl = new StringBuilder(authorizationEndpoint);
|
||||
|
||||
if (authorizationUrl.indexOf("?") == -1) {
|
||||
authorizationUrl.append('?');
|
||||
} else {
|
||||
authorizationUrl.append('&');
|
||||
}
|
||||
|
||||
try {
|
||||
authorizationUrl.append("response_type=code");
|
||||
authorizationUrl.append("&client_id=").append(URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()));
|
||||
if (state != null) {
|
||||
authorizationUrl.append("&state=").append(URLEncoder.encode(state, StandardCharsets.UTF_8.name()));
|
||||
}
|
||||
if (redirectURI != null) {
|
||||
authorizationUrl.append("&redirect_uri=")
|
||||
.append(URLEncoder.encode(redirectURI, StandardCharsets.UTF_8.name()));
|
||||
}
|
||||
if (scope != null) {
|
||||
authorizationUrl.append("&scope=").append(URLEncoder.encode(scope, StandardCharsets.UTF_8.name()));
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// never happens
|
||||
logger.error("Unknown encoding {}", e.getMessage(), e);
|
||||
}
|
||||
return authorizationUrl.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource Owner Password Credentials Grant
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.3">rfc6749 section-4.3</a>
|
||||
*
|
||||
* @param tokenUrl URL of the oauth provider that accepts access token requests.
|
||||
* @param username The resource owner username.
|
||||
* @param password The resource owner password.
|
||||
* @param clientId The client identifier issued to the client during the registration process
|
||||
* @param clientSecret The client secret. The client MAY omit the parameter if the client secret is an empty string.
|
||||
* @param scope Access Token Scope.
|
||||
* @param supportsBasicAuth Determines whether the oauth client should use HTTP Authorization header to the oauth
|
||||
* provider.
|
||||
* @return Access Token
|
||||
* @throws IOException IO/ network exceptions
|
||||
* @throws OAuthException Other exceptions
|
||||
* @throws OAuthErrorException Error codes given by authorization provider, as in RFC 6749 section 5.2 Error
|
||||
* Response
|
||||
*/
|
||||
public AccessTokenResponse grantTypePassword(String tokenUrl, String username, String password,
|
||||
@Nullable String clientId, @Nullable String clientSecret, @Nullable String scope, boolean supportsBasicAuth)
|
||||
throws OAuthResponseException, OAuthException, IOException {
|
||||
|
||||
HttpClient httpClient = null;
|
||||
try {
|
||||
httpClient = createHttpClient(tokenUrl);
|
||||
Request request = getMethod(httpClient, tokenUrl);
|
||||
Fields fields = initFields(GRANT_TYPE, PASSWORD, USERNAME, username, PASSWORD, password, SCOPE, scope);
|
||||
|
||||
setAuthentication(clientId, clientSecret, request, fields, supportsBasicAuth);
|
||||
return doRequest(PASSWORD, httpClient, request, fields);
|
||||
} finally {
|
||||
shutdownQuietly(httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh Token
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-6">rfc6749 section-6</a>
|
||||
*
|
||||
* @param tokenUrl URL of the oauth provider that accepts access token requests.
|
||||
* @param refreshToken The refresh token, which can be used to obtain new access tokens using authorization grant
|
||||
* @param clientId The client identifier issued to the client during the registration process
|
||||
* @param clientSecret The client secret. The client MAY omit the parameter if the client secret is an empty string.
|
||||
* @param scope Access Token Scope.
|
||||
* @param supportsBasicAuth Determines whether the oauth client should use HTTP Authorization header to the oauth
|
||||
* provider.
|
||||
* @return Access Token
|
||||
* @throws IOException IO/ network exceptions
|
||||
* @throws OAuthException Other exceptions
|
||||
* @throws OAuthErrorException Error codes given by authorization provider, as in RFC 6749 section 5.2 Error
|
||||
* Response
|
||||
*/
|
||||
public AccessTokenResponse grantTypeRefreshToken(String tokenUrl, String refreshToken, @Nullable String clientId,
|
||||
@Nullable String clientSecret, @Nullable String scope, boolean supportsBasicAuth)
|
||||
throws OAuthResponseException, OAuthException, IOException {
|
||||
|
||||
HttpClient httpClient = null;
|
||||
try {
|
||||
httpClient = createHttpClient(tokenUrl);
|
||||
Request request = getMethod(httpClient, tokenUrl);
|
||||
Fields fields = initFields(GRANT_TYPE, REFRESH_TOKEN, REFRESH_TOKEN, refreshToken, SCOPE, scope);
|
||||
|
||||
setAuthentication(clientId, clientSecret, request, fields, supportsBasicAuth);
|
||||
return doRequest(REFRESH_TOKEN, httpClient, request, fields);
|
||||
} finally {
|
||||
shutdownQuietly(httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization Code Grant - part (E)
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.1.3">rfc6749 section-4.1.3</a>
|
||||
*
|
||||
* @param tokenUrl URL of the oauth provider that accepts access token requests.
|
||||
* @param authorizationCode to be used to trade with the oauth provider for access token
|
||||
* @param clientId The client identifier issued to the client during the registration process
|
||||
* @param clientSecret The client secret. The client MAY omit the parameter if the client secret is an empty string.
|
||||
* @param redirectUrl is the http request parameter which tells the oauth provider the URI to redirect the
|
||||
* user-agent. This may/ may not be present as per agreement with the oauth provider.
|
||||
* @param supportsBasicAuth Determines whether the oauth client should use HTTP Authorization header to the oauth
|
||||
* provider
|
||||
* @return Access Token
|
||||
* @throws IOException IO/ network exceptions
|
||||
* @throws OAuthException Other exceptions
|
||||
* @throws OAuthErrorException Error codes given by authorization provider, as in RFC 6749 section 5.2 Error
|
||||
* Response
|
||||
*/
|
||||
public AccessTokenResponse grantTypeAuthorizationCode(String tokenUrl, String authorizationCode, String clientId,
|
||||
@Nullable String clientSecret, String redirectUrl, boolean supportsBasicAuth)
|
||||
throws OAuthResponseException, OAuthException, IOException {
|
||||
HttpClient httpClient = null;
|
||||
try {
|
||||
httpClient = createHttpClient(tokenUrl);
|
||||
Request request = getMethod(httpClient, tokenUrl);
|
||||
Fields fields = initFields(GRANT_TYPE, AUTHORIZATION_CODE, CODE, authorizationCode, REDIRECT_URI,
|
||||
redirectUrl);
|
||||
|
||||
setAuthentication(clientId, clientSecret, request, fields, supportsBasicAuth);
|
||||
return doRequest(AUTHORIZATION_CODE, httpClient, request, fields);
|
||||
} finally {
|
||||
shutdownQuietly(httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Client Credentials Grant
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.4">rfc6749 section-4.4</a>
|
||||
*
|
||||
* @param tokenUrl URL of the oauth provider that accepts access token requests.
|
||||
* @param clientId The client identifier issued to the client during the registration process
|
||||
* @param clientSecret The client secret. The client MAY omit the parameter if the client secret is an empty string.
|
||||
* @param scope Access Token Scope.
|
||||
* @param supportsBasicAuth Determines whether the oauth client should use HTTP Authorization header to the oauth
|
||||
* provider
|
||||
* @return Access Token
|
||||
* @throws IOException IO/ network exceptions
|
||||
* @throws OAuthException Other exceptions
|
||||
* @throws OAuthErrorException Error codes given by authorization provider, as in RFC 6749 section 5.2 Error
|
||||
* Response
|
||||
*/
|
||||
public AccessTokenResponse grantTypeClientCredentials(String tokenUrl, String clientId,
|
||||
@Nullable String clientSecret, @Nullable String scope, boolean supportsBasicAuth)
|
||||
throws OAuthResponseException, OAuthException, IOException {
|
||||
|
||||
HttpClient httpClient = null;
|
||||
try {
|
||||
httpClient = createHttpClient(tokenUrl);
|
||||
Request request = getMethod(httpClient, tokenUrl);
|
||||
Fields fields = initFields(GRANT_TYPE, CLIENT_CREDENTIALS, SCOPE, scope);
|
||||
|
||||
setAuthentication(clientId, clientSecret, request, fields, supportsBasicAuth);
|
||||
return doRequest(CLIENT_CREDENTIALS, httpClient, request, fields);
|
||||
} finally {
|
||||
shutdownQuietly(httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
private Request getMethod(HttpClient httpClient, String tokenUrl) {
|
||||
Request request = httpClient.newRequest(tokenUrl).method(HttpMethod.POST);
|
||||
request.header(HttpHeader.ACCEPT, "application/json");
|
||||
request.header(HttpHeader.ACCEPT_CHARSET, "UTF-8");
|
||||
return request;
|
||||
}
|
||||
|
||||
private void setAuthentication(@Nullable String clientId, @Nullable String clientSecret, Request request,
|
||||
Fields fields, boolean supportsBasicAuth) {
|
||||
logger.debug("Setting authentication for clientId {}. Using basic auth {}", clientId, supportsBasicAuth);
|
||||
if (supportsBasicAuth && clientSecret != null) {
|
||||
String authString = clientId + ":" + clientSecret;
|
||||
request.header(HttpHeader.AUTHORIZATION,
|
||||
"Basic " + Base64.getEncoder().encodeToString(authString.getBytes(StandardCharsets.UTF_8)));
|
||||
} else {
|
||||
if (clientId != null) {
|
||||
fields.add(CLIENT_ID, clientId);
|
||||
}
|
||||
if (clientSecret != null) {
|
||||
fields.add(CLIENT_SECRET, clientSecret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Fields initFields(String... parameters) {
|
||||
Fields fields = new Fields();
|
||||
|
||||
for (int i = 0; i < parameters.length; i += 2) {
|
||||
if (i + 1 < parameters.length && parameters[i] != null && parameters[i + 1] != null) {
|
||||
logger.debug("Oauth request parameter {}, value {}", parameters[i], parameters[i + 1]);
|
||||
fields.add(parameters[i], parameters[i + 1]);
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
private AccessTokenResponse doRequest(final String grantType, HttpClient httpClient, final Request request,
|
||||
Fields fields) throws OAuthResponseException, OAuthException, IOException {
|
||||
|
||||
int statusCode = 0;
|
||||
String content = "";
|
||||
try {
|
||||
final FormContentProvider entity = new FormContentProvider(fields);
|
||||
final ContentResponse response = AccessController
|
||||
.doPrivileged((PrivilegedExceptionAction<ContentResponse>) () -> {
|
||||
Request requestWithContent = request.content(entity);
|
||||
return requestWithContent.send();
|
||||
});
|
||||
|
||||
statusCode = response.getStatus();
|
||||
content = response.getContentAsString();
|
||||
|
||||
if (statusCode == HttpStatus.OK_200) {
|
||||
AccessTokenResponse jsonResponse = gson.fromJson(content, AccessTokenResponse.class);
|
||||
jsonResponse.setCreatedOn(LocalDateTime.now()); // this is not supplied by the response
|
||||
logger.info("grant type {} to URL {} success", grantType, request.getURI());
|
||||
return jsonResponse;
|
||||
} else if (statusCode == HttpStatus.BAD_REQUEST_400) {
|
||||
OAuthResponseException errorResponse = gson.fromJson(content, OAuthResponseException.class);
|
||||
logger.error("grant type {} to URL {} failed with error code {}, description {}", grantType,
|
||||
request.getURI(), errorResponse.getError(), errorResponse.getErrorDescription());
|
||||
|
||||
throw errorResponse;
|
||||
} else {
|
||||
logger.error("grant type {} to URL {} failed with HTTP response code {}", grantType, request.getURI(),
|
||||
statusCode);
|
||||
throw new OAuthException("Bad http response, http code " + statusCode);
|
||||
}
|
||||
} catch (PrivilegedActionException pae) {
|
||||
Exception underlyingException = pae.getException();
|
||||
if (underlyingException instanceof InterruptedException || underlyingException instanceof TimeoutException
|
||||
|| underlyingException instanceof ExecutionException) {
|
||||
throw new IOException("Exception in oauth communication, grant type " + grantType, underlyingException);
|
||||
}
|
||||
// Dont know what exception it is, wrap it up and throw it out
|
||||
throw new OAuthException("Exception in oauth communication, grant type " + grantType, underlyingException);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new OAuthException(String.format(
|
||||
"Unable to deserialize json into AccessTokenResponse/ OAuthResponseException. httpCode: %i json: %s",
|
||||
statusCode, content), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a special case where the httpClient (jetty) is created due to the need for certificate pinning.
|
||||
* If ceritificate pinning is needed, please refer to {@code TrustManagerProvider}. The http client is
|
||||
* created, used and then shutdown immediately after use. There is little reason to cache the client/ connections
|
||||
* because oauth requests are short; and it may take hours/ days before the next request is needed.
|
||||
*
|
||||
* @param tokenUrl access token url
|
||||
* @return http client. This http client
|
||||
* @throws OAuthException If any exception is thrown while starting the http client.
|
||||
* @see TrustManagerProvider
|
||||
*/
|
||||
private HttpClient createHttpClient(String tokenUrl) throws OAuthException {
|
||||
HttpClient httpClient = httpClientFactory.createHttpClient(HTTP_CLIENT_CONSUMER_NAME, tokenUrl);
|
||||
if (!httpClient.isStarted()) {
|
||||
try {
|
||||
AccessController.doPrivileged((PrivilegedExceptionAction<@Nullable Void>) () -> {
|
||||
httpClient.start();
|
||||
return null;
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new OAuthException("Exception while starting httpClient, tokenUrl: " + tokenUrl, e);
|
||||
}
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
private void shutdownQuietly(@Nullable HttpClient httpClient) {
|
||||
try {
|
||||
if (httpClient != null) {
|
||||
AccessController.doPrivileged((PrivilegedExceptionAction<@Nullable Void>) () -> {
|
||||
httpClient.stop();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// there is nothing we can do here
|
||||
logger.error("Exception while shutting down httpClient, {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.auth.oauth2client.internal;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.OAuthClientService;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.OAuthException;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.OAuthFactory;
|
||||
import org.eclipse.smarthome.io.net.http.HttpClientFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Implementation of {@link OAuthFactory}.
|
||||
*
|
||||
* @author Michael Bock - Initial contribution
|
||||
* @author Gary Tse - ESH adaptation
|
||||
* @author Hilbrand Bouwkamp - Changed implementation of createOAuthClientService
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component
|
||||
public class OAuthFactoryImpl implements OAuthFactory {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OAuthFactoryImpl.class);
|
||||
|
||||
@NonNullByDefault({})
|
||||
private OAuthStoreHandler oAuthStoreHandler;
|
||||
@NonNullByDefault({})
|
||||
private HttpClientFactory httpClientFactory;
|
||||
|
||||
private int tokenExpiresInBuffer = OAuthClientServiceImpl.DEFAULT_TOKEN_EXPIRES_IN_BUFFER_SECOND;
|
||||
|
||||
private final Map<String, OAuthClientService> oauthClientServiceCache = new ConcurrentHashMap<>();
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
// close each service
|
||||
for (OAuthClientService clientServiceImpl : oauthClientServiceCache.values()) {
|
||||
clientServiceImpl.close();
|
||||
}
|
||||
oauthClientServiceCache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuthClientService createOAuthClientService(String handle, String tokenUrl,
|
||||
@Nullable String authorizationUrl, String clientId, @Nullable String clientSecret, @Nullable String scope,
|
||||
@Nullable Boolean supportsBasicAuth) {
|
||||
PersistedParams params = oAuthStoreHandler.loadPersistedParams(handle);
|
||||
PersistedParams newParams = new PersistedParams(handle, tokenUrl, authorizationUrl, clientId, clientSecret,
|
||||
scope, supportsBasicAuth, tokenExpiresInBuffer);
|
||||
OAuthClientService clientImpl = null;
|
||||
|
||||
// If parameters in storage and parameters are the same as arguments passed get the client from storage
|
||||
if (params != null && params.equals(newParams)) {
|
||||
clientImpl = getOAuthClientService(handle);
|
||||
}
|
||||
// If no client with parameters or with different parameters create or update (if parameters are different)
|
||||
// client in storage.
|
||||
if (clientImpl == null) {
|
||||
clientImpl = OAuthClientServiceImpl.createInstance(handle, oAuthStoreHandler, httpClientFactory, newParams);
|
||||
oauthClientServiceCache.put(handle, clientImpl);
|
||||
}
|
||||
return clientImpl;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuthClientService getOAuthClientService(String handle) {
|
||||
OAuthClientService clientImpl = oauthClientServiceCache.get(handle);
|
||||
|
||||
if (clientImpl == null || clientImpl.isClosed()) {
|
||||
// This happens after reboot, or client was closed without factory knowing; create a new client
|
||||
// the store has the handle/config data
|
||||
clientImpl = OAuthClientServiceImpl.getInstance(handle, oAuthStoreHandler, tokenExpiresInBuffer,
|
||||
httpClientFactory);
|
||||
if (clientImpl == null) {
|
||||
return null;
|
||||
}
|
||||
oauthClientServiceCache.put(handle, clientImpl);
|
||||
}
|
||||
return clientImpl;
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Override
|
||||
public void ungetOAuthService(String handle) {
|
||||
OAuthClientService clientImpl = oauthClientServiceCache.get(handle);
|
||||
|
||||
if (clientImpl == null) {
|
||||
logger.debug("{} handle not found. Cannot unregisterOAuthServie", handle);
|
||||
return;
|
||||
}
|
||||
clientImpl.close();
|
||||
oauthClientServiceCache.remove(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteServiceAndAccessToken(String handle) {
|
||||
OAuthClientService clientImpl = oauthClientServiceCache.get(handle);
|
||||
|
||||
if (clientImpl != null) {
|
||||
try {
|
||||
clientImpl.remove();
|
||||
} catch (OAuthException e) {
|
||||
// client was already closed, does not matter
|
||||
}
|
||||
oauthClientServiceCache.remove(handle);
|
||||
}
|
||||
oAuthStoreHandler.remove(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Store handler is mandatory, but the actual storage service is not.
|
||||
* OAuthStoreHandler will handle when storage service is missing.
|
||||
*
|
||||
* Intended static mandatory 1..1 reference
|
||||
*
|
||||
* @param oAuthStoreHandler
|
||||
*/
|
||||
@Reference
|
||||
protected void setOAuthStoreHandler(OAuthStoreHandler oAuthStoreHandler) {
|
||||
this.oAuthStoreHandler = oAuthStoreHandler;
|
||||
}
|
||||
|
||||
protected void unsetOAuthStoreHandler(OAuthStoreHandler oAuthStoreHandler) {
|
||||
this.oAuthStoreHandler = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* HttpClientFactory is mandatory, and is used as a client for
|
||||
* all http(s) communications to the OAuth providers
|
||||
*
|
||||
* @param httpClientFactory
|
||||
*/
|
||||
@Reference
|
||||
protected void setHttpClientFactory(HttpClientFactory httpClientFactory) {
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
protected void unsetHttpClientFactory(HttpClientFactory httpClientFactory) {
|
||||
this.httpClientFactory = null;
|
||||
}
|
||||
|
||||
public int getTokenExpiresInBuffer() {
|
||||
return tokenExpiresInBuffer;
|
||||
}
|
||||
|
||||
public void setTokenExpiresInBuffer(int bufferInSeconds) {
|
||||
tokenExpiresInBuffer = bufferInSeconds;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.auth.oauth2client.internal;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.AccessTokenResponse;
|
||||
|
||||
/**
|
||||
* This is for OAuth client internal use.
|
||||
*
|
||||
* @author Gary Tse - Initial Contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface OAuthStoreHandler {
|
||||
|
||||
/**
|
||||
* Get an AccessTokenResponse from the store. The access token and refresh token are encrypted
|
||||
* and therefore will be decrypted before returning.
|
||||
*
|
||||
* If the storage is not available, it is still possible to get the AccessTokenResponse from memory cache.
|
||||
* However, the last-used statistics will be broken. It is a measured risk to take.
|
||||
*
|
||||
* @param handle the handle given by the call
|
||||
* {@code OAuthFactory#createOAuthClientService(String, String, String, String, String, Boolean)}
|
||||
* @return AccessTokenResponse if available, null if not.
|
||||
* @throws GeneralSecurityException when the token cannot be decrypted.
|
||||
*/
|
||||
@Nullable
|
||||
AccessTokenResponse loadAccessTokenResponse(String handle) throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Save the {@code AccessTokenResponse} by the handle
|
||||
*
|
||||
* @param handle unique string used as a handle/ reference to the OAuth client service, and the underlying
|
||||
* access tokens, configs.
|
||||
* @param accessTokenResponse This can be null, which explicitly removes the AccessTokenResponse from store.
|
||||
*/
|
||||
void saveAccessTokenResponse(String handle, @Nullable AccessTokenResponse accessTokenResponse);
|
||||
|
||||
/**
|
||||
* Remove the token for the given handler. No exception is thrown in all cases
|
||||
*
|
||||
* @param handle unique string used as a handle/ reference to the OAuth client service, and the underlying
|
||||
* access tokens, configs.
|
||||
*/
|
||||
void remove(String handle);
|
||||
|
||||
/**
|
||||
* Remove all data in the oauth store, !!!use with caution!!!
|
||||
*/
|
||||
void removeAll();
|
||||
|
||||
/**
|
||||
* Save the {@code PersistedParams} into the store
|
||||
*
|
||||
* @param handle unique string used as a handle/ reference to the OAuth client service, and the underlying
|
||||
* access tokens, configs.
|
||||
* @param persistedParams These parameters are static with respect to the oauth provider and thus can be persisted.
|
||||
*/
|
||||
void savePersistedParams(String handle, @Nullable PersistedParams persistedParams);
|
||||
|
||||
/**
|
||||
* Load the {@code PersistedParams} from the store
|
||||
*
|
||||
* @param handle unique string used as a handle/ reference to the OAuth client service, and the underlying
|
||||
* access tokens, configs.
|
||||
* @return PersistedParams when available, null if not exist
|
||||
*/
|
||||
@Nullable
|
||||
PersistedParams loadPersistedParams(String handle);
|
||||
}
|
@ -0,0 +1,439 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.auth.oauth2client.internal;
|
||||
|
||||
import static org.eclipse.smarthome.auth.oauth2client.internal.StorageRecordType.*;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.smarthome.auth.oauth2client.internal.cipher.SymmetricKeyCipher;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.AccessTokenResponse;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.StorageCipher;
|
||||
import org.eclipse.smarthome.core.storage.Storage;
|
||||
import org.eclipse.smarthome.core.storage.StorageService;
|
||||
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.component.annotations.ReferenceCardinality;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
/**
|
||||
* This class handles the storage directly. It is internal to the OAuthClientService and there is
|
||||
* little need to study this.
|
||||
*
|
||||
* The first role of this handler storing and caching the access token response, and persisted parameters.
|
||||
*
|
||||
* The storage contains these:
|
||||
* 1. INDEX_HANDLES = json string-set of all handles
|
||||
* 2. <handle>.LastUsed = system-time-milliseconds
|
||||
* 3. <handle>.AccessTokenResponse = Json of AccessTokenResponse
|
||||
* 4. <handle>.ServiceConfiguration = Json of PersistedParameters
|
||||
*
|
||||
* If at any time, the storage is not available, it is still possible to read existing access tokens from store.
|
||||
* The last-used statistics for this access token is broken. It is a measured risk to take.
|
||||
*
|
||||
* If at any time, the storage is not available, it is not able to write any new access tokens into store.
|
||||
*
|
||||
* All entries are subject to removal if they have not been used for 183 days or more (half year).
|
||||
* The recycle is performed when then instance is deactivated
|
||||
*
|
||||
* @author Gary Tse - Initial Contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(property = "CIPHER_TARGET=SymmetricKeyCipher")
|
||||
public class OAuthStoreHandlerImpl implements OAuthStoreHandler {
|
||||
|
||||
// easy mocking with protected access
|
||||
protected static final int EXPIRE_DAYS = 183;
|
||||
protected static final int ACCESS_TOKEN_CACHE_SIZE = 50;
|
||||
private static final String STORE_NAME = "StorageHandler.For.OAuthClientService";
|
||||
private static final String STORE_KEY_INDEX_OF_HANDLES = "INDEX_HANDLES";
|
||||
|
||||
private final Set<String> allHandles = new HashSet<>(); // must be initialized
|
||||
private @NonNullByDefault({}) StorageFacade storageFacade;
|
||||
|
||||
private final Set<StorageCipher> allAvailableStorageCiphers = new LinkedHashSet<>();
|
||||
private Optional<StorageCipher> storageCipher = Optional.empty();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OAuthStoreHandlerImpl.class);
|
||||
|
||||
@Activate
|
||||
public void activate(Map<String, Object> properties) throws GeneralSecurityException {
|
||||
// this allows future implementations to change cipher by just setting the CIPHER_TARGET
|
||||
String cipherTarget = (String) properties.getOrDefault("CIPHER_TARGET", SymmetricKeyCipher.CIPHER_ID);
|
||||
|
||||
// choose the cipher by the cipherTarget
|
||||
storageCipher = allAvailableStorageCiphers.stream()
|
||||
.filter(cipher -> cipher.getUniqueCipherId().equals(cipherTarget)).findFirst();
|
||||
|
||||
logger.debug("Using Cipher: {}", storageCipher
|
||||
.orElseThrow(() -> new GeneralSecurityException("No StorageCipher with target=" + cipherTarget)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate and free resources.
|
||||
*/
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
storageFacade.close(); // this removes old entries
|
||||
// DS will take care of other references
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AccessTokenResponse loadAccessTokenResponse(String handle) throws GeneralSecurityException {
|
||||
AccessTokenResponse accessTokenResponseFromStore = (AccessTokenResponse) storageFacade.get(handle,
|
||||
ACCESS_TOKEN_RESPONSE);
|
||||
|
||||
if (accessTokenResponseFromStore == null) {
|
||||
// token does not exist
|
||||
return null;
|
||||
}
|
||||
|
||||
AccessTokenResponse decryptedAccessToken = decryptToken(accessTokenResponseFromStore);
|
||||
return decryptedAccessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAccessTokenResponse(@NonNull String handle, @Nullable AccessTokenResponse pAccessTokenResponse) {
|
||||
AccessTokenResponse accessTokenResponse = pAccessTokenResponse;
|
||||
if (accessTokenResponse == null) {
|
||||
accessTokenResponse = new AccessTokenResponse(); // put empty
|
||||
}
|
||||
|
||||
AccessTokenResponse encryptedToken;
|
||||
try {
|
||||
encryptedToken = encryptToken(accessTokenResponse);
|
||||
} catch (GeneralSecurityException e) {
|
||||
logger.warn("Unable to encrypt token, storing as-is", e);
|
||||
encryptedToken = accessTokenResponse;
|
||||
}
|
||||
storageFacade.put(handle, encryptedToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String handle) {
|
||||
storageFacade.removeByHandle(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll() {
|
||||
storageFacade.removeAll();
|
||||
allHandles.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void savePersistedParams(String handle, @Nullable PersistedParams persistedParams) {
|
||||
storageFacade.put(handle, persistedParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PersistedParams loadPersistedParams(String handle) {
|
||||
PersistedParams persistedParams = (PersistedParams) storageFacade.get(handle, SERVICE_CONFIGURATION);
|
||||
return persistedParams;
|
||||
}
|
||||
|
||||
private AccessTokenResponse encryptToken(AccessTokenResponse accessTokenResponse) throws GeneralSecurityException {
|
||||
AccessTokenResponse encryptedAccessToken = (AccessTokenResponse) accessTokenResponse.clone();
|
||||
|
||||
if (accessTokenResponse.getAccessToken() != null) {
|
||||
encryptedAccessToken.setAccessToken(encrypt(accessTokenResponse.getAccessToken()));
|
||||
}
|
||||
if (accessTokenResponse.getRefreshToken() != null) {
|
||||
encryptedAccessToken.setRefreshToken(encrypt(accessTokenResponse.getRefreshToken()));
|
||||
}
|
||||
return encryptedAccessToken;
|
||||
}
|
||||
|
||||
private AccessTokenResponse decryptToken(AccessTokenResponse accessTokenResponse) throws GeneralSecurityException {
|
||||
AccessTokenResponse decryptedToken = (AccessTokenResponse) accessTokenResponse.clone();
|
||||
if (!storageCipher.isPresent()) {
|
||||
return decryptedToken; // do nothing if no cipher
|
||||
}
|
||||
logger.debug("Decrypting token: {}", accessTokenResponse);
|
||||
decryptedToken.setAccessToken(storageCipher.get().decrypt(accessTokenResponse.getAccessToken()));
|
||||
decryptedToken.setRefreshToken(storageCipher.get().decrypt(accessTokenResponse.getRefreshToken()));
|
||||
return decryptedToken;
|
||||
}
|
||||
|
||||
private @Nullable String encrypt(String token) throws GeneralSecurityException {
|
||||
if (!storageCipher.isPresent()) {
|
||||
return token; // do nothing if no cipher
|
||||
} else {
|
||||
StorageCipher cipher = storageCipher.get();
|
||||
return cipher.encrypt(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected synchronized void setStorageService(StorageService storageService) {
|
||||
storageFacade = new StorageFacade(storageService.getStorage(STORE_NAME));
|
||||
}
|
||||
|
||||
protected synchronized void unsetStorageService(StorageService storageService) {
|
||||
storageFacade.close();
|
||||
storageFacade = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static policy -- don't want to change cipher on the fly!
|
||||
* There may be multiple storage ciphers, choose the one that matches the target (done at activate)
|
||||
*
|
||||
*/
|
||||
@Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE)
|
||||
protected synchronized void setStorageCipher(StorageCipher storageCipher) {
|
||||
// keep all ciphers
|
||||
allAvailableStorageCiphers.add(storageCipher);
|
||||
}
|
||||
|
||||
protected synchronized void unsetStorageCipher(StorageCipher storageCipher) {
|
||||
allAvailableStorageCiphers.remove(storageCipher);
|
||||
if (this.storageCipher.isPresent() && this.storageCipher.get() == storageCipher) {
|
||||
this.storageCipher = Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isExpired(@Nullable LocalDateTime lastUsed) {
|
||||
if (lastUsed == null) {
|
||||
return false;
|
||||
}
|
||||
// (last used + 183 days < now) then it is expired
|
||||
return lastUsed.plusDays(EXPIRE_DAYS).isBefore(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* This is designed to simplify all the locking required for the store.
|
||||
*/
|
||||
private class StorageFacade implements AutoCloseable {
|
||||
private final Storage<String> storage;
|
||||
private final Lock storageLock = new ReentrantLock(); // for all operations on the storage
|
||||
private final Gson gson;
|
||||
|
||||
public StorageFacade(Storage<String> storage) {
|
||||
this.storage = storage;
|
||||
// Add adapters for LocalDateTime
|
||||
gson = new GsonBuilder()
|
||||
.registerTypeAdapter(LocalDateTime.class,
|
||||
(JsonDeserializer<LocalDateTime>) (json, typeOfT, context) -> LocalDateTime
|
||||
.parse(json.getAsString()))
|
||||
.registerTypeAdapter(LocalDateTime.class,
|
||||
(JsonSerializer<LocalDateTime>) (date, type,
|
||||
jsonSerializationContext) -> new JsonPrimitive(date.toString()))
|
||||
.setPrettyPrinting().create();
|
||||
}
|
||||
|
||||
public Set<String> getAllHandlesFromIndex() {
|
||||
Set<String> handlesFromStoreageIndex = new HashSet<>();
|
||||
try {
|
||||
String allHandlesStr = get(STORE_KEY_INDEX_OF_HANDLES);
|
||||
logger.debug("All available handles: {}", allHandlesStr);
|
||||
if (allHandlesStr == null) {
|
||||
return handlesFromStoreageIndex;
|
||||
}
|
||||
return gson.fromJson(allHandlesStr, HashSet.class);
|
||||
} catch (RuntimeException storeNotAvailable) {
|
||||
return handlesFromStoreageIndex; // empty
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String get(String key) {
|
||||
storageLock.lock();
|
||||
try {
|
||||
return storage.get(key);
|
||||
} finally {
|
||||
storageLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Object get(String handle, StorageRecordType recordType) {
|
||||
storageLock.lock();
|
||||
try {
|
||||
String value = storage.get(recordType.getKey(handle));
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// update last used when it is an access token
|
||||
if (recordType.equals(ACCESS_TOKEN_RESPONSE)) {
|
||||
try {
|
||||
AccessTokenResponse accessTokenResponse = gson.fromJson(value, AccessTokenResponse.class);
|
||||
return accessTokenResponse;
|
||||
} catch (Exception e) {
|
||||
logger.error(
|
||||
"Unable to deserialize json, discarding AccessTokenResponse. "
|
||||
+ "Please check json against standard or with oauth provider. json:\n{}",
|
||||
value, e);
|
||||
return null;
|
||||
}
|
||||
} else if (recordType.equals(SERVICE_CONFIGURATION)) {
|
||||
try {
|
||||
PersistedParams params = gson.fromJson(value, PersistedParams.class);
|
||||
return params;
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to deserialize json, discarding PersistedParams. json:\n{}", value, e);
|
||||
return null;
|
||||
}
|
||||
} else if (recordType.equals(LAST_USED)) {
|
||||
try {
|
||||
LocalDateTime lastUsedDate = gson.fromJson(value, LocalDateTime.class);
|
||||
return lastUsedDate;
|
||||
} catch (Exception e) {
|
||||
logger.info("Unable to deserialize json, reset LAST_USED to now. json:\n{}", value);
|
||||
return LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
storageLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void put(String handle, @Nullable LocalDateTime lastUsed) {
|
||||
storageLock.lock();
|
||||
try {
|
||||
if (lastUsed == null) {
|
||||
storage.put(LAST_USED.getKey(handle), (String) null);
|
||||
} else {
|
||||
String gsonStr = gson.toJson(lastUsed);
|
||||
storage.put(LAST_USED.getKey(handle), gsonStr);
|
||||
}
|
||||
} finally {
|
||||
storageLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void put(String handle, @Nullable AccessTokenResponse accessTokenResponse) {
|
||||
storageLock.lock();
|
||||
try {
|
||||
if (accessTokenResponse == null) {
|
||||
storage.put(ACCESS_TOKEN_RESPONSE.getKey(handle), (String) null);
|
||||
} else {
|
||||
String gsonAccessTokenStr = gson.toJson(accessTokenResponse);
|
||||
storage.put(ACCESS_TOKEN_RESPONSE.getKey(handle), gsonAccessTokenStr);
|
||||
String gsonDateStr = gson.toJson(LocalDateTime.now());
|
||||
storage.put(LAST_USED.getKey(handle), gsonDateStr);
|
||||
|
||||
if (!allHandles.contains(handle)) {
|
||||
// update all handles index
|
||||
allHandles.add(handle);
|
||||
storage.put(STORE_KEY_INDEX_OF_HANDLES, gson.toJson(allHandles));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
storageLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void put(String handle, @Nullable PersistedParams persistedParams) {
|
||||
storageLock.lock();
|
||||
try {
|
||||
if (persistedParams == null) {
|
||||
storage.put(SERVICE_CONFIGURATION.getKey(handle), (String) null);
|
||||
} else {
|
||||
String gsonPersistedParamsStr = gson.toJson(persistedParams);
|
||||
storage.put(SERVICE_CONFIGURATION.getKey(handle), gsonPersistedParamsStr);
|
||||
String gsonDateStr = gson.toJson(LocalDateTime.now());
|
||||
storage.put(LAST_USED.getKey(handle), gsonDateStr);
|
||||
if (!allHandles.contains(handle)) {
|
||||
// update all handles index
|
||||
allHandles.add(handle);
|
||||
storage.put(STORE_KEY_INDEX_OF_HANDLES, gson.toJson(allHandles));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
storageLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeByHandle(String handle) {
|
||||
logger.debug("Removing handle {} from storage", handle);
|
||||
storageLock.lock();
|
||||
try {
|
||||
if (allHandles.remove(handle)) { // entry exists and successfully removed
|
||||
storage.remove(ACCESS_TOKEN_RESPONSE.getKey(handle));
|
||||
storage.remove(LAST_USED.getKey(handle));
|
||||
storage.remove(SERVICE_CONFIGURATION.getKey(handle));
|
||||
storage.put(STORE_KEY_INDEX_OF_HANDLES, gson.toJson(allHandles)); // update all handles
|
||||
}
|
||||
} finally {
|
||||
storageLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAll() {
|
||||
// no need any locks, the other methods will take care of this
|
||||
Set<String> allHandlesFromStore = getAllHandlesFromIndex();
|
||||
for (String handle : allHandlesFromStore) {
|
||||
removeByHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
boolean lockGained = false;
|
||||
try {
|
||||
// dont want to wait too long during shutdown or update
|
||||
lockGained = storageLock.tryLock(15, TimeUnit.SECONDS);
|
||||
|
||||
// if lockGained within timeout, then try to remove old entries
|
||||
if (lockGained) {
|
||||
String handlesSSV = this.storage.get(STORE_KEY_INDEX_OF_HANDLES);
|
||||
if (handlesSSV != null) {
|
||||
String[] handles = handlesSSV.trim().split(" ");
|
||||
for (String handle : handles) {
|
||||
LocalDateTime lastUsed = (LocalDateTime) get(handle, LAST_USED);
|
||||
if (isExpired(lastUsed)) {
|
||||
removeByHandle(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// if lock is not acquired within the timeout or thread is interruted
|
||||
// then forget about the old entries, do not try to delete them.
|
||||
// re-setting thread state to interrupted
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
if (lockGained) {
|
||||
try {
|
||||
storageLock.unlock();
|
||||
} catch (IllegalMonitorStateException e) {
|
||||
// never reach here normally
|
||||
logger.error("Unexpected attempt to unlock without lock", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.auth.oauth2client.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Params that need to be persisted.
|
||||
*
|
||||
* @author Michael Bock - Initial contribution
|
||||
* @author Gary Tse - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Moved class to it's own file and added hashCode and equals methods
|
||||
*/
|
||||
class PersistedParams {
|
||||
String handle;
|
||||
String tokenUrl;
|
||||
String authorizationUrl;
|
||||
String clientId;
|
||||
String clientSecret;
|
||||
String scope;
|
||||
Boolean supportsBasicAuth;
|
||||
String state;
|
||||
String redirectUri;
|
||||
int tokenExpiresInSeconds = 60;
|
||||
|
||||
/**
|
||||
* Default constructor needed for json serialization.
|
||||
*/
|
||||
public PersistedParams() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param handle the handle to the oauth service
|
||||
* @param tokenUrl the token url of the oauth provider. This is used for getting access token.
|
||||
* @param authorizationUrl the authorization url of the oauth provider. This is used purely for generating
|
||||
* authorization code/ url.
|
||||
* @param clientId the client id
|
||||
* @param clientSecret the client secret (optional)
|
||||
* @param scope the desired scope
|
||||
* @param supportsBasicAuth whether the OAuth provider supports basic authorization or the client id and client
|
||||
* secret should be passed as form params. true - use http basic authentication, false - do not use http
|
||||
* basic authentication, null - unknown (default to do not use)
|
||||
* @param tokenExpiresInSeconds Positive integer; a small time buffer in seconds. It is used to calculate the expiry
|
||||
* of the access tokens. This allows the access token to expire earlier than the
|
||||
* official stated expiry time; thus prevents the caller obtaining a valid token at the time of invoke,
|
||||
* only to find the token immediately expired.
|
||||
*/
|
||||
public PersistedParams(String handle, String tokenUrl, String authorizationUrl, String clientId,
|
||||
String clientSecret, String scope, Boolean supportsBasicAuth, int tokenExpiresInSeconds) {
|
||||
this.handle = handle;
|
||||
this.tokenUrl = tokenUrl;
|
||||
this.authorizationUrl = authorizationUrl;
|
||||
this.clientId = clientId;
|
||||
this.clientSecret = clientSecret;
|
||||
this.scope = scope;
|
||||
this.supportsBasicAuth = supportsBasicAuth;
|
||||
this.tokenExpiresInSeconds = tokenExpiresInSeconds;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((authorizationUrl == null) ? 0 : authorizationUrl.hashCode());
|
||||
result = prime * result + ((clientId == null) ? 0 : clientId.hashCode());
|
||||
result = prime * result + ((clientSecret == null) ? 0 : clientSecret.hashCode());
|
||||
result = prime * result + ((handle == null) ? 0 : handle.hashCode());
|
||||
result = prime * result + ((redirectUri == null) ? 0 : redirectUri.hashCode());
|
||||
result = prime * result + ((scope == null) ? 0 : scope.hashCode());
|
||||
result = prime * result + ((state == null) ? 0 : state.hashCode());
|
||||
result = prime * result + ((supportsBasicAuth == null) ? 0 : supportsBasicAuth.hashCode());
|
||||
result = prime * result + tokenExpiresInSeconds;
|
||||
result = prime * result + ((tokenUrl == null) ? 0 : tokenUrl.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
PersistedParams other = (PersistedParams) obj;
|
||||
if (authorizationUrl == null) {
|
||||
if (other.authorizationUrl != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!authorizationUrl.equals(other.authorizationUrl)) {
|
||||
return false;
|
||||
}
|
||||
if (clientId == null) {
|
||||
if (other.clientId != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!clientId.equals(other.clientId)) {
|
||||
return false;
|
||||
}
|
||||
if (clientSecret == null) {
|
||||
if (other.clientSecret != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!clientSecret.equals(other.clientSecret)) {
|
||||
return false;
|
||||
}
|
||||
if (handle == null) {
|
||||
if (other.handle != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!handle.equals(other.handle)) {
|
||||
return false;
|
||||
}
|
||||
if (redirectUri == null) {
|
||||
if (other.redirectUri != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!redirectUri.equals(other.redirectUri)) {
|
||||
return false;
|
||||
}
|
||||
if (scope == null) {
|
||||
if (other.scope != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!scope.equals(other.scope)) {
|
||||
return false;
|
||||
}
|
||||
if (state == null) {
|
||||
if (other.state != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!state.equals(other.state)) {
|
||||
return false;
|
||||
}
|
||||
if (supportsBasicAuth == null) {
|
||||
if (other.supportsBasicAuth != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!supportsBasicAuth.equals(other.supportsBasicAuth)) {
|
||||
return false;
|
||||
}
|
||||
if (tokenExpiresInSeconds != other.tokenExpiresInSeconds) {
|
||||
return false;
|
||||
}
|
||||
if (tokenUrl == null) {
|
||||
if (other.tokenUrl != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!tokenUrl.equals(other.tokenUrl)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.auth.oauth2client.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Enum of types being used in the store
|
||||
*
|
||||
* @author Gary Tse - Initial Contribution
|
||||
*
|
||||
*/
|
||||
public enum StorageRecordType {
|
||||
|
||||
LAST_USED(".LastUsed"),
|
||||
ACCESS_TOKEN_RESPONSE(".AccessTokenResponse"),
|
||||
SERVICE_CONFIGURATION(".ServiceConfiguration");
|
||||
|
||||
private String suffix;
|
||||
|
||||
private StorageRecordType(String suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
public @NonNull String getKey(String handle) {
|
||||
return (handle == null) ? this.suffix : (handle + this.suffix);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.eclipse.smarthome.auth.oauth2client.internal.cipher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import java.util.Dictionary;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.smarthome.core.auth.client.oauth2.StorageCipher;
|
||||
import org.osgi.service.cm.Configuration;
|
||||
import org.osgi.service.cm.ConfigurationAdmin;
|
||||
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 is a symmetric key encryption service for encrypting the OAuth tokens.
|
||||
*
|
||||
* @author Gary Tse - Initial Contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component
|
||||
public class SymmetricKeyCipher implements StorageCipher {
|
||||
|
||||
public static final String CIPHER_ID = "SymmetricKeyCipher";
|
||||
public static final String PID = CIPHER_ID;
|
||||
private final Logger logger = LoggerFactory.getLogger(SymmetricKeyCipher.class);
|
||||
|
||||
private static final String ENCRYPTION_ALGO = "AES";
|
||||
private static final String ENCRYPTION_ALGO_MODE_WITH_PADDING = "AES/CBC/PKCS5Padding";
|
||||
private static final String PROPERTY_KEY_ENCRYPTION_KEY_BASE64 = "ENCRYPTION_KEY";
|
||||
private static final int ENCRYPTION_KEY_SIZE_BITS = 128; // do not use high grade encryption due to export limit
|
||||
private static final int IV_BYTE_SIZE = 16;
|
||||
|
||||
@NonNullByDefault({})
|
||||
private ConfigurationAdmin configurationAdmin;
|
||||
@NonNullByDefault({})
|
||||
private SecretKey encryptionKey;
|
||||
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
|
||||
/**
|
||||
* Activate will try to load the encryption key. If an existing encryption key does not exists,
|
||||
* it will generate a new one and save to {@code org.osgi.service.cm.ConfigurationAdmin}
|
||||
*
|
||||
* @throws NoSuchAlgorithmException When encryption algorithm is not available {@code #ENCRYPTION_ALGO}
|
||||
* @throws IOException if access to persistent storage fails (@code org.osgi.service.cm.ConfigurationAdmin)
|
||||
*/
|
||||
@Activate
|
||||
public void activate() throws NoSuchAlgorithmException, IOException {
|
||||
// load or generate the encryption key
|
||||
encryptionKey = getOrGenerateEncryptionKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueCipherId() {
|
||||
return CIPHER_ID;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String encrypt(@Nullable String plainText) throws GeneralSecurityException {
|
||||
if (plainText == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generate IV
|
||||
byte iv[] = new byte[IV_BYTE_SIZE];
|
||||
random.nextBytes(iv);
|
||||
Cipher cipherEnc = Cipher.getInstance(ENCRYPTION_ALGO_MODE_WITH_PADDING);
|
||||
cipherEnc.init(Cipher.ENCRYPT_MODE, encryptionKey, new IvParameterSpec(iv));
|
||||
byte[] encryptedBytes = cipherEnc.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
|
||||
byte[] encryptedBytesWithIV = new byte[encryptedBytes.length + IV_BYTE_SIZE];
|
||||
|
||||
// copy iv to the start of array
|
||||
System.arraycopy(iv, 0, encryptedBytesWithIV, 0, IV_BYTE_SIZE);
|
||||
// append encrypted text to tail
|
||||
System.arraycopy(encryptedBytes, 0, encryptedBytesWithIV, IV_BYTE_SIZE, encryptedBytes.length);
|
||||
String encryptedBase64String = Base64.getEncoder().encodeToString(encryptedBytesWithIV);
|
||||
|
||||
return encryptedBase64String;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String decrypt(@Nullable String base64CipherText) throws GeneralSecurityException {
|
||||
if (base64CipherText == null) {
|
||||
return null;
|
||||
}
|
||||
// base64 decode the base64CipherText
|
||||
byte[] decodedCipherTextWithIV = Base64.getDecoder().decode(base64CipherText);
|
||||
// Read IV
|
||||
byte[] iv = new byte[IV_BYTE_SIZE];
|
||||
System.arraycopy(decodedCipherTextWithIV, 0, iv, 0, IV_BYTE_SIZE);
|
||||
|
||||
byte[] cipherTextBytes = new byte[decodedCipherTextWithIV.length - IV_BYTE_SIZE];
|
||||
System.arraycopy(decodedCipherTextWithIV, IV_BYTE_SIZE, cipherTextBytes, 0, cipherTextBytes.length);
|
||||
|
||||
Cipher cipherDec = Cipher.getInstance(ENCRYPTION_ALGO_MODE_WITH_PADDING);
|
||||
cipherDec.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv));
|
||||
byte[] decryptedBytes = cipherDec.doFinal(cipherTextBytes);
|
||||
return new String(decryptedBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static SecretKey generateEncryptionKey() throws NoSuchAlgorithmException {
|
||||
KeyGenerator keygen = KeyGenerator.getInstance(ENCRYPTION_ALGO);
|
||||
keygen.init(ENCRYPTION_KEY_SIZE_BITS);
|
||||
SecretKey secretKey = keygen.generateKey();
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
private SecretKey getOrGenerateEncryptionKey() throws NoSuchAlgorithmException, IOException {
|
||||
Configuration configuration = configurationAdmin.getConfiguration(PID);
|
||||
String encryptionKeyInBase64 = null;
|
||||
Dictionary<String, Object> properties = configuration.getProperties();
|
||||
if (properties == null) {
|
||||
properties = new Hashtable<>();
|
||||
}
|
||||
|
||||
if (properties.get(PROPERTY_KEY_ENCRYPTION_KEY_BASE64) == null) {
|
||||
encryptionKey = generateEncryptionKey();
|
||||
encryptionKeyInBase64 = new String(Base64.getEncoder().encode(encryptionKey.getEncoded()));
|
||||
|
||||
// Put encryption key back into config
|
||||
properties.put(PROPERTY_KEY_ENCRYPTION_KEY_BASE64, encryptionKeyInBase64);
|
||||
configuration.update(properties);
|
||||
|
||||
logger.debug("Encryption key generated");
|
||||
} else {
|
||||
// encryption key already present in config
|
||||
encryptionKeyInBase64 = (String) properties.get(PROPERTY_KEY_ENCRYPTION_KEY_BASE64);
|
||||
byte[] encKeyBytes = Base64.getDecoder().decode(encryptionKeyInBase64);
|
||||
// 128 bit key/ 8 bit = 16 bytes length
|
||||
encryptionKey = new SecretKeySpec(encKeyBytes, 0, ENCRYPTION_KEY_SIZE_BITS / 8, ENCRYPTION_ALGO);
|
||||
|
||||
logger.debug("Encryption key loaded");
|
||||
}
|
||||
return encryptionKey;
|
||||
}
|
||||
|
||||
@Reference
|
||||
public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
|
||||
this.configurationAdmin = configurationAdmin;
|
||||
}
|
||||
|
||||
public void unsetConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
|
||||
this.configurationAdmin = configurationAdmin;
|
||||
}
|
||||
|
||||
}
|
27
bundles/org.openhab.core.automation.module.media/.classpath
Normal file
27
bundles/org.openhab.core.automation.module.media/.classpath
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<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>
|
23
bundles/org.openhab.core.automation.module.media/.project
Normal file
23
bundles/org.openhab.core.automation.module.media/.project
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.automation.module.media</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -0,0 +1,4 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
encoding/src=UTF-8
|
@ -0,0 +1,6 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
19
bundles/org.openhab.core.automation.module.media/NOTICE
Normal file
19
bundles/org.openhab.core.automation.module.media/NOTICE
Normal file
@ -0,0 +1,19 @@
|
||||
This content is produced and maintained by the Eclipse SmartHome project.
|
||||
|
||||
* Project home: https://eclipse.org/smarthome/
|
||||
|
||||
== 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/eclipse/smarthome
|
||||
|
||||
== Copyright Holders
|
||||
|
||||
See the NOTICE file distributed with the source code at
|
||||
https://github.com/eclipse/smarthome/blob/master/NOTICE
|
||||
for detailed information regarding copyright ownership.
|
29
bundles/org.openhab.core.automation.module.media/pom.xml
Normal file
29
bundles/org.openhab.core.automation.module.media/pom.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bundles</artifactId>
|
||||
<version>2.5.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.automation.module.media</artifactId>
|
||||
|
||||
<name>openHAB Core :: Bundles :: Automation Media Modules</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.automation.module.script</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.voice</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.automation.module.media.internal;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.smarthome.config.core.ConfigConstants;
|
||||
import org.eclipse.smarthome.config.core.ConfigDescriptionParameter;
|
||||
import org.eclipse.smarthome.config.core.ConfigDescriptionParameter.Type;
|
||||
import org.eclipse.smarthome.config.core.ConfigDescriptionParameterBuilder;
|
||||
import org.eclipse.smarthome.config.core.ParameterOption;
|
||||
import org.eclipse.smarthome.core.audio.AudioManager;
|
||||
import org.eclipse.smarthome.core.audio.AudioSink;
|
||||
import org.eclipse.smarthome.core.common.registry.ProviderChangeListener;
|
||||
import org.openhab.core.automation.Visibility;
|
||||
import org.openhab.core.automation.type.ActionType;
|
||||
import org.openhab.core.automation.type.ModuleType;
|
||||
import org.openhab.core.automation.type.ModuleTypeProvider;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* This class dynamically provides the Play action type.
|
||||
* This is necessary since there is no other way to provide dynamic config param options for module types.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution and API
|
||||
* @author Simon Kaufmann - added "say" action
|
||||
*
|
||||
*/
|
||||
@Component(immediate = true)
|
||||
public class MediaActionTypeProvider implements ModuleTypeProvider {
|
||||
|
||||
private AudioManager audioManager;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public ModuleType getModuleType(String UID, Locale locale) {
|
||||
if (PlayActionHandler.TYPE_ID.equals(UID)) {
|
||||
return getPlayActionType(locale);
|
||||
} else if (SayActionHandler.TYPE_ID.equals(UID)) {
|
||||
return getSayActionType(locale);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ModuleType> getModuleTypes(Locale locale) {
|
||||
return Stream.of(getPlayActionType(locale), getSayActionType(locale)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ModuleType getPlayActionType(Locale locale) {
|
||||
return new ActionType(PlayActionHandler.TYPE_ID, getConfigPlayDesc(locale), "play a sound",
|
||||
"Plays a sound file.", null, Visibility.VISIBLE, new ArrayList<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
private ModuleType getSayActionType(Locale locale) {
|
||||
return new ActionType(SayActionHandler.TYPE_ID, getConfigSayDesc(locale), "say something",
|
||||
"Speaks a given text through a natural voice.", null, Visibility.VISIBLE, new ArrayList<>(),
|
||||
new ArrayList<>());
|
||||
}
|
||||
|
||||
private List<ConfigDescriptionParameter> getConfigPlayDesc(Locale locale) {
|
||||
ConfigDescriptionParameter param1 = ConfigDescriptionParameterBuilder
|
||||
.create(PlayActionHandler.PARAM_SOUND, Type.TEXT).withRequired(true).withLabel("Sound")
|
||||
.withDescription("the sound to play").withOptions(getSoundOptions()).withLimitToOptions(true).build();
|
||||
return Stream.of(param1, getAudioSinkConfigDescParam(locale)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<ConfigDescriptionParameter> getConfigSayDesc(Locale locale) {
|
||||
ConfigDescriptionParameter param1 = ConfigDescriptionParameterBuilder
|
||||
.create(SayActionHandler.PARAM_TEXT, Type.TEXT).withRequired(true).withLabel("Text")
|
||||
.withDescription("the text to speak").build();
|
||||
return Stream.of(param1, getAudioSinkConfigDescParam(locale)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ConfigDescriptionParameter getAudioSinkConfigDescParam(Locale locale) {
|
||||
ConfigDescriptionParameter param2 = ConfigDescriptionParameterBuilder
|
||||
.create(SayActionHandler.PARAM_SINK, Type.TEXT).withRequired(false).withLabel("Sink")
|
||||
.withDescription("the audio sink id").withOptions(getSinkOptions(locale)).withLimitToOptions(true)
|
||||
.build();
|
||||
return param2;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates one option for every file that is found in the sounds directory.
|
||||
* As a label, the file extension is removed and the string is capitalized.
|
||||
*
|
||||
* @return a list of parameter options representing the sound files
|
||||
*/
|
||||
private List<ParameterOption> getSoundOptions() {
|
||||
List<ParameterOption> options = new ArrayList<>();
|
||||
File soundsDir = Paths.get(ConfigConstants.getConfigFolder(), AudioManager.SOUND_DIR).toFile();
|
||||
if (soundsDir.isDirectory()) {
|
||||
for (String fileName : soundsDir.list()) {
|
||||
if (fileName.contains(".") && !fileName.startsWith(".")) {
|
||||
String soundName = StringUtils.capitalize(fileName.substring(0, fileName.lastIndexOf(".")));
|
||||
options.add(new ParameterOption(fileName, soundName));
|
||||
}
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates one option for every sink that is found in the system.
|
||||
*
|
||||
* @return a list of parameter options representing the audio sinks
|
||||
*/
|
||||
private List<ParameterOption> getSinkOptions(Locale locale) {
|
||||
List<ParameterOption> options = new ArrayList<>();
|
||||
|
||||
for (AudioSink sink : audioManager.getAllSinks()) {
|
||||
options.add(new ParameterOption(sink.getId(), sink.getLabel(locale)));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProviderChangeListener(ProviderChangeListener<ModuleType> listener) {
|
||||
// does nothing because this provider does not change
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ModuleType> getAll() {
|
||||
return getModuleTypes(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProviderChangeListener(ProviderChangeListener<ModuleType> listener) {
|
||||
// does nothing because this provider does not change
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setAudioManager(AudioManager audioManager) {
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
protected void unsetAudioManager(AudioManager audioManager) {
|
||||
this.audioManager = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.automation.module.media.internal;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.smarthome.core.audio.AudioManager;
|
||||
import org.eclipse.smarthome.core.voice.VoiceManager;
|
||||
import org.openhab.core.automation.Action;
|
||||
import org.openhab.core.automation.Module;
|
||||
import org.openhab.core.automation.handler.BaseModuleHandlerFactory;
|
||||
import org.openhab.core.automation.handler.ModuleHandler;
|
||||
import org.openhab.core.automation.handler.ModuleHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@Component(service = ModuleHandlerFactory.class)
|
||||
public class MediaModuleHandlerFactory extends BaseModuleHandlerFactory {
|
||||
|
||||
private static final Collection<String> TYPES = unmodifiableList(
|
||||
asList(SayActionHandler.TYPE_ID, PlayActionHandler.TYPE_ID));
|
||||
private VoiceManager voiceManager;
|
||||
private AudioManager audioManager;
|
||||
|
||||
@Override
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getTypes() {
|
||||
return TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModuleHandler internalCreate(Module module, String ruleUID) {
|
||||
if (module instanceof Action) {
|
||||
switch (module.getTypeUID()) {
|
||||
case SayActionHandler.TYPE_ID:
|
||||
return new SayActionHandler((Action) module, voiceManager);
|
||||
case PlayActionHandler.TYPE_ID:
|
||||
return new PlayActionHandler((Action) module, audioManager);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setAudioManager(AudioManager audioManager) {
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
protected void unsetAudioManager(AudioManager audioManager) {
|
||||
this.audioManager = null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setVoiceManager(VoiceManager voiceManager) {
|
||||
this.voiceManager = voiceManager;
|
||||
}
|
||||
|
||||
protected void unsetVoiceManager(VoiceManager voiceManager) {
|
||||
this.voiceManager = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.automation.module.media.internal;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.smarthome.core.audio.AudioManager;
|
||||
import org.eclipse.smarthome.core.voice.VoiceManager;
|
||||
import org.openhab.core.automation.module.script.ScriptExtensionProvider;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* This is a scope provider for features that are related to audio and voice support.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
public class MediaScriptScopeProvider implements ScriptExtensionProvider {
|
||||
Map<String, Object> elements = new HashMap<>();
|
||||
|
||||
@Reference
|
||||
protected void setAudioManager(AudioManager audioManager) {
|
||||
elements.put("audio", audioManager);
|
||||
}
|
||||
|
||||
protected void unsetAudioManager(AudioManager audioManager) {
|
||||
elements.remove("audio");
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setVoiceManager(VoiceManager voiceManager) {
|
||||
elements.put("voice", voiceManager);
|
||||
}
|
||||
|
||||
protected void unsetVoiceManager(VoiceManager voiceManager) {
|
||||
elements.remove("voice");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getDefaultPresets() {
|
||||
return Collections.singleton("media");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getPresets() {
|
||||
return Collections.singleton("media");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getTypes() {
|
||||
return elements.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String scriptIdentifier, String type) {
|
||||
return elements.get(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> importPreset(String scriptIdentifier, String preset) {
|
||||
return elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload(String scriptIdentifier) {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.automation.module.media.internal;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.smarthome.core.audio.AudioException;
|
||||
import org.eclipse.smarthome.core.audio.AudioManager;
|
||||
import org.openhab.core.automation.Action;
|
||||
import org.openhab.core.automation.handler.ActionHandler;
|
||||
import org.openhab.core.automation.handler.BaseModuleHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is an ModuleHandler implementation for Actions that play a sound file from the file system.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
public class PlayActionHandler extends BaseModuleHandler<Action> implements ActionHandler {
|
||||
|
||||
public static final String TYPE_ID = "media.PlayAction";
|
||||
public static final String PARAM_SOUND = "sound";
|
||||
public static final String PARAM_SINK = "sink";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PlayActionHandler.class);
|
||||
|
||||
private final AudioManager audioManager;
|
||||
|
||||
public PlayActionHandler(Action module, AudioManager audioManager) {
|
||||
super(module);
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> execute(Map<String, Object> context) {
|
||||
String sound = module.getConfiguration().get(PARAM_SOUND).toString();
|
||||
String sink = (String) module.getConfiguration().get(PARAM_SINK);
|
||||
try {
|
||||
audioManager.playFile(sound, sink);
|
||||
} catch (AudioException e) {
|
||||
logger.error("Error playing sound '{}': {}", sound, e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2014,2019 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*
|
||||
* 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.automation.module.media.internal;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.smarthome.core.voice.VoiceManager;
|
||||
import org.openhab.core.automation.Action;
|
||||
import org.openhab.core.automation.handler.ActionHandler;
|
||||
import org.openhab.core.automation.handler.BaseModuleHandler;
|
||||
|
||||
/**
|
||||
* This is an ModuleHandler implementation for Actions that trigger a TTS output through "say".
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
public class SayActionHandler extends BaseModuleHandler<Action> implements ActionHandler {
|
||||
|
||||
public static final String TYPE_ID = "media.SayAction";
|
||||
public static final String PARAM_TEXT = "text";
|
||||
public static final String PARAM_SINK = "sink";
|
||||
|
||||
private final VoiceManager voiceManager;
|
||||
|
||||
public SayActionHandler(Action module, VoiceManager voiceManager) {
|
||||
super(module);
|
||||
this.voiceManager = voiceManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> execute(Map<String, Object> context) {
|
||||
String text = module.getConfiguration().get(PARAM_TEXT).toString();
|
||||
String sink = (String) module.getConfiguration().get(PARAM_SINK);
|
||||
voiceManager.say(text, null, sink);
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<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>
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.automation.module.script.rulesupport</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -0,0 +1,6 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding//src/main/resources=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
encoding/ESH-INF=UTF-8
|
||||
encoding/src=UTF-8
|
@ -0,0 +1,6 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user