51 changed files with 895 additions and 261 deletions
@ -0,0 +1,54 @@ |
|||
/** |
|||
* 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.service.mail; |
|||
|
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import com.google.common.util.concurrent.ListeningExecutorService; |
|||
import com.google.common.util.concurrent.MoreExecutors; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.rule.engine.api.ListeningExecutor; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.concurrent.Callable; |
|||
import java.util.concurrent.Executors; |
|||
|
|||
@Component |
|||
public class MailExecutorService implements ListeningExecutor { |
|||
|
|||
@Value("${actors.rule.mail_thread_pool_size}") |
|||
private int mailExecutorThreadPoolSize; |
|||
|
|||
private ListeningExecutorService service; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
this.service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(mailExecutorThreadPoolSize)); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void destroy() { |
|||
if (this.service != null) { |
|||
this.service.shutdown(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public <T> ListenableFuture<T> executeAsync(Callable<T> task) { |
|||
return service.submit(task); |
|||
} |
|||
} |
|||
@ -0,0 +1,151 @@ |
|||
/** |
|||
* 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.service.script; |
|||
|
|||
import com.datastax.driver.core.utils.UUIDs; |
|||
import com.google.common.collect.Sets; |
|||
import org.junit.Test; |
|||
import org.thingsboard.rule.engine.api.ScriptEngine; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.TbMsgMetaData; |
|||
|
|||
import javax.script.ScriptException; |
|||
|
|||
import java.util.Set; |
|||
|
|||
import static org.junit.Assert.*; |
|||
|
|||
public class NashornJsEngineTest { |
|||
|
|||
private ScriptEngine scriptEngine; |
|||
|
|||
@Test |
|||
public void msgCanBeUpdated() throws ScriptException { |
|||
String function = "metadata.temp = metadata.temp * 10; return {metadata: metadata};"; |
|||
scriptEngine = new NashornJsEngine(function, "Transform"); |
|||
|
|||
TbMsgMetaData metaData = new TbMsgMetaData(); |
|||
metaData.putValue("temp", "7"); |
|||
metaData.putValue("humidity", "99"); |
|||
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; |
|||
|
|||
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); |
|||
|
|||
TbMsg actual = scriptEngine.executeUpdate(msg); |
|||
assertEquals("70", actual.getMetaData().getValue("temp")); |
|||
} |
|||
|
|||
@Test |
|||
public void newAttributesCanBeAddedInMsg() throws ScriptException { |
|||
String function = "metadata.newAttr = metadata.humidity - msg.passed; return {metadata: metadata};"; |
|||
scriptEngine = new NashornJsEngine(function, "Transform"); |
|||
TbMsgMetaData metaData = new TbMsgMetaData(); |
|||
metaData.putValue("temp", "7"); |
|||
metaData.putValue("humidity", "99"); |
|||
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; |
|||
|
|||
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); |
|||
|
|||
TbMsg actual = scriptEngine.executeUpdate(msg); |
|||
assertEquals("94", actual.getMetaData().getValue("newAttr")); |
|||
} |
|||
|
|||
@Test |
|||
public void payloadCanBeUpdated() throws ScriptException { |
|||
String function = "msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine'; return {msg: msg};"; |
|||
scriptEngine = new NashornJsEngine(function, "Transform"); |
|||
TbMsgMetaData metaData = new TbMsgMetaData(); |
|||
metaData.putValue("temp", "7"); |
|||
metaData.putValue("humidity", "99"); |
|||
String rawJson = "{\"name\":\"Vit\",\"passed\": 5,\"bigObj\":{\"prop\":42}}"; |
|||
|
|||
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); |
|||
|
|||
TbMsg actual = scriptEngine.executeUpdate(msg); |
|||
|
|||
String expectedJson = "{\"name\":\"Vit\",\"passed\":35,\"bigObj\":{\"prop\":42,\"newProp\":\"Ukraine\"}}"; |
|||
assertEquals(expectedJson, actual.getData()); |
|||
} |
|||
|
|||
@Test |
|||
public void metadataAccessibleForFilter() throws ScriptException { |
|||
String function = "return metadata.humidity < 15;"; |
|||
scriptEngine = new NashornJsEngine(function, "Filter"); |
|||
TbMsgMetaData metaData = new TbMsgMetaData(); |
|||
metaData.putValue("temp", "7"); |
|||
metaData.putValue("humidity", "99"); |
|||
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; |
|||
|
|||
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); |
|||
assertFalse(scriptEngine.executeFilter(msg)); |
|||
} |
|||
|
|||
@Test |
|||
public void dataAccessibleForFilter() throws ScriptException { |
|||
String function = "return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 7 && msg.bigObj.prop == 42;"; |
|||
scriptEngine = new NashornJsEngine(function, "Filter"); |
|||
TbMsgMetaData metaData = new TbMsgMetaData(); |
|||
metaData.putValue("temp", "7"); |
|||
metaData.putValue("humidity", "99"); |
|||
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; |
|||
|
|||
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); |
|||
assertTrue(scriptEngine.executeFilter(msg)); |
|||
} |
|||
|
|||
@Test |
|||
public void dataAccessibleForSwitch() throws ScriptException { |
|||
String jsCode = "function nextRelation(metadata, msg) {\n" + |
|||
" if(msg.passed == 5 && metadata.temp == 10)\n" + |
|||
" return 'one'\n" + |
|||
" else\n" + |
|||
" return 'two';\n" + |
|||
"};\n" + |
|||
"\n" + |
|||
"return nextRelation(metadata, msg);"; |
|||
scriptEngine = new NashornJsEngine(jsCode, "Switch"); |
|||
TbMsgMetaData metaData = new TbMsgMetaData(); |
|||
metaData.putValue("temp", "10"); |
|||
metaData.putValue("humidity", "99"); |
|||
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; |
|||
|
|||
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); |
|||
Set<String> actual = scriptEngine.executeSwitch(msg); |
|||
assertEquals(Sets.newHashSet("one"), actual); |
|||
} |
|||
|
|||
@Test |
|||
public void multipleRelationsReturnedFromSwitch() throws ScriptException { |
|||
String jsCode = "function nextRelation(metadata, msg) {\n" + |
|||
" if(msg.passed == 5 && metadata.temp == 10)\n" + |
|||
" return ['three', 'one']\n" + |
|||
" else\n" + |
|||
" return 'two';\n" + |
|||
"};\n" + |
|||
"\n" + |
|||
"return nextRelation(metadata, msg);"; |
|||
scriptEngine = new NashornJsEngine(jsCode, "Switch"); |
|||
TbMsgMetaData metaData = new TbMsgMetaData(); |
|||
metaData.putValue("temp", "10"); |
|||
metaData.putValue("humidity", "99"); |
|||
String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; |
|||
|
|||
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); |
|||
Set<String> actual = scriptEngine.executeSwitch(msg); |
|||
assertEquals(Sets.newHashSet("one", "three"), actual); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* 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.rule.engine.mail; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@Builder |
|||
class EmailPojo { |
|||
|
|||
private final String from; |
|||
private final String to; |
|||
private final String cc; |
|||
private final String bcc; |
|||
private final String subject; |
|||
private final String body; |
|||
|
|||
} |
|||
@ -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. |
|||
*/ |
|||
package org.thingsboard.rule.engine.mail; |
|||
|
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import org.apache.velocity.Template; |
|||
import org.apache.velocity.VelocityContext; |
|||
import org.apache.velocity.runtime.RuntimeServices; |
|||
import org.apache.velocity.runtime.RuntimeSingleton; |
|||
import org.apache.velocity.runtime.parser.ParseException; |
|||
import org.apache.velocity.runtime.parser.node.SimpleNode; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
|
|||
import java.io.IOException; |
|||
import java.io.StringReader; |
|||
import java.io.StringWriter; |
|||
import java.util.Map; |
|||
|
|||
import static org.thingsboard.server.common.msg.TbMsgDataType.JSON; |
|||
|
|||
public class RuleVelocityUtils { |
|||
|
|||
public static VelocityContext createContext(TbMsg msg) throws IOException { |
|||
VelocityContext context = new VelocityContext(); |
|||
context.put("originator", msg.getOriginator()); |
|||
context.put("type", msg.getType()); |
|||
context.put("metadata", msg.getMetaData().values()); |
|||
if (msg.getDataType() == JSON) { |
|||
Map map = new ObjectMapper().readValue(msg.getData(), Map.class); |
|||
context.put("msg", map); |
|||
} else { |
|||
context.put("msg", msg.getData()); |
|||
} |
|||
return context; |
|||
} |
|||
|
|||
public static String merge(Template template, VelocityContext context) { |
|||
StringWriter writer = new StringWriter(); |
|||
template.merge(context, writer); |
|||
return writer.toString(); |
|||
} |
|||
|
|||
public static Template create(String source, String templateName) throws ParseException { |
|||
RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices(); |
|||
StringReader reader = new StringReader(source); |
|||
SimpleNode node = runtimeServices.parse(reader, templateName); |
|||
Template template = new Template(); |
|||
template.setRuntimeServices(runtimeServices); |
|||
template.setData(node); |
|||
template.initDocument(); |
|||
return template; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
/** |
|||
* 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.rule.engine.mail; |
|||
|
|||
import com.datastax.driver.core.utils.UUIDs; |
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.velocity.Template; |
|||
import org.apache.velocity.VelocityContext; |
|||
import org.apache.velocity.runtime.parser.ParseException; |
|||
import org.springframework.util.StringUtils; |
|||
import org.thingsboard.rule.engine.TbNodeUtils; |
|||
import org.thingsboard.rule.engine.api.*; |
|||
import org.thingsboard.server.common.data.plugin.ComponentType; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.Optional; |
|||
|
|||
import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; |
|||
|
|||
@Slf4j |
|||
@RuleNode( |
|||
type = ComponentType.TRANSFORMATION, |
|||
name = "to email", |
|||
configClazz = TbMsgToEmailNodeConfiguration.class, |
|||
nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", |
|||
nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + |
|||
"If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ") |
|||
public class TbMsgToEmailNode implements TbNode { |
|||
|
|||
private static final ObjectMapper MAPPER = new ObjectMapper(); |
|||
|
|||
private TbMsgToEmailNodeConfiguration config; |
|||
|
|||
private Optional<Template> fromTemplate; |
|||
private Optional<Template> toTemplate; |
|||
private Optional<Template> ccTemplate; |
|||
private Optional<Template> bccTemplate; |
|||
private Optional<Template> subjectTemplate; |
|||
private Optional<Template> bodyTemplate; |
|||
|
|||
@Override |
|||
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { |
|||
this.config = TbNodeUtils.convert(configuration, TbMsgToEmailNodeConfiguration.class); |
|||
try { |
|||
fromTemplate = toTemplate(config.getFromTemplate(), "From Template"); |
|||
toTemplate = toTemplate(config.getToTemplate(), "To Template"); |
|||
ccTemplate = toTemplate(config.getCcTemplate(), "Cc Template"); |
|||
bccTemplate = toTemplate(config.getBccTemplate(), "Bcc Template"); |
|||
subjectTemplate = toTemplate(config.getSubjectTemplate(), "Subject Template"); |
|||
bodyTemplate = toTemplate(config.getBodyTemplate(), "Body Template"); |
|||
} catch (ParseException e) { |
|||
log.error("Failed to create templates based on provided configuration!", e); |
|||
throw new TbNodeException(e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onMsg(TbContext ctx, TbMsg msg) { |
|||
try { |
|||
EmailPojo email = convert(msg); |
|||
TbMsg emailMsg = buildEmailMsg(msg, email); |
|||
ctx.tellNext(emailMsg); |
|||
} catch (Exception ex) { |
|||
log.warn("Can not convert message to email " + ex.getMessage()); |
|||
ctx.tellError(msg, ex); |
|||
} |
|||
} |
|||
|
|||
private TbMsg buildEmailMsg(TbMsg msg, EmailPojo email) throws JsonProcessingException { |
|||
String emailJson = MAPPER.writeValueAsString(email); |
|||
return new TbMsg(UUIDs.timeBased(), SEND_EMAIL_TYPE, msg.getOriginator(), msg.getMetaData().copy(), emailJson); |
|||
} |
|||
|
|||
private EmailPojo convert(TbMsg msg) throws IOException { |
|||
EmailPojo.EmailPojoBuilder builder = EmailPojo.builder(); |
|||
VelocityContext context = RuleVelocityUtils.createContext(msg); |
|||
fromTemplate.ifPresent(t -> builder.from(RuleVelocityUtils.merge(t, context))); |
|||
toTemplate.ifPresent(t -> builder.to(RuleVelocityUtils.merge(t, context))); |
|||
ccTemplate.ifPresent(t -> builder.cc(RuleVelocityUtils.merge(t, context))); |
|||
bccTemplate.ifPresent(t -> builder.bcc(RuleVelocityUtils.merge(t, context))); |
|||
subjectTemplate.ifPresent(t -> builder.subject(RuleVelocityUtils.merge(t, context))); |
|||
bodyTemplate.ifPresent(t -> builder.body(RuleVelocityUtils.merge(t, context))); |
|||
return builder.build(); |
|||
} |
|||
|
|||
private Optional<Template> toTemplate(String source, String name) throws ParseException { |
|||
if (!StringUtils.isEmpty(source)) { |
|||
return Optional.of(RuleVelocityUtils.create(source, name)); |
|||
} else { |
|||
return Optional.empty(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void destroy() { |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* 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.rule.engine.mail; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.rule.engine.api.NodeConfiguration; |
|||
|
|||
@Data |
|||
public class TbMsgToEmailNodeConfiguration implements NodeConfiguration { |
|||
|
|||
private String fromTemplate; |
|||
private String toTemplate; |
|||
private String ccTemplate; |
|||
private String bccTemplate; |
|||
private String subjectTemplate; |
|||
private String bodyTemplate; |
|||
|
|||
@Override |
|||
public TbMsgToEmailNodeConfiguration defaultConfiguration() { |
|||
TbMsgToEmailNodeConfiguration configuration = new TbMsgToEmailNodeConfiguration(); |
|||
configuration.fromTemplate = "info@testmail.org"; |
|||
configuration.toTemplate = "$metadata.userEmail"; |
|||
configuration.subjectTemplate = "Device $deviceType temperature high"; |
|||
configuration.bodyTemplate = "Device $metadata.deviceName has high temperature $msg.temp"; |
|||
|
|||
return configuration; |
|||
} |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
/** |
|||
* 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.rule.engine.mail; |
|||
|
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.thingsboard.rule.engine.TbNodeUtils; |
|||
import org.thingsboard.rule.engine.api.*; |
|||
import org.thingsboard.server.common.data.plugin.ComponentType; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
|||
|
|||
@Slf4j |
|||
@RuleNode( |
|||
type = ComponentType.ACTION, |
|||
name = "send email", |
|||
configClazz = TbSendEmailNodeConfiguration.class, |
|||
nodeDescription = "Log incoming messages using JS script for transformation Message into String", |
|||
nodeDetails = "Transform incoming Message with configured JS condition to String and log final value. " + |
|||
"Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" + |
|||
"Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>") |
|||
public class TbSendEmailNode implements TbNode { |
|||
|
|||
static final String SEND_EMAIL_TYPE = "SEND_EMAIL"; |
|||
private static final ObjectMapper MAPPER = new ObjectMapper(); |
|||
|
|||
private TbSendEmailNodeConfiguration config; |
|||
|
|||
@Override |
|||
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { |
|||
this.config = TbNodeUtils.convert(configuration, TbSendEmailNodeConfiguration.class); |
|||
} |
|||
|
|||
@Override |
|||
public void onMsg(TbContext ctx, TbMsg msg) { |
|||
try { |
|||
validateType(msg.getType()); |
|||
EmailPojo email = getEmail(msg); |
|||
withCallback(ctx.getMailExecutor().executeAsync(() -> { |
|||
ctx.getMailService().send(email.getFrom(), email.getTo(), email.getCc(), |
|||
email.getBcc(), email.getSubject(), email.getBody()); |
|||
return null; |
|||
}), |
|||
ok -> ctx.tellNext(msg), |
|||
fail -> ctx.tellError(msg, fail)); |
|||
} catch (Exception ex) { |
|||
ctx.tellError(msg, ex); |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
private EmailPojo getEmail(TbMsg msg) throws IOException { |
|||
EmailPojo email = MAPPER.readValue(msg.getData(), EmailPojo.class); |
|||
if (StringUtils.isBlank(email.getTo())) { |
|||
throw new IllegalStateException("Email destination can not be blank [" + email.getTo() + "]"); |
|||
} |
|||
return email; |
|||
} |
|||
|
|||
private void validateType(String type) { |
|||
if (!SEND_EMAIL_TYPE.equals(type)) { |
|||
log.warn("Not expected msg type [{}] for SendEmail Node", type); |
|||
throw new IllegalStateException("Not expected msg type " + type + " for SendEmail Node"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void destroy() { |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* 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.rule.engine.mail; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.rule.engine.api.NodeConfiguration; |
|||
|
|||
@Data |
|||
public class TbSendEmailNodeConfiguration implements NodeConfiguration { |
|||
|
|||
private String tmp; |
|||
|
|||
@Override |
|||
public TbSendEmailNodeConfiguration defaultConfiguration() { |
|||
TbSendEmailNodeConfiguration configuration = new TbSendEmailNodeConfiguration(); |
|||
configuration.tmp = ""; |
|||
return configuration; |
|||
} |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
/** |
|||
* 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.rule.engine.mail; |
|||
|
|||
import com.datastax.driver.core.utils.UUIDs; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import org.junit.Test; |
|||
import org.junit.runner.RunWith; |
|||
import org.mockito.ArgumentCaptor; |
|||
import org.mockito.Mock; |
|||
import org.mockito.runners.MockitoJUnitRunner; |
|||
import org.thingsboard.rule.engine.api.TbContext; |
|||
import org.thingsboard.rule.engine.api.TbNodeConfiguration; |
|||
import org.thingsboard.rule.engine.api.TbNodeException; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.TbMsgMetaData; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotSame; |
|||
import static org.mockito.Mockito.verify; |
|||
|
|||
@RunWith(MockitoJUnitRunner.class) |
|||
public class TbMsgToEmailNodeTest { |
|||
|
|||
private TbMsgToEmailNode emailNode; |
|||
|
|||
@Mock |
|||
private TbContext ctx; |
|||
|
|||
private EntityId originator = new DeviceId(UUIDs.timeBased()); |
|||
private TbMsgMetaData metaData = new TbMsgMetaData(); |
|||
private String rawJson = "{\"name\": \"temp\", \"passed\": 5 , \"complex\": {\"val\":12, \"count\":100}}"; |
|||
|
|||
@Test |
|||
public void msgCanBeConverted() throws IOException { |
|||
initWithScript(); |
|||
metaData.putValue("username", "oreo"); |
|||
metaData.putValue("userEmail", "user@email.io"); |
|||
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson); |
|||
|
|||
emailNode.onMsg(ctx, msg); |
|||
|
|||
ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class); |
|||
verify(ctx).tellNext(captor.capture()); |
|||
TbMsg actualMsg = captor.getValue(); |
|||
|
|||
assertEquals("SEND_EMAIL", actualMsg.getType()); |
|||
assertEquals(originator, actualMsg.getOriginator()); |
|||
assertEquals("oreo", actualMsg.getMetaData().getValue("username")); |
|||
assertNotSame(metaData, actualMsg.getMetaData()); |
|||
|
|||
|
|||
EmailPojo actual = new ObjectMapper().readValue(actualMsg.getData().getBytes(), EmailPojo.class); |
|||
|
|||
EmailPojo expected = new EmailPojo.EmailPojoBuilder() |
|||
.from("test@mail.org") |
|||
.to("user@email.io") |
|||
.subject("Hi oreo there") |
|||
.body("temp is to high. Current 5 and 100") |
|||
.build(); |
|||
assertEquals(expected, actual); |
|||
} |
|||
|
|||
private void initWithScript() { |
|||
try { |
|||
TbMsgToEmailNodeConfiguration config = new TbMsgToEmailNodeConfiguration(); |
|||
config.setFromTemplate("test@mail.org"); |
|||
config.setToTemplate("$metadata.userEmail"); |
|||
config.setSubjectTemplate("Hi $metadata.username there"); |
|||
config.setBodyTemplate("$msg.name is to high. Current $msg.passed and $msg.complex.count"); |
|||
ObjectMapper mapper = new ObjectMapper(); |
|||
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); |
|||
|
|||
emailNode = new TbMsgToEmailNode(); |
|||
emailNode.init(ctx, nodeConfiguration); |
|||
} catch (TbNodeException ex) { |
|||
throw new IllegalStateException(ex); |
|||
} |
|||
} |
|||
|
|||
} |
|||
Loading…
Reference in new issue