com.sitewhere.web.rest.controllers.AssignmentsController.java Source code

Java tutorial

Introduction

Here is the source code for com.sitewhere.web.rest.controllers.AssignmentsController.java

Source

/*
 * Copyright (c) SiteWhere, LLC. All rights reserved. http://www.sitewhere.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package com.sitewhere.web.rest.controllers;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.sitewhere.SiteWhere;
import com.sitewhere.Tracer;
import com.sitewhere.device.charting.ChartBuilder;
import com.sitewhere.device.communication.symbology.DefaultEntityUriProvider;
import com.sitewhere.device.marshaling.DeviceAssignmentMarshalHelper;
import com.sitewhere.device.marshaling.DeviceCommandInvocationMarshalHelper;
import com.sitewhere.rest.model.common.MetadataProvider;
import com.sitewhere.rest.model.device.DeviceAssignment;
import com.sitewhere.rest.model.device.event.DeviceAlert;
import com.sitewhere.rest.model.device.event.DeviceCommandInvocation;
import com.sitewhere.rest.model.device.event.DeviceCommandResponse;
import com.sitewhere.rest.model.device.event.DeviceLocation;
import com.sitewhere.rest.model.device.event.DeviceMeasurements;
import com.sitewhere.rest.model.device.event.DeviceStateChange;
import com.sitewhere.rest.model.device.event.request.DeviceAlertCreateRequest;
import com.sitewhere.rest.model.device.event.request.DeviceCommandInvocationCreateRequest;
import com.sitewhere.rest.model.device.event.request.DeviceCommandResponseCreateRequest;
import com.sitewhere.rest.model.device.event.request.DeviceLocationCreateRequest;
import com.sitewhere.rest.model.device.event.request.DeviceMeasurementsCreateRequest;
import com.sitewhere.rest.model.device.event.request.DeviceStateChangeCreateRequest;
import com.sitewhere.rest.model.device.event.request.DeviceStreamDataCreateRequest;
import com.sitewhere.rest.model.device.request.DeviceAssignmentCreateRequest;
import com.sitewhere.rest.model.device.request.DeviceStreamCreateRequest;
import com.sitewhere.rest.model.device.streaming.DeviceStream;
import com.sitewhere.rest.model.search.DateRangeSearchCriteria;
import com.sitewhere.rest.model.search.SearchResults;
import com.sitewhere.server.scheduling.ScheduledJobHelper;
import com.sitewhere.spi.SiteWhereException;
import com.sitewhere.spi.SiteWhereSystemException;
import com.sitewhere.spi.device.DeviceAssignmentStatus;
import com.sitewhere.spi.device.DeviceAssignmentType;
import com.sitewhere.spi.device.IDeviceAssignment;
import com.sitewhere.spi.device.IDeviceManagement;
import com.sitewhere.spi.device.charting.IChartSeries;
import com.sitewhere.spi.device.command.IDeviceCommand;
import com.sitewhere.spi.device.event.IDeviceAlert;
import com.sitewhere.spi.device.event.IDeviceCommandInvocation;
import com.sitewhere.spi.device.event.IDeviceCommandResponse;
import com.sitewhere.spi.device.event.IDeviceEvent;
import com.sitewhere.spi.device.event.IDeviceLocation;
import com.sitewhere.spi.device.event.IDeviceMeasurements;
import com.sitewhere.spi.device.event.IDeviceStateChange;
import com.sitewhere.spi.device.event.IDeviceStreamData;
import com.sitewhere.spi.device.streaming.IDeviceStream;
import com.sitewhere.spi.device.symbology.IEntityUriProvider;
import com.sitewhere.spi.device.symbology.ISymbolGenerator;
import com.sitewhere.spi.device.symbology.ISymbolGeneratorManager;
import com.sitewhere.spi.error.ErrorCode;
import com.sitewhere.spi.error.ErrorLevel;
import com.sitewhere.spi.scheduling.IScheduledJob;
import com.sitewhere.spi.scheduling.request.IScheduledJobCreateRequest;
import com.sitewhere.spi.search.ISearchResults;
import com.sitewhere.spi.server.debug.TracerCategory;
import com.sitewhere.spi.user.SiteWhereRoles;
import com.sitewhere.web.rest.RestController;
import com.sitewhere.web.rest.annotations.Concerns;
import com.sitewhere.web.rest.annotations.Concerns.ConcernType;
import com.sitewhere.web.rest.annotations.Documented;
import com.sitewhere.web.rest.annotations.DocumentedController;
import com.sitewhere.web.rest.annotations.Example;
import com.sitewhere.web.rest.annotations.Example.Stage;
import com.sitewhere.web.rest.documentation.Assignments;
import com.sitewhere.web.rest.documentation.Schedules;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;

/*
 * Controller for assignment operations.
 * 
 * @author Derek Adams
 */
@Controller
@CrossOrigin
@RequestMapping(value = "/assignments")
@Api(value = "assignments", description = "Operations related to SiteWhere device assignments.")
@DocumentedController(name = "Device Assignments")
public class AssignmentsController extends RestController {

    /** Static logger instance */
    private static Logger LOGGER = LogManager.getLogger();

    /**
     * Used by AJAX calls to create a device assignment.
     * 
     * @param request
     * @return
     */
    @RequestMapping(method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "Create a new device assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Request, json = Assignments.CreateUnassociatedRequest.class, description = "createUnassociatedRequest.md"),
            @Example(stage = Stage.Request, json = Assignments.CreateAssociatedRequest.class, description = "createAssociatedRequest.md"),
            @Example(stage = Stage.Response, json = Assignments.CreateAssociatedResponse.class, description = "createAssociatedResponse.md") })
    public DeviceAssignment createDeviceAssignment(@RequestBody DeviceAssignmentCreateRequest request,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "createDeviceAssignment", LOGGER);
        try {
            if (StringUtils.isEmpty(request.getDeviceHardwareId())) {
                throw new SiteWhereException("Hardware id required.");
            }
            if (request.getAssignmentType() == null) {
                throw new SiteWhereException("Assignment type required.");
            }
            if (request.getAssignmentType() != DeviceAssignmentType.Unassociated) {
                if (request.getAssetModuleId() == null) {
                    throw new SiteWhereException("Asset module id required.");
                }
                if (request.getAssetId() == null) {
                    throw new SiteWhereException("Asset id required.");
                }
            }
            IDeviceManagement management = SiteWhere.getServer().getDeviceManagement(getTenant(servletRequest));
            IDeviceAssignment created = management.createDeviceAssignment(request);
            DeviceAssignmentMarshalHelper helper = new DeviceAssignmentMarshalHelper(getTenant(servletRequest));
            helper.setIncludeAsset(true);
            helper.setIncludeDevice(true);
            helper.setIncludeSite(true);
            return helper.convert(created, SiteWhere.getServer().getAssetModuleManager(getTenant(servletRequest)));
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Get an assignment by its unique token.
     * 
     * @param token
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "Get device assignment by token")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.CreateAssociatedResponse.class, description = "getDeviceAssignmentResponse.md") })
    public DeviceAssignment getDeviceAssignment(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "getDeviceAssignment", LOGGER);
        try {
            IDeviceAssignment assignment = assureAssignment(token, servletRequest);
            DeviceAssignmentMarshalHelper helper = new DeviceAssignmentMarshalHelper(getTenant(servletRequest));
            helper.setIncludeAsset(true);
            helper.setIncludeDevice(true);
            helper.setIncludeSite(true);
            return helper.convert(assignment,
                    SiteWhere.getServer().getAssetModuleManager(getTenant(servletRequest)));
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Delete an existing device assignment.
     * 
     * @param token
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}", method = RequestMethod.DELETE)
    @ResponseBody
    @ApiOperation(value = "Delete an existing device assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.CreateAssociatedResponse.class, description = "deleteDeviceAssignmentResponse.md") })
    public DeviceAssignment deleteDeviceAssignment(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Delete permanently", required = false) @RequestParam(defaultValue = "false") @Concerns(values = {
                    ConcernType.ForceDelete }) boolean force,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "deleteDeviceAssignment", LOGGER);
        try {
            IDeviceAssignment assignment = SiteWhere.getServer().getDeviceManagement(getTenant(servletRequest))
                    .deleteDeviceAssignment(token, force);
            DeviceAssignmentMarshalHelper helper = new DeviceAssignmentMarshalHelper(getTenant(servletRequest));
            helper.setIncludeAsset(true);
            helper.setIncludeDevice(true);
            helper.setIncludeSite(true);
            return helper.convert(assignment,
                    SiteWhere.getServer().getAssetModuleManager(getTenant(servletRequest)));
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Update metadata associated with an assignment.
     * 
     * @param request
     * @return
     */
    @RequestMapping(value = "/{token}/metadata", method = RequestMethod.PUT)
    @ResponseBody
    @ApiOperation(value = "Update device assignment metadata")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Request, json = Assignments.UpdateAssignmentMetadataRequest.class, description = "updateAssignmentMetadataRequest.md") })
    public DeviceAssignment updateDeviceAssignmentMetadata(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @RequestBody MetadataProvider metadata, HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "updateDeviceAssignmentMetadata", LOGGER);
        try {
            IDeviceAssignment result = SiteWhere.getServer().getDeviceManagement(getTenant(servletRequest))
                    .updateDeviceAssignmentMetadata(token, metadata);
            DeviceAssignmentMarshalHelper helper = new DeviceAssignmentMarshalHelper(getTenant(servletRequest));
            helper.setIncludeAsset(true);
            helper.setIncludeDevice(true);
            helper.setIncludeSite(true);
            return helper.convert(result, SiteWhere.getServer().getAssetModuleManager(getTenant(servletRequest)));
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * List all device events for an assignment that match the given criteria.
     * 
     * @param token
     * @param page
     * @param pageSize
     * @param startDate
     * @param endDate
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/events", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "List events for device assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.ListAssignmentEventsResponse.class, description = "listEventsResponse.md") })
    public ISearchResults<IDeviceEvent> listEvents(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Page number", required = false) @RequestParam(required = false, defaultValue = "1") @Concerns(values = {
                    ConcernType.Paging }) int page,
            @ApiParam(value = "Page size", required = false) @RequestParam(required = false, defaultValue = "100") @Concerns(values = {
                    ConcernType.Paging }) int pageSize,
            @ApiParam(value = "Start date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
            @ApiParam(value = "End date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "listEvents", LOGGER);
        try {
            DateRangeSearchCriteria criteria = new DateRangeSearchCriteria(page, pageSize, startDate, endDate);
            return SiteWhere.getServer().getDeviceEventManagement(getTenant(servletRequest)).listDeviceEvents(token,
                    criteria);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * List all device measurements for a given assignment.
     * 
     * @param assignmentToken
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/measurements", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "List measurement events for device assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.ListAssignmentMeasurementsResponse.class, description = "listMeasurementsResponse.md") })
    public ISearchResults<IDeviceMeasurements> listMeasurements(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Page number", required = false) @RequestParam(required = false, defaultValue = "1") @Concerns(values = {
                    ConcernType.Paging }) int page,
            @ApiParam(value = "Page size", required = false) @RequestParam(required = false, defaultValue = "100") @Concerns(values = {
                    ConcernType.Paging }) int pageSize,
            @ApiParam(value = "Start date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
            @ApiParam(value = "End date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "listMeasurements", LOGGER);
        try {
            DateRangeSearchCriteria criteria = new DateRangeSearchCriteria(page, pageSize, startDate, endDate);
            return SiteWhere.getServer().getDeviceEventManagement(getTenant(servletRequest))
                    .listDeviceMeasurements(token, criteria);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * List device measurements for a given assignment.
     * 
     * @param assignmentToken
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/measurements/series", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "List assignment measurements as chart series")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.ListAssignmentMeasurementsChartSeriesResponse.class, description = "listMeasurementsAsChartSeriesResponse.md") })
    public List<IChartSeries<Double>> listMeasurementsAsChartSeries(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Page number", required = false) @RequestParam(required = false, defaultValue = "1") @Concerns(values = {
                    ConcernType.Paging }) int page,
            @ApiParam(value = "Page size", required = false) @RequestParam(required = false, defaultValue = "100") @Concerns(values = {
                    ConcernType.Paging }) int pageSize,
            @ApiParam(value = "Start date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
            @ApiParam(value = "End date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate,
            @ApiParam(value = "Measurement Ids", required = false) @RequestParam(required = false) String[] measurementIds,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "listMeasurementsAsChartSeries", LOGGER);
        try {
            DateRangeSearchCriteria criteria = new DateRangeSearchCriteria(page, pageSize, startDate, endDate);
            ISearchResults<IDeviceMeasurements> measurements = SiteWhere.getServer()
                    .getDeviceEventManagement(getTenant(servletRequest)).listDeviceMeasurements(token, criteria);
            ChartBuilder builder = new ChartBuilder();
            return builder.process(measurements.getResults(), measurementIds);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Create measurements to be associated with a device assignment.
     * 
     * @param input
     * @param token
     * @param updateState
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/measurements", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "Create measurements event for device assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Request, json = Assignments.CreateAssignmentMeasurementsRequest.class, description = "createMeasurementsRequest.md"),
            @Example(stage = Stage.Response, json = Assignments.CreateAssignmentMeasurementsResponse.class, description = "createMeasurementsResponse.md") })
    public DeviceMeasurements createMeasurements(@RequestBody DeviceMeasurementsCreateRequest input,
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "createMeasurements", LOGGER);
        try {
            IDeviceMeasurements result = SiteWhere.getServer().getDeviceEventManagement(getTenant(servletRequest))
                    .addDeviceMeasurements(token, input);
            return DeviceMeasurements.copy(result);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * List device locations for a given assignment.
     * 
     * @param assignmentToken
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/locations", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "List location events for device assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.ListAssignmentLocationsResponse.class, description = "listLocationsResponse.md") })
    public ISearchResults<IDeviceLocation> listLocations(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Page number", required = false) @RequestParam(required = false, defaultValue = "1") @Concerns(values = {
                    ConcernType.Paging }) int page,
            @ApiParam(value = "Page size", required = false) @RequestParam(required = false, defaultValue = "100") @Concerns(values = {
                    ConcernType.Paging }) int pageSize,
            @ApiParam(value = "Start date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
            @ApiParam(value = "End date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "listLocations", LOGGER);
        try {
            DateRangeSearchCriteria criteria = new DateRangeSearchCriteria(page, pageSize, startDate, endDate);
            return SiteWhere.getServer().getDeviceEventManagement(getTenant(servletRequest))
                    .listDeviceLocations(token, criteria);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Create location to be associated with a device assignment.
     * 
     * @param input
     * @param token
     * @param updateState
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/locations", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "Create location event for device assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Request, json = Assignments.CreateAssignmentLocationRequest.class, description = "createLocationRequest.md"),
            @Example(stage = Stage.Response, json = Assignments.CreateAssignmentLocationResponse.class, description = "createLocationResponse.md") })
    public DeviceLocation createLocation(@RequestBody DeviceLocationCreateRequest input,
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "createLocation", LOGGER);
        try {
            IDeviceLocation result = SiteWhere.getServer().getDeviceEventManagement(getTenant(servletRequest))
                    .addDeviceLocation(token, input);
            return DeviceLocation.copy(result);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * List device alerts for a given assignment.
     * 
     * @param assignmentToken
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/alerts", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "List alert events for device assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.ListAssignmenAlertsResponse.class, description = "listAlertsResponse.md") })
    public ISearchResults<IDeviceAlert> listAlerts(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Page number", required = false) @RequestParam(required = false, defaultValue = "1") @Concerns(values = {
                    ConcernType.Paging }) int page,
            @ApiParam(value = "Page size", required = false) @RequestParam(required = false, defaultValue = "100") @Concerns(values = {
                    ConcernType.Paging }) int pageSize,
            @ApiParam(value = "Start date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
            @ApiParam(value = "End date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "listAlerts", LOGGER);
        try {
            DateRangeSearchCriteria criteria = new DateRangeSearchCriteria(page, pageSize, startDate, endDate);
            return SiteWhere.getServer().getDeviceEventManagement(getTenant(servletRequest)).listDeviceAlerts(token,
                    criteria);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Create alert to be associated with a device assignment.
     * 
     * @param input
     * @param token
     * @param updateState
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/alerts", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "Create alert event for device assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Request, json = Assignments.CreateAssignmentAlertRequest.class, description = "createAlertRequest.md"),
            @Example(stage = Stage.Response, json = Assignments.CreateAssignmentAlertResponse.class, description = "createAlertResponse.md") })
    public DeviceAlert createAlert(@RequestBody DeviceAlertCreateRequest input,
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "createAlert", LOGGER);
        try {
            IDeviceAlert result = SiteWhere.getServer().getDeviceEventManagement(getTenant(servletRequest))
                    .addDeviceAlert(token, input);
            return DeviceAlert.copy(result);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Create a stream to be associated with a device assignment.
     * 
     * @param input
     * @param token
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/streams", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "Create data stream for a device assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Request, json = Assignments.CreateDeviceStreamRequest.class, description = "createDeviceStreamRequest.md"),
            @Example(stage = Stage.Response, json = Assignments.CreateDeviceStreamResponse.class, description = "createDeviceStreamResponse.md") })
    public DeviceStream createDeviceStream(@RequestBody DeviceStreamCreateRequest request,
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "createDeviceStream", LOGGER);
        try {
            IDeviceStream result = SiteWhere.getServer().getDeviceManagement(getTenant(servletRequest))
                    .createDeviceStream(token, request);
            return DeviceStream.copy(result);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    @RequestMapping(value = "/{token}/streams", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "List data streams for device assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.ListDeviceStreamsResponse.class, description = "listDeviceStreamsResponse.md") })
    public ISearchResults<IDeviceStream> listDeviceStreams(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Page number", required = false) @RequestParam(required = false, defaultValue = "1") @Concerns(values = {
                    ConcernType.Paging }) int page,
            @ApiParam(value = "Page size", required = false) @RequestParam(required = false, defaultValue = "100") @Concerns(values = {
                    ConcernType.Paging }) int pageSize,
            @ApiParam(value = "Start date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
            @ApiParam(value = "End date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "listDeviceStreams", LOGGER);
        try {
            DateRangeSearchCriteria criteria = new DateRangeSearchCriteria(page, pageSize, startDate, endDate);
            ISearchResults<IDeviceStream> matches = SiteWhere.getServer()
                    .getDeviceManagement(getTenant(servletRequest)).listDeviceStreams(token, criteria);
            List<IDeviceStream> converted = new ArrayList<IDeviceStream>();
            for (IDeviceStream stream : matches.getResults()) {
                converted.add(DeviceStream.copy(stream));
            }
            return new SearchResults<IDeviceStream>(converted);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Get an existing device stream associated with an assignment.
     * 
     * @param token
     * @param streamId
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/streams/{streamId:.+}", method = RequestMethod.GET, produces = "application/json")
    @ResponseBody
    @ApiOperation(value = "Get device assignment data stream by id")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.GetDeviceStreamResponse.class, description = "getDeviceStreamResponse.md") })
    public DeviceStream getDeviceStream(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Stream Id", required = true) @PathVariable String streamId,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "getDeviceStream", LOGGER);
        try {
            IDeviceStream result = SiteWhere.getServer().getDeviceManagement(getTenant(servletRequest))
                    .getDeviceStream(token, streamId);
            if (result == null) {
                throw new SiteWhereSystemException(ErrorCode.InvalidStreamId, ErrorLevel.ERROR,
                        HttpServletResponse.SC_NOT_FOUND);
            }
            return DeviceStream.copy(result);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Adds data to an existing device stream.
     * 
     * @param token
     * @param streamId
     * @param sequenceNumber
     * @param svtRequest
     * @param svtResponse
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/streams/{streamId:.+}", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "Add data to device assignment data stream")
    @Secured({ SiteWhereRoles.REST })
    @Documented
    public void addDeviceStreamData(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Stream Id", required = true) @PathVariable String streamId,
            @ApiParam(value = "Sequence Number", required = false) @RequestParam(required = false) Long sequenceNumber,
            HttpServletRequest servletRequest, HttpServletResponse svtResponse) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "addDeviceStreamData", LOGGER);
        try {
            ServletInputStream inData = servletRequest.getInputStream();
            ByteArrayOutputStream byteData = new ByteArrayOutputStream();
            int data;
            while ((data = inData.read()) != -1) {
                byteData.write(data);
            }
            byte[] payload = byteData.toByteArray();
            DeviceStreamDataCreateRequest request = new DeviceStreamDataCreateRequest();
            request.setStreamId(streamId);
            request.setSequenceNumber(sequenceNumber);
            request.setEventDate(new Date());
            request.setUpdateState(false);
            request.setData(payload);
            SiteWhere.getServer().getDeviceEventManagement(getTenant(servletRequest)).addDeviceStreamData(token,
                    request);
            svtResponse.setStatus(HttpServletResponse.SC_CREATED);
        } catch (SiteWhereSystemException e) {
            if (e.getCode() == ErrorCode.InvalidStreamId) {
                svtResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
            } else {
                LOGGER.error("Unhandled SiteWhere exception.", e);
                svtResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
        } catch (IOException e) {
            LOGGER.error(e);
            svtResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Get a single chunk of data from a device stream.
     * 
     * @param token
     * @param streamId
     * @param sequenceNumber
     * @param svtResponse
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/streams/{streamId:.+}/data/{sequenceNumber}", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "Get data from device assignment data stream")
    @Secured({ SiteWhereRoles.REST })
    @Documented
    public void getDeviceStreamData(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Stream Id", required = true) @PathVariable String streamId,
            @ApiParam(value = "Sequence Number", required = true) @PathVariable long sequenceNumber,
            HttpServletRequest servletRequest, HttpServletResponse svtResponse) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "listDeviceStreamData", LOGGER);
        IDeviceStreamData chunk = SiteWhere.getServer().getDeviceEventManagement(getTenant(servletRequest))
                .getDeviceStreamData(token, streamId, sequenceNumber);
        if (chunk == null) {
            svtResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        try {
            svtResponse.getOutputStream().write(chunk.getData());
        } catch (IOException e) {
            throw new SiteWhereException("Unable to write device stream data chunk.", e);
        }
    }

    @RequestMapping(value = "/{token}/streams/{streamId:.+}/data", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "Get all data from device assignment data stream")
    @Secured({ SiteWhereRoles.REST })
    @Documented
    public void listDeviceStreamData(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Stream Id", required = true) @PathVariable String streamId,
            HttpServletRequest servletRequest, HttpServletResponse svtResponse) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "listDeviceStreamData", LOGGER);
        IDeviceStream stream = SiteWhere.getServer().getDeviceManagement(getTenant(servletRequest))
                .getDeviceStream(token, streamId);
        if (stream == null) {
            throw new SiteWhereSystemException(ErrorCode.InvalidStreamId, ErrorLevel.ERROR,
                    HttpServletResponse.SC_NOT_FOUND);
        }
        svtResponse.setContentType(stream.getContentType());

        DateRangeSearchCriteria criteria = new DateRangeSearchCriteria(1, 0, null, null);
        ISearchResults<IDeviceStreamData> data = SiteWhere.getServer()
                .getDeviceEventManagement(getTenant(servletRequest))
                .listDeviceStreamData(token, streamId, criteria);

        // Sort results by sequence number.
        Collections.sort(data.getResults(), new Comparator<IDeviceStreamData>() {

            @Override
            public int compare(IDeviceStreamData o1, IDeviceStreamData o2) {
                return o1.getSequenceNumber().compareTo(o2.getSequenceNumber());
            }
        });
        for (IDeviceStreamData chunk : data.getResults()) {
            try {
                svtResponse.getOutputStream().write(chunk.getData());
            } catch (IOException e) {
                LOGGER.error("Error writing chunk to servlet output stream.", e);
            }
        }
    }

    /**
     * Create command invocation to be associated with a device assignment.
     * 
     * @param input
     * @param token
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/invocations", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "Create command invocation event for assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Request, json = Assignments.CreateCommandInvocationRequest.class, description = "createCommandInvocationRequest.md"),
            @Example(stage = Stage.Response, json = Assignments.CreateCommandInvocationResponse.class, description = "createCommandInvocationResponse.md") })
    public DeviceCommandInvocation createCommandInvocation(
            @RequestBody DeviceCommandInvocationCreateRequest request,
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "createCommandInvocation", LOGGER);
        try {
            IDeviceCommand command = assureDeviceCommand(request.getCommandToken(), servletRequest);
            IDeviceCommandInvocation result = SiteWhere.getServer()
                    .getDeviceEventManagement(getTenant(servletRequest))
                    .addDeviceCommandInvocation(token, command, request);
            DeviceCommandInvocationMarshalHelper helper = new DeviceCommandInvocationMarshalHelper(
                    getTenant(servletRequest));
            return helper.convert(result);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    @RequestMapping(value = "/{token}/invocations/schedules/{scheduleToken}", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "Schedule command invocation")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Request, json = Assignments.CreateCommandInvocationRequest.class, description = "scheduleCommandInvocationRequest.md"),
            @Example(stage = Stage.Response, json = Schedules.CreateScheduledJobResponse.class, description = "scheduleCommandInvocationResponse.md") })
    public IScheduledJob scheduleCommandInvocation(@RequestBody DeviceCommandInvocationCreateRequest request,
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Schedule token", required = true) @PathVariable String scheduleToken,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "scheduleCommandInvocation", LOGGER);
        try {
            assureDeviceCommand(request.getCommandToken(), servletRequest);
            IScheduledJobCreateRequest job = ScheduledJobHelper.createCommandInvocationJob(
                    UUID.randomUUID().toString(), token, request.getCommandToken(), request.getParameterValues(),
                    scheduleToken);
            return SiteWhere.getServer().getScheduleManagement(getTenant(servletRequest)).createScheduledJob(job);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * List device command invocations for a given assignment.
     * 
     * @param assignmentToken
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/invocations", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "List command invocation events for assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.ListCommandInvocationsResponse.class, description = "listCommandInvocationsResponse.md") })
    public ISearchResults<IDeviceCommandInvocation> listCommandInvocations(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Include command information", required = false) @RequestParam(defaultValue = "true") boolean includeCommand,
            @ApiParam(value = "Page number", required = false) @RequestParam(required = false, defaultValue = "1") @Concerns(values = {
                    ConcernType.Paging }) int page,
            @ApiParam(value = "Page size", required = false) @RequestParam(required = false, defaultValue = "100") @Concerns(values = {
                    ConcernType.Paging }) int pageSize,
            @ApiParam(value = "Start date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
            @ApiParam(value = "End date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "listCommandInvocations", LOGGER);
        try {
            DateRangeSearchCriteria criteria = new DateRangeSearchCriteria(page, pageSize, startDate, endDate);
            ISearchResults<IDeviceCommandInvocation> matches = SiteWhere.getServer()
                    .getDeviceEventManagement(getTenant(servletRequest))
                    .listDeviceCommandInvocations(token, criteria);
            DeviceCommandInvocationMarshalHelper helper = new DeviceCommandInvocationMarshalHelper(
                    getTenant(servletRequest));
            helper.setIncludeCommand(includeCommand);
            List<IDeviceCommandInvocation> converted = new ArrayList<IDeviceCommandInvocation>();
            for (IDeviceCommandInvocation invocation : matches.getResults()) {
                converted.add(helper.convert(invocation));
            }
            return new SearchResults<IDeviceCommandInvocation>(converted);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Create state change to be associated with a device assignment.
     * 
     * @param input
     * @param token
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/statechanges", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "Create an state change event for a device assignment")
    @Secured({ SiteWhereRoles.REST })
    public DeviceStateChange createStateChange(@RequestBody DeviceStateChangeCreateRequest input,
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "createStateChange", LOGGER);
        try {
            IDeviceStateChange result = SiteWhere.getServer().getDeviceEventManagement(getTenant(servletRequest))
                    .addDeviceStateChange(token, input);
            return DeviceStateChange.copy(result);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * List device state changes for a given assignment.
     * 
     * @param assignmentToken
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/statechanges", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "List state change events for a device assignment")
    @Secured({ SiteWhereRoles.REST })
    public ISearchResults<IDeviceStateChange> listStateChanges(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Page number", required = false) @RequestParam(required = false, defaultValue = "1") @Concerns(values = {
                    ConcernType.Paging }) int page,
            @ApiParam(value = "Page size", required = false) @RequestParam(required = false, defaultValue = "100") @Concerns(values = {
                    ConcernType.Paging }) int pageSize,
            @ApiParam(value = "Start date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
            @ApiParam(value = "End date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "listStateChanges", LOGGER);
        try {
            DateRangeSearchCriteria criteria = new DateRangeSearchCriteria(page, pageSize, startDate, endDate);
            return SiteWhere.getServer().getDeviceEventManagement(getTenant(servletRequest))
                    .listDeviceStateChanges(token, criteria);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Create command response to be associated with a device assignment.
     * 
     * @param input
     * @param token
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/responses", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "Create command response event for assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Request, json = Assignments.CreateCommandResponseEventRequest.class, description = "createCommandResponseEventRequest.md"),
            @Example(stage = Stage.Request, json = Assignments.CreateCommandResponseSimpleRequest.class, description = "createCommandResponseSimpleRequest.md"),
            @Example(stage = Stage.Response, json = Assignments.CreateCommandResponseResponse.class, description = "createCommandResponseResponse.md") })
    public DeviceCommandResponse createCommandResponse(@RequestBody DeviceCommandResponseCreateRequest input,
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "createCommandResponse", LOGGER);
        try {
            IDeviceCommandResponse result = SiteWhere.getServer()
                    .getDeviceEventManagement(getTenant(servletRequest)).addDeviceCommandResponse(token, input);
            return DeviceCommandResponse.copy(result);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * List device command responses for a given assignment.
     * 
     * @param assignmentToken
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/responses", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "List command response events for assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.ListCommandResponsesResponse.class, description = "listCommandResponsesResponse.md") })
    public ISearchResults<IDeviceCommandResponse> listCommandResponses(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            @ApiParam(value = "Page number", required = false) @RequestParam(required = false, defaultValue = "1") @Concerns(values = {
                    ConcernType.Paging }) int page,
            @ApiParam(value = "Page size", required = false) @RequestParam(required = false, defaultValue = "100") @Concerns(values = {
                    ConcernType.Paging }) int pageSize,
            @ApiParam(value = "Start date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date startDate,
            @ApiParam(value = "End date", required = false) @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date endDate,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "listCommandResponses", LOGGER);
        try {
            DateRangeSearchCriteria criteria = new DateRangeSearchCriteria(page, pageSize, startDate, endDate);
            return SiteWhere.getServer().getDeviceEventManagement(getTenant(servletRequest))
                    .listDeviceCommandResponses(token, criteria);
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Get the default symbol for a device assignment.
     * 
     * @param token
     * @param servletRequest
     * @param response
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/symbol", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "Get default symbol for assignment")
    public ResponseEntity<byte[]> getDeviceAssignmentSymbol(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            HttpServletRequest servletRequest, HttpServletResponse response) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "getDeviceAssignmentSymbol", LOGGER);
        try {
            IDeviceAssignment assignment = assureAssignmentWithoutUserValidation(token, servletRequest);
            IEntityUriProvider provider = DefaultEntityUriProvider.getInstance();
            ISymbolGeneratorManager symbols = SiteWhere.getServer()
                    .getDeviceCommunication(getTenant(servletRequest, false)).getSymbolGeneratorManager();
            ISymbolGenerator generator = symbols.getDefaultSymbolGenerator();
            if (generator != null) {
                byte[] image = generator.getDeviceAssigmentSymbol(assignment, provider);

                final HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.IMAGE_PNG);
                return new ResponseEntity<byte[]>(image, headers, HttpStatus.CREATED);
            } else {
                return null;
            }
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * End an existing device assignment.
     * 
     * @param token
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/end", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "Release an active device assignment")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.EndDeviceAssignmentResponse.class, description = "endDeviceAssignmentResponse.md") })
    public DeviceAssignment endDeviceAssignment(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "endDeviceAssignment", LOGGER);
        try {
            IDeviceManagement management = SiteWhere.getServer().getDeviceManagement(getTenant(servletRequest));
            IDeviceAssignment updated = management.endDeviceAssignment(token);
            DeviceAssignmentMarshalHelper helper = new DeviceAssignmentMarshalHelper(getTenant(servletRequest));
            helper.setIncludeAsset(true);
            helper.setIncludeDevice(true);
            helper.setIncludeSite(true);
            return helper.convert(updated, SiteWhere.getServer().getAssetModuleManager(getTenant(servletRequest)));
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Mark a device assignment as missing.
     * 
     * @param token
     * @return
     * @throws SiteWhereException
     */
    @RequestMapping(value = "/{token}/missing", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "Mark device assignment as missing")
    @Secured({ SiteWhereRoles.REST })
    @Documented(examples = {
            @Example(stage = Stage.Response, json = Assignments.MissingDeviceAssignmentResponse.class, description = "missingDeviceAssignmentResponse.md") })
    public DeviceAssignment missingDeviceAssignment(
            @ApiParam(value = "Assignment token", required = true) @PathVariable String token,
            HttpServletRequest servletRequest) throws SiteWhereException {
        Tracer.start(TracerCategory.RestApiCall, "missingDeviceAssignment", LOGGER);
        try {
            IDeviceManagement management = SiteWhere.getServer().getDeviceManagement(getTenant(servletRequest));
            IDeviceAssignment updated = management.updateDeviceAssignmentStatus(token,
                    DeviceAssignmentStatus.Missing);
            DeviceAssignmentMarshalHelper helper = new DeviceAssignmentMarshalHelper(getTenant(servletRequest));
            helper.setIncludeAsset(true);
            helper.setIncludeDevice(true);
            helper.setIncludeSite(true);
            return helper.convert(updated, SiteWhere.getServer().getAssetModuleManager(getTenant(servletRequest)));
        } finally {
            Tracer.stop(LOGGER);
        }
    }

    /**
     * Get an assignment by unique token. Throw an exception if not found.
     * 
     * @param token
     * @param servletRequest
     * @return
     * @throws SiteWhereException
     */
    protected IDeviceAssignment assureAssignment(String token, HttpServletRequest servletRequest)
            throws SiteWhereException {
        IDeviceAssignment assignment = SiteWhere.getServer().getDeviceManagement(getTenant(servletRequest))
                .getDeviceAssignmentByToken(token);
        if (assignment == null) {
            throw new SiteWhereSystemException(ErrorCode.InvalidDeviceAssignmentToken, ErrorLevel.ERROR);
        }
        return assignment;
    }

    /**
     * Gets an assignment given its unique token. This implementation skips the
     * validation which ensures the authenticated user has access to the tenant.
     * It should *only* be used to access resources that are not protected.
     * 
     * @param token
     * @param servletRequest
     * @return
     * @throws SiteWhereException
     */
    protected IDeviceAssignment assureAssignmentWithoutUserValidation(String token,
            HttpServletRequest servletRequest) throws SiteWhereException {
        IDeviceAssignment assignment = SiteWhere.getServer().getDeviceManagement(getTenant(servletRequest, false))
                .getDeviceAssignmentByToken(token);
        if (assignment == null) {
            throw new SiteWhereSystemException(ErrorCode.InvalidDeviceAssignmentToken, ErrorLevel.ERROR);
        }
        return assignment;
    }

    /**
     * Get a device command by unique token. Throw an exception if not found.
     * 
     * @param token
     * @param servletRequest
     * @return
     * @throws SiteWhereException
     */
    protected IDeviceCommand assureDeviceCommand(String token, HttpServletRequest servletRequest)
            throws SiteWhereException {
        IDeviceCommand command = SiteWhere.getServer().getDeviceManagement(getTenant(servletRequest))
                .getDeviceCommandByToken(token);
        if (command == null) {
            throw new SiteWhereSystemException(ErrorCode.InvalidDeviceCommandToken, ErrorLevel.ERROR);
        }
        return command;
    }
}