165 changed files with 5093 additions and 768 deletions
@ -0,0 +1,53 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.service.sms; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.rule.engine.api.sms.SmsSender; |
||||
|
import org.thingsboard.rule.engine.api.sms.exception.SmsParseException; |
||||
|
|
||||
|
import java.util.regex.Pattern; |
||||
|
|
||||
|
@Slf4j |
||||
|
public abstract class AbstractSmsSender implements SmsSender { |
||||
|
|
||||
|
private static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$"); |
||||
|
|
||||
|
private static final int MAX_SMS_MESSAGE_LENGTH = 1600; |
||||
|
private static final int MAX_SMS_SEGMENT_LENGTH = 70; |
||||
|
|
||||
|
protected String validatePhoneNumber(String phoneNumber) throws SmsParseException { |
||||
|
phoneNumber = phoneNumber.trim(); |
||||
|
if (!E_164_PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches()) { |
||||
|
throw new SmsParseException("Invalid phone number format. Phone number must be in E.164 format."); |
||||
|
} |
||||
|
return phoneNumber; |
||||
|
} |
||||
|
|
||||
|
protected String prepareMessage(String message) { |
||||
|
message = message.replaceAll("^\"|\"$", "").replaceAll("\\\\n", "\n"); |
||||
|
if (message.length() > MAX_SMS_MESSAGE_LENGTH) { |
||||
|
log.warn("SMS message exceeds maximum symbols length and will be truncated"); |
||||
|
message = message.substring(0, MAX_SMS_MESSAGE_LENGTH); |
||||
|
} |
||||
|
return message; |
||||
|
} |
||||
|
|
||||
|
protected int countMessageSegments(String message) { |
||||
|
return (int)Math.ceil((double) message.length() / (double) MAX_SMS_SEGMENT_LENGTH); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.service.sms; |
||||
|
|
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.rule.engine.api.sms.SmsSender; |
||||
|
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; |
||||
|
import org.thingsboard.server.service.sms.aws.AwsSmsSender; |
||||
|
import org.thingsboard.server.service.sms.twilio.TwilioSmsSender; |
||||
|
|
||||
|
@Component |
||||
|
public class DefaultSmsSenderFactory implements SmsSenderFactory { |
||||
|
|
||||
|
@Override |
||||
|
public SmsSender createSmsSender(SmsProviderConfiguration config) { |
||||
|
switch (config.getType()) { |
||||
|
case AWS_SNS: |
||||
|
return new AwsSmsSender((AwsSnsSmsProviderConfiguration)config); |
||||
|
case TWILIO: |
||||
|
return new TwilioSmsSender((TwilioSmsProviderConfiguration)config); |
||||
|
default: |
||||
|
throw new RuntimeException("Unknown SMS provider type " + config.getType()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,143 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.service.sms; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.core.NestedRuntimeException; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.rule.engine.api.SmsService; |
||||
|
import org.thingsboard.rule.engine.api.sms.SmsSender; |
||||
|
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.SmsProviderConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; |
||||
|
import org.thingsboard.server.common.data.AdminSettings; |
||||
|
import org.thingsboard.server.common.data.ApiUsageRecordKey; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.dao.settings.AdminSettingsService; |
||||
|
import org.thingsboard.server.dao.util.mapping.JacksonUtil; |
||||
|
import org.thingsboard.server.queue.usagestats.TbApiUsageClient; |
||||
|
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import javax.annotation.PreDestroy; |
||||
|
|
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class DefaultSmsService implements SmsService { |
||||
|
|
||||
|
private final SmsSenderFactory smsSenderFactory; |
||||
|
private final AdminSettingsService adminSettingsService; |
||||
|
private final TbApiUsageStateService apiUsageStateService; |
||||
|
private final TbApiUsageClient apiUsageClient; |
||||
|
|
||||
|
private SmsSender smsSender; |
||||
|
|
||||
|
public DefaultSmsService(SmsSenderFactory smsSenderFactory, AdminSettingsService adminSettingsService, TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { |
||||
|
this.smsSenderFactory = smsSenderFactory; |
||||
|
this.adminSettingsService = adminSettingsService; |
||||
|
this.apiUsageStateService = apiUsageStateService; |
||||
|
this.apiUsageClient = apiUsageClient; |
||||
|
} |
||||
|
|
||||
|
@PostConstruct |
||||
|
private void init() { |
||||
|
updateSmsConfiguration(); |
||||
|
} |
||||
|
|
||||
|
@PreDestroy |
||||
|
private void destroy() { |
||||
|
if (this.smsSender != null) { |
||||
|
this.smsSender.destroy(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void updateSmsConfiguration() { |
||||
|
AdminSettings settings = adminSettingsService.findAdminSettingsByKey(new TenantId(EntityId.NULL_UUID), "sms"); |
||||
|
if (settings != null) { |
||||
|
try { |
||||
|
JsonNode jsonConfig = settings.getJsonValue(); |
||||
|
SmsProviderConfiguration configuration = JacksonUtil.convertValue(jsonConfig, SmsProviderConfiguration.class); |
||||
|
SmsSender newSmsSender = this.smsSenderFactory.createSmsSender(configuration); |
||||
|
if (this.smsSender != null) { |
||||
|
this.smsSender.destroy(); |
||||
|
} |
||||
|
this.smsSender = newSmsSender; |
||||
|
} catch (Exception e) { |
||||
|
log.error("Failed to create SMS sender", e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private int sendSms(String numberTo, String message) throws ThingsboardException { |
||||
|
if (this.smsSender == null) { |
||||
|
throw new ThingsboardException("Unable to send SMS: no SMS provider configured!", ThingsboardErrorCode.GENERAL); |
||||
|
} |
||||
|
return this.sendSms(this.smsSender, numberTo, message); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void sendSms(TenantId tenantId, String[] numbersTo, String message) throws ThingsboardException { |
||||
|
if (apiUsageStateService.getApiUsageState(tenantId).isSmsSendEnabled()) { |
||||
|
int smsCount = 0; |
||||
|
try { |
||||
|
for (String numberTo : numbersTo) { |
||||
|
smsCount += this.sendSms(numberTo, message); |
||||
|
} |
||||
|
} finally { |
||||
|
if (smsCount > 0) { |
||||
|
apiUsageClient.report(tenantId, ApiUsageRecordKey.SMS_EXEC_COUNT, smsCount); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException { |
||||
|
SmsSender testSmsSender; |
||||
|
try { |
||||
|
testSmsSender = this.smsSenderFactory.createSmsSender(testSmsRequest.getProviderConfiguration()); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
this.sendSms(testSmsSender, testSmsRequest.getNumberTo(), testSmsRequest.getMessage()); |
||||
|
testSmsSender.destroy(); |
||||
|
} |
||||
|
|
||||
|
private int sendSms(SmsSender smsSender, String numberTo, String message) throws ThingsboardException { |
||||
|
try { |
||||
|
return smsSender.sendSms(numberTo, message); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private ThingsboardException handleException(Exception exception) { |
||||
|
String message; |
||||
|
if (exception instanceof NestedRuntimeException) { |
||||
|
message = ((NestedRuntimeException) exception).getMostSpecificCause().getMessage(); |
||||
|
} else { |
||||
|
message = exception.getMessage(); |
||||
|
} |
||||
|
log.warn("Unable to send SMS: {}", message); |
||||
|
return new ThingsboardException(String.format("Unable to send SMS: %s", message), |
||||
|
ThingsboardErrorCode.GENERAL); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.service.sms; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.common.util.AbstractListeningExecutor; |
||||
|
|
||||
|
@Component |
||||
|
public class SmsExecutorService extends AbstractListeningExecutor { |
||||
|
|
||||
|
@Value("${actors.rule.sms_thread_pool_size}") |
||||
|
private int smsExecutorThreadPoolSize; |
||||
|
|
||||
|
@Override |
||||
|
protected int getThreadPollSize() { |
||||
|
return smsExecutorThreadPoolSize; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.service.sms.aws; |
||||
|
|
||||
|
import com.amazonaws.auth.AWSCredentials; |
||||
|
import com.amazonaws.auth.AWSStaticCredentialsProvider; |
||||
|
import com.amazonaws.auth.BasicAWSCredentials; |
||||
|
import com.amazonaws.services.sns.AmazonSNS; |
||||
|
import com.amazonaws.services.sns.AmazonSNSClient; |
||||
|
import com.amazonaws.services.sns.model.PublishRequest; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.AwsSnsSmsProviderConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.sms.exception.SmsException; |
||||
|
import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; |
||||
|
import org.thingsboard.server.service.sms.AbstractSmsSender; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class AwsSmsSender extends AbstractSmsSender { |
||||
|
|
||||
|
private AmazonSNS snsClient; |
||||
|
|
||||
|
public AwsSmsSender(AwsSnsSmsProviderConfiguration config) { |
||||
|
if (StringUtils.isEmpty(config.getAccessKeyId()) || StringUtils.isEmpty(config.getSecretAccessKey()) || StringUtils.isEmpty(config.getRegion())) { |
||||
|
throw new IllegalArgumentException("Invalid AWS sms provider configuration: aws accessKeyId, aws secretAccessKey and aws region should be specified!"); |
||||
|
} |
||||
|
AWSCredentials awsCredentials = new BasicAWSCredentials(config.getAccessKeyId(), config.getSecretAccessKey()); |
||||
|
AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials); |
||||
|
this.snsClient = AmazonSNSClient.builder() |
||||
|
.withCredentials(credProvider) |
||||
|
.withRegion(config.getRegion()) |
||||
|
.build(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int sendSms(String numberTo, String message) throws SmsException { |
||||
|
numberTo = this.validatePhoneNumber(numberTo); |
||||
|
message = this.prepareMessage(message); |
||||
|
try { |
||||
|
PublishRequest publishRequest = new PublishRequest() |
||||
|
.withPhoneNumber(numberTo) |
||||
|
.withMessage(message); |
||||
|
this.snsClient.publish(publishRequest); |
||||
|
return this.countMessageSegments(message); |
||||
|
} catch (Exception e) { |
||||
|
throw new SmsSendException("Failed to send SMS message - " + e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void destroy() { |
||||
|
if (this.snsClient != null) { |
||||
|
try { |
||||
|
this.snsClient.shutdown(); |
||||
|
} catch (Exception e) { |
||||
|
log.error("Failed to shutdown SNS client during destroy()", e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.service.sms.twilio; |
||||
|
|
||||
|
import com.twilio.http.TwilioRestClient; |
||||
|
import com.twilio.rest.api.v2010.account.Message; |
||||
|
import com.twilio.type.PhoneNumber; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.thingsboard.rule.engine.api.sms.config.TwilioSmsProviderConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.sms.exception.SmsException; |
||||
|
import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; |
||||
|
import org.thingsboard.server.service.sms.AbstractSmsSender; |
||||
|
|
||||
|
public class TwilioSmsSender extends AbstractSmsSender { |
||||
|
|
||||
|
private TwilioRestClient twilioRestClient; |
||||
|
private String numberFrom; |
||||
|
|
||||
|
public TwilioSmsSender(TwilioSmsProviderConfiguration config) { |
||||
|
if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) { |
||||
|
throw new IllegalArgumentException("Invalid twilio sms provider configuration: accountSid, accountToken and numberFrom should be specified!"); |
||||
|
} |
||||
|
this.numberFrom = this.validatePhoneNumber(config.getNumberFrom()); |
||||
|
this.twilioRestClient = new TwilioRestClient.Builder(config.getAccountSid(), config.getAccountToken()).build(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int sendSms(String numberTo, String message) throws SmsException { |
||||
|
numberTo = this.validatePhoneNumber(numberTo); |
||||
|
message = this.prepareMessage(message); |
||||
|
try { |
||||
|
String numSegments = Message.creator(new PhoneNumber(numberTo), new PhoneNumber(this.numberFrom), message).create(this.twilioRestClient).getNumSegments(); |
||||
|
return Integer.valueOf(numSegments); |
||||
|
} catch (Exception e) { |
||||
|
throw new SmsSendException("Failed to send SMS message - " + e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void destroy() { |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,161 @@ |
|||||
|
<#-- |
||||
|
|
||||
|
Copyright © 2016-2020 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. |
||||
|
|
||||
|
--> |
||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
||||
|
<html xmlns="http://www.w3.org/1999/xhtml" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<head> |
||||
|
<meta name="viewport" content="width=device-width"/> |
||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> |
||||
|
<title>Thingsboard - Api Usage State</title> |
||||
|
|
||||
|
|
||||
|
<style type="text/css"> |
||||
|
img { |
||||
|
max-width: 100%; |
||||
|
} |
||||
|
|
||||
|
body { |
||||
|
-webkit-font-smoothing: antialiased; |
||||
|
-webkit-text-size-adjust: none; |
||||
|
width: 100% !important; |
||||
|
height: 100%; |
||||
|
line-height: 1.6em; |
||||
|
} |
||||
|
|
||||
|
body { |
||||
|
background-color: #f6f6f6; |
||||
|
} |
||||
|
|
||||
|
@media only screen and (max-width: 640px) { |
||||
|
body { |
||||
|
padding: 0 !important; |
||||
|
} |
||||
|
|
||||
|
h1 { |
||||
|
font-weight: 800 !important; |
||||
|
margin: 20px 0 5px !important; |
||||
|
} |
||||
|
|
||||
|
h2 { |
||||
|
font-weight: 800 !important; |
||||
|
margin: 20px 0 5px !important; |
||||
|
} |
||||
|
|
||||
|
h3 { |
||||
|
font-weight: 800 !important; |
||||
|
margin: 20px 0 5px !important; |
||||
|
} |
||||
|
|
||||
|
h4 { |
||||
|
font-weight: 800 !important; |
||||
|
margin: 20px 0 5px !important; |
||||
|
} |
||||
|
|
||||
|
h1 { |
||||
|
font-size: 22px !important; |
||||
|
} |
||||
|
|
||||
|
h2 { |
||||
|
font-size: 18px !important; |
||||
|
} |
||||
|
|
||||
|
h3 { |
||||
|
font-size: 16px !important; |
||||
|
} |
||||
|
|
||||
|
.container { |
||||
|
padding: 0 !important; |
||||
|
width: 100% !important; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
padding: 0 !important; |
||||
|
} |
||||
|
|
||||
|
.content-wrap { |
||||
|
padding: 10px !important; |
||||
|
} |
||||
|
|
||||
|
.invoice { |
||||
|
width: 100% !important; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
|
||||
|
<body itemscope itemtype="http://schema.org/EmailMessage" |
||||
|
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;" |
||||
|
bgcolor="#f6f6f6"> |
||||
|
|
||||
|
<table class="main" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;" |
||||
|
cellspacing="0" cellpadding="0" bgcolor="#f6f6f6"> |
||||
|
<tbody> |
||||
|
<tr style="box-sizing: border-box; margin: 0px;"> |
||||
|
<td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" |
||||
|
align="center" valign="top"> |
||||
|
<table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 223px; padding: 20px; background-color: #ffffff; width: 600px; max-width: 600px !important;" |
||||
|
width="600" cellspacing="0" cellpadding="0"> |
||||
|
<tbody> |
||||
|
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<td class="content-block" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 84px;" |
||||
|
valign="top"> |
||||
|
<h2>Your ThingsBoard account feature was disabled</h2> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<td class="content-block" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;" |
||||
|
valign="top">We have disabled the ${apiFeature} for your account because ThingsBoard has already ${apiLimitValueLabel}. |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<td class="content-block" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 59px;" |
||||
|
valign="top">Please contact your system administrator to resolve the issue. |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<td class="content-block" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;" |
||||
|
valign="top">— The ThingsBoard |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" |
||||
|
cellpadding="0px 0px 20px"> |
||||
|
<tbody> |
||||
|
<tr style="box-sizing: border-box; margin: 0px;"> |
||||
|
<td class="aligncenter content-block" |
||||
|
style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;" |
||||
|
align="center" valign="top">This email was sent to <a |
||||
|
style="box-sizing: border-box; color: #999999; margin: 0px;" |
||||
|
href="mailto:${targetEmail}">${targetEmail}</a> by ThingsBoard. |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</body> |
||||
|
</html> |
||||
@ -0,0 +1,155 @@ |
|||||
|
<#-- |
||||
|
|
||||
|
Copyright © 2016-2020 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. |
||||
|
|
||||
|
--> |
||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
||||
|
<html xmlns="http://www.w3.org/1999/xhtml" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<head> |
||||
|
<meta name="viewport" content="width=device-width"/> |
||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> |
||||
|
<title>Thingsboard - Api Usage State</title> |
||||
|
|
||||
|
|
||||
|
<style type="text/css"> |
||||
|
img { |
||||
|
max-width: 100%; |
||||
|
} |
||||
|
|
||||
|
body { |
||||
|
-webkit-font-smoothing: antialiased; |
||||
|
-webkit-text-size-adjust: none; |
||||
|
width: 100% !important; |
||||
|
height: 100%; |
||||
|
line-height: 1.6em; |
||||
|
} |
||||
|
|
||||
|
body { |
||||
|
background-color: #f6f6f6; |
||||
|
} |
||||
|
|
||||
|
@media only screen and (max-width: 640px) { |
||||
|
body { |
||||
|
padding: 0 !important; |
||||
|
} |
||||
|
|
||||
|
h1 { |
||||
|
font-weight: 800 !important; |
||||
|
margin: 20px 0 5px !important; |
||||
|
} |
||||
|
|
||||
|
h2 { |
||||
|
font-weight: 800 !important; |
||||
|
margin: 20px 0 5px !important; |
||||
|
} |
||||
|
|
||||
|
h3 { |
||||
|
font-weight: 800 !important; |
||||
|
margin: 20px 0 5px !important; |
||||
|
} |
||||
|
|
||||
|
h4 { |
||||
|
font-weight: 800 !important; |
||||
|
margin: 20px 0 5px !important; |
||||
|
} |
||||
|
|
||||
|
h1 { |
||||
|
font-size: 22px !important; |
||||
|
} |
||||
|
|
||||
|
h2 { |
||||
|
font-size: 18px !important; |
||||
|
} |
||||
|
|
||||
|
h3 { |
||||
|
font-size: 16px !important; |
||||
|
} |
||||
|
|
||||
|
.container { |
||||
|
padding: 0 !important; |
||||
|
width: 100% !important; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
padding: 0 !important; |
||||
|
} |
||||
|
|
||||
|
.content-wrap { |
||||
|
padding: 10px !important; |
||||
|
} |
||||
|
|
||||
|
.invoice { |
||||
|
width: 100% !important; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
|
||||
|
<body itemscope itemtype="http://schema.org/EmailMessage" |
||||
|
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;" |
||||
|
bgcolor="#f6f6f6"> |
||||
|
|
||||
|
<table class="main" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;" |
||||
|
cellspacing="0" cellpadding="0" bgcolor="#f6f6f6"> |
||||
|
<tbody> |
||||
|
<tr style="box-sizing: border-box; margin: 0px;"> |
||||
|
<td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" |
||||
|
align="center" valign="top"> |
||||
|
<table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 223px; padding: 20px; background-color: #ffffff; width: 600px; max-width: 600px !important;" |
||||
|
width="600" cellspacing="0" cellpadding="0"> |
||||
|
<tbody> |
||||
|
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<td class="content-block" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 84px;" |
||||
|
valign="top"> |
||||
|
<h2>Your ThingsBoard account feature was enabled</h2> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<td class="content-block" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;" |
||||
|
valign="top">We have enabled the ${apiFeature} for your account and ThingsBoard is already able to ${apiLabel} messages. |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<td class="content-block" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;" |
||||
|
valign="top">— The ThingsBoard |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" |
||||
|
cellpadding="0px 0px 20px"> |
||||
|
<tbody> |
||||
|
<tr style="box-sizing: border-box; margin: 0px;"> |
||||
|
<td class="aligncenter content-block" |
||||
|
style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;" |
||||
|
align="center" valign="top">This email was sent to <a |
||||
|
style="box-sizing: border-box; color: #999999; margin: 0px;" |
||||
|
href="mailto:${targetEmail}">${targetEmail}</a> by ThingsBoard. |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</body> |
||||
|
</html> |
||||
@ -0,0 +1,162 @@ |
|||||
|
<#-- |
||||
|
|
||||
|
Copyright © 2016-2020 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. |
||||
|
|
||||
|
--> |
||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
||||
|
<html xmlns="http://www.w3.org/1999/xhtml" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<head> |
||||
|
<meta name="viewport" content="width=device-width"/> |
||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> |
||||
|
<title>Thingsboard - Api Usage State</title> |
||||
|
|
||||
|
|
||||
|
<style type="text/css"> |
||||
|
img { |
||||
|
max-width: 100%; |
||||
|
} |
||||
|
|
||||
|
body { |
||||
|
-webkit-font-smoothing: antialiased; |
||||
|
-webkit-text-size-adjust: none; |
||||
|
width: 100% !important; |
||||
|
height: 100%; |
||||
|
line-height: 1.6em; |
||||
|
} |
||||
|
|
||||
|
body { |
||||
|
background-color: #f6f6f6; |
||||
|
} |
||||
|
|
||||
|
@media only screen and (max-width: 640px) { |
||||
|
body { |
||||
|
padding: 0 !important; |
||||
|
} |
||||
|
|
||||
|
h1 { |
||||
|
font-weight: 800 !important; |
||||
|
margin: 20px 0 5px !important; |
||||
|
} |
||||
|
|
||||
|
h2 { |
||||
|
font-weight: 800 !important; |
||||
|
margin: 20px 0 5px !important; |
||||
|
} |
||||
|
|
||||
|
h3 { |
||||
|
font-weight: 800 !important; |
||||
|
margin: 20px 0 5px !important; |
||||
|
} |
||||
|
|
||||
|
h4 { |
||||
|
font-weight: 800 !important; |
||||
|
margin: 20px 0 5px !important; |
||||
|
} |
||||
|
|
||||
|
h1 { |
||||
|
font-size: 22px !important; |
||||
|
} |
||||
|
|
||||
|
h2 { |
||||
|
font-size: 18px !important; |
||||
|
} |
||||
|
|
||||
|
h3 { |
||||
|
font-size: 16px !important; |
||||
|
} |
||||
|
|
||||
|
.container { |
||||
|
padding: 0 !important; |
||||
|
width: 100% !important; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
padding: 0 !important; |
||||
|
} |
||||
|
|
||||
|
.content-wrap { |
||||
|
padding: 10px !important; |
||||
|
} |
||||
|
|
||||
|
.invoice { |
||||
|
width: 100% !important; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
|
||||
|
<body itemscope itemtype="http://schema.org/EmailMessage" |
||||
|
style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;" |
||||
|
bgcolor="#f6f6f6"> |
||||
|
|
||||
|
<table class="main" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;" |
||||
|
cellspacing="0" cellpadding="0" bgcolor="#f6f6f6"> |
||||
|
<tbody> |
||||
|
<tr style="box-sizing: border-box; margin: 0px;"> |
||||
|
<td class="content-wrap" style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 20px;" |
||||
|
align="center" valign="top"> |
||||
|
<table style="box-sizing: border-box; border: 1px solid #e9e9e9; border-radius: 3px; margin: 0px; height: 223px; padding: 20px; background-color: #ffffff; width: 600px; max-width: 600px !important;" |
||||
|
width="600" cellspacing="0" cellpadding="0"> |
||||
|
<tbody> |
||||
|
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<td class="content-block" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #348eda; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 84px;" |
||||
|
valign="top"> |
||||
|
<h2>Your ThingsBoard account feature may be disabled soon</h2> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<td class="content-block" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;" |
||||
|
valign="top"> |
||||
|
ThingsBoard has already ${apiValueLabel}.<br> ${apiFeature} will be disabled for your account once the limit will be reached. |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<td class="content-block" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 59px;" |
||||
|
valign="top">Please contact your system administrator to resolve the issue. |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
|
<td class="content-block" |
||||
|
style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0px; padding: 0px 0px 20px; height: 40px;" |
||||
|
valign="top">— The ThingsBoard |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<table style="color: #999999; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; box-sizing: border-box; margin: 0px auto; height: 64px; background-color: #f6f6f6; width: 100%;" |
||||
|
cellpadding="0px 0px 20px"> |
||||
|
<tbody> |
||||
|
<tr style="box-sizing: border-box; margin: 0px;"> |
||||
|
<td class="aligncenter content-block" |
||||
|
style="box-sizing: border-box; font-size: 12px; margin: 0px; padding: 0px 0px 20px; width: 600px; text-align: center; vertical-align: middle;" |
||||
|
align="center" valign="top">This email was sent to <a |
||||
|
style="box-sizing: border-box; color: #999999; margin: 0px;" |
||||
|
href="mailto:${targetEmail}">${targetEmail}</a> by ThingsBoard. |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</body> |
||||
|
</html> |
||||
@ -0,0 +1,25 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.common.data; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class ApiUsageStateMailMessage { |
||||
|
private final ApiUsageRecordKey key; |
||||
|
private final long threshold; |
||||
|
private final long value; |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.common.data.device.profile; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.TransportPayloadType; |
||||
|
|
||||
|
@Data |
||||
|
public class JsonTransportPayloadConfiguration implements TransportPayloadTypeConfiguration { |
||||
|
|
||||
|
@Override |
||||
|
public TransportPayloadType getTransportPayloadType() { |
||||
|
return TransportPayloadType.JSON; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,200 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.common.data.device.profile; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import com.github.os72.protobuf.dynamic.DynamicSchema; |
||||
|
import com.github.os72.protobuf.dynamic.EnumDefinition; |
||||
|
import com.github.os72.protobuf.dynamic.MessageDefinition; |
||||
|
import com.google.protobuf.Descriptors; |
||||
|
import com.google.protobuf.DynamicMessage; |
||||
|
import com.squareup.wire.schema.Location; |
||||
|
import com.squareup.wire.schema.internal.parser.EnumConstantElement; |
||||
|
import com.squareup.wire.schema.internal.parser.EnumElement; |
||||
|
import com.squareup.wire.schema.internal.parser.FieldElement; |
||||
|
import com.squareup.wire.schema.internal.parser.MessageElement; |
||||
|
import com.squareup.wire.schema.internal.parser.OneOfElement; |
||||
|
import com.squareup.wire.schema.internal.parser.ProtoFileElement; |
||||
|
import com.squareup.wire.schema.internal.parser.ProtoParser; |
||||
|
import com.squareup.wire.schema.internal.parser.TypeElement; |
||||
|
import lombok.Data; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.server.common.data.TransportPayloadType; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Data |
||||
|
public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeConfiguration { |
||||
|
|
||||
|
public static final Location LOCATION = new Location("", "", -1, -1); |
||||
|
public static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; |
||||
|
public static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; |
||||
|
|
||||
|
private String deviceTelemetryProtoSchema; |
||||
|
private String deviceAttributesProtoSchema; |
||||
|
|
||||
|
@Override |
||||
|
public TransportPayloadType getTransportPayloadType() { |
||||
|
return TransportPayloadType.PROTOBUF; |
||||
|
} |
||||
|
|
||||
|
public Descriptors.Descriptor getTelemetryDynamicMessageDescriptor(String deviceTelemetryProtoSchema) { |
||||
|
return getDescriptor(deviceTelemetryProtoSchema, TELEMETRY_PROTO_SCHEMA); |
||||
|
} |
||||
|
|
||||
|
public Descriptors.Descriptor getAttributesDynamicMessageDescriptor(String deviceAttributesProtoSchema) { |
||||
|
return getDescriptor(deviceAttributesProtoSchema, ATTRIBUTES_PROTO_SCHEMA); |
||||
|
} |
||||
|
|
||||
|
private Descriptors.Descriptor getDescriptor(String protoSchema, String schemaName) { |
||||
|
try { |
||||
|
ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); |
||||
|
DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName); |
||||
|
String lastMsgName = getMessageTypes(protoFileElement.getTypes()).stream() |
||||
|
.map(MessageElement::getName).reduce((previous, last) -> last).get(); |
||||
|
DynamicMessage.Builder builder = dynamicSchema.newMessageBuilder(lastMsgName); |
||||
|
return builder.getDescriptorForType(); |
||||
|
} catch (Exception e) { |
||||
|
log.warn("Failed to get Message Descriptor due to {}", e.getMessage()); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public DynamicSchema getDynamicSchema(ProtoFileElement protoFileElement, String schemaName) { |
||||
|
DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder(); |
||||
|
schemaBuilder.setName(schemaName); |
||||
|
schemaBuilder.setPackage(!isEmptyStr(protoFileElement.getPackageName()) ? |
||||
|
protoFileElement.getPackageName() : schemaName.toLowerCase()); |
||||
|
List<TypeElement> types = protoFileElement.getTypes(); |
||||
|
List<MessageElement> messageTypes = getMessageTypes(types); |
||||
|
|
||||
|
if (!messageTypes.isEmpty()) { |
||||
|
List<EnumElement> enumTypes = getEnumElements(types); |
||||
|
if (!enumTypes.isEmpty()) { |
||||
|
enumTypes.forEach(enumElement -> { |
||||
|
EnumDefinition enumDefinition = getEnumDefinition(enumElement); |
||||
|
schemaBuilder.addEnumDefinition(enumDefinition); |
||||
|
}); |
||||
|
} |
||||
|
List<MessageDefinition> messageDefinitions = getMessageDefinitions(messageTypes); |
||||
|
messageDefinitions.forEach(schemaBuilder::addMessageDefinition); |
||||
|
try { |
||||
|
return schemaBuilder.build(); |
||||
|
} catch (Descriptors.DescriptorValidationException e) { |
||||
|
throw new RuntimeException("Failed to create dynamic schema due to: " + e.getMessage()); |
||||
|
} |
||||
|
} else { |
||||
|
throw new RuntimeException("Failed to get Dynamic Schema! Message types is empty for schema:" + schemaName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public ProtoFileElement getTransportProtoSchema(String protoSchema) { |
||||
|
return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); |
||||
|
} |
||||
|
|
||||
|
private List<MessageElement> getMessageTypes(List<TypeElement> types) { |
||||
|
return types.stream() |
||||
|
.filter(typeElement -> typeElement instanceof MessageElement) |
||||
|
.map(typeElement -> (MessageElement) typeElement) |
||||
|
.collect(Collectors.toList()); |
||||
|
} |
||||
|
|
||||
|
private List<EnumElement> getEnumElements(List<TypeElement> types) { |
||||
|
return types.stream() |
||||
|
.filter(typeElement -> typeElement instanceof EnumElement) |
||||
|
.map(typeElement -> (EnumElement) typeElement) |
||||
|
.collect(Collectors.toList()); |
||||
|
} |
||||
|
|
||||
|
private List<MessageDefinition> getMessageDefinitions(List<MessageElement> messageElementsList) { |
||||
|
if (!messageElementsList.isEmpty()) { |
||||
|
List<MessageDefinition> messageDefinitions = new ArrayList<>(); |
||||
|
messageElementsList.forEach(messageElement -> { |
||||
|
MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); |
||||
|
|
||||
|
List<TypeElement> nestedTypes = messageElement.getNestedTypes(); |
||||
|
if (!nestedTypes.isEmpty()) { |
||||
|
List<EnumElement> nestedEnumTypes = getEnumElements(nestedTypes); |
||||
|
if (!nestedEnumTypes.isEmpty()) { |
||||
|
nestedEnumTypes.forEach(enumElement -> { |
||||
|
EnumDefinition nestedEnumDefinition = getEnumDefinition(enumElement); |
||||
|
messageDefinitionBuilder.addEnumDefinition(nestedEnumDefinition); |
||||
|
}); |
||||
|
} |
||||
|
List<MessageElement> nestedMessageTypes = getMessageTypes(nestedTypes); |
||||
|
List<MessageDefinition> nestedMessageDefinitions = getMessageDefinitions(nestedMessageTypes); |
||||
|
nestedMessageDefinitions.forEach(messageDefinitionBuilder::addMessageDefinition); |
||||
|
} |
||||
|
List<FieldElement> messageElementFields = messageElement.getFields(); |
||||
|
List<OneOfElement> oneOfs = messageElement.getOneOfs(); |
||||
|
if (!oneOfs.isEmpty()) { |
||||
|
for (OneOfElement oneOfelement : oneOfs) { |
||||
|
MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); |
||||
|
addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); |
||||
|
} |
||||
|
} |
||||
|
if (!messageElementFields.isEmpty()) { |
||||
|
addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); |
||||
|
} |
||||
|
messageDefinitions.add(messageDefinitionBuilder.build()); |
||||
|
}); |
||||
|
return messageDefinitions; |
||||
|
} else { |
||||
|
return Collections.emptyList(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private EnumDefinition getEnumDefinition(EnumElement enumElement) { |
||||
|
List<EnumConstantElement> enumElementTypeConstants = enumElement.getConstants(); |
||||
|
EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); |
||||
|
if (!enumElementTypeConstants.isEmpty()) { |
||||
|
enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); |
||||
|
} |
||||
|
return enumDefinitionBuilder.build(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private void addMessageFieldsToTheMessageDefinition(List<FieldElement> messageElementFields, MessageDefinition.Builder messageDefinitionBuilder) { |
||||
|
messageElementFields.forEach(fieldElement -> { |
||||
|
String labelStr = null; |
||||
|
if (fieldElement.getLabel() != null) { |
||||
|
labelStr = fieldElement.getLabel().name().toLowerCase(); |
||||
|
} |
||||
|
messageDefinitionBuilder.addField( |
||||
|
labelStr, |
||||
|
fieldElement.getType(), |
||||
|
fieldElement.getName(), |
||||
|
fieldElement.getTag()); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void addMessageFieldsToTheOneOfDefinition(List<FieldElement> oneOfsElementFields, MessageDefinition.OneofBuilder oneofBuilder) { |
||||
|
oneOfsElementFields.forEach(fieldElement -> oneofBuilder.addField( |
||||
|
fieldElement.getType(), |
||||
|
fieldElement.getName(), |
||||
|
fieldElement.getTag())); |
||||
|
oneofBuilder.msgDefBuilder(); |
||||
|
} |
||||
|
|
||||
|
private boolean isEmptyStr(String str) { |
||||
|
return str == null || "".equals(str); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.common.data.device.profile; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes; |
||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
||||
|
import org.thingsboard.server.common.data.TransportPayloadType; |
||||
|
|
||||
|
@JsonIgnoreProperties(ignoreUnknown = true) |
||||
|
@JsonTypeInfo( |
||||
|
use = JsonTypeInfo.Id.NAME, |
||||
|
include = JsonTypeInfo.As.PROPERTY, |
||||
|
property = "transportPayloadType") |
||||
|
@JsonSubTypes({ |
||||
|
@JsonSubTypes.Type(value = JsonTransportPayloadConfiguration.class, name = "JSON"), |
||||
|
@JsonSubTypes.Type(value = ProtoTransportPayloadConfiguration.class, name = "PROTOBUF")}) |
||||
|
public interface TransportPayloadTypeConfiguration { |
||||
|
|
||||
|
@JsonIgnore |
||||
|
TransportPayloadType getTransportPayloadType(); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.common.transport.session; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.DeviceProfile; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos; |
||||
|
|
||||
|
import java.util.Optional; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
public interface SessionContext { |
||||
|
|
||||
|
UUID getSessionId(); |
||||
|
|
||||
|
int nextMsgId(); |
||||
|
|
||||
|
void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile); |
||||
|
|
||||
|
void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt); |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.rule.engine.api; |
||||
|
|
||||
|
import org.thingsboard.rule.engine.api.sms.config.TestSmsRequest; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
public interface SmsService { |
||||
|
|
||||
|
void updateSmsConfiguration(); |
||||
|
|
||||
|
void sendSms(TenantId tenantId, String[] numbersTo, String message) throws ThingsboardException;; |
||||
|
|
||||
|
void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException; |
||||
|
|
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue