All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.amazonaws.services.iot.client.shadow.AwsIotDeviceCommandManager Maven / Gradle / Ivy
// Generated by delombok at Tue Jun 13 16:52:50 PDT 2017
/*
* Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 com.amazonaws.services.iot.client.shadow;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.amazonaws.services.iot.client.AWSIotDeviceErrorCode;
import com.amazonaws.services.iot.client.AWSIotException;
import com.amazonaws.services.iot.client.AWSIotMessage;
import com.amazonaws.services.iot.client.AWSIotTimeoutException;
import com.amazonaws.services.iot.client.core.AwsIotRuntimeException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* This class manages the commands sent to the shadow. It maintains a list of
* pending commands that are yet to be accepted or rejected by the shadow. Upon
* receiving the shadow response for a command, it will notify therefore resume
* the execution of the caller.
*/
public class AwsIotDeviceCommandManager {
private static final Logger LOGGER = Logger.getLogger(AwsIotDeviceCommandManager.class.getName());
private static final String TOPIC_PREFIX = "$aws/things/?/shadow";
private static final String COMMAND_ID_FIELD = "clientToken";
private static final String ERROR_CODE_FIELD = "code";
private static final String ERROR_MESSAGE_FIELD = "message";
private static final Map COMMAND_PATHS;
private static final Map COMMAND_ACK_PATHS;
private static final Pattern commandPattern;
private static final Pattern deltaPattern;
private final ConcurrentMap pendingCommands;
private final AbstractAwsIotDevice device;
private final ObjectMapper objectMapper;
public static enum Command {
GET, UPDATE, DELETE, DELTA;
}
public static enum CommandAck {
ACCEPTED, REJECTED;
}
static {
COMMAND_PATHS = new HashMap();
COMMAND_PATHS.put(Command.GET, "/get");
COMMAND_PATHS.put(Command.UPDATE, "/update");
COMMAND_PATHS.put(Command.DELETE, "/delete");
COMMAND_PATHS.put(Command.DELTA, "/update/delta");
COMMAND_ACK_PATHS = new HashMap();
COMMAND_ACK_PATHS.put(CommandAck.ACCEPTED, "/accepted");
COMMAND_ACK_PATHS.put(CommandAck.REJECTED, "/rejected");
commandPattern = Pattern.compile("^\\$aws/things/[^/]+/shadow/(get|update|delete)/(?:accepted|rejected)$");
deltaPattern = Pattern.compile("^\\$aws/things/[^/]+/shadow/update/delta$");
}
public AwsIotDeviceCommandManager(AbstractAwsIotDevice device) {
this.pendingCommands = new ConcurrentHashMap<>();
this.device = device;
this.objectMapper = new ObjectMapper();
}
public String getTopic(Command command, CommandAck ack) {
String topic = TOPIC_PREFIX.replace("?", device.getThingName());
if (COMMAND_PATHS.containsKey(command)) {
topic += COMMAND_PATHS.get(command);
}
if (COMMAND_ACK_PATHS.containsKey(ack)) {
topic += COMMAND_ACK_PATHS.get(ack);
}
return topic;
}
public String runCommandSync(Command command, AWSIotMessage request) throws AWSIotException {
try {
return runCommand(command, request, 0, false);
} catch (AWSIotTimeoutException e) {
// We shouldn't get timeout exception because timeout is 0
throw new AwsIotRuntimeException(e);
}
}
public String runCommandSync(Command command, AWSIotMessage request, long commandTimeout) throws AWSIotException, AWSIotTimeoutException {
return runCommand(command, request, commandTimeout, false);
}
public String runCommand(Command command, AWSIotMessage request, long commandTimeout) throws AWSIotException {
try {
return runCommand(command, request, commandTimeout, true);
} catch (AWSIotTimeoutException e) {
// We shouldn't get timeout exception because it's asynchronous call
throw new AwsIotRuntimeException(e);
}
}
public String runCommand(Command command, AWSIotMessage request, long commandTimeout, boolean isAsync) throws AWSIotException, AWSIotTimeoutException {
String commandId = newCommandId();
appendCommandId(request, commandId);
request.setTopic(getTopic(command, null));
AwsIotDeviceCommand deviceCommand = new AwsIotDeviceCommand(this, command, commandId, request, commandTimeout, isAsync);
pendingCommands.put(commandId, deviceCommand);
LOGGER.fine("Number of pending commands: " + pendingCommands.size());
try {
deviceCommand.put(device);
} catch (AWSIotException e) {
// if exception happens during publish, we remove the command
// from the pending list as we'll never get ack for it.
pendingCommands.remove(commandId);
throw e;
}
return deviceCommand.get(device);
}
public void onCommandAck(AWSIotMessage response) {
if (response == null || response.getTopic() == null) {
return;
}
AwsIotDeviceCommand command = getPendingCommand(response);
if (command == null) {
LOGGER.warning("Unknown command received from topic " + response.getTopic());
return;
}
boolean success = response.getTopic().endsWith(COMMAND_ACK_PATHS.get(CommandAck.ACCEPTED));
if (!success && (Command.DELETE.equals(command.getCommand()) && AWSIotDeviceErrorCode.NOT_FOUND.equals(command.getErrorCode()))) {
// Ignore empty document error (NOT_FOUND) for delete command
success = true;
}
if (success) {
command.setResponse(response);
command.onSuccess();
} else {
command.onFailure();
}
}
public void onCommandTimeout(AwsIotDeviceCommand command) {
pendingCommands.remove(command.getCommandId());
}
public void onSubscriptionAck(String topic, boolean success) {
boolean ready = false;
Command command = getCommandFromTopic(topic);
if (command == null) {
return;
}
String accepted = getTopic(command, CommandAck.ACCEPTED);
String rejected = getTopic(command, CommandAck.REJECTED);
if (accepted.equals(topic) || rejected.equals(topic)) {
if (success && device.isTopicReady(accepted) && device.isTopicReady(rejected)) {
ready = true;
}
}
Iterator> it = pendingCommands.entrySet().iterator();
while (it.hasNext()) {
Entry entry = it.next();
AwsIotDeviceCommand deviceCommand = entry.getValue();
boolean failCommand = false;
if (command.equals(deviceCommand.getCommand())) {
if (ready) {
if (!deviceCommand.onReady(device)) {
failCommand = true;
}
} else if (!success) {
failCommand = true;
}
}
if (failCommand) {
it.remove();
deviceCommand.onFailure();
}
}
}
public void onDeactivate() {
Iterator> it = pendingCommands.entrySet().iterator();
while (it.hasNext()) {
Entry entry = it.next();
it.remove();
final AwsIotDeviceCommand deviceCommand = entry.getValue();
LOGGER.warning("Request was cancelled: " + deviceCommand.getCommand().name() + "/" + deviceCommand.getCommandId());
device.getClient().scheduleTask(new Runnable() {
@Override
public void run() {
deviceCommand.onFailure();
}
});
}
}
public boolean isDeltaTopic(String topic) {
if (topic == null) {
return false;
}
Matcher matcher = deltaPattern.matcher(topic);
return matcher.matches();
}
private Command getCommandFromTopic(String topic) {
if (topic == null) {
return null;
}
Matcher matcher = commandPattern.matcher(topic);
if (matcher.find()) {
String name = matcher.group(1);
return Command.valueOf(name.toUpperCase());
}
return null;
}
private void appendCommandId(AWSIotMessage message, String commandId) throws AWSIotException {
String payload = message.getStringPayload();
if (payload == null) {
payload = "{}";
}
try {
JsonNode jsonNode = objectMapper.readTree(payload);
if (!jsonNode.isObject()) {
throw new AWSIotException("Invalid Json string in payload");
}
((ObjectNode) jsonNode).put(COMMAND_ID_FIELD, commandId);
message.setStringPayload(jsonNode.toString());
} catch (IOException e) {
throw new AWSIotException(e);
}
}
private AwsIotDeviceCommand getPendingCommand(AWSIotMessage message) {
String payload = message.getStringPayload();
if (payload == null) {
return null;
}
try {
JsonNode jsonNode = objectMapper.readTree(payload);
if (!jsonNode.isObject()) {
return null;
}
JsonNode node = jsonNode.get(COMMAND_ID_FIELD);
if (node == null) {
return null;
}
String commandId = node.textValue();
AwsIotDeviceCommand command = pendingCommands.remove(commandId);
if (command == null) {
return null;
}
node = jsonNode.get(ERROR_CODE_FIELD);
if (node != null) {
command.setErrorCode(AWSIotDeviceErrorCode.valueOf(node.longValue()));
}
node = jsonNode.get(ERROR_MESSAGE_FIELD);
if (node != null) {
command.setErrorMessage(node.textValue());
}
return command;
} catch (IOException e) {
return null;
}
}
private String newCommandId() {
return UUID.randomUUID().toString();
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
public ConcurrentMap getPendingCommands() {
return this.pendingCommands;
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
public AbstractAwsIotDevice getDevice() {
return this.device;
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
public ObjectMapper getObjectMapper() {
return this.objectMapper;
}
}