Commit 19d3afa650f6796cfd261bab03c67331e0a601ca

Authored by Volodymyr Babak
1 parent 0c98ec78

Kafka, RabbitMQ and REST API call extensions + Docker image

Showing 50 changed files with 2587 additions and 0 deletions
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.cache;
  17 +
  18 +import org.springframework.cache.interceptor.KeyGenerator;
  19 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  20 +import org.thingsboard.server.dao.device.DeviceCredentialsService;
  21 +
  22 +import java.lang.reflect.Method;
  23 +
  24 +public class PreviousDeviceCredentialsIdKeyGenerator implements KeyGenerator {
  25 +
  26 + @Override
  27 + public Object generate(Object o, Method method, Object... objects) {
  28 + DeviceCredentialsService deviceCredentialsService = (DeviceCredentialsService) o;
  29 + DeviceCredentials deviceCredentials = (DeviceCredentials) objects[0];
  30 + if (deviceCredentials.getDeviceId() != null) {
  31 + DeviceCredentials oldDeviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceCredentials.getDeviceId());
  32 + if (oldDeviceCredentials != null) {
  33 + return oldDeviceCredentials.getCredentialsId();
  34 + }
  35 + }
  36 + return null;
  37 + }
  38 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.cache;
  17 +
  18 +import com.hazelcast.config.Config;
  19 +import com.hazelcast.config.DiscoveryStrategyConfig;
  20 +import com.hazelcast.config.MapConfig;
  21 +import com.hazelcast.config.MaxSizeConfig;
  22 +import com.hazelcast.core.Hazelcast;
  23 +import com.hazelcast.core.HazelcastInstance;
  24 +import com.hazelcast.instance.GroupProperty;
  25 +import com.hazelcast.spring.cache.HazelcastCacheManager;
  26 +import com.hazelcast.zookeeper.ZookeeperDiscoveryProperties;
  27 +import com.hazelcast.zookeeper.ZookeeperDiscoveryStrategyFactory;
  28 +import org.springframework.beans.factory.annotation.Value;
  29 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  30 +import org.springframework.cache.CacheManager;
  31 +import org.springframework.cache.annotation.EnableCaching;
  32 +import org.springframework.cache.interceptor.KeyGenerator;
  33 +import org.springframework.context.annotation.Bean;
  34 +import org.springframework.context.annotation.Configuration;
  35 +import org.thingsboard.server.common.data.CacheConstants;
  36 +
  37 +@Configuration
  38 +@EnableCaching
  39 +@ConditionalOnProperty(prefix = "cache", value = "enabled", havingValue = "true")
  40 +public class ServiceCacheConfiguration {
  41 +
  42 + private static final String HAZELCAST_CLUSTER_NAME = "hazelcast";
  43 +
  44 + @Value("${cache.device_credentials.max_size}")
  45 + private Integer deviceCredentialsCacheMaxSize;
  46 + @Value("${cache.device_credentials.time_to_live}")
  47 + private Integer deviceCredentialsCacheTTL;
  48 +
  49 + @Value("${zk.enabled}")
  50 + private boolean zkEnabled;
  51 + @Value("${zk.url}")
  52 + private String zkUrl;
  53 + @Value("${zk.zk_dir}")
  54 + private String zkDir;
  55 +
  56 + @Bean
  57 + public HazelcastInstance hazelcastInstance() {
  58 + Config config = new Config();
  59 +
  60 + if (zkEnabled) {
  61 + config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
  62 +
  63 + config.setProperty(GroupProperty.DISCOVERY_SPI_ENABLED.getName(), Boolean.TRUE.toString());
  64 + DiscoveryStrategyConfig discoveryStrategyConfig = new DiscoveryStrategyConfig(new ZookeeperDiscoveryStrategyFactory());
  65 + discoveryStrategyConfig.addProperty(ZookeeperDiscoveryProperties.ZOOKEEPER_URL.key(), zkUrl);
  66 + discoveryStrategyConfig.addProperty(ZookeeperDiscoveryProperties.ZOOKEEPER_PATH.key(), zkDir);
  67 + discoveryStrategyConfig.addProperty(ZookeeperDiscoveryProperties.GROUP.key(), HAZELCAST_CLUSTER_NAME);
  68 + config.getNetworkConfig().getJoin().getDiscoveryConfig().addDiscoveryStrategyConfig(discoveryStrategyConfig);
  69 + }
  70 +
  71 + MapConfig deviceCredentialsCacheConfig = new MapConfig(CacheConstants.DEVICE_CREDENTIALS_CACHE);
  72 + deviceCredentialsCacheConfig.setTimeToLiveSeconds(deviceCredentialsCacheTTL);
  73 + deviceCredentialsCacheConfig.setMaxSizeConfig(new MaxSizeConfig(deviceCredentialsCacheMaxSize, MaxSizeConfig.MaxSizePolicy.PER_NODE));
  74 + config.addMapConfig(deviceCredentialsCacheConfig);
  75 +
  76 + return Hazelcast.newHazelcastInstance(config);
  77 + }
  78 +
  79 + @Bean
  80 + public KeyGenerator previousDeviceCredentialsId() {
  81 + return new PreviousDeviceCredentialsIdKeyGenerator();
  82 + }
  83 +
  84 + @Bean
  85 + public CacheManager cacheManager() {
  86 + return new HazelcastCacheManager(hazelcastInstance());
  87 + }
  88 +}
  1 +#Db schema configuration
  2 +
  3 +SKIP_SCHEMA_CREATION=false
  4 +SKIP_SYSTEM_DATA=false
  5 +SKIP_DEMO_DATA=false
  1 +#
  2 +# Copyright © 2016 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +FROM cassandra:3.9
  18 +
  19 +ADD install_schema.sh /root/install_schema.sh
  20 +
  21 +RUN apt-get update \
  22 + && apt-get install -y nmap
  23 +
  24 +RUN chmod +x /root/install_schema.sh
  25 +
  26 +WORKDIR /root
  1 +#!/bin/bash
  2 +#
  3 +# Copyright © 2016 The Thingsboard Authors
  4 +#
  5 +# Licensed under the Apache License, Version 2.0 (the "License");
  6 +# you may not use this file except in compliance with the License.
  7 +# You may obtain a copy of the License at
  8 +#
  9 +# http://www.apache.org/licenses/LICENSE-2.0
  10 +#
  11 +# Unless required by applicable law or agreed to in writing, software
  12 +# distributed under the License is distributed on an "AS IS" BASIS,
  13 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 +# See the License for the specific language governing permissions and
  15 +# limitations under the License.
  16 +#
  17 +
  18 +
  19 +until nmap db -p 9042 | grep "9042/tcp open"
  20 +do
  21 + echo "Wait for Cassandra..."
  22 + sleep 10
  23 +done
  24 +
  25 +if [ "$SKIP_SCHEMA_CREATION" == "false" ]; then
  26 + echo "Creating 'Thingsboard' keyspace..."
  27 + cqlsh db -f /root/schema.cql
  28 + if [ "$?" -eq 0 ]; then
  29 + echo "'Thingsboard' keyspace was successfully created!"
  30 + else
  31 + echo "There were issues while creating 'Thingsboard' keyspace!"
  32 + fi
  33 +fi
  34 +
  35 +if [ "$SKIP_SYSTEM_DATA" == "false" ]; then
  36 + echo "Adding system data..."
  37 + cqlsh db -f /root/system-data.cql
  38 + if [ "$?" -eq 0 ]; then
  39 + echo "System data was successfully added!"
  40 + else
  41 + echo "There were issues while adding System data!"
  42 + fi
  43 +fi
  44 +
  45 +if [ "$SKIP_DEMO_DATA" == "false" ]; then
  46 + echo "Adding demo data..."
  47 + cqlsh db -f /root/demo-data.cql
  48 + if [ "$?" -eq 0 ]; then
  49 + echo "Demo data was successfully added!"
  50 + else
  51 + echo "There were issues while adding Demo data!"
  52 + fi
  53 +fi
  1 +#!/bin/bash
  2 +#
  3 +# Copyright © 2016 The Thingsboard Authors
  4 +#
  5 +# Licensed under the Apache License, Version 2.0 (the "License");
  6 +# you may not use this file except in compliance with the License.
  7 +# You may obtain a copy of the License at
  8 +#
  9 +# http://www.apache.org/licenses/LICENSE-2.0
  10 +#
  11 +# Unless required by applicable law or agreed to in writing, software
  12 +# distributed under the License is distributed on an "AS IS" BASIS,
  13 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 +# See the License for the specific language governing permissions and
  15 +# limitations under the License.
  16 +#
  17 +
  18 +
  19 +command='docker-compose -f docker-compose.yml -f docker-compose.random.yml'
  20 +
  21 +echo "stopping images.."
  22 +$command stop
  23 +
  24 +echo "removing stopped images.."
  25 +$command rm -f
  26 +
  27 +echo "building images.."
  28 +$command build
  29 +
  30 +echo "starting images..."
  31 +$command up -d
  1 +#!/bin/bash
  2 +#
  3 +# Copyright © 2016 The Thingsboard Authors
  4 +#
  5 +# Licensed under the Apache License, Version 2.0 (the "License");
  6 +# you may not use this file except in compliance with the License.
  7 +# You may obtain a copy of the License at
  8 +#
  9 +# http://www.apache.org/licenses/LICENSE-2.0
  10 +#
  11 +# Unless required by applicable law or agreed to in writing, software
  12 +# distributed under the License is distributed on an "AS IS" BASIS,
  13 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 +# See the License for the specific language governing permissions and
  15 +# limitations under the License.
  16 +#
  17 +
  18 +
  19 +command='docker-compose -f docker-compose.yml -f docker-compose.static.yml'
  20 +
  21 +echo "stopping images.."
  22 +$command stop
  23 +
  24 +echo "removing stopped images.."
  25 +$command rm -f
  26 +
  27 +echo "building images.."
  28 +$command build
  29 +
  30 +echo "starting cassandra, zookeeper, db-schema images..."
  31 +$command up -d cassandra zookeeper db-schema
  1 +#
  2 +# Copyright © 2016 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +version: '2'
  18 +
  19 +services:
  20 + cassandra:
  21 + ports:
  22 + - "9042"
  23 + - "9160"
  24 + zookeeper:
  25 + ports:
  26 + - "2181"
  1 +#
  2 +# Copyright © 2016 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +version: '2'
  18 +
  19 +services:
  20 + cassandra:
  21 + ports:
  22 + - "9042:9042"
  23 + - "9160:9160"
  24 + zookeeper:
  25 + ports:
  26 + - "2181:2181"
  1 +#
  2 +# Copyright © 2016 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +version: '2'
  18 +
  19 +services:
  20 + thingsboard:
  21 + build: thingsboard
  22 + ports:
  23 + - "8080:8080"
  24 + - "1883:1883"
  25 + - "5683:5683"
  26 + links:
  27 + - cassandra:db
  28 + - zookeeper:zk
  29 + - db-schema:db-schema
  30 + volumes:
  31 + - "../application/target/thingsboard.deb:/root/thingsboard.deb"
  32 + env_file:
  33 + - thingsboard.env
  34 + entrypoint: ./run_web_app.sh
  35 + db-schema:
  36 + build: db-schema
  37 + links:
  38 + - cassandra:db
  39 + env_file:
  40 + - db-schema.env
  41 + volumes:
  42 + - "../dao/src/main/resources/schema.cql:/root/schema.cql"
  43 + - "../dao/src/main/resources/demo-data.cql:/root/demo-data.cql"
  44 + - "../dao/src/main/resources/system-data.cql:/root/system-data.cql"
  45 + entrypoint: ./install_schema.sh
  46 + cassandra:
  47 + image: "cassandra:3.9"
  48 + volumes:
  49 + - "${CASSANDRA_DATA_DIR}:/var/lib/cassandra"
  50 + zookeeper:
  51 + image: "zookeeper:3.4.9"
  52 + restart: always
  1 +#Thingsboard server configuration
  2 +
  3 +CASSANDRA_URL=db:9042
  4 +ZOOKEEPER_URL=zk:2181
  5 +MQTT_BIND_ADDRESS=0.0.0.0
  6 +MQTT_BIND_PORT=1883
  7 +COAP_BIND_ADDRESS=0.0.0.0
  8 +COAP_BIND_PORT=5683
  1 +#
  2 +# Copyright © 2016 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +FROM java:8-jre
  18 +
  19 +ADD run_web_app.sh /root/run_web_app.sh
  20 +
  21 +RUN chmod +x /root/run_web_app.sh
  22 +
  23 +WORKDIR /root
  1 +#!/bin/bash
  2 +#
  3 +# Copyright © 2016 The Thingsboard Authors
  4 +#
  5 +# Licensed under the Apache License, Version 2.0 (the "License");
  6 +# you may not use this file except in compliance with the License.
  7 +# You may obtain a copy of the License at
  8 +#
  9 +# http://www.apache.org/licenses/LICENSE-2.0
  10 +#
  11 +# Unless required by applicable law or agreed to in writing, software
  12 +# distributed under the License is distributed on an "AS IS" BASIS,
  13 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 +# See the License for the specific language governing permissions and
  15 +# limitations under the License.
  16 +#
  17 +
  18 +
  19 +dpkg -i /root/thingsboard.deb
  20 +
  21 +reachable=0
  22 +while [ $reachable -eq 0 ];
  23 +do
  24 + echo "db-schema container is still in progress. waiting until it completed..."
  25 + sleep 3
  26 + ping -q -c 1 db-schema > /dev/null 2>&1
  27 + if [ "$?" -ne 0 ];
  28 + then
  29 + echo "db-schema container completed!"
  30 + reachable=1
  31 + fi
  32 +done
  33 +
  34 +echo "Starting 'Thingsboard' service..."
  35 +thingsboard start
  36 +
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!--
  3 +
  4 + Copyright © 2016 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  20 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  21 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  22 + <modelVersion>4.0.0</modelVersion>
  23 + <parent>
  24 + <groupId>org.thingsboard.server</groupId>
  25 + <version>0.0.1-SNAPSHOT</version>
  26 + <artifactId>extensions</artifactId>
  27 + </parent>
  28 + <groupId>org.thingsboard.server.extensions</groupId>
  29 + <artifactId>extension-kafka</artifactId>
  30 + <packaging>jar</packaging>
  31 +
  32 + <name>Thingsboard Server Kafka Extension</name>
  33 + <url>http://thingsboard.org</url>
  34 +
  35 + <properties>
  36 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  37 + <main.dir>${basedir}/../..</main.dir>
  38 + </properties>
  39 +
  40 + <dependencies>
  41 + <dependency>
  42 + <groupId>ch.qos.logback</groupId>
  43 + <artifactId>logback-core</artifactId>
  44 + <scope>provided</scope>
  45 + </dependency>
  46 + <dependency>
  47 + <groupId>ch.qos.logback</groupId>
  48 + <artifactId>logback-classic</artifactId>
  49 + <scope>provided</scope>
  50 + </dependency>
  51 + <dependency>
  52 + <groupId>org.thingsboard.server</groupId>
  53 + <artifactId>extensions-api</artifactId>
  54 + <scope>provided</scope>
  55 + </dependency>
  56 + <dependency>
  57 + <groupId>org.thingsboard.server</groupId>
  58 + <artifactId>extensions-core</artifactId>
  59 + <scope>provided</scope>
  60 + </dependency>
  61 + <dependency>
  62 + <groupId>org.apache.velocity</groupId>
  63 + <artifactId>velocity</artifactId>
  64 + <scope>provided</scope>
  65 + </dependency>
  66 + <dependency>
  67 + <groupId>org.apache.velocity</groupId>
  68 + <artifactId>velocity-tools</artifactId>
  69 + <scope>provided</scope>
  70 + </dependency>
  71 + <dependency>
  72 + <groupId>org.apache.kafka</groupId>
  73 + <artifactId>kafka_2.10</artifactId>
  74 + </dependency>
  75 + </dependencies>
  76 + <build>
  77 + <plugins>
  78 + <plugin>
  79 + <artifactId>maven-assembly-plugin</artifactId>
  80 + <configuration>
  81 + <descriptors>
  82 + <descriptor>src/assembly/extension.xml</descriptor>
  83 + </descriptors>
  84 + </configuration>
  85 + <executions>
  86 + <execution>
  87 + <id>make-assembly</id>
  88 + <phase>package</phase>
  89 + <goals>
  90 + <goal>single</goal>
  91 + </goals>
  92 + </execution>
  93 + </executions>
  94 + </plugin>
  95 + </plugins>
  96 + </build>
  97 +</project>
  1 +<!--
  2 +
  3 + Copyright © 2016 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
  19 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  20 + xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
  21 + <id>extension</id>
  22 + <formats>
  23 + <format>jar</format>
  24 + </formats>
  25 + <includeBaseDirectory>false</includeBaseDirectory>
  26 + <dependencySets>
  27 + <dependencySet>
  28 + <outputDirectory>/</outputDirectory>
  29 + <useProjectArtifact>true</useProjectArtifact>
  30 + <unpack>true</unpack>
  31 + <scope>runtime</scope>
  32 + <excludes>
  33 + <exclude>org.apache.zookeeper:zookeeper</exclude>
  34 + <exclude>org.scala-lang:scala-library</exclude>
  35 + <exclude>io.netty:netty</exclude>
  36 + </excludes>
  37 + </dependencySet>
  38 + </dependencySets>
  39 +</assembly>
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.kafka.action;
  17 +
  18 +import org.thingsboard.server.common.data.id.CustomerId;
  19 +import org.thingsboard.server.common.data.id.DeviceId;
  20 +import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg;
  22 +
  23 +public class KafkaActionMsg extends AbstractRuleToPluginMsg<KafkaActionPayload> {
  24 +
  25 + public KafkaActionMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, KafkaActionPayload payload) {
  26 + super(tenantId, customerId, deviceId, payload);
  27 + }
  28 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.kafka.action;
  17 +
  18 +import lombok.Builder;
  19 +import lombok.Data;
  20 +import org.thingsboard.server.common.msg.session.MsgType;
  21 +
  22 +import java.io.Serializable;
  23 +
  24 +@Data
  25 +@Builder
  26 +public class KafkaActionPayload implements Serializable {
  27 +
  28 + private final String topic;
  29 + private final String msgBody;
  30 + private final boolean sync;
  31 +
  32 + private final Integer requestId;
  33 + private final MsgType msgType;
  34 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.kafka.action;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  20 +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
  21 +import org.thingsboard.server.extensions.api.component.Action;
  22 +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
  23 +import org.thingsboard.server.extensions.api.rules.RuleContext;
  24 +import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction;
  25 +
  26 +import java.util.Optional;
  27 +
  28 +@Action(name = "Kafka Plugin Action", descriptor = "KafkaActionDescriptor.json", configuration = KafkaPluginActionConfiguration.class)
  29 +@Slf4j
  30 +public class KafkaPluginAction extends AbstractTemplatePluginAction<KafkaPluginActionConfiguration> {
  31 +
  32 + @Override
  33 + protected Optional<RuleToPluginMsg<?>> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
  34 + KafkaActionPayload.KafkaActionPayloadBuilder builder = KafkaActionPayload.builder();
  35 + builder.msgType(payload.getMsgType());
  36 + builder.requestId(payload.getRequestId());
  37 + builder.sync(configuration.isSync());
  38 + builder.topic(configuration.getTopic());
  39 + builder.msgBody(getMsgBody(ctx, msg));
  40 + return Optional.of(new KafkaActionMsg(msg.getTenantId(),
  41 + msg.getCustomerId(),
  42 + msg.getDeviceId(),
  43 + builder.build()));
  44 + }
  45 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.kafka.action;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.extensions.core.action.template.TemplateActionConfiguration;
  20 +
  21 +@Data
  22 +public class KafkaPluginActionConfiguration implements TemplateActionConfiguration {
  23 + private boolean sync;
  24 + private String topic;
  25 + private String template;
  26 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.kafka.plugin;
  17 +
  18 +import lombok.RequiredArgsConstructor;
  19 +import org.apache.kafka.clients.producer.Producer;
  20 +import org.apache.kafka.clients.producer.ProducerRecord;
  21 +import org.thingsboard.server.common.data.id.RuleId;
  22 +import org.thingsboard.server.common.data.id.TenantId;
  23 +import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
  24 +import org.thingsboard.server.extensions.api.plugins.PluginContext;
  25 +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler;
  26 +import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg;
  27 +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
  28 +import org.thingsboard.server.extensions.api.rules.RuleException;
  29 +import org.thingsboard.server.extensions.kafka.action.KafkaActionMsg;
  30 +import org.thingsboard.server.extensions.kafka.action.KafkaActionPayload;
  31 +
  32 +@RequiredArgsConstructor
  33 +public class KafkaMsgHandler implements RuleMsgHandler {
  34 +
  35 + private final Producer<?, String> producer;
  36 +
  37 + @Override
  38 + public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) throws RuleException {
  39 + if (!(msg instanceof KafkaActionMsg)) {
  40 + throw new RuleException("Unsupported message type " + msg.getClass().getName() + "!");
  41 + }
  42 + KafkaActionPayload payload = ((KafkaActionMsg) msg).getPayload();
  43 +
  44 + try {
  45 + producer.send(new ProducerRecord<>(payload.getTopic(), payload.getMsgBody()),
  46 + (metadata, e) -> {
  47 + if (payload.isSync()) {
  48 + if (metadata != null) {
  49 + ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
  50 + BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId())));
  51 + } else {
  52 + ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
  53 + BasicStatusCodeResponse.onError(payload.getMsgType(), payload.getRequestId(), e)));
  54 + }
  55 + }
  56 + });
  57 + } catch (Exception e) {
  58 + throw new RuleException(e.getMessage(), e);
  59 + }
  60 + }
  61 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.kafka.plugin;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.apache.kafka.clients.producer.KafkaProducer;
  20 +import org.apache.kafka.clients.producer.Producer;
  21 +import org.thingsboard.server.extensions.api.component.Plugin;
  22 +import org.thingsboard.server.extensions.api.plugins.AbstractPlugin;
  23 +import org.thingsboard.server.extensions.api.plugins.PluginContext;
  24 +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler;
  25 +import org.thingsboard.server.extensions.kafka.action.KafkaPluginAction;
  26 +
  27 +import java.util.Properties;
  28 +
  29 +@Plugin(name = "Kafka Plugin", actions = {KafkaPluginAction.class},
  30 + descriptor = "KafkaPluginDescriptor.json", configuration = KafkaPluginConfiguration.class)
  31 +@Slf4j
  32 +public class KafkaPlugin extends AbstractPlugin<KafkaPluginConfiguration> {
  33 +
  34 + private KafkaMsgHandler handler;
  35 + private Producer<?, String> producer;
  36 + private final Properties properties = new Properties();
  37 +
  38 + @Override
  39 + public void init(KafkaPluginConfiguration configuration) {
  40 + properties.put("bootstrap.servers", configuration.getBootstrapServers());
  41 + properties.put("value.serializer", configuration.getValueSerializer());
  42 + properties.put("key.serializer", configuration.getKeySerializer());
  43 + properties.put("acks", String.valueOf(configuration.getAcks()));
  44 + properties.put("retries", configuration.getRetries());
  45 + properties.put("batch.size", configuration.getBatchSize());
  46 + properties.put("linger.ms", configuration.getLinger());
  47 + properties.put("buffer.memory", configuration.getBufferMemory());
  48 + if (configuration.getOtherProperties() != null) {
  49 + configuration.getOtherProperties()
  50 + .stream().forEach(p -> properties.put(p.getKey(), p.getValue()));
  51 + }
  52 + init();
  53 + }
  54 +
  55 + private void init() {
  56 + try {
  57 + this.producer = new KafkaProducer<>(properties);
  58 + this.handler = new KafkaMsgHandler(producer);
  59 + } catch (Exception e) {
  60 + log.error("Failed to start kafka producer", e);
  61 + throw new RuntimeException(e);
  62 + }
  63 + }
  64 +
  65 + private void destroy() {
  66 + try {
  67 + this.handler = null;
  68 + this.producer.close();
  69 + } catch (Exception e) {
  70 + log.error("Failed to close producer during destroy()", e);
  71 + }
  72 + }
  73 +
  74 + @Override
  75 + protected RuleMsgHandler getRuleMsgHandler() {
  76 + return handler;
  77 + }
  78 +
  79 + @Override
  80 + public void resume(PluginContext ctx) {
  81 + init();
  82 + }
  83 +
  84 + @Override
  85 + public void suspend(PluginContext ctx) {
  86 + destroy();
  87 + }
  88 +
  89 + @Override
  90 + public void stop(PluginContext ctx) {
  91 + destroy();
  92 + }
  93 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.kafka.plugin;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.extensions.core.plugin.KeyValuePluginProperties;
  20 +
  21 +import java.util.List;
  22 +
  23 +@Data
  24 +public class KafkaPluginConfiguration {
  25 + private String bootstrapServers;
  26 + private int retries;
  27 + private int batchSize;
  28 + private int linger;
  29 + private int bufferMemory;
  30 + private int acks;
  31 + private String keySerializer;
  32 + private String valueSerializer;
  33 + private List<KeyValuePluginProperties> otherProperties;
  34 +}
  1 +{
  2 + "schema": {
  3 + "title": "Kafka Action Configuration",
  4 + "type": "object",
  5 + "properties": {
  6 + "sync": {
  7 + "title": "Requires delivery confirmation",
  8 + "type": "boolean"
  9 + },
  10 + "topic": {
  11 + "title": "Topic Name",
  12 + "type": "string"
  13 + },
  14 + "template": {
  15 + "title": "Body Template",
  16 + "type": "string"
  17 + }
  18 + },
  19 + "required": [
  20 + "sync",
  21 + "topic",
  22 + "template"
  23 + ]
  24 + },
  25 + "form": [
  26 + "sync",
  27 + "topic",
  28 + {
  29 + "key": "template",
  30 + "type": "textarea",
  31 + "rows": 5
  32 + }
  33 + ]
  34 +}
  1 +{
  2 + "schema": {
  3 + "title": "Kafka Plugin Configuration",
  4 + "type": "object",
  5 + "properties": {
  6 + "bootstrapServers": {
  7 + "title": "Bootstrap Servers",
  8 + "type": "string",
  9 + "default": "localhost:9092"
  10 + },
  11 + "retries": {
  12 + "title": "Automatically Retry Times If Fails",
  13 + "type": "integer",
  14 + "default": 0
  15 + },
  16 + "batchSize": {
  17 + "title": "Producer Batch Size On Client",
  18 + "type": "integer",
  19 + "default": 16384
  20 + },
  21 + "linger": {
  22 + "title": "Time To Buffer Locally Before Sending To Kafka Broker (in ms)",
  23 + "type": "integer",
  24 + "default": 0
  25 + },
  26 + "bufferMemory": {
  27 + "title": "Buffer Max Size On Client",
  28 + "type": "integer",
  29 + "default": 33554432
  30 + },
  31 + "acks": {
  32 + "title": "Minimum Number Of Replicas That Must Acknowledge A Write (-1 for 'all')",
  33 + "type": "integer",
  34 + "default": -1
  35 + },
  36 + "keySerializer": {
  37 + "title": "Key Serializer",
  38 + "type": "string",
  39 + "default": "org.apache.kafka.common.serialization.StringSerializer"
  40 + },
  41 + "valueSerializer": {
  42 + "title": "Value Serializer",
  43 + "type": "string",
  44 + "default": "org.apache.kafka.common.serialization.StringSerializer"
  45 + },
  46 + "otherProperties": {
  47 + "title": "Other Kafka properties",
  48 + "type": "array",
  49 + "items": {
  50 + "title": "Kafka property",
  51 + "type": "object",
  52 + "properties": {
  53 + "key": {
  54 + "title": "Key",
  55 + "type": "string"
  56 + },
  57 + "value": {
  58 + "title": "Value",
  59 + "type": "string"
  60 + }
  61 + }
  62 + }
  63 + }
  64 + },
  65 + "required": [
  66 + "bootstrapServers"
  67 + ]
  68 + },
  69 + "form": [
  70 + "bootstrapServers",
  71 + "retries",
  72 + "batchSize",
  73 + "linger",
  74 + "bufferMemory",
  75 + "acks",
  76 + "keySerializer",
  77 + "valueSerializer",
  78 + "otherProperties"
  79 + ]
  80 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.kafka;
  17 +
  18 +import kafka.server.KafkaConfig;
  19 +import kafka.server.KafkaServerStartable;
  20 +import org.apache.zookeeper.server.ServerConfig;
  21 +import org.apache.zookeeper.server.ZooKeeperServerMain;
  22 +import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
  23 +
  24 +import java.io.File;
  25 +import java.io.IOException;
  26 +import java.util.Properties;
  27 +
  28 +public class KafkaDemoClient {
  29 +
  30 + private static final int ZK_PORT = 2222;
  31 + private static final String HOSTNAME = "localhost";
  32 + private static final String ZOOKEEPER_CONNECT = HOSTNAME + ":" + ZK_PORT;
  33 + private static final int KAFKA_PORT = 9092;
  34 + private static final int BROKER_ID = 1;
  35 +
  36 + public static void main(String[] args) {
  37 + try {
  38 + startZkLocal();
  39 + startKafkaLocal();
  40 + } catch (Exception e) {
  41 + System.out.println("Error running local Kafka broker");
  42 + e.printStackTrace(System.out);
  43 + }
  44 + }
  45 +
  46 + private static void startZkLocal() throws Exception {
  47 + final File zkTmpDir = File.createTempFile("zookeeper", "test");
  48 + if (zkTmpDir.delete() && zkTmpDir.mkdir()) {
  49 + Properties zkProperties = new Properties();
  50 + zkProperties.setProperty("dataDir", zkTmpDir.getAbsolutePath());
  51 + zkProperties.setProperty("clientPort", String.valueOf(ZK_PORT));
  52 +
  53 + ServerConfig configuration = new ServerConfig();
  54 + QuorumPeerConfig quorumConfiguration = new QuorumPeerConfig();
  55 + quorumConfiguration.parseProperties(zkProperties);
  56 + configuration.readFrom(quorumConfiguration);
  57 +
  58 + new Thread() {
  59 + public void run() {
  60 + try {
  61 + new ZooKeeperServerMain().runFromConfig(configuration);
  62 + } catch (IOException e) {
  63 + System.out.println("Start of Local ZooKeeper Failed");
  64 + e.printStackTrace(System.err);
  65 + }
  66 + }
  67 + }.start();
  68 + } else {
  69 + System.out.println("Failed to delete or create data dir for Zookeeper");
  70 + }
  71 + }
  72 +
  73 + private static void startKafkaLocal() {
  74 + Properties kafkaProperties = new Properties();
  75 + kafkaProperties.setProperty("host.name", HOSTNAME);
  76 + kafkaProperties.setProperty("port", String.valueOf(KAFKA_PORT));
  77 + kafkaProperties.setProperty("broker.id", String.valueOf(BROKER_ID));
  78 + kafkaProperties.setProperty("zookeeper.connect", ZOOKEEPER_CONNECT);
  79 + KafkaConfig kafkaConfig = new KafkaConfig(kafkaProperties);
  80 + KafkaServerStartable kafka = new KafkaServerStartable(kafkaConfig);
  81 + kafka.startup();
  82 + }
  83 +}
  1 +<!--
  2 +
  3 + Copyright © 2016 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  19 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  20 + <modelVersion>4.0.0</modelVersion>
  21 + <parent>
  22 + <groupId>org.thingsboard.server</groupId>
  23 + <version>0.0.1-SNAPSHOT</version>
  24 + <artifactId>extensions</artifactId>
  25 + </parent>
  26 + <groupId>org.thingsboard.server.extensions</groupId>
  27 + <artifactId>extension-rabbitmq</artifactId>
  28 + <packaging>jar</packaging>
  29 +
  30 + <name>Thingsboard Server RabbitMQ Extension</name>
  31 + <url>http://thingsboard.org</url>
  32 +
  33 + <properties>
  34 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  35 + <main.dir>${basedir}/../..</main.dir>
  36 + </properties>
  37 +
  38 + <dependencies>
  39 + <dependency>
  40 + <groupId>com.rabbitmq</groupId>
  41 + <artifactId>amqp-client</artifactId>
  42 + </dependency>
  43 + <dependency>
  44 + <groupId>org.thingsboard.server</groupId>
  45 + <artifactId>extensions-api</artifactId>
  46 + <scope>provided</scope>
  47 + </dependency>
  48 + <dependency>
  49 + <groupId>org.thingsboard.server</groupId>
  50 + <artifactId>extensions-core</artifactId>
  51 + <scope>provided</scope>
  52 + </dependency>
  53 + <dependency>
  54 + <groupId>org.apache.velocity</groupId>
  55 + <artifactId>velocity</artifactId>
  56 + <scope>provided</scope>
  57 + </dependency>
  58 + <dependency>
  59 + <groupId>org.apache.velocity</groupId>
  60 + <artifactId>velocity-tools</artifactId>
  61 + <scope>provided</scope>
  62 + </dependency>
  63 + <dependency>
  64 + <groupId>org.springframework.boot</groupId>
  65 + <artifactId>spring-boot-starter-web</artifactId>
  66 + <scope>provided</scope>
  67 + </dependency>
  68 + <dependency>
  69 + <groupId>org.springframework</groupId>
  70 + <artifactId>spring-context-support</artifactId>
  71 + <scope>provided</scope>
  72 + </dependency>
  73 + <dependency>
  74 + <groupId>org.slf4j</groupId>
  75 + <artifactId>slf4j-api</artifactId>
  76 + <scope>provided</scope>
  77 + </dependency>
  78 + <dependency>
  79 + <groupId>org.slf4j</groupId>
  80 + <artifactId>log4j-over-slf4j</artifactId>
  81 + <scope>provided</scope>
  82 + </dependency>
  83 + <dependency>
  84 + <groupId>ch.qos.logback</groupId>
  85 + <artifactId>logback-core</artifactId>
  86 + <scope>provided</scope>
  87 + </dependency>
  88 + <dependency>
  89 + <groupId>ch.qos.logback</groupId>
  90 + <artifactId>logback-classic</artifactId>
  91 + <scope>provided</scope>
  92 + </dependency>
  93 + <dependency>
  94 + <groupId>junit</groupId>
  95 + <artifactId>junit</artifactId>
  96 + <scope>test</scope>
  97 + </dependency>
  98 + <dependency>
  99 + <groupId>org.mockito</groupId>
  100 + <artifactId>mockito-all</artifactId>
  101 + <scope>test</scope>
  102 + </dependency>
  103 + </dependencies>
  104 +
  105 + <build>
  106 + <plugins>
  107 + <plugin>
  108 + <groupId>org.apache.maven.plugins</groupId>
  109 + <artifactId>maven-dependency-plugin</artifactId>
  110 + </plugin>
  111 + <plugin>
  112 + <groupId>org.xolstice.maven.plugins</groupId>
  113 + <artifactId>protobuf-maven-plugin</artifactId>
  114 + </plugin>
  115 + <plugin>
  116 + <groupId>org.codehaus.mojo</groupId>
  117 + <artifactId>build-helper-maven-plugin</artifactId>
  118 + </plugin>
  119 + <plugin>
  120 + <artifactId>maven-assembly-plugin</artifactId>
  121 + <configuration>
  122 + <descriptors>
  123 + <descriptor>src/assembly/extension.xml</descriptor>
  124 + </descriptors>
  125 + </configuration>
  126 + <executions>
  127 + <execution>
  128 + <id>make-assembly</id>
  129 + <phase>package</phase>
  130 + <goals>
  131 + <goal>single</goal>
  132 + </goals>
  133 + </execution>
  134 + </executions>
  135 + </plugin>
  136 + </plugins>
  137 + </build>
  138 +
  139 +</project>
  1 +<!--
  2 +
  3 + Copyright © 2016 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
  19 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  20 + xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
  21 + <id>extension</id>
  22 + <formats>
  23 + <format>jar</format>
  24 + </formats>
  25 + <includeBaseDirectory>false</includeBaseDirectory>
  26 + <dependencySets>
  27 + <dependencySet>
  28 + <outputDirectory>/</outputDirectory>
  29 + <useProjectArtifact>true</useProjectArtifact>
  30 + <unpack>true</unpack>
  31 + <scope>runtime</scope>
  32 + </dependencySet>
  33 + </dependencySets>
  34 +</assembly>
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rabbitmq.action;
  17 +
  18 +import org.thingsboard.server.common.data.id.CustomerId;
  19 +import org.thingsboard.server.common.data.id.DeviceId;
  20 +import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg;
  22 +
  23 +/**
  24 + * @author Andrew Shvayka
  25 + */
  26 +public class RabbitMqActionMsg extends AbstractRuleToPluginMsg<RabbitMqActionPayload> {
  27 +
  28 + public RabbitMqActionMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, RabbitMqActionPayload payload) {
  29 + super(tenantId, customerId, deviceId, payload);
  30 + }
  31 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rabbitmq.action;
  17 +
  18 +import lombok.Builder;
  19 +import lombok.Data;
  20 +import org.thingsboard.server.common.msg.session.MsgType;
  21 +
  22 +import java.io.Serializable;
  23 +
  24 +/**
  25 + * @author Andrew Shvayka
  26 + */
  27 +@Data
  28 +@Builder
  29 +public class RabbitMqActionPayload implements Serializable {
  30 +
  31 + private final String exchange;
  32 + private final String queueName;
  33 + private final String messageProperties;
  34 + private final String payload;
  35 +
  36 + private final boolean sync;
  37 + private final Integer requestId;
  38 + private final MsgType msgType;
  39 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rabbitmq.action;
  17 +
  18 +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  19 +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
  20 +import org.thingsboard.server.extensions.api.component.Action;
  21 +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
  22 +import org.thingsboard.server.extensions.api.rules.RuleContext;
  23 +import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction;
  24 +
  25 +import java.util.Optional;
  26 +
  27 +/**
  28 + * @author Andrew Shvayka
  29 + */
  30 +@Action(name = "RabbitMQ Plugin Action",
  31 + descriptor = "RabbitMqActionDescriptor.json", configuration = RabbitMqPluginActionConfiguration.class)
  32 +public class RabbitMqPluginAction extends AbstractTemplatePluginAction<RabbitMqPluginActionConfiguration> {
  33 +
  34 + @Override
  35 + protected Optional<RuleToPluginMsg<?>> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
  36 + RabbitMqActionPayload.RabbitMqActionPayloadBuilder builder = RabbitMqActionPayload.builder();
  37 + builder.sync(configuration.isSync());
  38 + builder.exchange(configuration.getExchange());
  39 + builder.queueName(configuration.getQueueName());
  40 + builder.messageProperties(configuration.getMessageProperties()[0]);
  41 + builder.msgType(payload.getMsgType());
  42 + builder.requestId(payload.getRequestId());
  43 + builder.payload(getMsgBody(ctx, msg));
  44 + return Optional.of(new RabbitMqActionMsg(msg.getTenantId(),
  45 + msg.getCustomerId(),
  46 + msg.getDeviceId(),
  47 + builder.build()));
  48 + }
  49 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rabbitmq.action;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.extensions.core.action.template.TemplateActionConfiguration;
  20 +
  21 +/**
  22 + * @author Andrew Shvayka
  23 + */
  24 +@Data
  25 +public class RabbitMqPluginActionConfiguration implements TemplateActionConfiguration{
  26 +
  27 + private boolean sync;
  28 + private String exchange;
  29 + private String queueName;
  30 + private String[] messageProperties;
  31 + private String template;
  32 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rabbitmq.plugin;
  17 +
  18 +import com.rabbitmq.client.AMQP;
  19 +import com.rabbitmq.client.Channel;
  20 +import com.rabbitmq.client.MessageProperties;
  21 +import lombok.RequiredArgsConstructor;
  22 +import org.thingsboard.server.common.data.id.RuleId;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
  25 +import org.thingsboard.server.extensions.api.plugins.PluginContext;
  26 +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler;
  27 +import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg;
  28 +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
  29 +import org.thingsboard.server.extensions.api.rules.RuleException;
  30 +import org.thingsboard.server.extensions.rabbitmq.action.RabbitMqActionMsg;
  31 +import org.thingsboard.server.extensions.rabbitmq.action.RabbitMqActionPayload;
  32 +
  33 +import java.io.IOException;
  34 +import java.nio.charset.Charset;
  35 +
  36 +/**
  37 + * @author Andrew Shvayka
  38 + */
  39 +@RequiredArgsConstructor
  40 +public class RabbitMqMsgHandler implements RuleMsgHandler {
  41 + private static final Charset UTF8 = Charset.forName("UTF-8");
  42 +
  43 + private final Channel channel;
  44 +
  45 + @Override
  46 + public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) throws RuleException {
  47 + if (!(msg instanceof RabbitMqActionMsg)) {
  48 + throw new RuleException("Unsupported message type " + msg.getClass().getName() + "!");
  49 + }
  50 + RabbitMqActionPayload payload = ((RabbitMqActionMsg) msg).getPayload();
  51 + AMQP.BasicProperties properties = convert(payload.getMessageProperties());
  52 + try {
  53 + channel.basicPublish(
  54 + payload.getExchange() != null ? payload.getExchange() : "",
  55 + payload.getQueueName(),
  56 + properties,
  57 + payload.getPayload().getBytes(UTF8));
  58 + if (payload.isSync()) {
  59 + ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
  60 + BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId())));
  61 + }
  62 + } catch (IOException e) {
  63 + throw new RuleException(e.getMessage(), e);
  64 + }
  65 + }
  66 +
  67 + private static AMQP.BasicProperties convert(String name) throws RuleException {
  68 + switch (name) {
  69 + case "BASIC":
  70 + return MessageProperties.BASIC;
  71 + case "TEXT_PLAIN":
  72 + return MessageProperties.TEXT_PLAIN;
  73 + case "MINIMAL_BASIC":
  74 + return MessageProperties.MINIMAL_BASIC;
  75 + case "MINIMAL_PERSISTENT_BASIC":
  76 + return MessageProperties.MINIMAL_PERSISTENT_BASIC;
  77 + case "PERSISTENT_BASIC":
  78 + return MessageProperties.PERSISTENT_BASIC;
  79 + case "PERSISTENT_TEXT_PLAIN":
  80 + return MessageProperties.PERSISTENT_TEXT_PLAIN;
  81 + default:
  82 + throw new RuleException("Message Properties: '" + name + "' is undefined!");
  83 + }
  84 + }
  85 +
  86 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rabbitmq.plugin;
  17 +
  18 +import com.rabbitmq.client.Connection;
  19 +import com.rabbitmq.client.ConnectionFactory;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.apache.commons.lang.StringUtils;
  22 +import org.thingsboard.server.extensions.api.component.Plugin;
  23 +import org.thingsboard.server.extensions.api.plugins.AbstractPlugin;
  24 +import org.thingsboard.server.extensions.api.plugins.PluginContext;
  25 +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler;
  26 +import org.thingsboard.server.extensions.rabbitmq.action.RabbitMqPluginAction;
  27 +
  28 +import java.io.IOException;
  29 +import java.util.concurrent.TimeoutException;
  30 +import java.util.function.Consumer;
  31 +import java.util.stream.Collectors;
  32 +
  33 +/**
  34 + * @author Andrew Shvayka
  35 + */
  36 +@Plugin(name = "RabbitMQ Plugin", actions = {RabbitMqPluginAction.class},
  37 +descriptor = "RabbitMqPluginDescriptor.json", configuration = RabbitMqPluginConfiguration.class)
  38 +@Slf4j
  39 +public class RabbitMqPlugin extends AbstractPlugin<RabbitMqPluginConfiguration> {
  40 +
  41 + private ConnectionFactory factory;
  42 + private Connection connection;
  43 + private RabbitMqMsgHandler handler;
  44 +
  45 + @Override
  46 + public void init(RabbitMqPluginConfiguration configuration) {
  47 + factory = new ConnectionFactory();
  48 + factory.setHost(configuration.getHost());
  49 + factory.setPort(configuration.getPort());
  50 + set(configuration.getVirtualHost(), factory::setVirtualHost);
  51 + set(configuration.getUserName(), factory::setUsername);
  52 + set(configuration.getPassword(), factory::setPassword);
  53 + set(configuration.getAutomaticRecoveryEnabled(), factory::setAutomaticRecoveryEnabled);
  54 + set(configuration.getConnectionTimeout(), factory::setConnectionTimeout);
  55 + set(configuration.getHandshakeTimeout(), factory::setHandshakeTimeout);
  56 + set(configuration.getClientProperties(), props -> {
  57 + factory.setClientProperties(props.stream().collect(Collectors.toMap(
  58 + RabbitMqPluginConfiguration.RabbitMqPluginProperties::getKey,
  59 + RabbitMqPluginConfiguration.RabbitMqPluginProperties::getValue)));
  60 + });
  61 +
  62 + init();
  63 + }
  64 +
  65 + private <T> void set(T source, Consumer<T> setter) {
  66 + if (source != null && !StringUtils.isEmpty(source.toString())) {
  67 + setter.accept(source);
  68 + }
  69 + }
  70 +
  71 + private void init() {
  72 + try {
  73 + this.connection = factory.newConnection();
  74 + this.handler = new RabbitMqMsgHandler(connection.createChannel());
  75 + } catch (IOException | TimeoutException e) {
  76 + throw new RuntimeException(e);
  77 + }
  78 + }
  79 +
  80 + private void destroy() {
  81 + try {
  82 + this.handler = null;
  83 + this.connection.close();
  84 + } catch (Exception e) {
  85 + log.info("Failed to close connection during destroy()", e);
  86 + }
  87 + }
  88 +
  89 + @Override
  90 + protected RuleMsgHandler getRuleMsgHandler() {
  91 + return handler;
  92 + }
  93 +
  94 + @Override
  95 + public void resume(PluginContext ctx) {
  96 + init();
  97 + }
  98 +
  99 + @Override
  100 + public void suspend(PluginContext ctx) {
  101 + destroy();
  102 + }
  103 +
  104 + @Override
  105 + public void stop(PluginContext ctx) {
  106 + destroy();
  107 + }
  108 +
  109 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rabbitmq.plugin;
  17 +
  18 +import lombok.Data;
  19 +
  20 +import java.util.List;
  21 +
  22 +/**
  23 + * @author Andrew Shvayka
  24 + */
  25 +@Data
  26 +public class RabbitMqPluginConfiguration {
  27 + private String host;
  28 + private int port;
  29 + private String virtualHost;
  30 +
  31 + private String userName;
  32 + private String password;
  33 +
  34 + private Boolean automaticRecoveryEnabled;
  35 +
  36 + private Integer connectionTimeout;
  37 + private Integer handshakeTimeout;
  38 +
  39 + private List<RabbitMqPluginProperties> clientProperties;
  40 +
  41 + @Data
  42 + public static class RabbitMqPluginProperties {
  43 + private String key;
  44 + private String value;
  45 + }
  46 +
  47 +}
  1 +{
  2 + "schema": {
  3 + "title": "RabbitMQ Action Configuration",
  4 + "type": "object",
  5 + "properties": {
  6 + "sync": {
  7 + "title": "Requires delivery confirmation",
  8 + "type": "boolean"
  9 + },
  10 + "exchange": {
  11 + "title": "Exchange",
  12 + "type": "string",
  13 + "default": ""
  14 + },
  15 + "queueName": {
  16 + "title": "Queue Name",
  17 + "type": "string"
  18 + },
  19 + "messageProperties": {
  20 + "title": "Message properties",
  21 + "type": "array",
  22 + "minItems" : 1,
  23 + "items": [
  24 + {
  25 + "value": "BASIC",
  26 + "label": "BASIC"
  27 + },
  28 + {
  29 + "value": "MINIMAL_BASIC",
  30 + "label": "MINIMAL_BASIC"
  31 + },
  32 + {
  33 + "value": "MINIMAL_PERSISTENT_BASIC",
  34 + "label": "MINIMAL_PERSISTENT_BASIC"
  35 + },
  36 + {
  37 + "value": "PERSISTENT_BASIC",
  38 + "label": "PERSISTENT_BASIC"
  39 + },
  40 + {
  41 + "value": "PERSISTENT_TEXT_PLAIN",
  42 + "label": "PERSISTENT_TEXT_PLAIN"
  43 + },
  44 + {
  45 + "value": "TEXT_PLAIN",
  46 + "label": "TEXT_PLAIN"
  47 + }
  48 + ],
  49 + "uniqueItems": true
  50 + },
  51 + "template": {
  52 + "title": "Body Template",
  53 + "type": "string"
  54 + }
  55 + },
  56 + "required": [
  57 + "sync",
  58 + "queueName",
  59 + "messageProperties",
  60 + "template"
  61 + ]
  62 + },
  63 + "form": [
  64 + "sync",
  65 + "exchange",
  66 + "queueName",
  67 + {
  68 + "key": "messageProperties",
  69 + "type": "rc-select",
  70 + "multiple": false
  71 + },
  72 + {
  73 + "key": "template",
  74 + "type": "textarea",
  75 + "rows": 5
  76 + }
  77 + ]
  78 +}
  1 +{
  2 + "schema": {
  3 + "title": "RabbitMQ Plugin Configuration",
  4 + "type": "object",
  5 + "properties": {
  6 + "host": {
  7 + "title": "Host",
  8 + "type": "string"
  9 + },
  10 + "port": {
  11 + "title": "Port",
  12 + "type": "integer",
  13 + "default": 5672,
  14 + "minimum": 0,
  15 + "maximum": 65536
  16 + },
  17 + "virtualHost": {
  18 + "title": "Virtual Host",
  19 + "type": "string"
  20 + },
  21 + "userName": {
  22 + "title": "Username",
  23 + "type": "string"
  24 + },
  25 + "password": {
  26 + "title": "Password",
  27 + "type": "string"
  28 + },
  29 + "automaticRecoveryEnabled": {
  30 + "title": "Automatic Recovery Enabled",
  31 + "type": "boolean"
  32 + },
  33 + "connectionTimeout": {
  34 + "title": "Connection Timeout",
  35 + "type": "integer"
  36 + },
  37 + "handshakeTimeout": {
  38 + "title": "Handshake Timeout",
  39 + "type": "integer"
  40 + },
  41 + "clientProperties": {
  42 + "title": "Client properties",
  43 + "type": "array",
  44 + "items": {
  45 + "title": "Client property",
  46 + "type": "object",
  47 + "properties": {
  48 + "key": {
  49 + "title": "Key",
  50 + "type": "string"
  51 + },
  52 + "value": {
  53 + "title": "Value",
  54 + "type": "string"
  55 + }
  56 + }
  57 + }
  58 + }
  59 + },
  60 + "required": [
  61 + "host",
  62 + "port"
  63 + ]
  64 + },
  65 + "form": [
  66 + "host",
  67 + "port",
  68 + "virtualHost",
  69 + "userName",
  70 + {
  71 + "key": "password",
  72 + "type": "password"
  73 + },
  74 + "automaticRecoveryEnabled",
  75 + "connectionTimeout",
  76 + "handshakeTimeout",
  77 + "clientProperties"
  78 + ]
  79 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rabbitmq;
  17 +
  18 +import com.rabbitmq.client.*;
  19 +
  20 +import java.io.IOException;
  21 +
  22 +/**
  23 + * @author Andrew Shvayka
  24 + */
  25 +public class DemoClient {
  26 +
  27 + private static final String HOST = "localhost";
  28 + private static final String USERNAME = "guest";
  29 + private static final String PASSWORD = "guest";
  30 + private static final String QUEUE_NAME = "queue";
  31 +
  32 +
  33 + public static void main(String[] argv) throws Exception {
  34 +
  35 + ConnectionFactory factory = new ConnectionFactory();
  36 + factory.setHost(HOST);
  37 + factory.setUsername(USERNAME);
  38 + factory.setPassword(PASSWORD);
  39 +
  40 + Connection connection = factory.newConnection();
  41 + Channel channel = connection.createChannel();
  42 +
  43 + channel.queueDeclare(QUEUE_NAME, false, false, false, null);
  44 + System.out.println(" [*] Waiting for messages.");
  45 + Consumer consumer = new DefaultConsumer(channel) {
  46 + @Override
  47 + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
  48 + throws IOException {
  49 + String message = new String(body, "UTF-8");
  50 + System.out.println(" [x] Received '" + message + "'");
  51 + }
  52 + };
  53 + channel.basicConsume(QUEUE_NAME, true, consumer);
  54 +
  55 + }
  56 +}
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!--
  3 +
  4 + Copyright © 2016 The Thingsboard Authors
  5 +
  6 + Licensed under the Apache License, Version 2.0 (the "License");
  7 + you may not use this file except in compliance with the License.
  8 + You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +
  18 +-->
  19 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  20 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  21 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  22 + <modelVersion>4.0.0</modelVersion>
  23 + <parent>
  24 + <groupId>org.thingsboard.server</groupId>
  25 + <version>0.0.1-SNAPSHOT</version>
  26 + <artifactId>extensions</artifactId>
  27 + </parent>
  28 + <groupId>org.thingsboard.server.extensions</groupId>
  29 + <artifactId>extension-rest-api-call</artifactId>
  30 + <packaging>jar</packaging>
  31 +
  32 + <name>Thingsboard Server REST API Call Extension</name>
  33 + <url>http://thingsboard.org</url>
  34 +
  35 + <properties>
  36 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  37 + <main.dir>${basedir}/../..</main.dir>
  38 + </properties>
  39 +
  40 + <dependencies>
  41 + <dependency>
  42 + <groupId>ch.qos.logback</groupId>
  43 + <artifactId>logback-core</artifactId>
  44 + <scope>provided</scope>
  45 + </dependency>
  46 + <dependency>
  47 + <groupId>ch.qos.logback</groupId>
  48 + <artifactId>logback-classic</artifactId>
  49 + <scope>provided</scope>
  50 + </dependency>
  51 + <dependency>
  52 + <groupId>org.thingsboard.server</groupId>
  53 + <artifactId>extensions-api</artifactId>
  54 + <scope>provided</scope>
  55 + </dependency>
  56 + <dependency>
  57 + <groupId>org.springframework</groupId>
  58 + <artifactId>spring-web</artifactId>
  59 + <scope>provided</scope>
  60 + </dependency>
  61 + <dependency>
  62 + <groupId>org.thingsboard.server</groupId>
  63 + <artifactId>extensions-core</artifactId>
  64 + <scope>provided</scope>
  65 + </dependency>
  66 + <dependency>
  67 + <groupId>org.apache.velocity</groupId>
  68 + <artifactId>velocity</artifactId>
  69 + <scope>provided</scope>
  70 + </dependency>
  71 + <dependency>
  72 + <groupId>org.apache.velocity</groupId>
  73 + <artifactId>velocity-tools</artifactId>
  74 + <scope>provided</scope>
  75 + </dependency>
  76 + </dependencies>
  77 + <build>
  78 + <plugins>
  79 + <plugin>
  80 + <artifactId>maven-assembly-plugin</artifactId>
  81 + <configuration>
  82 + <descriptors>
  83 + <descriptor>src/assembly/extension.xml</descriptor>
  84 + </descriptors>
  85 + </configuration>
  86 + <executions>
  87 + <execution>
  88 + <id>make-assembly</id>
  89 + <phase>package</phase>
  90 + <goals>
  91 + <goal>single</goal>
  92 + </goals>
  93 + </execution>
  94 + </executions>
  95 + </plugin>
  96 + </plugins>
  97 + </build>
  98 +</project>
  1 +<!--
  2 +
  3 + Copyright © 2016 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
  19 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  20 + xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
  21 + <id>extension</id>
  22 + <formats>
  23 + <format>jar</format>
  24 + </formats>
  25 + <includeBaseDirectory>false</includeBaseDirectory>
  26 + <dependencySets>
  27 + <dependencySet>
  28 + <outputDirectory>/</outputDirectory>
  29 + <useProjectArtifact>true</useProjectArtifact>
  30 + <unpack>true</unpack>
  31 + <scope>runtime</scope>
  32 + </dependencySet>
  33 + </dependencySets>
  34 +</assembly>
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rest.action;
  17 +
  18 +import org.thingsboard.server.common.data.id.CustomerId;
  19 +import org.thingsboard.server.common.data.id.DeviceId;
  20 +import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg;
  22 +
  23 +public class RestApiCallActionMsg extends AbstractRuleToPluginMsg<RestApiCallActionPayload> {
  24 +
  25 + public RestApiCallActionMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, RestApiCallActionPayload payload) {
  26 + super(tenantId, customerId, deviceId, payload);
  27 + }
  28 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rest.action;
  17 +
  18 +import lombok.Builder;
  19 +import lombok.Data;
  20 +import org.springframework.http.HttpMethod;
  21 +import org.springframework.http.HttpStatus;
  22 +import org.thingsboard.server.common.msg.session.MsgType;
  23 +
  24 +import java.io.Serializable;
  25 +
  26 +@Data
  27 +@Builder
  28 +public class RestApiCallActionPayload implements Serializable {
  29 + private final String actionPath;
  30 + private final String msgBody;
  31 + private final HttpMethod httpMethod;
  32 + private final HttpStatus expectedResultCode;
  33 + private final boolean sync;
  34 +
  35 + private final Integer requestId;
  36 + private final MsgType msgType;
  37 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rest.action;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.apache.velocity.Template;
  20 +import org.apache.velocity.VelocityContext;
  21 +import org.apache.velocity.runtime.parser.ParseException;
  22 +import org.springframework.http.HttpMethod;
  23 +import org.springframework.http.HttpStatus;
  24 +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  25 +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
  26 +import org.thingsboard.server.common.msg.session.ToDeviceMsg;
  27 +import org.thingsboard.server.extensions.api.component.Action;
  28 +import org.thingsboard.server.extensions.api.plugins.PluginAction;
  29 +import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
  30 +import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg;
  31 +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
  32 +import org.thingsboard.server.extensions.api.rules.RuleContext;
  33 +import org.thingsboard.server.extensions.api.rules.RuleProcessingMetaData;
  34 +import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction;
  35 +import org.thingsboard.server.extensions.core.utils.VelocityUtils;
  36 +
  37 +import java.util.Optional;
  38 +
  39 +@Action(name = "REST API Call Plugin Action",
  40 + descriptor = "RestApiCallActionDescriptor.json", configuration = RestApiCallPluginActionConfiguration.class)
  41 +@Slf4j
  42 +public class RestApiCallPluginAction extends AbstractTemplatePluginAction<RestApiCallPluginActionConfiguration> {
  43 +
  44 + @Override
  45 + protected Optional<RuleToPluginMsg<?>> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) {
  46 + RestApiCallActionPayload.RestApiCallActionPayloadBuilder builder = RestApiCallActionPayload.builder();
  47 + builder.msgType(payload.getMsgType());
  48 + builder.requestId(payload.getRequestId());
  49 + builder.sync(configuration.isSync());
  50 + builder.actionPath(configuration.getActionPath());
  51 + builder.httpMethod(HttpMethod.valueOf(configuration.getRequestMethod()[0]));
  52 + builder.expectedResultCode(HttpStatus.valueOf(configuration.getExpectedResultCode()));
  53 + builder.msgBody(getMsgBody(ctx, msg));
  54 + return Optional.of(new RestApiCallActionMsg(msg.getTenantId(),
  55 + msg.getCustomerId(),
  56 + msg.getDeviceId(),
  57 + builder.build()));
  58 + }
  59 +
  60 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rest.action;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.extensions.core.action.template.TemplateActionConfiguration;
  20 +
  21 +@Data
  22 +public class RestApiCallPluginActionConfiguration implements TemplateActionConfiguration {
  23 + private boolean sync;
  24 + private String template;
  25 + private String actionPath;
  26 + private int expectedResultCode;
  27 + private String[] requestMethod;
  28 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rest.plugin;
  17 +
  18 +import lombok.RequiredArgsConstructor;
  19 +import org.springframework.http.HttpEntity;
  20 +import org.springframework.http.HttpHeaders;
  21 +import org.springframework.http.ResponseEntity;
  22 +import org.springframework.web.client.RestClientException;
  23 +import org.springframework.web.client.RestTemplate;
  24 +import org.thingsboard.server.common.data.id.RuleId;
  25 +import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
  27 +import org.thingsboard.server.extensions.api.plugins.PluginContext;
  28 +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler;
  29 +import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg;
  30 +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
  31 +import org.thingsboard.server.extensions.api.rules.RuleException;
  32 +import org.thingsboard.server.extensions.rest.action.RestApiCallActionMsg;
  33 +import org.thingsboard.server.extensions.rest.action.RestApiCallActionPayload;
  34 +
  35 +@RequiredArgsConstructor
  36 +public class RestApiCallMsgHandler implements RuleMsgHandler {
  37 +
  38 + private final String baseUrl;
  39 + private final HttpHeaders headers;
  40 +
  41 + @Override
  42 + public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) throws RuleException {
  43 + if (!(msg instanceof RestApiCallActionMsg)) {
  44 + throw new RuleException("Unsupported message type " + msg.getClass().getName() + "!");
  45 + }
  46 + RestApiCallActionPayload payload = ((RestApiCallActionMsg)msg).getPayload();
  47 + try {
  48 + ResponseEntity<String> exchangeResponse = new RestTemplate().exchange(
  49 + baseUrl + payload.getActionPath(),
  50 + payload.getHttpMethod(),
  51 + new HttpEntity<>(payload.getMsgBody(), headers),
  52 + String.class);
  53 + if (exchangeResponse.getStatusCode().equals(payload.getExpectedResultCode()) && payload.isSync()) {
  54 + ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId,
  55 + BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId())));
  56 + } else if(!exchangeResponse.getStatusCode().equals(payload.getExpectedResultCode())) {
  57 + throw new RuntimeException("Response Status Code '"
  58 + + exchangeResponse.getStatusCode()
  59 + + "' doesn't equals to Expected Status Code '"
  60 + + payload.getExpectedResultCode() + "'");
  61 + }
  62 +
  63 + } catch (RestClientException e) {
  64 + throw new RuleException(e.getMessage(), e);
  65 + }
  66 + }
  67 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rest.plugin;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.http.HttpHeaders;
  20 +import org.thingsboard.server.extensions.api.component.Plugin;
  21 +import org.thingsboard.server.extensions.api.plugins.AbstractPlugin;
  22 +import org.thingsboard.server.extensions.api.plugins.PluginContext;
  23 +import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler;
  24 +import org.thingsboard.server.extensions.rest.action.RestApiCallPluginAction;
  25 +
  26 +import java.util.Base64;
  27 +
  28 +@Plugin(name = "REST API Call Plugin", actions = {RestApiCallPluginAction.class},
  29 + descriptor = "RestApiCallPluginDescriptor.json", configuration = RestApiCallPluginConfiguration.class)
  30 +@Slf4j
  31 +public class RestApiCallPlugin extends AbstractPlugin<RestApiCallPluginConfiguration> {
  32 +
  33 + private static final String BASIC_AUTH_METHOD = "BASIC_AUTH";
  34 + private static final String AUTHORIZATION_HEADER_NAME = "Authorization";
  35 + private static final String AUTHORIZATION_HEADER_FORMAT = "Basic %s";
  36 + private static final String CREDENTIALS_TEMPLATE = "%s:%s";
  37 + private static final String BASE_URL_TEMPLATE = "http://%s:%d%s";
  38 + private RestApiCallMsgHandler handler;
  39 + private String baseUrl;
  40 + private HttpHeaders headers = new HttpHeaders();
  41 +
  42 + @Override
  43 + public void init(RestApiCallPluginConfiguration configuration) {
  44 + this.baseUrl = String.format(
  45 + BASE_URL_TEMPLATE,
  46 + configuration.getHost(),
  47 + configuration.getPort(),
  48 + configuration.getBasePath());
  49 +
  50 + if (configuration.getAuthMethod()[0].equals(BASIC_AUTH_METHOD)) {
  51 + String userName = configuration.getUserName();
  52 + String password = configuration.getPassword();
  53 + String credentials = String.format(CREDENTIALS_TEMPLATE, userName, password);
  54 + byte[] token = Base64.getEncoder().encode(credentials.getBytes());
  55 + this.headers.add(AUTHORIZATION_HEADER_NAME, String.format(AUTHORIZATION_HEADER_FORMAT, new String(token)));
  56 + }
  57 +
  58 + init();
  59 + }
  60 +
  61 + private void init() {
  62 + this.handler = new RestApiCallMsgHandler(baseUrl, headers);
  63 + }
  64 +
  65 + @Override
  66 + protected RuleMsgHandler getRuleMsgHandler() {
  67 + return handler;
  68 + }
  69 +
  70 + @Override
  71 + public void resume(PluginContext ctx) {
  72 + init();
  73 + }
  74 +
  75 + @Override
  76 + public void suspend(PluginContext ctx) {
  77 + log.debug("Suspend method was called, but no impl provided!");
  78 + }
  79 +
  80 + @Override
  81 + public void stop(PluginContext ctx) {
  82 + log.debug("Stop method was called, but no impl provided!");
  83 + }
  84 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rest.plugin;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class RestApiCallPluginConfiguration {
  22 + private String host;
  23 + private int port;
  24 + private String basePath;
  25 +
  26 + private String[] authMethod;
  27 +
  28 + private String userName;
  29 + private String password;
  30 +}
  1 +{
  2 + "schema": {
  3 + "title": "REST API Call Action Configuration",
  4 + "type": "object",
  5 + "properties": {
  6 + "sync": {
  7 + "title": "Requires delivery confirmation",
  8 + "type": "boolean"
  9 + },
  10 + "template": {
  11 + "title": "Body Template",
  12 + "type": "string"
  13 + },
  14 + "actionPath": {
  15 + "title": "Action Path",
  16 + "type": "string",
  17 + "default": "/"
  18 + },
  19 + "requestMethod": {
  20 + "title": "Request method",
  21 + "type": "array",
  22 + "minItems" : 1,
  23 + "items": [
  24 + {
  25 + "value": "POST",
  26 + "label": "POST"
  27 + },
  28 + {
  29 + "value": "PUT",
  30 + "label": "PUT"
  31 + }
  32 + ],
  33 + "uniqueItems": true
  34 + },
  35 + "expectedResultCode": {
  36 + "title": "Expected Result Code",
  37 + "type": "integer"
  38 + }
  39 + },
  40 + "required": [
  41 + "sync",
  42 + "template",
  43 + "actionPath",
  44 + "expectedResultCode",
  45 + "requestMethod"
  46 + ]
  47 + },
  48 + "form": [
  49 + "sync",
  50 + {
  51 + "key": "template",
  52 + "type": "textarea",
  53 + "rows": 5
  54 + },
  55 + "actionPath",
  56 + {
  57 + "key": "requestMethod",
  58 + "type": "rc-select",
  59 + "multiple": false
  60 + },
  61 + "expectedResultCode"
  62 + ]
  63 +}
  1 +{
  2 + "schema": {
  3 + "title": "REST API Call Plugin Configuration",
  4 + "type": "object",
  5 + "properties": {
  6 + "host": {
  7 + "title": "Host",
  8 + "type": "string"
  9 + },
  10 + "port": {
  11 + "title": "Port",
  12 + "type": "integer",
  13 + "default": 8080,
  14 + "minimum": 0,
  15 + "maximum": 65536
  16 + },
  17 + "basePath": {
  18 + "title": "Base Path",
  19 + "type": "string",
  20 + "default": "/"
  21 + },
  22 + "authMethod": {
  23 + "title": "Authentication method",
  24 + "type": "array",
  25 + "minItems" : 1,
  26 + "items": [
  27 + {
  28 + "value": "NO_AUTH",
  29 + "label": "No authentication"
  30 + },
  31 + {
  32 + "value": "BASIC_AUTH",
  33 + "label": "Basic authentication"
  34 + }
  35 + ],
  36 + "uniqueItems": true
  37 + },
  38 + "userName": {
  39 + "title": "Username",
  40 + "type": "string"
  41 + },
  42 + "password": {
  43 + "title": "Password",
  44 + "type": "string"
  45 + }
  46 + },
  47 + "required": [
  48 + "host",
  49 + "port",
  50 + "basePath",
  51 + "authMethod"
  52 + ]
  53 + },
  54 + "form": [
  55 + "host",
  56 + "port",
  57 + "basePath",
  58 + {
  59 + "key": "authMethod",
  60 + "type": "rc-select",
  61 + "multiple": false
  62 + },
  63 + "userName",
  64 + {
  65 + "key": "password",
  66 + "type": "password"
  67 + }
  68 + ]
  69 +}
  1 +/**
  2 + * Copyright © 2016 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.extensions.rest;
  17 +
  18 +import com.sun.net.httpserver.*;
  19 +
  20 +import java.io.BufferedReader;
  21 +import java.io.IOException;
  22 +import java.io.InputStreamReader;
  23 +import java.io.OutputStream;
  24 +import java.net.InetSocketAddress;
  25 +import java.util.stream.Collectors;
  26 +
  27 +public class RestApiCallDemoClient {
  28 +
  29 + private static final String DEMO_REST_BASIC_AUTH = "/demo-rest-basic-auth";
  30 + private static final String DEMO_REST_NO_AUTH = "/demo-rest-no-auth";
  31 + private static final String USERNAME = "demo";
  32 + private static final String PASSWORD = "demo";
  33 + private static final int HTTP_SERVER_PORT = 8888;
  34 +
  35 + public static void main(String[] args) throws IOException {
  36 + HttpServer server = HttpServer.create(new InetSocketAddress(HTTP_SERVER_PORT), 0);
  37 +
  38 + HttpContext secureContext = server.createContext(DEMO_REST_BASIC_AUTH, new RestDemoHandler());
  39 + secureContext.setAuthenticator(new BasicAuthenticator("demo-auth") {
  40 + @Override
  41 + public boolean checkCredentials(String user, String pwd) {
  42 + return user.equals(USERNAME) && pwd.equals(PASSWORD);
  43 + }
  44 + });
  45 +
  46 + server.createContext(DEMO_REST_NO_AUTH, new RestDemoHandler());
  47 + server.setExecutor(null);
  48 + System.out.println("[*] Waiting for messages.");
  49 + server.start();
  50 + }
  51 +
  52 + private static class RestDemoHandler implements HttpHandler {
  53 + @Override
  54 + public void handle(HttpExchange exchange) throws IOException {
  55 + String requestBody;
  56 + try (BufferedReader br = new BufferedReader(new InputStreamReader(exchange.getRequestBody(), "utf-8"))) {
  57 + requestBody = br.lines().collect(Collectors.joining(System.lineSeparator()));
  58 + }
  59 + System.out.println("[x] Received body: \n" + requestBody);
  60 +
  61 + String response = "Hello from demo client!";
  62 + exchange.sendResponseHeaders(200, response.length());
  63 + System.out.println("[x] Sending response: \n" + response);
  64 +
  65 + OutputStream os = exchange.getResponseBody();
  66 + os.write(response.getBytes());
  67 + os.close();
  68 + }
  69 + }
  70 +}
  1 +<!--
  2 +
  3 + Copyright © 2016 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  19 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  20 + <modelVersion>4.0.0</modelVersion>
  21 + <parent>
  22 + <groupId>org.thingsboard</groupId>
  23 + <version>0.0.1-SNAPSHOT</version>
  24 + <artifactId>server</artifactId>
  25 + </parent>
  26 + <groupId>org.thingsboard.server</groupId>
  27 + <artifactId>extensions</artifactId>
  28 + <packaging>pom</packaging>
  29 +
  30 + <name>Thingsboard Extensions</name>
  31 + <url>http://thingsboard.org</url>
  32 +
  33 + <properties>
  34 + <main.dir>${basedir}/..</main.dir>
  35 + </properties>
  36 +
  37 + <modules>
  38 + <module>extension-rabbitmq</module>
  39 + <module>extension-rest-api-call</module>
  40 + <module>extension-kafka</module>
  41 + </modules>
  42 +
  43 +</project>