diff --git a/application/pom.xml b/application/pom.xml index 309f7ded5b..d99f60773e 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -60,10 +60,6 @@ org.thingsboard.common.transport transport-api - - - - org.thingsboard.common.transport mqtt @@ -72,6 +68,10 @@ org.thingsboard.common.transport http + + org.thingsboard.common.transport + coap + org.thingsboard dao diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 6238a3e232..90d15003bc 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -455,7 +455,6 @@ transport: enabled: "${MQTT_ENABLED:true}" bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}" bind_port: "${MQTT_BIND_PORT:1883}" - adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}" timeout: "${MQTT_TIMEOUT:10000}" netty: leak_detector_level: "${NETTY_LEASK_DETECTOR_LVL:DISABLED}" @@ -482,5 +481,4 @@ transport: enabled: "${COAP_ENABLED:true}" bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" bind_port: "${COAP_BIND_PORT:5683}" - adaptor: "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}" timeout: "${COAP_TIMEOUT:10000}" diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml new file mode 100644 index 0000000000..d68a6db39f --- /dev/null +++ b/common/transport/coap/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + org.thingsboard.common + 2.2.0-SNAPSHOT + transport + + org.thingsboard.common.transport + coap + jar + + Thingsboard CoAP Transport Common + https://thingsboard.io + + + UTF-8 + ${basedir}/../../.. + + + + + org.thingsboard.common.transport + transport-api + + + org.eclipse.californium + californium-core + + + org.springframework + spring-context-support + + + org.springframework + spring-context + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + org.springframework.boot + spring-boot-starter-test + test + + + junit + junit + test + + + org.mockito + mockito-all + test + + + + diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java new file mode 100644 index 0000000000..13662cf1be --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.TransportContext; +import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; + +/** + * Created by ashvayka on 18.10.18. + */ +@Slf4j +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.coap.enabled}'=='true')") +@Component +public class CoapTransportContext extends TransportContext { + + @Getter + @Value("${transport.coap.bind_address}") + private String host; + + @Getter + @Value("${transport.coap.bind_port}") + private Integer port; + + @Getter + @Value("${transport.coap.timeout}") + private Long timeout; + + @Getter + @Autowired + private CoapTransportAdaptor adaptor; + +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java new file mode 100644 index 0000000000..0e2a794a45 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -0,0 +1,445 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapResource; +import org.eclipse.californium.core.coap.CoAP.ResponseCode; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.core.network.Exchange; +import org.eclipse.californium.core.network.ExchangeObserver; +import org.eclipse.californium.core.server.resources.CoapExchange; +import org.eclipse.californium.core.server.resources.Resource; +import org.springframework.util.ReflectionUtils; +import org.thingsboard.server.common.data.security.DeviceTokenCredentials; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.TransportContext; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +@Slf4j +public class CoapTransportResource extends CoapResource { + // coap://localhost:port/api/v1/DEVICE_TOKEN/[attributes|telemetry|rpc[/requestId]] + private static final int ACCESS_TOKEN_POSITION = 3; + private static final int FEATURE_TYPE_POSITION = 4; + private static final int REQUEST_ID_POSITION = 5; + + private final CoapTransportContext transportContext; + private final TransportService transportService; + private final Field observerField; + private final long timeout; + private final ConcurrentMap tokenToSessionIdMap = new ConcurrentHashMap<>(); + + public CoapTransportResource(CoapTransportContext context, String name) { + super(name); + this.transportContext = context; + this.transportService = context.getTransportService(); + this.timeout = context.getTimeout(); + // This is important to turn off existing observable logic in + // CoapResource. We will have our own observe monitoring due to 1:1 + // observe relationship. + this.setObservable(false); + observerField = ReflectionUtils.findField(Exchange.class, "observer"); + observerField.setAccessible(true); + } + + @Override + public void handleGET(CoapExchange exchange) { + if (transportContext.getQuotaService().isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) { + log.warn("CoAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort()); + exchange.respond(ResponseCode.BAD_REQUEST); + return; + } + + Optional featureType = getFeatureType(exchange.advanced().getRequest()); + if (!featureType.isPresent()) { + log.trace("Missing feature type parameter"); + exchange.respond(ResponseCode.BAD_REQUEST); + } else if (featureType.get() == FeatureType.TELEMETRY) { + log.trace("Can't fetch/subscribe to timeseries updates"); + exchange.respond(ResponseCode.BAD_REQUEST); + } else if (exchange.getRequestOptions().hasObserve()) { + processExchangeGetRequest(exchange, featureType.get()); + } else if (featureType.get() == FeatureType.ATTRIBUTES) { + processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); + } else { + log.trace("Invalid feature type parameter"); + exchange.respond(ResponseCode.BAD_REQUEST); + } + } + + private void processExchangeGetRequest(CoapExchange exchange, FeatureType featureType) { + boolean unsubscribe = exchange.getRequestOptions().getObserve() == 1; + SessionMsgType sessionMsgType; + if (featureType == FeatureType.RPC) { + sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; + } else { + sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; + } + processRequest(exchange, sessionMsgType); + } + + @Override + public void handlePOST(CoapExchange exchange) { + if (transportContext.getQuotaService().isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) { + log.warn("CoAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort()); + exchange.respond(ResponseCode.BAD_REQUEST); + return; + } + + Optional featureType = getFeatureType(exchange.advanced().getRequest()); + if (!featureType.isPresent()) { + log.trace("Missing feature type parameter"); + exchange.respond(ResponseCode.BAD_REQUEST); + } else { + switch (featureType.get()) { + case ATTRIBUTES: + processRequest(exchange, SessionMsgType.POST_ATTRIBUTES_REQUEST); + break; + case TELEMETRY: + processRequest(exchange, SessionMsgType.POST_TELEMETRY_REQUEST); + break; + case RPC: + Optional requestId = getRequestId(exchange.advanced().getRequest()); + if (requestId.isPresent()) { + processRequest(exchange, SessionMsgType.TO_DEVICE_RPC_RESPONSE); + } else { + processRequest(exchange, SessionMsgType.TO_SERVER_RPC_REQUEST); + } + break; + } + } + } + + private void processRequest(CoapExchange exchange, SessionMsgType type) { + log.trace("Processing {}", exchange.advanced().getRequest()); + exchange.accept(); + Exchange advanced = exchange.advanced(); + Request request = advanced.getRequest(); + + Optional credentials = decodeCredentials(request); + if (!credentials.isPresent()) { + exchange.respond(ResponseCode.BAD_REQUEST); + return; + } + + transportService.process(TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), + new DeviceAuthCallback(transportContext, exchange, sessionInfo -> { + UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); + try { + switch (type) { + case POST_ATTRIBUTES_REQUEST: + transportService.process(sessionInfo, + transportContext.getAdaptor().convertToPostAttributes(sessionId, request), + new CoapOkCallback(exchange)); + break; + case POST_TELEMETRY_REQUEST: + transportService.process(sessionInfo, + transportContext.getAdaptor().convertToPostTelemetry(sessionId, request), + new CoapOkCallback(exchange)); + break; + case SUBSCRIBE_ATTRIBUTES_REQUEST: + advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced), + registerAsyncCoapSession(exchange, request, sessionInfo, sessionId))); + transportService.process(sessionInfo, + TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), + new CoapNoOpCallback(exchange)); + break; + case UNSUBSCRIBE_ATTRIBUTES_REQUEST: + TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(request); + if (attrSession != null) { + transportService.process(attrSession, + TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), + new CoapOkCallback(exchange)); + closeAndDeregister(sessionInfo); + } + break; + case SUBSCRIBE_RPC_COMMANDS_REQUEST: + advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced), + registerAsyncCoapSession(exchange, request, sessionInfo, sessionId))); + transportService.process(sessionInfo, + TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), + new CoapNoOpCallback(exchange)); + break; + case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: + TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(request); + if (rpcSession != null) { + transportService.process(rpcSession, + TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), + new CoapOkCallback(exchange)); + transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); + transportService.deregisterSession(rpcSession); + } + break; + case TO_DEVICE_RPC_RESPONSE: + transportService.process(sessionInfo, + transportContext.getAdaptor().convertToDeviceRpcResponse(sessionId, request), + new CoapOkCallback(exchange)); + break; + case TO_SERVER_RPC_REQUEST: + transportService.process(sessionInfo, + transportContext.getAdaptor().convertToServerRpcRequest(sessionId, request), + new CoapNoOpCallback(exchange)); + break; + case GET_ATTRIBUTES_REQUEST: + transportService.registerSyncSession(sessionInfo, new CoapSessionListener(sessionId, exchange), transportContext.getTimeout()); + transportService.process(sessionInfo, + transportContext.getAdaptor().convertToGetAttributes(sessionId, request), + new CoapNoOpCallback(exchange)); + break; + } + } catch (AdaptorException e) { + log.trace("[{}] Failed to decode message: ", sessionId, e); + exchange.respond(ResponseCode.BAD_REQUEST); + } catch (IllegalAccessException e) { + log.trace("[{}] Failed to process message: ", sessionId, e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + })); + } + + private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(Request request) { + String token = request.getSource().getHostAddress() + ":" + request.getSourcePort() + ":" + request.getTokenString(); + return tokenToSessionIdMap.remove(token); + } + + private String registerAsyncCoapSession(CoapExchange exchange, Request request, TransportProtos.SessionInfoProto sessionInfo, UUID sessionId) { + String token = request.getSource().getHostAddress() + ":" + request.getSourcePort() + ":" + request.getTokenString(); + tokenToSessionIdMap.putIfAbsent(token, sessionInfo); + CoapSessionListener attrListener = new CoapSessionListener(sessionId, exchange); + transportService.registerAsyncSession(sessionInfo, attrListener); + transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); + return token; + } + + private static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) { + return TransportProtos.SessionEventMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) + .setEvent(event).build(); + } + + private Optional decodeCredentials(Request request) { + List uriPath = request.getOptions().getUriPath(); + if (uriPath.size() >= ACCESS_TOKEN_POSITION) { + return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1))); + } else { + return Optional.empty(); + } + } + + private Optional getFeatureType(Request request) { + List uriPath = request.getOptions().getUriPath(); + try { + if (uriPath.size() >= FEATURE_TYPE_POSITION) { + return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); + } + } catch (RuntimeException e) { + log.warn("Failed to decode feature type: {}", uriPath); + } + return Optional.empty(); + } + + public static Optional getRequestId(Request request) { + List uriPath = request.getOptions().getUriPath(); + try { + if (uriPath.size() >= REQUEST_ID_POSITION) { + return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1))); + } + } catch (RuntimeException e) { + log.warn("Failed to decode feature type: {}", uriPath); + } + return Optional.empty(); + } + + @Override + public Resource getChild(String name) { + return this; + } + + private static class DeviceAuthCallback implements TransportServiceCallback { + private final TransportContext transportContext; + private final CoapExchange exchange; + private final Consumer onSuccess; + + DeviceAuthCallback(TransportContext transportContext, CoapExchange exchange, Consumer onSuccess) { + this.transportContext = transportContext; + this.exchange = exchange; + this.onSuccess = onSuccess; + } + + @Override + public void onSuccess(TransportProtos.ValidateDeviceCredentialsResponseMsg msg) { + if (msg.hasDeviceInfo()) { + UUID sessionId = UUID.randomUUID(); + TransportProtos.DeviceInfoProto deviceInfoProto = msg.getDeviceInfo(); + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setNodeId(transportContext.getNodeId()) + .setTenantIdMSB(deviceInfoProto.getTenantIdMSB()) + .setTenantIdLSB(deviceInfoProto.getTenantIdLSB()) + .setDeviceIdMSB(deviceInfoProto.getDeviceIdMSB()) + .setDeviceIdLSB(deviceInfoProto.getDeviceIdLSB()) + .setSessionIdMSB(sessionId.getMostSignificantBits()) + .setSessionIdLSB(sessionId.getLeastSignificantBits()) + .build(); + onSuccess.accept(sessionInfo); + } else { + exchange.respond(ResponseCode.UNAUTHORIZED); + } + } + + @Override + public void onError(Throwable e) { + log.warn("Failed to process request", e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + private static class CoapOkCallback implements TransportServiceCallback { + private final CoapExchange exchange; + + CoapOkCallback(CoapExchange exchange) { + this.exchange = exchange; + } + + @Override + public void onSuccess(Void msg) { + exchange.respond(ResponseCode.VALID); + } + + @Override + public void onError(Throwable e) { + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + private static class CoapNoOpCallback implements TransportServiceCallback { + private final CoapExchange exchange; + + CoapNoOpCallback(CoapExchange exchange) { + this.exchange = exchange; + } + + @Override + public void onSuccess(Void msg) { + + } + + @Override + public void onError(Throwable e) { + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + public class CoapSessionListener implements SessionMsgListener { + + private final CoapExchange exchange; + private final AtomicInteger seqNumber = new AtomicInteger(2); + + CoapSessionListener(UUID sessionId, CoapExchange exchange) { + this.exchange = exchange; + } + + @Override + public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg msg) { + try { + exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); + } catch (AdaptorException e) { + log.trace("Failed to reply due to error", e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + @Override + public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg) { + try { + exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); + } catch (AdaptorException e) { + log.trace("Failed to reply due to error", e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + @Override + public void onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { + exchange.respond(ResponseCode.SERVICE_UNAVAILABLE); + } + + @Override + public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg msg) { + try { + exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); + } catch (AdaptorException e) { + log.trace("Failed to reply due to error", e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + @Override + public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg msg) { + try { + exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); + } catch (AdaptorException e) { + log.trace("Failed to reply due to error", e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + public int getNextSeqNumber() { + return seqNumber.getAndIncrement(); + } + } + + public class CoapExchangeObserverProxy implements ExchangeObserver { + + private final ExchangeObserver proxy; + private final String token; + + CoapExchangeObserverProxy(ExchangeObserver proxy, String token) { + super(); + this.proxy = proxy; + this.token = token; + } + + @Override + public void completed(Exchange exchange) { + proxy.completed(exchange); + TransportProtos.SessionInfoProto session = tokenToSessionIdMap.remove(token); + if (session != null) { + closeAndDeregister(session); + } + } + } + + private void closeAndDeregister(TransportProtos.SessionInfoProto session) { + transportService.process(session, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); + transportService.deregisterSession(session); + } + +} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java similarity index 72% rename from transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java rename to common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java index ece0bb7e10..278ca6d8c5 100644 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java @@ -21,6 +21,7 @@ import org.eclipse.californium.core.CoapServer; import org.eclipse.californium.core.network.CoapEndpoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; @@ -37,49 +38,26 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; @Service("CoapTransportService") -@ConditionalOnProperty(prefix = "transport.coap", value = "enabled", havingValue = "true") +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.coap.enabled}'=='true')") @Slf4j public class CoapTransportService { private static final String V1 = "v1"; private static final String API = "api"; - private CoapServer server; - - @Autowired(required = false) - private ApplicationContext appContext; - - @Autowired(required = false) - private SessionMsgProcessor processor; - - @Autowired(required = false) - private DeviceAuthService authService; + @Autowired + private CoapTransportContext coapTransportContext; - @Autowired(required = false) - private HostRequestsQuotaService quotaService; - - - @Value("${coap.bind_address}") - private String host; - @Value("${coap.bind_port}") - private Integer port; - @Value("${coap.adaptor}") - private String adaptorName; - @Value("${coap.timeout}") - private Long timeout; - - private CoapTransportAdaptor adaptor; + private CoapServer server; @PostConstruct public void init() throws UnknownHostException { log.info("Starting CoAP transport..."); - log.info("Lookup CoAP transport adaptor {}", adaptorName); - this.adaptor = (CoapTransportAdaptor) appContext.getBean(adaptorName); log.info("Starting CoAP transport server"); this.server = new CoapServer(); createResources(); - InetAddress addr = InetAddress.getByName(host); - InetSocketAddress sockAddr = new InetSocketAddress(addr, port); + InetAddress addr = InetAddress.getByName(coapTransportContext.getHost()); + InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort()); server.addEndpoint(new CoapEndpoint(sockAddr)); server.start(); log.info("CoAP transport started!"); @@ -87,7 +65,7 @@ public class CoapTransportService { private void createResources() { CoapResource api = new CoapResource(API); - api.add(new CoapTransportResource(processor, authService, adaptor, V1, timeout, quotaService)); + api.add(new CoapTransportResource(coapTransportContext, V1)); server.add(api); } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java new file mode 100644 index 0000000000..9e48cd3030 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.adaptors; + +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.coap.CoapTransportResource; + +import java.util.UUID; +import java.util.Optional; + +public interface CoapTransportAdaptor { + + TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound) throws AdaptorException; + + TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound) throws AdaptorException; + + TransportProtos.GetAttributeRequestMsg convertToGetAttributes(UUID sessionId, Request inbound) throws AdaptorException; + + TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException; + + TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(UUID sessionId, Request inbound) throws AdaptorException; + + Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException; + + Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException; + + Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException; + + Response convertToPublish(CoapTransportResource.CoapSessionListener coapSessionListener, TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException; + +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java new file mode 100644 index 0000000000..f5bfb49c08 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java @@ -0,0 +1,155 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap.adaptors; + +import java.util.*; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.msg.kv.AttributesKVMsg; +import org.thingsboard.server.common.msg.session.SessionContext; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.coap.CoapTransportResource; + +@Component("JsonCoapAdaptor") +@Slf4j +public class JsonCoapAdaptor implements CoapTransportAdaptor { + + @Override + public TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound) throws AdaptorException { + String payload = validatePayload(sessionId, inbound); + try { + return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload)); + } catch (IllegalStateException | JsonSyntaxException ex) { + throw new AdaptorException(ex); + } + } + + @Override + public TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound) throws AdaptorException { + String payload = validatePayload(sessionId, inbound); + try { + return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload)); + } catch (IllegalStateException | JsonSyntaxException ex) { + throw new AdaptorException(ex); + } + } + + @Override + public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(UUID sessionId, Request inbound) throws AdaptorException { + List queryElements = inbound.getOptions().getUriQuery(); + TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder(); + if (queryElements != null && queryElements.size() > 0) { + Set clientKeys = toKeys(queryElements, "clientKeys"); + Set sharedKeys = toKeys(queryElements, "sharedKeys"); + if (clientKeys != null) { + result.addAllClientAttributeNames(clientKeys); + } + if (sharedKeys != null) { + result.addAllSharedAttributeNames(sharedKeys); + } + } + return result.build(); + } + + @Override + public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException { + Optional requestId = CoapTransportResource.getRequestId(inbound); + String payload = validatePayload(sessionId, inbound); + JsonObject response = new JsonParser().parse(payload).getAsJsonObject(); + return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId.orElseThrow(() -> new AdaptorException("Request id is missing!"))) + .setPayload(response.toString()).build(); + } + + @Override + public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(UUID sessionId, Request inbound) throws AdaptorException { + String payload = validatePayload(sessionId, inbound); + return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), 0); + } + + @Override + public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.AttributeUpdateNotificationMsg msg) throws AdaptorException { + return getObserveNotification(session.getNextSeqNumber(), JsonConverter.toJson(msg)); + } + + @Override + public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.ToDeviceRpcRequestMsg msg) throws AdaptorException { + return getObserveNotification(session.getNextSeqNumber(), JsonConverter.toJson(msg, true)); + } + + @Override + public Response convertToPublish(CoapTransportResource.CoapSessionListener coapSessionListener, TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException { + Response response = new Response(CoAP.ResponseCode.CONTENT); + JsonElement result = JsonConverter.toJson(msg); + response.setPayload(result.toString()); + return response; + } + + @Override + public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException { + if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0 && msg.getDeletedAttributeKeysCount() == 0) { + return new Response(CoAP.ResponseCode.NOT_FOUND); + } else { + Response response = new Response(CoAP.ResponseCode.CONTENT); + JsonObject result = JsonConverter.toJson(msg); + response.setPayload(result.toString()); + return response; + } + } + + private Response getObserveNotification(int seqNumber, JsonElement json) { + Response response = new Response(CoAP.ResponseCode.CONTENT); + response.getOptions().setObserve(seqNumber); + response.setPayload(json.toString()); + return response; + } + + private String validatePayload(UUID sessionId, Request inbound) throws AdaptorException { + String payload = inbound.getPayloadString(); + if (payload == null) { + log.warn("[{}] Payload is empty!", sessionId); + throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); + } + return payload; + } + + private Set toKeys(List queryElements, String attributeName) throws AdaptorException { + String keys = null; + for (String queryElement : queryElements) { + String[] queryItem = queryElement.split("="); + if (queryItem.length == 2 && queryItem[0].equals(attributeName)) { + keys = queryItem[1]; + } + } + if (keys != null && !StringUtils.isEmpty(keys)) { + return new HashSet<>(Arrays.asList(keys.split(","))); + } else { + return null; + } + } + +} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java similarity index 100% rename from transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java rename to common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java index c1639ced7d..55df31a735 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java @@ -43,8 +43,6 @@ public class MqttTransportService { private String host; @Value("${transport.mqtt.bind_port}") private Integer port; - @Value("${transport.mqtt.adaptor}") - private String adaptorName; @Value("${transport.mqtt.netty.leak_detector_level}") private String leakDetectorLevel; diff --git a/common/transport/pom.xml b/common/transport/pom.xml index c9b47e876f..83e7ba806a 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -38,7 +38,7 @@ transport-api mqtt http - + coap diff --git a/pom.xml b/pom.xml index f4c2957b08..684c3d02a4 100755 --- a/pom.xml +++ b/pom.xml @@ -370,7 +370,7 @@ ${project.version} - org.thingsboard.transport + org.thingsboard.common.transport coap ${project.version} diff --git a/transport/coap/build.gradle b/transport/coap/build.gradle new file mode 100644 index 0000000000..6d54cb4789 --- /dev/null +++ b/transport/coap/build.gradle @@ -0,0 +1,140 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import org.apache.tools.ant.filters.ReplaceTokens + +buildscript { + ext { + osPackageVersion = "3.8.0" + } + repositories { + jcenter() + } + dependencies { + classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}") + } +} + +apply plugin: "nebula.ospackage" + +buildDir = projectBuildDir +version = projectVersion +distsDirName = "./" + +// OS Package plugin configuration +ospackage { + packageName = pkgName + version = "${project.version}" + release = 1 + os = LINUX + type = BINARY + + into pkgInstallFolder + + user pkgName + permissionGroup pkgName + + // Copy the actual .jar file + from(mainJar) { + // Strip the version from the jar filename + rename { String fileName -> + "${pkgName}.jar" + } + fileMode 0500 + into "bin" + } + + // Copy the config files + from("target/conf") { + exclude "${pkgName}.conf" + fileType CONFIG | NOREPLACE + fileMode 0754 + into "conf" + } + +} + +// Configure our RPM build task +buildRpm { + + arch = NOARCH + + version = projectVersion.replace('-', '') + archiveName = "${pkgName}.rpm" + + requires("java-1.8.0") + + from("target/conf") { + include "${pkgName}.conf" + filter(ReplaceTokens, tokens: ['pkg.platform': 'rpm']) + fileType CONFIG | NOREPLACE + fileMode 0754 + into "${pkgInstallFolder}/conf" + } + + preInstall file("${buildDir}/control/rpm/preinst") + postInstall file("${buildDir}/control/rpm/postinst") + preUninstall file("${buildDir}/control/rpm/prerm") + postUninstall file("${buildDir}/control/rpm/postrm") + + user pkgName + permissionGroup pkgName + + // Copy the system unit files + from("${buildDir}/control/${pkgName}.service") { + addParentDirs = false + fileMode 0644 + into "/usr/lib/systemd/system" + } + + directory(pkgLogFolder, 0755) + link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") + link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") +} + +// Same as the buildRpm task +buildDeb { + + arch = "all" + + archiveName = "${pkgName}.deb" + + requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer").or("openjdk-8-jre-headless") + + from("target/conf") { + include "${pkgName}.conf" + filter(ReplaceTokens, tokens: ['pkg.platform': 'deb']) + fileType CONFIG | NOREPLACE + fileMode 0754 + into "${pkgInstallFolder}/conf" + } + + configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf") + configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml") + configurationFile("${pkgInstallFolder}/conf/logback.xml") + + preInstall file("${buildDir}/control/deb/preinst") + postInstall file("${buildDir}/control/deb/postinst") + preUninstall file("${buildDir}/control/deb/prerm") + postUninstall file("${buildDir}/control/deb/postrm") + + user pkgName + permissionGroup pkgName + + directory(pkgLogFolder, 0755) + link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar") + link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") + link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") +} diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index a7015f0a35..a5e8107ab6 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -27,42 +27,37 @@ coap jar - Thingsboard COAP Transport + Thingsboard CoAP Transport Service https://thingsboard.io UTF-8 ${basedir}/../.. + tb-coap-transport + /var/log/${pkg.name} + /usr/share/${pkg.name} + ${project.build.directory}/windows - org.thingsboard.common - transport - - - org.eclipse.californium - californium-core - - - org.springframework - spring-context + org.thingsboard.common.transport + coap - org.slf4j - slf4j-api - - - org.slf4j - log4j-over-slf4j + org.thingsboard.common + queue - ch.qos.logback - logback-core + org.springframework.boot + spring-boot-starter-web - ch.qos.logback - logback-classic + com.sun.winsw + winsw + bin + exe + provided org.springframework.boot @@ -81,4 +76,266 @@ + + ${pkg.name}-${project.version} + + + ${project.basedir}/src/main/resources + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-conf + process-resources + + copy-resources + + + ${project.build.directory}/conf + + + src/main/resources + + logback.xml + + false + + + + + + copy-service-conf + process-resources + + copy-resources + + + ${project.build.directory}/conf + + + src/main/conf + true + + + + src/main/filters/unix.properties + + + + + copy-win-conf + process-resources + + copy-resources + + + ${pkg.win.dist}/conf + + + src/main/resources + + logback.xml + + false + + + src/main/conf + + tb-coap-transport.conf + + true + + + + src/main/filters/windows.properties + + + + + copy-control + process-resources + + copy-resources + + + ${project.build.directory}/control + + + src/main/scripts/control + true + + + + src/main/filters/unix.properties + + + + + copy-windows-control + process-resources + + copy-resources + + + ${pkg.win.dist} + + + src/main/scripts/windows + true + + + + src/main/filters/windows.properties + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-winsw-service + package + + copy + + + + + com.sun.winsw + winsw + bin + exe + service.exe + + + ${pkg.win.dist} + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + **/logback.xml + + + + ThingsBoard CoAP Transport Service + ${project.version} + + + + + + org.springframework.boot + spring-boot-maven-plugin + + org.thingsboard.server.coap.ThingsboardCoapTransportApplication + boot + ZIP + true + true + + ${pkg.installFolder}/conf + ${pkg.unixLogFolder} + ${pkg.name}.out + ${pkg.name} + + + + + + repackage + + + + + + org.fortasoft + gradle-maven-plugin + + + build + buildDeb + buildRpm + + + -PprojectBuildDir=${project.build.directory} + -PprojectVersion=${project.version} + -PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging} + -PpkgName=${pkg.name} + -PpkgInstallFolder=${pkg.installFolder} + -PpkgLogFolder=${pkg.unixLogFolder} + + + + + package + + invoke + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + ${pkg.name} + + src/main/assembly/windows.xml + + + + + assembly + package + + single + + + + + + org.apache.maven.plugins + maven-install-plugin + + ${project.build.directory}/${pkg.name}.deb + ${project.artifactId} + ${project.groupId} + ${project.version} + deb + deb + + + + install-deb + package + + install-file + + + + + + + + + jenkins + Jenkins Repository + http://repo.jenkins-ci.org/releases + + false + + + diff --git a/transport/coap/src/main/assembly/windows.xml b/transport/coap/src/main/assembly/windows.xml new file mode 100644 index 0000000000..82da34ed13 --- /dev/null +++ b/transport/coap/src/main/assembly/windows.xml @@ -0,0 +1,71 @@ + + + windows + + + zip + + + + + + ${pkg.win.dist} + logs + + */** + + + + ${pkg.win.dist}/conf + conf + windows + + + + + + ${project.build.directory}/${project.build.finalName}-boot.${project.packaging} + lib + ${pkg.name}.jar + + + ${pkg.win.dist}/service.exe + + ${pkg.name}.exe + + + ${pkg.win.dist}/service.xml + + ${pkg.name}.xml + windows + + + ${pkg.win.dist}/install.bat + + windows + + + ${pkg.win.dist}/uninstall.bat + + windows + + + diff --git a/transport/coap/src/main/conf/logback.xml b/transport/coap/src/main/conf/logback.xml new file mode 100644 index 0000000000..f36469d5f2 --- /dev/null +++ b/transport/coap/src/main/conf/logback.xml @@ -0,0 +1,43 @@ + + + + + + + ${pkg.logFolder}/${pkg.name}.log + + ${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log + 100MB + 30 + 3GB + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/transport/coap/src/main/conf/tb-coap-transport.conf b/transport/coap/src/main/conf/tb-coap-transport.conf new file mode 100644 index 0000000000..0afa91c18e --- /dev/null +++ b/transport/coap/src/main/conf/tb-coap-transport.conf @@ -0,0 +1,23 @@ +# +# Copyright © 2016-2018 The Thingsboard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" +export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" +export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" +export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly" +export LOG_FILENAME=${pkg.name}.out +export LOADER_PATH=${pkg.installFolder}/conf diff --git a/transport/coap/src/main/filters/unix.properties b/transport/coap/src/main/filters/unix.properties new file mode 100644 index 0000000000..8967278673 --- /dev/null +++ b/transport/coap/src/main/filters/unix.properties @@ -0,0 +1 @@ +pkg.logFolder=${pkg.unixLogFolder} \ No newline at end of file diff --git a/transport/coap/src/main/filters/windows.properties b/transport/coap/src/main/filters/windows.properties new file mode 100644 index 0000000000..a6e48d91ba --- /dev/null +++ b/transport/coap/src/main/filters/windows.properties @@ -0,0 +1,2 @@ +pkg.logFolder=${BASE}\\logs +pkg.winWrapperLogFolder=%BASE%\\logs diff --git a/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.java b/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.java new file mode 100644 index 0000000000..1ad86dd166 --- /dev/null +++ b/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.coap; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +import java.util.Arrays; + +@SpringBootConfiguration +@EnableAsync +@EnableScheduling +@ComponentScan({"org.thingsboard.server.coap", "org.thingsboard.server.common", "org.thingsboard.server.transport.coap", "org.thingsboard.server.kafka"}) +public class ThingsboardCoapTransportApplication { + + private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; + private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "tb-coap-transport"; + + public static void main(String[] args) { + SpringApplication.run(ThingsboardCoapTransportApplication.class, updateArguments(args)); + } + + private static String[] updateArguments(String[] args) { + if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) { + String[] modifiedArgs = new String[args.length + 1]; + System.arraycopy(args, 0, modifiedArgs, 0, args.length); + modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM; + return modifiedArgs; + } + return args; + } +} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java deleted file mode 100644 index 88fb69ecd0..0000000000 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ /dev/null @@ -1,242 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.coap; - -import java.lang.reflect.Field; -import java.util.List; -import java.util.Optional; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapResource; -import org.eclipse.californium.core.coap.CoAP.ResponseCode; -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.network.Exchange; -import org.eclipse.californium.core.network.ExchangeObserver; -import org.eclipse.californium.core.server.resources.CoapExchange; -import org.eclipse.californium.core.server.resources.Resource; -import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; -import org.thingsboard.server.common.data.security.DeviceTokenCredentials; -import org.thingsboard.server.common.msg.session.*; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.common.transport.quota.QuotaService; -import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; -import org.thingsboard.server.transport.coap.session.CoapExchangeObserverProxy; -import org.thingsboard.server.transport.coap.session.CoapSessionCtx; -import org.springframework.util.ReflectionUtils; - -@Slf4j -public class CoapTransportResource extends CoapResource { - // coap://localhost:port/api/v1/DEVICE_TOKEN/[attributes|telemetry|rpc[/requestId]] - private static final int ACCESS_TOKEN_POSITION = 3; - private static final int FEATURE_TYPE_POSITION = 4; - private static final int REQUEST_ID_POSITION = 5; - - private final CoapTransportAdaptor adaptor; - private final SessionMsgProcessor processor; - private final DeviceAuthService authService; - private final QuotaService quotaService; - private final Field observerField; - private final long timeout; - - public CoapTransportResource(SessionMsgProcessor processor, DeviceAuthService authService, CoapTransportAdaptor adaptor, String name, - long timeout, QuotaService quotaService) { - super(name); - this.processor = processor; - this.authService = authService; - this.quotaService = quotaService; - this.adaptor = adaptor; - this.timeout = timeout; - // This is important to turn off existing observable logic in - // CoapResource. We will have our own observe monitoring due to 1:1 - // observe relationship. - this.setObservable(false); - observerField = ReflectionUtils.findField(Exchange.class, "observer"); - observerField.setAccessible(true); - } - - @Override - public void handleGET(CoapExchange exchange) { - if(quotaService.isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) { - log.warn("COAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort()); - exchange.respond(ResponseCode.BAD_REQUEST); - return; - } - - Optional featureType = getFeatureType(exchange.advanced().getRequest()); - if (!featureType.isPresent()) { - log.trace("Missing feature type parameter"); - exchange.respond(ResponseCode.BAD_REQUEST); - } else if (featureType.get() == FeatureType.TELEMETRY) { - log.trace("Can't fetch/subscribe to timeseries updates"); - exchange.respond(ResponseCode.BAD_REQUEST); - } else if (exchange.getRequestOptions().hasObserve()) { - processExchangeGetRequest(exchange, featureType.get()); - } else if (featureType.get() == FeatureType.ATTRIBUTES) { - processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); - } else { - log.trace("Invalid feature type parameter"); - exchange.respond(ResponseCode.BAD_REQUEST); - } - } - - private void processExchangeGetRequest(CoapExchange exchange, FeatureType featureType) { - boolean unsubscribe = exchange.getRequestOptions().getObserve() == 1; - SessionMsgType sessionMsgType; - if (featureType == FeatureType.RPC) { - sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; - } else { - sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; - } - Optional sessionId = processRequest(exchange, sessionMsgType); - if (sessionId.isPresent()) { - if (exchange.getRequestOptions().getObserve() == 1) { - exchange.respond(ResponseCode.VALID); - } - } - } - - @Override - public void handlePOST(CoapExchange exchange) { - if(quotaService.isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) { - log.warn("COAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort()); - exchange.respond(ResponseCode.BAD_REQUEST); - return; - } - - Optional featureType = getFeatureType(exchange.advanced().getRequest()); - if (!featureType.isPresent()) { - log.trace("Missing feature type parameter"); - exchange.respond(ResponseCode.BAD_REQUEST); - } else { - switch (featureType.get()) { - case ATTRIBUTES: - processRequest(exchange, SessionMsgType.POST_ATTRIBUTES_REQUEST); - break; - case TELEMETRY: - processRequest(exchange, SessionMsgType.POST_TELEMETRY_REQUEST); - break; - case RPC: - Optional requestId = getRequestId(exchange.advanced().getRequest()); - if (requestId.isPresent()) { - processRequest(exchange, SessionMsgType.TO_DEVICE_RPC_RESPONSE); - } else { - processRequest(exchange, SessionMsgType.TO_SERVER_RPC_REQUEST); - } - break; - } - } - } - - private Optional processRequest(CoapExchange exchange, SessionMsgType type) { - log.trace("Processing {}", exchange.advanced().getRequest()); - exchange.accept(); - Exchange advanced = exchange.advanced(); - Request request = advanced.getRequest(); - - Optional credentials = decodeCredentials(request); - if (!credentials.isPresent()) { - exchange.respond(ResponseCode.BAD_REQUEST); - return Optional.empty(); - } - - CoapSessionCtx ctx = new CoapSessionCtx(exchange, adaptor, processor, authService, timeout); - -// if (!ctx.login(credentials.get())) { -// exchange.respond(ResponseCode.UNAUTHORIZED); -// return Optional.empty(); -// } - - AdaptorToSessionActorMsg msg; - try { - switch (type) { - case GET_ATTRIBUTES_REQUEST: - case POST_ATTRIBUTES_REQUEST: - case POST_TELEMETRY_REQUEST: - case TO_DEVICE_RPC_RESPONSE: - case TO_SERVER_RPC_REQUEST: - ctx.setSessionType(SessionType.SYNC); - msg = adaptor.convertToActorMsg(ctx, type, request); - break; - case SUBSCRIBE_ATTRIBUTES_REQUEST: - case SUBSCRIBE_RPC_COMMANDS_REQUEST: - ExchangeObserver systemObserver = (ExchangeObserver) observerField.get(advanced); - advanced.setObserver(new CoapExchangeObserverProxy(systemObserver, ctx)); - ctx.setSessionType(SessionType.ASYNC); - msg = adaptor.convertToActorMsg(ctx, type, request); - break; - case UNSUBSCRIBE_ATTRIBUTES_REQUEST: - case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: - ctx.setSessionType(SessionType.ASYNC); - msg = adaptor.convertToActorMsg(ctx, type, request); - break; - default: - log.trace("[{}] Unsupported msg type: {}", ctx.getSessionId(), type); - throw new IllegalArgumentException("Unsupported msg type: " + type); - } - log.trace("Processing msg: {}", msg); -// processor.process(new BasicTransportToDeviceSessionActorMsg(ctx.getDevice(), msg)); - } catch (AdaptorException e) { - log.debug("Failed to decode payload {}", e); - exchange.respond(ResponseCode.BAD_REQUEST, e.getMessage()); - return Optional.empty(); - } catch (IllegalArgumentException | IllegalAccessException e) { - log.debug("Failed to process payload {}", e); - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR, e.getMessage()); - } - return Optional.of(ctx.getSessionId()); - } - - private Optional decodeCredentials(Request request) { - List uriPath = request.getOptions().getUriPath(); - DeviceCredentialsFilter credentials = null; - if (uriPath.size() >= ACCESS_TOKEN_POSITION) { - credentials = new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1)); - } - return Optional.ofNullable(credentials); - } - - private Optional getFeatureType(Request request) { - List uriPath = request.getOptions().getUriPath(); - try { - if (uriPath.size() >= FEATURE_TYPE_POSITION) { - return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); - } - } catch (RuntimeException e) { - log.warn("Failed to decode feature type: {}", uriPath); - } - return Optional.empty(); - } - - public static Optional getRequestId(Request request) { - List uriPath = request.getOptions().getUriPath(); - try { - if (uriPath.size() >= REQUEST_ID_POSITION) { - return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1))); - } - } catch (RuntimeException e) { - log.warn("Failed to decode feature type: {}", uriPath); - } - return Optional.empty(); - } - - @Override - public Resource getChild(String name) { - return this; - } - -} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java deleted file mode 100644 index d9a04b163a..0000000000 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.coap.adaptors; - -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; -import org.thingsboard.server.common.transport.TransportAdaptor; -import org.thingsboard.server.transport.coap.session.CoapSessionCtx; - -public interface CoapTransportAdaptor extends TransportAdaptor { - -} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java deleted file mode 100644 index 1c587f0fae..0000000000 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java +++ /dev/null @@ -1,260 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.coap.adaptors; - -import java.util.*; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.coap.CoAP.ResponseCode; -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.msg.core.*; -import org.thingsboard.server.common.msg.kv.AttributesKVMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionContext; -import org.thingsboard.server.common.msg.session.ex.ProcessingTimeoutException; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.springframework.stereotype.Component; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; -import org.thingsboard.server.transport.coap.CoapTransportResource; -import org.thingsboard.server.transport.coap.session.CoapSessionCtx; - -@Component("JsonCoapAdaptor") -@Slf4j -public class JsonCoapAdaptor implements CoapTransportAdaptor { - - @Override - public AdaptorToSessionActorMsg convertToActorMsg(CoapSessionCtx ctx, SessionMsgType type, Request inbound) throws AdaptorException { - FromDeviceMsg msg = null; - switch (type) { - case POST_TELEMETRY_REQUEST: - msg = convertToTelemetryUploadRequest(ctx, inbound); - break; - case POST_ATTRIBUTES_REQUEST: - msg = convertToUpdateAttributesRequest(ctx, inbound); - break; - case GET_ATTRIBUTES_REQUEST: - msg = convertToGetAttributesRequest(ctx, inbound); - break; - case SUBSCRIBE_RPC_COMMANDS_REQUEST: - msg = new RpcSubscribeMsg(); - break; - case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: - msg = new RpcUnsubscribeMsg(); - break; - case TO_DEVICE_RPC_RESPONSE: - msg = convertToDeviceRpcResponse(ctx, inbound); - break; - case TO_SERVER_RPC_REQUEST: - msg = convertToServerRpcRequest(ctx, inbound); - break; - case SUBSCRIBE_ATTRIBUTES_REQUEST: - msg = new AttributesSubscribeMsg(); - break; - case UNSUBSCRIBE_ATTRIBUTES_REQUEST: - msg = new AttributesUnsubscribeMsg(); - break; - default: - log.warn("[{}] Unsupported msg type: {}!", ctx.getSessionId(), type); - throw new AdaptorException(new IllegalArgumentException("Unsupported msg type: " + type + "!")); - } - return new BasicAdaptorToSessionActorMsg(ctx, msg); - } - - private FromDeviceMsg convertToDeviceRpcResponse(CoapSessionCtx ctx, Request inbound) throws AdaptorException { - Optional requestId = CoapTransportResource.getRequestId(inbound); - String payload = validatePayload(ctx, inbound); - JsonObject response = new JsonParser().parse(payload).getAsJsonObject(); - return new ToDeviceRpcResponseMsg( - requestId.orElseThrow(() -> new AdaptorException("Request id is missing!")), - response.get("response").toString()); - } - - private FromDeviceMsg convertToServerRpcRequest(CoapSessionCtx ctx, Request inbound) throws AdaptorException { - - String payload = validatePayload(ctx, inbound); - -// return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), 0); - return null; - } - - @Override - public Optional convertToAdaptorMsg(CoapSessionCtx ctx, SessionActorToAdaptorMsg source) throws AdaptorException { - ToDeviceMsg msg = source.getMsg(); - switch (msg.getSessionMsgType()) { - case STATUS_CODE_RESPONSE: - case TO_DEVICE_RPC_RESPONSE_ACK: - return Optional.of(convertStatusCodeResponse((StatusCodeResponse) msg)); - case GET_ATTRIBUTES_RESPONSE: - return Optional.of(convertGetAttributesResponse((GetAttributesResponse) msg)); - case ATTRIBUTES_UPDATE_NOTIFICATION: - return Optional.of(convertNotificationResponse(ctx, (AttributesUpdateNotification) msg)); - case TO_DEVICE_RPC_REQUEST: - return Optional.of(convertToDeviceRpcRequest(ctx, (ToDeviceRpcRequestMsg) msg)); - case TO_SERVER_RPC_RESPONSE: - return Optional.of(convertToServerRpcResponse(ctx, (ToServerRpcResponseMsg) msg)); - case RULE_ENGINE_ERROR: - return Optional.of(convertToRuleEngineErrorResponse(ctx, (RuleEngineErrorMsg) msg)); - default: - log.warn("[{}] Unsupported msg type: {}!", source.getSessionId(), msg.getSessionMsgType()); - throw new AdaptorException(new IllegalArgumentException("Unsupported msg type: " + msg.getSessionMsgType() + "!")); - } - } - - private Response convertToRuleEngineErrorResponse(CoapSessionCtx ctx, RuleEngineErrorMsg msg) { - ResponseCode status = ResponseCode.INTERNAL_SERVER_ERROR; - switch (msg.getError()) { - case QUEUE_PUT_TIMEOUT: - status = ResponseCode.GATEWAY_TIMEOUT; - break; - default: - if (msg.getInSessionMsgType() == SessionMsgType.TO_SERVER_RPC_REQUEST) { - status = ResponseCode.BAD_REQUEST; - } - break; - } - Response response = new Response(status); - response.setPayload(JsonConverter.toErrorJson(msg.getErrorMsg()).toString()); - return response; - } - - private Response convertNotificationResponse(CoapSessionCtx ctx, AttributesUpdateNotification msg) { - return getObserveNotification(ctx, JsonConverter.toJson(msg.getData(), false)); - } - - private Response convertToDeviceRpcRequest(CoapSessionCtx ctx, ToDeviceRpcRequestMsg msg) { - return getObserveNotification(ctx, JsonConverter.toJson(msg, true)); - } - - private Response getObserveNotification(CoapSessionCtx ctx, JsonObject json) { - Response response = new Response(ResponseCode.CONTENT); - response.getOptions().setObserve(ctx.nextSeqNumber()); - response.setPayload(json.toString()); - return response; - } - - private AttributesUpdateRequest convertToUpdateAttributesRequest(SessionContext ctx, Request inbound) throws AdaptorException { - String payload = validatePayload(ctx, inbound); - try { - return JsonConverter.convertToAttributes(new JsonParser().parse(payload)); - } catch (IllegalStateException | JsonSyntaxException ex) { - throw new AdaptorException(ex); - } - } - - private FromDeviceMsg convertToGetAttributesRequest(SessionContext ctx, Request inbound) throws AdaptorException { - List queryElements = inbound.getOptions().getUriQuery(); - if (queryElements != null && queryElements.size() > 0) { - Set clientKeys = toKeys(ctx, queryElements, "clientKeys"); - Set sharedKeys = toKeys(ctx, queryElements, "sharedKeys"); - return new BasicGetAttributesRequest(0, clientKeys, sharedKeys); - } else { - return new BasicGetAttributesRequest(0); - } - } - - private Set toKeys(SessionContext ctx, List queryElements, String attributeName) throws AdaptorException { - String keys = null; - for (String queryElement : queryElements) { - String[] queryItem = queryElement.split("="); - if (queryItem.length == 2 && queryItem[0].equals(attributeName)) { - keys = queryItem[1]; - } - } - if (keys != null && !StringUtils.isEmpty(keys)) { - return new HashSet<>(Arrays.asList(keys.split(","))); - } else { - return null; - } - } - - private TelemetryUploadRequest convertToTelemetryUploadRequest(SessionContext ctx, Request inbound) throws AdaptorException { - String payload = validatePayload(ctx, inbound); - try { - return JsonConverter.convertToTelemetry(new JsonParser().parse(payload)); - } catch (IllegalStateException | JsonSyntaxException ex) { - throw new AdaptorException(ex); - } - } - - private Response convertStatusCodeResponse(StatusCodeResponse msg) { - if (msg.isSuccess()) { - Optional code = msg.getData(); - if (code.isPresent() && code.get() == 200) { - return new Response(ResponseCode.VALID); - } else { - return new Response(ResponseCode.CREATED); - } - } else { - return convertError(msg.getError()); - } - } - - private String validatePayload(SessionContext ctx, Request inbound) throws AdaptorException { - String payload = inbound.getPayloadString(); - if (payload == null) { - log.warn("[{}] Payload is empty!", ctx.getSessionId()); - throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); - } - return payload; - } - - private Response convertToServerRpcResponse(SessionContext ctx, ToServerRpcResponseMsg msg) { - if (msg.isSuccess()) { - Response response = new Response(ResponseCode.CONTENT); -// JsonElement result = JsonConverter.toJson(msg); -// response.setPayload(result.toString()); - return response; - } else { - return convertError(Optional.of(new RuntimeException("Server RPC response is empty!"))); - } - } - - private Response convertGetAttributesResponse(GetAttributesResponse msg) { - if (msg.isSuccess()) { - Optional payload = msg.getData(); - if (!payload.isPresent() || (payload.get().getClientAttributes().isEmpty() && payload.get().getSharedAttributes().isEmpty())) { - return new Response(ResponseCode.NOT_FOUND); - } else { - Response response = new Response(ResponseCode.CONTENT); - JsonObject result = JsonConverter.toJson(payload.get(), false); - response.setPayload(result.toString()); - return response; - } - } else { - return convertError(msg.getError()); - } - } - - private Response convertError(Optional exception) { - if (exception.isPresent()) { - log.warn("Converting exception: {}", exception.get().getMessage(), exception.get()); - if (exception.get() instanceof ProcessingTimeoutException) { - return new Response(ResponseCode.SERVICE_UNAVAILABLE); - } else { - return new Response(ResponseCode.INTERNAL_SERVER_ERROR); - } - } else { - return new Response(ResponseCode.INTERNAL_SERVER_ERROR); - } - } - -} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapExchangeObserverProxy.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapExchangeObserverProxy.java deleted file mode 100644 index 0c264817d5..0000000000 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapExchangeObserverProxy.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.coap.session; - -import org.eclipse.californium.core.network.Exchange; -import org.eclipse.californium.core.network.ExchangeObserver; - -public class CoapExchangeObserverProxy implements ExchangeObserver { - - private final ExchangeObserver proxy; - private final CoapSessionCtx ctx; - - public CoapExchangeObserverProxy(ExchangeObserver proxy, CoapSessionCtx ctx) { - super(); - this.proxy = proxy; - this.ctx = ctx; - } - - @Override - public void completed(Exchange exchange) { - proxy.completed(exchange); - ctx.close(); - } - -} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionCtx.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionCtx.java deleted file mode 100644 index 60d54087bf..0000000000 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionCtx.java +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.coap.session; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.coap.CoAP.ResponseCode; -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; -import org.eclipse.californium.core.server.resources.CoapExchange; -import org.thingsboard.server.common.msg.session.ex.SessionException; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; -import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; - -import java.util.concurrent.atomic.AtomicInteger; - -@Slf4j -public class CoapSessionCtx extends DeviceAwareSessionContext { - - private final SessionId sessionId; - private final CoapExchange exchange; - private final CoapTransportAdaptor adaptor; - private final String token; - private final long timeout; - private SessionType sessionType; - private final AtomicInteger seqNumber = new AtomicInteger(2); - - public CoapSessionCtx(CoapExchange exchange, CoapTransportAdaptor adaptor, SessionMsgProcessor processor, DeviceAuthService authService, long timeout) { - super(); - Request request = exchange.advanced().getRequest(); - this.token = request.getTokenString(); - this.sessionId = new CoapSessionId(request.getSource().getHostAddress(), request.getSourcePort(), this.token); - this.exchange = exchange; - this.adaptor = adaptor; - this.timeout = timeout; - } - - - @Override - public void onMsg(SessionActorToAdaptorMsg msg) throws SessionException { - try { - adaptor.convertToAdaptorMsg(this, msg).ifPresent(this::pushToNetwork); - } catch (AdaptorException e) { - logAndWrap(e); - } - } - - private void pushToNetwork(Response response) { - exchange.respond(response); - } - - private void logAndWrap(AdaptorException e) throws SessionException { - log.warn("Failed to convert msg: {}", e.getMessage(), e); - throw new SessionException(e); - } - - @Override - public void onMsg(SessionCtrlMsg msg) throws SessionException { - log.debug("[{}] onCtrl: {}", sessionId, msg); - if (msg instanceof SessionCloseMsg) { - onSessionClose((SessionCloseMsg) msg); - } - } - - private void onSessionClose(SessionCloseMsg msg) { - if (msg.isTimeout()) { - exchange.respond(ResponseCode.SERVICE_UNAVAILABLE); - } else if (msg.isCredentialsRevoked()) { - exchange.respond(ResponseCode.UNAUTHORIZED); - } else { - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); - } - } - - @Override - public SessionId getSessionId() { - return sessionId; - } - - @Override - public String toString() { - return "CoapSessionCtx [sessionId=" + sessionId + "]"; - } - - @Override - public boolean isClosed() { - return exchange.advanced().isComplete() || exchange.advanced().isTimedOut(); - } - - public void close() { - log.info("[{}] Closing processing context. Timeout: {}", sessionId, exchange.advanced().isTimedOut()); -// processor.process(exchange.advanced().isTimedOut() ? SessionCloseMsg.onTimeout(sessionId) : SessionCloseMsg.onError(sessionId)); - } - - @Override - public long getTimeout() { - return timeout; - } - - public void setSessionType(SessionType sessionType) { - this.sessionType = sessionType; - } - - @Override - public SessionType getSessionType() { - return sessionType; - } - - public int nextSeqNumber() { - return seqNumber.getAndIncrement(); - } -} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionId.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionId.java deleted file mode 100644 index 2bfcf1d2c6..0000000000 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionId.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.coap.session; - -public final class CoapSessionId implements SessionId { - - private final String clientAddress; - private final int clientPort; - private final String token; - - public CoapSessionId(String host, int port, String token) { - super(); - this.clientAddress = host; - this.clientPort = port; - this.token = token; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((clientAddress == null) ? 0 : clientAddress.hashCode()); - result = prime * result + clientPort; - result = prime * result + ((token == null) ? 0 : token.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - CoapSessionId other = (CoapSessionId) obj; - if (clientAddress == null) { - if (other.clientAddress != null) - return false; - } else if (!clientAddress.equals(other.clientAddress)) - return false; - if (clientPort != other.clientPort) - return false; - if (token == null) { - if (other.token != null) - return false; - } else if (!token.equals(other.token)) - return false; - return true; - } - - @Override - public String toString() { - return "CoapSessionId [clientAddress=" + clientAddress + ", clientPort=" + clientPort + ", token=" + token + "]"; - } - - @Override - public String toUidStr() { - return clientAddress + ":" + clientPort + ":" + token; - } - -} diff --git a/transport/coap/src/main/resources/logback.xml b/transport/coap/src/main/resources/logback.xml new file mode 100644 index 0000000000..18864a9fab --- /dev/null +++ b/transport/coap/src/main/resources/logback.xml @@ -0,0 +1,34 @@ + + + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml new file mode 100644 index 0000000000..1790d822d2 --- /dev/null +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -0,0 +1,68 @@ +# +# Copyright © 2016-2018 The Thingsboard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +spring.main.web-environment: false +spring.main.web-application-type: none + +# MQTT server parameters +transport: + coap: + bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" + bind_port: "${COAP_BIND_PORT:5683}" + timeout: "${COAP_TIMEOUT:10000}" + +#Quota parameters +quota: + host: + # Max allowed number of API requests in interval for single host + limit: "${QUOTA_HOST_LIMIT:10000}" + # Interval duration + intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}" + # Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs + ttlMs: "${QUOTA_HOST_TTL_MS:60000}" + # Interval for scheduled task that cleans expired records. TTL is used for expiring + cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}" + # Enable Host API Limits + enabled: "${QUOTA_HOST_ENABLED:true}" + # Array of whitelist hosts + whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}" + # Array of blacklist hosts + blacklist: "${QUOTA_HOST_BLACKLIST:}" + log: + topSize: 10 + intervalMin: 2 + +kafka: + enabled: true + bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" + acks: "${TB_KAFKA_ACKS:all}" + retries: "${TB_KAFKA_RETRIES:1}" + batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" + linger.ms: "${TB_KAFKA_LINGER_MS:1}" + buffer.memory: "${TB_BUFFER_MEMORY:33554432}" + transport_api: + requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}" + responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}" + max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}" + max_requests_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" + response_poll_interval: "${TB_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}" + response_auto_commit_interval: "${TB_TRANSPORT_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}" + rule_engine: + topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}" + notifications: + topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" + poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" + auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}" diff --git a/transport/coap/src/main/scripts/control/deb/postinst b/transport/coap/src/main/scripts/control/deb/postinst new file mode 100644 index 0000000000..d4066c027b --- /dev/null +++ b/transport/coap/src/main/scripts/control/deb/postinst @@ -0,0 +1,6 @@ +#!/bin/sh + +chown -R ${pkg.name}: ${pkg.logFolder} +chown -R ${pkg.name}: ${pkg.installFolder} +update-rc.d ${pkg.name} defaults + diff --git a/transport/coap/src/main/scripts/control/deb/postrm b/transport/coap/src/main/scripts/control/deb/postrm new file mode 100644 index 0000000000..61865803c3 --- /dev/null +++ b/transport/coap/src/main/scripts/control/deb/postrm @@ -0,0 +1,3 @@ +#!/bin/sh + +update-rc.d -f ${pkg.name} remove diff --git a/transport/coap/src/main/scripts/control/deb/preinst b/transport/coap/src/main/scripts/control/deb/preinst new file mode 100644 index 0000000000..6be5959285 --- /dev/null +++ b/transport/coap/src/main/scripts/control/deb/preinst @@ -0,0 +1,18 @@ +#!/bin/sh + +if ! getent group ${pkg.name} >/dev/null; then + addgroup --system ${pkg.name} +fi + +if ! getent passwd ${pkg.name} >/dev/null; then + adduser --quiet \ + --system \ + --ingroup ${pkg.name} \ + --quiet \ + --disabled-login \ + --disabled-password \ + --home ${pkg.installFolder} \ + --no-create-home \ + -gecos "Thingsboard application" \ + ${pkg.name} +fi diff --git a/transport/coap/src/main/scripts/control/deb/prerm b/transport/coap/src/main/scripts/control/deb/prerm new file mode 100644 index 0000000000..898d3efd5c --- /dev/null +++ b/transport/coap/src/main/scripts/control/deb/prerm @@ -0,0 +1,5 @@ +#!/bin/sh + +if [ -e /var/run/${pkg.name}/${pkg.name}.pid ]; then + service ${pkg.name} stop +fi diff --git a/transport/coap/src/main/scripts/control/rpm/postinst b/transport/coap/src/main/scripts/control/rpm/postinst new file mode 100644 index 0000000000..8a7a88f7e0 --- /dev/null +++ b/transport/coap/src/main/scripts/control/rpm/postinst @@ -0,0 +1,9 @@ +#!/bin/sh + +chown -R ${pkg.name}: ${pkg.logFolder} +chown -R ${pkg.name}: ${pkg.installFolder} + +if [ $1 -eq 1 ] ; then + # Initial installation + systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || : +fi diff --git a/transport/coap/src/main/scripts/control/rpm/postrm b/transport/coap/src/main/scripts/control/rpm/postrm new file mode 100644 index 0000000000..8e1f8a2048 --- /dev/null +++ b/transport/coap/src/main/scripts/control/rpm/postrm @@ -0,0 +1,6 @@ +#!/bin/sh + +if [ $1 -ge 1 ] ; then + # Package upgrade, not uninstall + systemctl try-restart ${pkg.name}.service >/dev/null 2>&1 || : +fi diff --git a/transport/coap/src/main/scripts/control/rpm/preinst b/transport/coap/src/main/scripts/control/rpm/preinst new file mode 100644 index 0000000000..e19fc884c8 --- /dev/null +++ b/transport/coap/src/main/scripts/control/rpm/preinst @@ -0,0 +1,6 @@ +#!/bin/sh + +getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name} +getent passwd ${pkg.name} >/dev/null || \ +useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \ +-c "Thingsboard application" diff --git a/transport/coap/src/main/scripts/control/rpm/prerm b/transport/coap/src/main/scripts/control/rpm/prerm new file mode 100644 index 0000000000..accb487b8e --- /dev/null +++ b/transport/coap/src/main/scripts/control/rpm/prerm @@ -0,0 +1,6 @@ +#!/bin/sh + +if [ $1 -eq 0 ] ; then + # Package removal, not upgrade + systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || : +fi diff --git a/transport/coap/src/main/scripts/control/tb-coap-transport.service b/transport/coap/src/main/scripts/control/tb-coap-transport.service new file mode 100644 index 0000000000..d456fc03c0 --- /dev/null +++ b/transport/coap/src/main/scripts/control/tb-coap-transport.service @@ -0,0 +1,11 @@ +[Unit] +Description=${pkg.name} +After=syslog.target + +[Service] +User=${pkg.name} +ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar +SuccessExitStatus=143 + +[Install] +WantedBy=multi-user.target diff --git a/transport/coap/src/main/scripts/windows/install.bat b/transport/coap/src/main/scripts/windows/install.bat new file mode 100644 index 0000000000..dba773663c --- /dev/null +++ b/transport/coap/src/main/scripts/windows/install.bat @@ -0,0 +1,87 @@ +@ECHO OFF + +setlocal ENABLEEXTENSIONS + +@ECHO Detecting Java version installed. +:CHECK_JAVA_64 +@ECHO Detecting if it is 64 bit machine +set KEY_NAME="HKEY_LOCAL_MACHINE\Software\Wow6432Node\JavaSoft\Java Runtime Environment" +set VALUE_NAME=CurrentVersion + +FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName=%%A + set ValueType=%%B + set ValueValue=%%C +) +@ECHO CurrentVersion %ValueValue% + +SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%" +SET VALUE_NAME=JavaHome + +if defined ValueName ( + FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName2=%%A + set ValueType2=%%B + set JRE_PATH2=%%C + + if defined ValueName2 ( + set ValueName = %ValueName2% + set ValueType = %ValueType2% + set ValueValue = %JRE_PATH2% + ) + ) +) + +IF NOT "%JRE_PATH2%" == "" GOTO JAVA_INSTALLED + +:CHECK_JAVA_32 +@ECHO Detecting if it is 32 bit machine +set KEY_NAME="HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment" +set VALUE_NAME=CurrentVersion + +FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName=%%A + set ValueType=%%B + set ValueValue=%%C +) +@ECHO CurrentVersion %ValueValue% + +SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%" +SET VALUE_NAME=JavaHome + +if defined ValueName ( + FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName2=%%A + set ValueType2=%%B + set JRE_PATH2=%%C + + if defined ValueName2 ( + set ValueName = %ValueName2% + set ValueType = %ValueType2% + set ValueValue = %JRE_PATH2% + ) + ) +) + +IF "%JRE_PATH2%" == "" GOTO JAVA_NOT_INSTALLED + +:JAVA_INSTALLED + +@ECHO Java 1.8 found! +@ECHO Installing ${pkg.name} ... + +%BASE%${pkg.name}.exe install + +@ECHO ${pkg.name} installed successfully! + +GOTO END + +:JAVA_NOT_INSTALLED +@ECHO Java 1.8 or above is not installed +@ECHO Please go to https://java.com/ and install Java. Then retry installation. +PAUSE +GOTO END + +:END + + diff --git a/transport/coap/src/main/scripts/windows/service.xml b/transport/coap/src/main/scripts/windows/service.xml new file mode 100644 index 0000000000..f7b9d307c8 --- /dev/null +++ b/transport/coap/src/main/scripts/windows/service.xml @@ -0,0 +1,36 @@ + + ${pkg.name} + ${project.name} + ${project.description} + %BASE%\conf + ${pkg.winWrapperLogFolder} + rotate + + java + -Xloggc:%BASE%\logs\gc.log + -XX:+HeapDumpOnOutOfMemoryError + -XX:+PrintGCDetails + -XX:+PrintGCDateStamps + -XX:+PrintHeapAtGC + -XX:+PrintTenuringDistribution + -XX:+PrintGCApplicationStoppedTime + -XX:+UseGCLogFileRotation + -XX:NumberOfGCLogFiles=10 + -XX:GCLogFileSize=10M + -XX:-UseBiasedLocking + -XX:+UseTLAB + -XX:+ResizeTLAB + -XX:+PerfDisableSharedMem + -XX:+UseCondCardMark + -XX:CMSWaitDuration=10000 + -XX:+UseParNewGC + -XX:+UseConcMarkSweepGC + -XX:+CMSParallelRemarkEnabled + -XX:+CMSParallelInitialMarkEnabled + -XX:+CMSEdenChunksRecordAlways + -XX:CMSInitiatingOccupancyFraction=75 + -XX:+UseCMSInitiatingOccupancyOnly + -jar + %BASE%\lib\${pkg.name}.jar + + diff --git a/transport/coap/src/main/scripts/windows/uninstall.bat b/transport/coap/src/main/scripts/windows/uninstall.bat new file mode 100644 index 0000000000..921e4c8395 --- /dev/null +++ b/transport/coap/src/main/scripts/windows/uninstall.bat @@ -0,0 +1,9 @@ +@ECHO OFF + +@ECHO Stopping ${pkg.name} ... +net stop ${pkg.name} + +@ECHO Uninstalling ${pkg.name} ... +%~dp0${pkg.name}.exe uninstall + +@ECHO DONE. \ No newline at end of file diff --git a/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java b/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java deleted file mode 100644 index 84aeb848c8..0000000000 --- a/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java +++ /dev/null @@ -1,197 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.coap; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; -import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.CoAP.ResponseCode; -import org.eclipse.californium.core.coap.MediaTypeRegistry; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.junit4.SpringRunner; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; -import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.common.data.security.DeviceTokenCredentials; -import org.thingsboard.server.common.msg.session.*; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.auth.DeviceAuthResult; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService; - -import java.util.Optional; -import java.util.UUID; - -@RunWith(SpringRunner.class) -@DirtiesContext(classMode = ClassMode.BEFORE_CLASS) -@Slf4j -public class CoapServerTest { - - private static final int TEST_PORT = 5555; - private static final String TELEMETRY_POST_MESSAGE = "[{\"key1\":\"value1\"}]"; - private static final String TEST_ATTRIBUTES_RESPONSE = "{\"key1\":\"value1\",\"key2\":42}"; - private static final String DEVICE1_TOKEN = "Device1Token"; - - @Configuration - public static class EchoCoapServerITConfiguration extends CoapServerTestConfiguration { - - @Bean - public static DeviceAuthService authService() { - return new DeviceAuthService() { - - private final DeviceId devId = new DeviceId(UUID.randomUUID()); - - @Override - public DeviceAuthResult process(DeviceCredentialsFilter credentials) { - if (credentials != null && credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { - DeviceTokenCredentials tokenCredentials = (DeviceTokenCredentials) credentials; - if (tokenCredentials.getCredentialsId().equals(DEVICE1_TOKEN)) { - return DeviceAuthResult.of(devId); - } - } - return DeviceAuthResult.of("Credentials are invalid!"); - } - - @Override - public Optional findDeviceById(DeviceId deviceId) { - if (deviceId.equals(devId)) { - Device dev = new Device(); - dev.setId(devId); - dev.setTenantId(new TenantId(UUID.randomUUID())); - dev.setCustomerId(new CustomerId(UUID.randomUUID())); - return Optional.of(dev); - } else { - return Optional.empty(); - } - } - }; - } - - @Bean - public static SessionMsgProcessor sessionMsgProcessor() { - return new SessionMsgProcessor() { - -// @Override -// public void process(SessionAwareMsg toActorMsg) { -// if (toActorMsg instanceof TransportToDeviceSessionActorMsg) { -// AdaptorToSessionActorMsg sessionMsg = ((TransportToDeviceSessionActorMsg) toActorMsg).getSessionMsg(); -// try { -// FromDeviceMsg deviceMsg = sessionMsg.getMsg(); -// ToDeviceMsg toDeviceMsg = null; -// if (deviceMsg.getMsgType() == SessionMsgType.POST_TELEMETRY_REQUEST) { -// toDeviceMsg = BasicStatusCodeResponse.onSuccess(deviceMsg.getMsgType(), BasicRequest.DEFAULT_REQUEST_ID); -// } else if (deviceMsg.getMsgType() == SessionMsgType.GET_ATTRIBUTES_REQUEST) { -// List data = new ArrayList<>(); -// data.add(new BaseAttributeKvEntry(new StringDataEntry("key1", "value1"), System.currentTimeMillis())); -// data.add(new BaseAttributeKvEntry(new LongDataEntry("key2", 42L), System.currentTimeMillis())); -// BasicAttributeKVMsg kv = BasicAttributeKVMsg.fromClient(data); -// toDeviceMsg = BasicGetAttributesResponse.onSuccess(deviceMsg.getMsgType(), BasicRequest.DEFAULT_REQUEST_ID, kv); -// } -// if (toDeviceMsg != null) { -// sessionMsg.getSessionContext().onMsg(new BasicSessionActorToAdaptorMsg(sessionMsg.getSessionContext(), toDeviceMsg)); -// } -// } catch (Exception e) { -// e.printStackTrace(); -// } -// } -// } - - @Override - public void onDeviceAdded(Device device) { - - } - - - }; - } - - @Bean - public static HostRequestsQuotaService quotaService() { - return new HostRequestsQuotaService(null, null, null, null, false); - } - } - - @Autowired - private CoapTransportService service; - - @Before - public void beforeTest() { - log.info("Service info: {}", service.toString()); - } - - @Test - public void testBadJsonTelemetryPostRequest() { - CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.TELEMETRY.name().toLowerCase()); - CoapResponse response = client.setTimeout(6000).post("test", MediaTypeRegistry.APPLICATION_JSON); - Assert.assertEquals(ResponseCode.BAD_REQUEST, response.getCode()); - log.info("Response: {}, {}", response.getCode(), response.getResponseText()); - } - - @Test - public void testNoCredentialsPostRequest() { - CoapClient client = new CoapClient(getBaseTestUrl()); - CoapResponse response = client.setTimeout(6000).post(TELEMETRY_POST_MESSAGE, MediaTypeRegistry.APPLICATION_JSON); - Assert.assertEquals(ResponseCode.BAD_REQUEST, response.getCode()); - log.info("Response: {}, {}", response.getCode(), response.getResponseText()); - } - - @Test - public void testValidJsonTelemetryPostRequest() { - CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.TELEMETRY.name().toLowerCase()); - CoapResponse response = client.setTimeout(6000).post(TELEMETRY_POST_MESSAGE, MediaTypeRegistry.APPLICATION_JSON); - Assert.assertEquals(ResponseCode.CREATED, response.getCode()); - log.info("Response: {}, {}", response.getCode(), response.getResponseText()); - } - - @Test - public void testNoCredentialsAttributesGetRequest() { - CoapClient client = new CoapClient("coap://localhost:5555/api/v1?keys=key1,key2"); - CoapResponse response = client.setTimeout(6000).get(); - Assert.assertEquals(ResponseCode.BAD_REQUEST, response.getCode()); - } - - @Test - public void testNoKeysAttributesGetRequest() { - CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.ATTRIBUTES.name().toLowerCase() + "?data=key1,key2"); - CoapResponse response = client.setTimeout(6000).get(); - Assert.assertEquals(ResponseCode.CONTENT, response.getCode()); - } - - @Test - public void testValidAttributesGetRequest() { - CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.ATTRIBUTES.name().toLowerCase() + "?clientKeys=key1,key2"); - CoapResponse response = client.setTimeout(6000).get(); - Assert.assertEquals(ResponseCode.CONTENT, response.getCode()); - Assert.assertEquals(TEST_ATTRIBUTES_RESPONSE, response.getResponseText()); - log.info("Response: {}, {}", response.getCode(), response.getResponseText()); - } - - private String getBaseTestUrl() { - return "coap://localhost:" + TEST_PORT + "/api/v1/"; - } - -} diff --git a/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTestConfiguration.java b/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTestConfiguration.java deleted file mode 100644 index f2cd080293..0000000000 --- a/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTestConfiguration.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright © 2016-2018 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.coap; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; -import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; -import org.springframework.test.context.TestPropertySource; - -@Configuration -@ComponentScan({ "org.thingsboard.server.transport.coap" }) -@PropertySource("classpath:coap-transport-test.properties") -public class CoapServerTestConfiguration { - - @Bean - public static PropertySourcesPlaceholderConfigurer propertyConfig() { - return new PropertySourcesPlaceholderConfigurer(); - } - -} diff --git a/transport/coap/src/test/resources/coap-transport-test.properties b/transport/coap/src/test/resources/coap-transport-test.properties deleted file mode 100644 index 469b0348b1..0000000000 --- a/transport/coap/src/test/resources/coap-transport-test.properties +++ /dev/null @@ -1,4 +0,0 @@ -coap.bind_address=0.0.0.0 -coap.bind_port=5555 -coap.adaptor=JsonCoapAdaptor -coap.timeout=10000 \ No newline at end of file diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 225f064f17..407d995381 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -145,7 +145,7 @@ src/main/conf - tb-mqtt-transport.conf + tb-http-transport.conf true @@ -229,7 +229,7 @@ - ThingsBoard MQTT Transport Service + ThingsBoard HTTP Transport Service ${project.version} @@ -239,7 +239,7 @@ org.springframework.boot spring-boot-maven-plugin - org.thingsboard.server.mqtt.ThingsboardHttpTransportApplication + org.thingsboard.server.http.ThingsboardHttpTransportApplication boot ZIP true diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 2750e1e511..6395b40365 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -48,6 +48,10 @@ org.thingsboard.common queue + + org.springframework.boot + spring-boot-starter-web + com.sun.winsw winsw diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index e46de8e9ab..1d425e4761 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -14,6 +14,7 @@ # limitations under the License. # +spring.main.web-environment: false spring.main.web-application-type: none # MQTT server parameters diff --git a/transport/pom.xml b/transport/pom.xml index 281f511550..0bb9300b27 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -36,6 +36,7 @@ http mqtt + coap