From c28f820455edbbdf88ab7fb21baeffd776a5ccd1 Mon Sep 17 00:00:00 2001 From: ayyoob Date: Wed, 19 Apr 2017 16:32:06 +0530 Subject: [PATCH 001/358] added missing apis - operation and device management --- .../mgt/jaxrs/beans/OperationRequest.java | 45 ++ .../service/api/DeviceManagementService.java | 463 ++++++++++++++++++ .../impl/DeviceManagementServiceImpl.java | 242 ++++++++- .../wso2/carbon/device/mgt/common/Device.java | 2 +- 4 files changed, 750 insertions(+), 2 deletions(-) create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/OperationRequest.java diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/OperationRequest.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/OperationRequest.java new file mode 100644 index 0000000000..6d980cbee0 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/OperationRequest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) 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. + * 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.wso2.carbon.device.mgt.jaxrs.beans; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; + +import java.util.List; + +@ApiModel(value = "OperationRequest", description = "Operation details together with deviceIdentifier") +public class OperationRequest { + + @ApiModelProperty(name = "deviceIdentifiers", value = "list of devices that needs to be verified against the user", required = true) + List deviceIdentifiers; + @ApiModelProperty(name = "permission", value = "if null then checks against the owner else it could be grouping permission", required = false) + Operation operation; + + public List getDeviceIdentifiers() { + return deviceIdentifiers; + } + + public void setDeviceIdentifiers(List deviceIdentifiers) { + this.deviceIdentifiers = deviceIdentifiers; + } + + public Operation getOperation() { + return operation; + } + + public void setOperation(Operation operation) { + this.operation = operation; + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceManagementService.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceManagementService.java index ddbdfcdc08..06d2451044 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceManagementService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceManagementService.java @@ -32,17 +32,22 @@ import io.swagger.annotations.Tag; import org.wso2.carbon.apimgt.annotations.api.Scope; import org.wso2.carbon.apimgt.annotations.api.Scopes; import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; import org.wso2.carbon.device.mgt.common.EnrolmentInfo; import org.wso2.carbon.device.mgt.common.Feature; import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.operation.mgt.Activity; import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; import org.wso2.carbon.device.mgt.common.policy.mgt.Policy; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData; import org.wso2.carbon.device.mgt.common.search.SearchContext; import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList; import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse; +import org.wso2.carbon.device.mgt.jaxrs.beans.OperationList; +import org.wso2.carbon.device.mgt.jaxrs.beans.OperationRequest; import org.wso2.carbon.device.mgt.jaxrs.util.Constants; +import javax.validation.Valid; import javax.validation.constraints.Size; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -56,6 +61,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.util.List; /** * Device related REST-API. This can be used to manipulated device related details. @@ -137,6 +143,12 @@ import javax.ws.rs.core.Response; key = "perm:devices:compliance-data", permissions = {"/device-mgt/devices/owning-device/view"} ), + @Scope( + name = "Create a new device Instance", + description = "Create a new device Instance", + key = "perm:devices:add", + permissions = {"/device-mgt/devices/owning-device/add"} + ), @Scope( name = "Change device status.", description = "Change device status.", @@ -423,6 +435,169 @@ public interface DeviceManagementService { @HeaderParam("If-Modified-Since") String ifModifiedSince); + @POST + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "POST", + value = "Create a device instance", + notes = "Create a device Instance", + tags = "Device Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:add") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully created a device instance.", + responseHeaders = { + @ResponseHeader( + name = "Content-Type", + description = "The content type of the body"), + @ResponseHeader( + name = "ETag", + description = "Entity Tag of the response resource.\n" + + "Used by caches, or in conditional requests."), + @ResponseHeader( + name = "Last-Modified", + description = "Date and time the resource was last modified.\n" + + "Used by caches, or in conditional requests."), + }), + @ApiResponse( + code = 304, + message = "Not Modified. Empty body because the client already has the latest version" + + " of the requested resource.\n"), + @ApiResponse( + code = 400, + message = "Bad Request. \n Invalid request or validation error.", + response = ErrorResponse.class), + @ApiResponse( + code = 404, + message = "Not Found. \n A deviceType with the specified device type was not found.", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n " + + "Server error occurred while retrieving the device details.", + response = ErrorResponse.class) + }) + Response addDevice(@ApiParam(name = "device", value = "Device object with data.", required = true) + @Valid Device device); + + @GET + @Path("/{type}/{id}/status") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Get device enrollment status", + notes = "Get device enrollment status", + tags = "Device Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:view") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully created a device instance.", + responseHeaders = { + @ResponseHeader( + name = "Content-Type", + description = "The content type of the body"), + @ResponseHeader( + name = "ETag", + description = "Entity Tag of the response resource.\n" + + "Used by caches, or in conditional requests."), + @ResponseHeader( + name = "Last-Modified", + description = "Date and time the resource was last modified.\n" + + "Used by caches, or in conditional requests."), + }), + @ApiResponse( + code = 304, + message = "Not Modified. Empty body because the client already has the latest version" + + " of the requested resource.\n"), + @ApiResponse( + code = 400, + message = "Bad Request. \n Invalid request or validation error.", + response = ErrorResponse.class), + @ApiResponse( + code = 404, + message = "Not Found. \n A deviceType with the specified device type was not found.", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n " + + "Server error occurred while retrieving the device details.", + response = ErrorResponse.class) + }) + Response updateDevice(@ApiParam(name = "type", value = "The device type, such as ios, android or windows.", required = true) + @PathParam("type") String type, + @ApiParam(name = "id", value = "The device id.", required = true) + @PathParam("id") String deviceId, + @ApiParam(name = "device", value = "Device object with data.", required = true) + @Valid Device updateDevice); + + @PUT + @Path("/{type}/{id}") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Get device enrollment status", + notes = "Get device enrollment status", + tags = "Device Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:view") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully created a device instance.", + responseHeaders = { + @ResponseHeader( + name = "Content-Type", + description = "The content type of the body"), + @ResponseHeader( + name = "ETag", + description = "Entity Tag of the response resource.\n" + + "Used by caches, or in conditional requests."), + @ResponseHeader( + name = "Last-Modified", + description = "Date and time the resource was last modified.\n" + + "Used by caches, or in conditional requests."), + }), + @ApiResponse( + code = 304, + message = "Not Modified. Empty body because the client already has the latest version" + + " of the requested resource.\n"), + @ApiResponse( + code = 400, + message = "Bad Request. \n Invalid request or validation error.", + response = ErrorResponse.class), + @ApiResponse( + code = 404, + message = "Not Found. \n A deviceType with the specified device type was not found.", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n " + + "Server error occurred while retrieving the device details.", + response = ErrorResponse.class) + }) + Response isEnrolled(@ApiParam(name = "type", value = "The device type, such as ios, android or windows.", required = true) + @PathParam("type") String type, + @ApiParam(name = "id", value = "The device id.", required = true) + @PathParam("id") String deviceId); @GET @Path("/{type}/{id}/location") @@ -1233,4 +1408,292 @@ public interface DeviceManagementService { @QueryParam("newStatus") EnrolmentInfo.Status newStatus); + @POST + @Path("/{type}/operations") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + consumes = MediaType.APPLICATION_JSON, + httpMethod = "POST", + value = "Add operation to set of devices for a given device type", + notes = "Returns the Activity Related to the operation.", + tags = "Device Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:operations") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 201, + message = "OK. \n Successfully added the operation.", + response = Activity.class, + responseHeaders = { + @ResponseHeader( + name = "Content-Type", + description = "The content type of the body"), + @ResponseHeader( + name = "ETag", + description = "Entity Tag of the response resource.\n" + + "Used by caches, or in conditional requests."), + @ResponseHeader( + name = "Last-Modified", + description = "Date and time the resource has been modified the last time.\n" + + "Used by caches, or in conditional requests."), + }), + @ApiResponse( + code = 304, + message = "Not Modified. Empty body because the client already has the latest " + + "version of the requested resource."), + @ApiResponse( + code = 400, + message = "Bad Request. \n Invalid request or validation error.", + response = ErrorResponse.class), + @ApiResponse( + code = 404, + message = "Not Found. \n No device is found under the provided type and id.", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n " + + "Server error occurred while retrieving information requested device.", + response = ErrorResponse.class) + }) + Response addOperation(@ApiParam(name = "type", value = "The device type, such as ios, android or windows... etc.", required = true) + @PathParam("type") String type, + @ApiParam(name = "deviceOperation", value = "Operation object with device ids.", required = true) + @Valid OperationRequest operationRequest); + + @GET + @Path("/{type}/{id}/pending/operations") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + consumes = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Get pending operation of the given device", + notes = "Returns the Operations.", + tags = "Device Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:operations") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully retrieved the operations.", + response = OperationList.class, + responseHeaders = { + @ResponseHeader( + name = "Content-Type", + description = "The content type of the body"), + @ResponseHeader( + name = "ETag", + description = "Entity Tag of the response resource.\n" + + "Used by caches, or in conditional requests."), + @ResponseHeader( + name = "Last-Modified", + description = "Date and time the resource has been modified the last time.\n" + + "Used by caches, or in conditional requests."), + }), + @ApiResponse( + code = 304, + message = "Not Modified. Empty body because the client already has the latest " + + "version of the requested resource."), + @ApiResponse( + code = 400, + message = "Bad Request. \n Invalid request or validation error.", + response = ErrorResponse.class), + @ApiResponse( + code = 404, + message = "Not Found. \n No device is found under the provided type and id.", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n " + + "Server error occurred while retrieving information requested device.", + response = ErrorResponse.class) + }) + Response getPendingOperations(@ApiParam(name = "type", value = "The device type, such as ios, android or windows.", required = true) + @PathParam("type") String type, + @ApiParam(name = "id", value = "The device id.", required = true) + @PathParam("id") String deviceId); + + @GET + @Path("/{type}/{id}/last-pending/operation") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + consumes = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Get pending operation of the given device", + notes = "Returns the Operation.", + tags = "Device Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:operations") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully retrieved the operation.", + response = Operation.class, + responseHeaders = { + @ResponseHeader( + name = "Content-Type", + description = "The content type of the body"), + @ResponseHeader( + name = "ETag", + description = "Entity Tag of the response resource.\n" + + "Used by caches, or in conditional requests."), + @ResponseHeader( + name = "Last-Modified", + description = "Date and time the resource has been modified the last time.\n" + + "Used by caches, or in conditional requests."), + }), + @ApiResponse( + code = 304, + message = "Not Modified. Empty body because the client already has the latest " + + "version of the requested resource."), + @ApiResponse( + code = 400, + message = "Bad Request. \n Invalid request or validation error.", + response = ErrorResponse.class), + @ApiResponse( + code = 404, + message = "Not Found. \n No device is found under the provided type and id.", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n " + + "Server error occurred while retrieving information requested device.", + response = ErrorResponse.class) + }) + Response getNextPendingOperation(@ApiParam(name = "type", value = "The device type, such as ios, android or windows.", required = true) + @PathParam("type") String type, + @ApiParam(name = "id", value = "The device id.", required = true) + @PathParam("id") String deviceId); + + @PUT + @Path("/{type}/{id}/operations") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + consumes = MediaType.APPLICATION_JSON, + httpMethod = "PUT", + value = "Update Operation", + notes = "Update the Operations.", + tags = "Device Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:operations") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully updated the operations.", + responseHeaders = { + @ResponseHeader( + name = "Content-Type", + description = "The content type of the body"), + @ResponseHeader( + name = "ETag", + description = "Entity Tag of the response resource.\n" + + "Used by caches, or in conditional requests."), + @ResponseHeader( + name = "Last-Modified", + description = "Date and time the resource has been modified the last time.\n" + + "Used by caches, or in conditional requests."), + }), + @ApiResponse( + code = 304, + message = "Not Modified. Empty body because the client already has the latest " + + "version of the requested resource."), + @ApiResponse( + code = 400, + message = "Bad Request. \n Invalid request or validation error.", + response = ErrorResponse.class), + @ApiResponse( + code = 404, + message = "Not Found. \n No device is found under the provided type and id.", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n " + + "Server error occurred while retrieving information requested device.", + response = ErrorResponse.class) + }) + Response updateOperation(@ApiParam(name = "type", value = "The device type, such as ios, android or windows.", required = true) + @PathParam("type") String type, + @ApiParam(name = "id", value = "The device id.", required = true) + @PathParam("id") String deviceId, + @ApiParam(name = "operation", value = "Operation object with data.", required = true) + @Valid Operation operation); + + @GET + @Path("/{type}/{id}/status/operations") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + consumes = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Get pending operation of the given device", + notes = "Returns the Operations.", + tags = "Device Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:operations") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully retrieved the operations.", + response = OperationList.class, + responseHeaders = { + @ResponseHeader( + name = "Content-Type", + description = "The content type of the body"), + @ResponseHeader( + name = "ETag", + description = "Entity Tag of the response resource.\n" + + "Used by caches, or in conditional requests."), + @ResponseHeader( + name = "Last-Modified", + description = "Date and time the resource has been modified the last time.\n" + + "Used by caches, or in conditional requests."), + }), + @ApiResponse( + code = 304, + message = "Not Modified. Empty body because the client already has the latest " + + "version of the requested resource."), + @ApiResponse( + code = 400, + message = "Bad Request. \n Invalid request or validation error.", + response = ErrorResponse.class), + @ApiResponse( + code = 404, + message = "Not Found. \n No device is found under the provided type and id.", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n " + + "Server error occurred while retrieving information requested device.", + response = ErrorResponse.class) + }) + Response getOperationsByDeviceAndStatus(@ApiParam(name = "type", value = "The device type, such as ios, android or windows.", required = true) + @PathParam("type") String type, + @ApiParam(name = "id", value = "The device id.", required = true) + @PathParam("id") String deviceId, + @ApiParam(name = "status", value = "status of the operation.", required = true) + @QueryParam("status")Operation.Status status); + } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java index 3f3185a30d..4bbbfdb130 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java @@ -18,17 +18,18 @@ */ package org.wso2.carbon.device.mgt.jaxrs.service.impl; -import io.swagger.annotations.ApiParam; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.context.CarbonContext; import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; import org.wso2.carbon.device.mgt.common.DeviceManagementException; import org.wso2.carbon.device.mgt.common.EnrolmentInfo; import org.wso2.carbon.device.mgt.common.Feature; import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.common.InvalidDeviceException; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.PaginationResult; import org.wso2.carbon.device.mgt.common.app.mgt.Application; @@ -36,6 +37,7 @@ import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; import org.wso2.carbon.device.mgt.common.authorization.DeviceAccessAuthorizationException; import org.wso2.carbon.device.mgt.common.authorization.DeviceAccessAuthorizationService; import org.wso2.carbon.device.mgt.common.device.details.DeviceLocation; +import org.wso2.carbon.device.mgt.common.operation.mgt.Activity; import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; import org.wso2.carbon.device.mgt.common.policy.mgt.Policy; @@ -52,6 +54,7 @@ import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceCompliance; import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList; import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse; import org.wso2.carbon.device.mgt.jaxrs.beans.OperationList; +import org.wso2.carbon.device.mgt.jaxrs.beans.OperationRequest; import org.wso2.carbon.device.mgt.jaxrs.service.api.DeviceManagementService; import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.RequestValidationUtil; import org.wso2.carbon.device.mgt.jaxrs.util.DeviceMgtAPIUtils; @@ -59,6 +62,7 @@ import org.wso2.carbon.policy.mgt.common.PolicyManagementException; import org.wso2.carbon.policy.mgt.core.PolicyManagerService; import org.wso2.carbon.utils.multitenancy.MultitenantUtils; +import javax.validation.Valid; import javax.validation.constraints.Size; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -85,6 +89,126 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { private static final Log log = LogFactory.getLog(DeviceManagementServiceImpl.class); + @POST + @Override + public Response addDevice(Device device) { + if (device == null) { + String errorMessage = "The payload of the device enrollment is incorrect."; + return Response.status(Response.Status.BAD_REQUEST).entity(errorMessage).build(); + } + try { + DeviceManagementProviderService dms = DeviceMgtAPIUtils.getDeviceManagementService(); + device.getEnrolmentInfo().setOwner(DeviceMgtAPIUtils.getAuthenticatedUser()); + boolean status = dms.enrollDevice(device); + return Response.status(Response.Status.OK).entity(status).build(); + } catch (DeviceManagementException e) { + String msg = "Error occurred while enrolling the android, which carries the id '" + + device.getDeviceIdentifier() + "'"; + log.error(msg, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); + } + } + + @GET + @Path("/{type}/{id}/status") + @Override + public Response isEnrolled(@PathParam("type") String type, @PathParam("id") String id) { + boolean result; + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(id, type); + try { + result = DeviceMgtAPIUtils.getDeviceManagementService().isEnrolled(deviceIdentifier); + if (result) { + return Response.status(Response.Status.OK).build(); + } else { + return Response.status(Response.Status.NOT_FOUND).build(); + } + } catch (DeviceManagementException e) { + String msg = "Error occurred while checking enrollment status of the device."; + log.error(msg, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); + } + } + + @PUT + @Path("/{type}/{id}") + @Override + public Response updateDevice(@PathParam("type") String type, @PathParam("id") String id, @Valid Device updateDevice) { + Device device; + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(id); + deviceIdentifier.setType(type); + try { + device = DeviceMgtAPIUtils.getDeviceManagementService().getDevice(deviceIdentifier); + } catch (DeviceManagementException e) { + String msg = "Error occurred while getting enrollment details of the Android device that carries the id '" + + id + "'"; + log.error(msg, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); + } + + if (updateDevice == null) { + String errorMessage = "The payload of the device enrollment is incorrect."; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).entity(errorMessage).build(); + } + if (device == null) { + String errorMessage = "The device to be modified doesn't exist."; + log.error(errorMessage); + return Response.status(Response.Status.NOT_FOUND).entity(errorMessage).build(); + } + if (device.getEnrolmentInfo().getStatus() == EnrolmentInfo.Status.ACTIVE ) { + DeviceAccessAuthorizationService deviceAccessAuthorizationService = + DeviceMgtAPIUtils.getDeviceAccessAuthorizationService(); + boolean status = false; + try { + status = deviceAccessAuthorizationService.isUserAuthorized(new DeviceIdentifier(id, type)); + } catch (DeviceAccessAuthorizationException e) { + String msg = "Error occurred while modifying enrollment of the Android device that carries the id '" + + id + "'"; + log.error(msg, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); + } + if (!status) { + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + } + if(updateDevice.getEnrolmentInfo() != null) { + device.setEnrolmentInfo(device.getEnrolmentInfo()); + } + device.getEnrolmentInfo().setOwner(DeviceMgtAPIUtils.getAuthenticatedUser()); + if(updateDevice.getDeviceInfo() != null) { + device.setDeviceInfo(updateDevice.getDeviceInfo()); + } + device.setDeviceIdentifier(id); + if(updateDevice.getDescription() != null) { + device.setDescription(updateDevice.getDescription()); + } + if(updateDevice.getName() != null) { + device.setName(updateDevice.getName()); + } + if(updateDevice.getFeatures() != null) { + device.setFeatures(updateDevice.getFeatures()); + } + if(updateDevice.getProperties() != null) { + device.setProperties(updateDevice.getProperties()); + } + boolean result; + try { + device.setType(type); + result = DeviceMgtAPIUtils.getDeviceManagementService().modifyEnrollment(device); + if (result) { + return Response.status(Response.Status.ACCEPTED).build(); + } else { + return Response.status(Response.Status.NOT_MODIFIED).build(); + } + } catch (DeviceManagementException e) { + String msg = "Error occurred while modifying enrollment of the Android device that carries the id '" + + id + "'"; + log.error(msg, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); + } + } + @GET @Override public Response getDevices( @@ -601,4 +725,120 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); } } + + @POST + @Path("/{type}/operations") + public Response addOperation(@PathParam("type") String type, OperationRequest operationRequest) { + try { + if (operationRequest == null || operationRequest.getDeviceIdentifiers() == null) { + String errorMessage = "Device identifier list is empty"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } + DeviceIdentifier deviceIdentifier; + List deviceIdentifiers = new ArrayList<>(); + for (String deviceId : operationRequest.getDeviceIdentifiers()) { + deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(type); + deviceIdentifiers.add(deviceIdentifier); + } + Activity activity = DeviceMgtAPIUtils.getDeviceManagementService().addOperation(type + , operationRequest.getOperation(), deviceIdentifiers); + return Response.status(Response.Status.CREATED).entity(activity).build(); + } catch (InvalidDeviceException e) { + String errorMessage = "Invalid Device Identifiers found."; + log.error(errorMessage, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + log.error(errorMessage, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); + } + } + + @GET + @Path("/{type}/{id}/pending/operations") + public Response getPendingOperations(@PathParam("type") String type, @PathParam("id") String deviceId) { + try { + List operations = DeviceMgtAPIUtils.getDeviceManagementService().getPendingOperations( + new DeviceIdentifier(deviceId, type)); + OperationList operationsList = new OperationList(); + operationsList.setList(operations); + operationsList.setCount(operations.size()); + return Response.status(Response.Status.OK).entity(operationsList).build(); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + log.error(errorMessage, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); + } + } + + @GET + @Path("/{type}/{id}/last-pending/operation") + public Response getNextPendingOperation(@PathParam("type") String type, @PathParam("id") String deviceId) { + try { + Operation operation = DeviceMgtAPIUtils.getDeviceManagementService().getNextPendingOperation( + new DeviceIdentifier(deviceId, type)); + return Response.status(Response.Status.OK).entity(operation).build(); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + log.error(errorMessage, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); + } + } + + @PUT + @Path("/{type}/{id}/operations") + public Response updateOperation(@PathParam("type") String type, @PathParam("id") String deviceId, Operation operation) { + try { + if (operation == null) { + String errorMessage = "Device identifier list is empty"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } + DeviceMgtAPIUtils.getDeviceManagementService().updateOperation + (new DeviceIdentifier(deviceId, type), operation); + return Response.status(Response.Status.ACCEPTED).build(); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + log.error(errorMessage, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); + } + } + + @GET + @Path("/{type}/{id}/status/operations") + public Response getOperationsByDeviceAndStatus(@PathParam("type") String type, @PathParam("id") String deviceId, + @QueryParam("status")Operation.Status status) { + if (status == null) { + String errorMessage = "status is empty"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } + + try { + List operations = DeviceMgtAPIUtils.getDeviceManagementService() + .getOperationsByDeviceAndStatus(new DeviceIdentifier(deviceId, type), status); + OperationList operationsList = new OperationList(); + operationsList.setList(operations); + operationsList.setCount(operations.size()); + return Response.status(Response.Status.OK).entity(operationsList).build(); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + log.error(errorMessage, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service"; + log.error(errorMessage, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); + } + } } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/Device.java b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/Device.java index 04df8c7dcd..d492ce22e1 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/Device.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/Device.java @@ -30,7 +30,7 @@ public class Device implements Serializable { private static final long serialVersionUID = 1998101711L; - @ApiModelProperty(name = "id", value = "ID of the device in the WSO2 EMM device information database.", + @ApiModelProperty(name = "id", value = "ID of the device in the device information database.", required = true) private int id; From 0e9bb7caf6694cf04fef156c2aefd784a8c2f23a Mon Sep 17 00:00:00 2001 From: ayyoob Date: Wed, 19 Apr 2017 16:34:18 +0530 Subject: [PATCH 002/358] corrected operation definition --- .../wso2/carbon/device/mgt/jaxrs/beans/OperationRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/OperationRequest.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/OperationRequest.java index 6d980cbee0..d8cd2ec769 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/OperationRequest.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/OperationRequest.java @@ -24,7 +24,7 @@ public class OperationRequest { @ApiModelProperty(name = "deviceIdentifiers", value = "list of devices that needs to be verified against the user", required = true) List deviceIdentifiers; - @ApiModelProperty(name = "permission", value = "if null then checks against the owner else it could be grouping permission", required = false) + @ApiModelProperty(name = "operation", value = "operation data", required = false) Operation operation; public List getDeviceIdentifiers() { From ea6afee772092f7c2dab1b0ceade732e5a9153b8 Mon Sep 17 00:00:00 2001 From: ayyoob Date: Wed, 19 Apr 2017 21:04:56 +0530 Subject: [PATCH 003/358] added device properties to a single table --- .../type/deployer/config/DeviceDetails.java | 44 ++-- .../type/deployer/config/Properties.java | 76 ++++++ .../deployer/template/DeviceTypeManager.java | 5 + .../dao/DeviceTypePluginDAOManager.java | 14 +- ...O.java => PerDeviceTypePluginDAOImpl.java} | 8 +- .../type/deployer/template/dao/PluginDAO.java | 36 +++ .../dao/PropertyBasedPluginDAOImpl.java | 239 ++++++++++++++++++ .../src/test/resources/sample.xml | 55 ++-- .../src/main/resources/dbscripts/cdm/h2.sql | 8 + .../main/resources/dbscripts/cdm/mssql.sql | 9 + .../main/resources/dbscripts/cdm/mysql.sql | 8 + .../main/resources/dbscripts/cdm/oracle.sql | 10 + .../resources/dbscripts/cdm/postgresql.sql | 8 + 13 files changed, 468 insertions(+), 52 deletions(-) create mode 100644 components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/config/Properties.java rename components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/{DeviceTypePluginDAO.java => PerDeviceTypePluginDAOImpl.java} (97%) create mode 100644 components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/PluginDAO.java create mode 100644 components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/PropertyBasedPluginDAOImpl.java diff --git a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/config/DeviceDetails.java b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/config/DeviceDetails.java index 6428bf097f..4939557863 100644 --- a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/config/DeviceDetails.java +++ b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/config/DeviceDetails.java @@ -21,8 +21,8 @@ package org.wso2.carbon.device.mgt.extensions.device.type.deployer.config; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; -import javax.xml.bind.annotation.XmlValue; /** @@ -44,61 +44,61 @@ import javax.xml.bind.annotation.XmlValue; */ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "DeviceDetails", propOrder = { - "value" + "properties" }) public class DeviceDetails { - @XmlValue - protected String value; @XmlAttribute(name = "table-id") protected String tableId; + @XmlElement(name = "Properties", required = true) + protected Properties properties; /** - * Gets the value of the value property. + * Gets the value of the tableId property. * * @return * possible object is * {@link String } * */ - public String getValue() { - return value; + public String getTableId() { + return tableId; } /** - * Sets the value of the value property. + * Sets the value of the tableId property. * * @param value * allowed object is * {@link String } * */ - public void setValue(String value) { - this.value = value; + public void setTableId(String value) { + this.tableId = value; } /** - * Gets the value of the tableId property. - * + * Gets the value of the properties property. + * * @return * possible object is - * {@link String } - * + * {@link Properties } + * */ - public String getTableId() { - return tableId; + public Properties getProperties() { + return properties; } /** - * Sets the value of the tableId property. - * + * Sets the value of the properties property. + * * @param value * allowed object is - * {@link String } - * + * {@link Properties } + * */ - public void setTableId(String value) { - this.tableId = value; + public void setProperties(Properties value) { + this.properties = value; } } diff --git a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/config/Properties.java b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/config/Properties.java new file mode 100644 index 0000000000..f2be9763bf --- /dev/null +++ b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/config/Properties.java @@ -0,0 +1,76 @@ + +package org.wso2.carbon.device.mgt.extensions.device.type.deployer.config; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for Properties complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType name="Properties">
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element name="Property" maxOccurs="unbounded" minOccurs="0">
+ *           <simpleType>
+ *             <restriction base="{http://www.w3.org/2001/XMLSchema}string">
+ *               <enumeration value="attr1"/>
+ *               <enumeration value="attr2"/>
+ *             </restriction>
+ *           </simpleType>
+ *         </element>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "Properties", propOrder = { + "property" +}) +public class Properties { + + @XmlElement(name = "Property") + protected List property; + + /** + * Gets the value of the property property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the property property. + * + *

+ * For example, to add a new item, do as follows: + *

+     *    getProperty().add(newItem);
+     * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link String } + * + * + */ + public List getProperty() { + if (property == null) { + property = new ArrayList(); + } + return this.property; + } + +} diff --git a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/DeviceTypeManager.java b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/DeviceTypeManager.java index 98c5ef2870..87e0d072d9 100644 --- a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/DeviceTypeManager.java +++ b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/DeviceTypeManager.java @@ -155,6 +155,11 @@ public class DeviceTypeManager implements DeviceManager { } finally { PrivilegedCarbonContext.endTenantFlow(); } + } else { + if (deviceDetails.getProperties() != null && deviceDetails.getProperties().getProperty() != null + && deviceDetails.getProperties().getProperty().size() > 0 ) { + deviceTypePluginDAOManager = new DeviceTypePluginDAOManager(deviceType, deviceDetails); + } } } } diff --git a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/DeviceTypePluginDAOManager.java b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/DeviceTypePluginDAOManager.java index 10a9b018b3..8b6589193d 100644 --- a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/DeviceTypePluginDAOManager.java +++ b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/DeviceTypePluginDAOManager.java @@ -19,17 +19,25 @@ package org.wso2.carbon.device.mgt.extensions.device.type.deployer.template.dao; +import org.wso2.carbon.device.mgt.extensions.device.type.deployer.config.DeviceDetails; + public class DeviceTypePluginDAOManager { - private DeviceTypePluginDAO deviceTypePluginDAO; + private PluginDAO deviceTypePluginDAO; private DeviceTypeDAOHandler deviceTypeDAOHandler; + private static String DEFAULT_DATASOURCE_NAME = "jdbc/DM_DS"; public DeviceTypePluginDAOManager(String datasourceName, DeviceDAODefinition deviceDAODefinition) { deviceTypeDAOHandler = new DeviceTypeDAOHandler(datasourceName); - deviceTypePluginDAO = new DeviceTypePluginDAO(deviceDAODefinition, deviceTypeDAOHandler); + deviceTypePluginDAO = new PerDeviceTypePluginDAOImpl(deviceDAODefinition, deviceTypeDAOHandler); + } + + public DeviceTypePluginDAOManager(String deviceType, DeviceDetails deviceDetails) { + deviceTypeDAOHandler = new DeviceTypeDAOHandler(DEFAULT_DATASOURCE_NAME); + deviceTypePluginDAO = new PropertyBasedPluginDAOImpl(deviceDetails, deviceTypeDAOHandler, deviceType); } - public DeviceTypePluginDAO getDeviceDAO() { + public PluginDAO getDeviceDAO() { return deviceTypePluginDAO; } diff --git a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/DeviceTypePluginDAO.java b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/PerDeviceTypePluginDAOImpl.java similarity index 97% rename from components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/DeviceTypePluginDAO.java rename to components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/PerDeviceTypePluginDAOImpl.java index 163ac64711..0ed009a07c 100644 --- a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/DeviceTypePluginDAO.java +++ b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/PerDeviceTypePluginDAOImpl.java @@ -36,9 +36,9 @@ import java.util.List; * Implements CRUD for Devices. This holds the generic implementation. An instance of this will be created for * each device type. */ -public class DeviceTypePluginDAO { +public class PerDeviceTypePluginDAOImpl implements PluginDAO{ - private static final Log log = LogFactory.getLog(DeviceTypePluginDAO.class); + private static final Log log = LogFactory.getLog(PerDeviceTypePluginDAOImpl.class); private DeviceTypeDAOHandler deviceTypeDAOHandler; private DeviceDAODefinition deviceDAODefinition; private String selectDBQueryForGetDevice; @@ -47,8 +47,8 @@ public class DeviceTypePluginDAO { private String deleteDBQueryToRemoveDevicd; private String selectDBQueryToGetAllDevice; - public DeviceTypePluginDAO(DeviceDAODefinition deviceDAODefinition, - DeviceTypeDAOHandler deviceTypeDAOHandler) { + public PerDeviceTypePluginDAOImpl(DeviceDAODefinition deviceDAODefinition, + DeviceTypeDAOHandler deviceTypeDAOHandler) { this.deviceTypeDAOHandler = deviceTypeDAOHandler; this.deviceDAODefinition = deviceDAODefinition; initializeDbQueries(); diff --git a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/PluginDAO.java b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/PluginDAO.java new file mode 100644 index 0000000000..85029371d9 --- /dev/null +++ b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/PluginDAO.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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.wso2.carbon.device.mgt.extensions.device.type.deployer.template.dao; + +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.extensions.device.type.deployer.exception.DeviceTypeMgtPluginException; +import java.util.List; + +public interface PluginDAO { + + Device getDevice(String deviceId) throws DeviceTypeMgtPluginException; + + boolean addDevice(Device device) throws DeviceTypeMgtPluginException; + + boolean updateDevice(Device device) throws DeviceTypeMgtPluginException; + + boolean deleteDevice(String deviceId) throws DeviceTypeMgtPluginException; + + List getAllDevices() throws DeviceTypeMgtPluginException; +} diff --git a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/PropertyBasedPluginDAOImpl.java b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/PropertyBasedPluginDAOImpl.java new file mode 100644 index 0000000000..3f27023d33 --- /dev/null +++ b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/deployer/template/dao/PropertyBasedPluginDAOImpl.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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.wso2.carbon.device.mgt.extensions.device.type.deployer.template.dao; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.extensions.device.type.deployer.config.DeviceDetails; +import org.wso2.carbon.device.mgt.extensions.device.type.deployer.exception.DeviceTypeMgtPluginException; +import org.wso2.carbon.device.mgt.extensions.device.type.deployer.template.util.DeviceTypeUtils; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implements CRUD for Devices. This holds the generic implementation. An instance of this will be created for + * each device type. + */ +public class PropertyBasedPluginDAOImpl implements PluginDAO { + + private static final Log log = LogFactory.getLog(PropertyBasedPluginDAOImpl.class); + private DeviceTypeDAOHandler deviceTypeDAOHandler; + private List deviceProps; + private String deviceType; + private static final String PROPERTY_KEY_COLUMN_NAME = "PROPERTY_NAME"; + private static final String PROPERTY_VALUE_COLUMN_NAME = "PROPERTY_VALUE"; + + public PropertyBasedPluginDAOImpl(DeviceDetails deviceDetails, + DeviceTypeDAOHandler deviceTypeDAOHandler, String deviceType) { + this.deviceTypeDAOHandler = deviceTypeDAOHandler; + this.deviceProps = deviceDetails.getProperties().getProperty(); + this.deviceType = deviceType; + } + + public Device getDevice(String deviceId) throws DeviceTypeMgtPluginException { + Connection conn = null; + PreparedStatement stmt = null; + Device device = null; + ResultSet resultSet = null; + try { + conn = deviceTypeDAOHandler.getConnection(); + stmt = conn.prepareStatement( + "SELECT * FROM DM_DEVICE_PROPERTIES WHERE DEVICE_TYPE_NAME = ? AND DEVICE_IDENTIFICATION = ?"); + stmt.setString(1, deviceType); + stmt.setString(2, deviceId); + resultSet = stmt.executeQuery(); + device = new Device(); + device.setDeviceIdentifier(deviceId); + device.setType(deviceType); + List properties = new ArrayList<>(); + while (resultSet.next()) { + Device.Property property = new Device.Property(); + property.setName(resultSet.getString(PROPERTY_KEY_COLUMN_NAME)); + property.setValue(resultSet.getString(PROPERTY_VALUE_COLUMN_NAME)); + properties.add(property); + } + device.setProperties(properties); + } catch (SQLException e) { + String msg = "Error occurred while fetching device : '" + deviceId + "' type " + deviceType; + log.error(msg, e); + throw new DeviceTypeMgtPluginException(msg, e); + } finally { + DeviceTypeUtils.cleanupResources(stmt, resultSet); + deviceTypeDAOHandler.closeConnection(); + } + + return device; + } + + public boolean addDevice(Device device) throws DeviceTypeMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = deviceTypeDAOHandler.getConnection(); + stmt = conn.prepareStatement( + "INSERT INTO DM_DEVICE_PROPERTIES(DEVICE_TYPE_NAME, DEVICE_IDENTIFICATION, PROPERTY_NAME, " + + "PROPERTY_VALUE) VALUES (?, ?, ?, ?)"); + for (String propertyKey : deviceProps) { + stmt.setString(1, deviceType); + stmt.setString(2, device.getDeviceIdentifier()); + stmt.setString(3, propertyKey); + stmt.setString(4, getPropertyValue(device.getProperties(), propertyKey)); + stmt.addBatch(); + } + stmt.executeBatch(); + status = true; + } catch (SQLException e) { + String msg = "Error occurred while adding the device '" + + device.getDeviceIdentifier() + "' to the type " + deviceType + " db."; + log.error(msg, e); + throw new DeviceTypeMgtPluginException(msg, e); + } finally { + DeviceTypeUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean updateDevice(Device device) throws DeviceTypeMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = deviceTypeDAOHandler.getConnection(); + stmt = conn.prepareStatement( + "UPDATE DM_DEVICE_PROPERTIES SET PROPERTY_VALUE = ? WHERE DEVICE_TYPE_NAME = ? AND " + + "DEVICE_IDENTIFICATION = ? AND PROPERTY_NAME = ?"); + + for (Device.Property property : device.getProperties()) { + stmt.setString(1, getPropertyValue(device.getProperties(), property.getValue())); + stmt.setString(1, deviceType); + stmt.setString(2, device.getDeviceIdentifier()); + stmt.setString(3, property.getName()); + stmt.addBatch(); + } + stmt.executeBatch(); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("device " + device.getDeviceIdentifier() + " data has been modified."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while modifying the device '" + + device.getDeviceIdentifier() + "' data on" + deviceType; + log.error(msg, e); + throw new DeviceTypeMgtPluginException(msg, e); + } finally { + DeviceTypeUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean deleteDevice(String deviceId) throws DeviceTypeMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = deviceTypeDAOHandler.getConnection(); + stmt = conn.prepareStatement("DELETE FROM DM_DEVICE_PROPERTIES WHERE DEVICE_TYPE_NAME = ? " + + "AND DEVICE_IDENTIFICATION = ?"); + stmt.setString(1, deviceType); + stmt.setString(2, deviceId); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("device " + deviceId + " data has deleted from the " + + deviceType + " table."); + } + } + } catch (SQLException e) { + String msg = + "Error occurred while deleting " + deviceType + " device " + deviceId; + log.error(msg, e); + throw new DeviceTypeMgtPluginException(msg, e); + } finally { + DeviceTypeUtils.cleanupResources(stmt, null); + } + return status; + } + + public List getAllDevices() throws DeviceTypeMgtPluginException { + Connection conn; + PreparedStatement stmt = null; + ResultSet resultSet = null; + Map deviceMap = new HashMap<>(); + try { + conn = deviceTypeDAOHandler.getConnection(); + stmt = conn.prepareStatement("SELECT DEVICE_IDENTIFICATION, PROPERTY_NAME" + + ", PROPERTY_VALUE FROM DM_DEVICE_PROPERTIES WHERE DEVICE_TYPE_NAME = ?"); + stmt.setString(1, deviceType); + resultSet = stmt.executeQuery(); + while (resultSet.next()) { + String deviceId = resultSet.getString("DEVICE_IDENTIFICATION"); + Device deviceInMap = deviceMap.get(deviceId); + if (deviceInMap == null) { + deviceInMap = new Device(); + deviceInMap.setDeviceIdentifier(deviceId); + deviceInMap.setType(deviceType); + List properties = new ArrayList<>(); + deviceInMap.setProperties(properties); + deviceMap.put(deviceId, deviceInMap); + } + Device.Property prop = new Device.Property(); + prop.setName(resultSet.getString(PROPERTY_KEY_COLUMN_NAME)); + prop.setName(resultSet.getString(PROPERTY_VALUE_COLUMN_NAME)); + deviceInMap.getProperties().add(prop); + } + if (log.isDebugEnabled()) { + log.debug( + "All device details have fetched from " + deviceType + " table."); + } + return Arrays.asList((Device[])deviceMap.values().toArray()); + } catch (SQLException e) { + String msg = + "Error occurred while fetching all " + deviceType + " device data'"; + log.error(msg, e); + throw new DeviceTypeMgtPluginException(msg, e); + } finally { + DeviceTypeUtils.cleanupResources(stmt, resultSet); + deviceTypeDAOHandler.closeConnection(); + } + } + + private String getPropertyValue(List properties, String propertyName) { + for (Device.Property property : properties) { + if (property.getName() != null && property.getName().equals(propertyName)) { + return property.getValue(); + } + } + return null; + } + +} \ No newline at end of file diff --git a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/test/resources/sample.xml b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/test/resources/sample.xml index 414bcaa8dd..8adefb2207 100644 --- a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/test/resources/sample.xml +++ b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/test/resources/sample.xml @@ -19,7 +19,16 @@ --> - + + + + + + attr1 + attr2 + + + @@ -38,28 +47,28 @@ - - - temperature sensor fitted - org.wso2.temperature.stream - this is a sensor - - celcius - atmeggga11234 - - - - temperature sensor fitted - org.wso2.temperature.stream - this is a sensor - - celcius - - - - - - + + + + + + + + + + + + + + + + + + + + + + false diff --git a/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/h2.sql b/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/h2.sql index 498a439c07..b8107880c8 100644 --- a/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/h2.sql +++ b/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/h2.sql @@ -39,6 +39,14 @@ CREATE TABLE IF NOT EXISTS DM_DEVICE ( CONSTRAINT uk_DM_DEVICE UNIQUE (NAME, DEVICE_TYPE_ID, DEVICE_IDENTIFICATION, TENANT_ID) ); +CREATE TABLE IF NOT EXISTS DM_DEVICE_PROPERTIES ( + DEVICE_TYPE_NAME VARCHAR(300) NOT NULL, + DEVICE_IDENTIFICATION VARCHAR(300) NOT NULL, + PROPERTY_NAME VARCHAR(100) DEFAULT 0, + PROPERTY_VALUE VARCHAR(100) DEFAULT NULL, + PRIMARY KEY (DEVICE_TYPE_NAME, DEVICE_IDENTIFICATION, PROPERTY_NAME) +); + CREATE TABLE IF NOT EXISTS DM_DEVICE_GROUP_MAP ( ID INTEGER AUTO_INCREMENT NOT NULL, DEVICE_ID INTEGER DEFAULT NULL, diff --git a/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/mssql.sql b/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/mssql.sql index ed33177429..2d52f05a19 100644 --- a/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/mssql.sql +++ b/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/mssql.sql @@ -52,6 +52,15 @@ CREATE TABLE DM_DEVICE ( REFERENCES DM_DEVICE_TYPE (ID) ON DELETE NO ACTION ON UPDATE NO ACTION ); +IF NOT EXISTS (SELECT * FROM SYS.OBJECTS WHERE OBJECT_ID = OBJECT_ID(N'[DBO].[DM_DEVICE_PROPERTIES]') AND TYPE IN (N'U')) +CREATE TABLE IF NOT EXISTS DM_DEVICE_PROPERTIES ( + DEVICE_TYPE_NAME VARCHAR(300) NOT NULL, + DEVICE_IDENTIFICATION VARCHAR(300) NOT NULL, + PROPERTY_NAME VARCHAR(100) DEFAULT 0, + PROPERTY_VALUE VARCHAR(100) DEFAULT NULL, + PRIMARY KEY (DEVICE_TYPE_NAME, DEVICE_IDENTIFICATION, PROPERTY_NAME) +); + IF NOT EXISTS(SELECT * FROM SYS.OBJECTS WHERE OBJECT_ID = OBJECT_ID(N'[DBO].[DM_DEVICE_GROUP_MAP]') AND TYPE IN (N'U')) diff --git a/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/mysql.sql b/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/mysql.sql index 6bec98fc5b..5a71ad0c21 100644 --- a/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/mysql.sql +++ b/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/mysql.sql @@ -46,6 +46,14 @@ CREATE TABLE IF NOT EXISTS DM_DEVICE ( CREATE INDEX IDX_DM_DEVICE ON DM_DEVICE(TENANT_ID, DEVICE_TYPE_ID); +CREATE TABLE IF NOT EXISTS DM_DEVICE_PROPERTIES ( + DEVICE_TYPE_NAME VARCHAR(300) NOT NULL, + DEVICE_IDENTIFICATION VARCHAR(300) NOT NULL, + PROPERTY_NAME VARCHAR(100) DEFAULT 0, + PROPERTY_VALUE VARCHAR(100) DEFAULT NULL, + PRIMARY KEY (DEVICE_TYPE_NAME, DEVICE_IDENTIFICATION, PROPERTY_NAME) +)ENGINE = InnoDB; + CREATE TABLE IF NOT EXISTS DM_DEVICE_GROUP_MAP ( ID INTEGER AUTO_INCREMENT NOT NULL, DEVICE_ID INTEGER DEFAULT NULL, diff --git a/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/oracle.sql b/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/oracle.sql index 4bd5f55de9..00419049bc 100644 --- a/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/oracle.sql +++ b/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/oracle.sql @@ -97,6 +97,16 @@ WHEN (NEW.ID IS NULL) END; / +CREATE TABLE IF NOT EXISTS DM_DEVICE_PROPERTIES ( + DEVICE_TYPE_NAME VARCHAR2(300) NOT NULL, + DEVICE_IDENTIFICATION VARCHAR2(300) NOT NULL, + PROPERTY_NAME VARCHAR2(100) DEFAULT 0, + PROPERTY_VALUE VARCHAR2(100) DEFAULT NULL, + CONSTRAINT PK_DM_DEVICE_PROPERTY PRIMARY KEY (DEVICE_TYPE_NAME, DEVICE_IDENTIFICATION, PROPERTY_NAME) +) +/ + + CREATE TABLE DM_DEVICE_GROUP_MAP ( ID NUMBER(10) NOT NULL, DEVICE_ID NUMBER(10) DEFAULT NULL, diff --git a/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/postgresql.sql b/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/postgresql.sql index a4f07d7781..d0bd24dac7 100644 --- a/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/postgresql.sql +++ b/features/device-mgt/org.wso2.carbon.device.mgt.server.feature/src/main/resources/dbscripts/cdm/postgresql.sql @@ -38,6 +38,14 @@ CREATE TABLE IF NOT EXISTS DM_DEVICE ( REFERENCES DM_DEVICE_TYPE (ID) ON DELETE NO ACTION ON UPDATE NO ACTION ); +CREATE TABLE IF NOT EXISTS DM_DEVICE_PROPERTIES ( + DEVICE_TYPE_NAME VARCHAR(300) NOT NULL, + DEVICE_IDENTIFICATION VARCHAR(300) NOT NULL, + PROPERTY_NAME VARCHAR(100) DEFAULT 0, + PROPERTY_VALUE VARCHAR(100) DEFAULT NULL, + PRIMARY KEY (DEVICE_TYPE_NAME, DEVICE_IDENTIFICATION, PROPERTY_NAME) +); + CREATE INDEX IDX_DM_DEVICE ON DM_DEVICE(TENANT_ID, DEVICE_TYPE_ID); CREATE TABLE IF NOT EXISTS DM_DEVICE_GROUP_MAP ( From 586bcae2b80915877dead03862d02bfa25eb65f3 Mon Sep 17 00:00:00 2001 From: ayyoob Date: Thu, 20 Apr 2017 17:55:23 +0530 Subject: [PATCH 004/358] added default UI - initial commit --- .../src/test/resources/sample.xml | 8 + .../devicemgt/app/modules/utility.js | 6 + .../pages/cdmf.page.device.type.view/view.js | 17 +- .../app/pages/cdmf.page.device.view/view.js | 6 +- .../app/pages/cdmf.page.devices/devices.js | 22 +- .../device-view.hbs | 77 ++++ .../device-view.js | 37 ++ .../device-view.json | 3 + .../public/images/deviceType.png | Bin 0 -> 7764 bytes .../private/config.json | 49 +++ .../public/css/styles.css | 61 +++ .../public/images/deviceType.png | Bin 0 -> 7764 bytes .../public/images/schematicsGuide.png | Bin 0 -> 128984 bytes .../public/images/thumb.png | Bin 0 -> 7764 bytes .../public/js/type-view.js | 388 ++++++++++++++++++ .../type-view.hbs | 142 +++++++ .../type-view.js | 24 ++ .../type-view.json | 3 + 18 files changed, 828 insertions(+), 15 deletions(-) create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.hbs create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.json create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/public/images/deviceType.png create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/private/config.json create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/css/styles.css create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/images/deviceType.png create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/images/schematicsGuide.png create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/images/thumb.png create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/js/type-view.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/type-view.hbs create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/type-view.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/type-view.json diff --git a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/test/resources/sample.xml b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/test/resources/sample.xml index 8adefb2207..e79f988710 100644 --- a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/test/resources/sample.xml +++ b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.device.type.deployer/src/test/resources/sample.xml @@ -134,4 +134,12 @@ + + + DEVICE_INFO + APPLICATION_LIST + DEVICE_LOCATION + + + \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/modules/utility.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/modules/utility.js index 7b6add5fa8..159b61d999 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/modules/utility.js +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/modules/utility.js @@ -63,6 +63,9 @@ utility = function () { publicMethods.getDeviceTypeConfig = function (deviceType) { var unitName = publicMethods.getTenantedDeviceUnitName(deviceType, "type-view"); + if (!unitName) { + return null; + } if (deviceType in deviceTypeConfigMap) { return deviceTypeConfigMap[deviceType]; @@ -99,6 +102,9 @@ utility = function () { publicMethods.getDeviceThumb = function (deviceType) { var unitName = publicMethods.getTenantedDeviceUnitName(deviceType, "type-view"); + if (!unitName) { + unitName = "cdmf.unit.default.device.type.type-view"; + } var iconPath = "/app/units/" + unitName + "/public/images/thumb.png"; var icon = new File(iconPath); if (icon.isExists()) { diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.device.type.view/view.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.device.type.view/view.js index 693769dd11..6810da2496 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.device.type.view/view.js +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.device.type.view/view.js @@ -32,15 +32,18 @@ function onRequest(context) { var utility = require("/app/modules/utility.js").utility; var deviceType = context.uriParams.deviceType; - var configs = utility.getDeviceTypeConfig(deviceType); - if(!configs["deviceType"]){ - throw new Error("Invalid Device Type Configurations Found!",""); - } - + var label = deviceType; + if (configs) { + label = configs["deviceType"]["label"]; + } + var unitName = utility.getTenantedDeviceUnitName(deviceType, "type-view"); + if (!unitName) { + unitName = "cdmf.unit.default.device.type.type-view"; + } return { - "deviceTypeViewUnitName": utility.getTenantedDeviceUnitName(deviceType, "type-view"), + "deviceTypeViewUnitName": unitName, "deviceType": deviceType, - "label" : configs["deviceType"]["label"] + "label" : label }; } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.device.view/view.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.device.view/view.js index 21ce993020..754cef4fa8 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.device.view/view.js +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.device.view/view.js @@ -29,5 +29,9 @@ function onRequest(context){ }); var deviceType = context.uriParams.deviceType; - return {"deviceViewUnitName": utility.getTenantedDeviceUnitName(deviceType, "device-view")}; + var unitName = utility.getTenantedDeviceUnitName(deviceType, "device-view"); + if (!unitName) { + unitName = "cdmf.unit.default.device.type.device-view"; + } + return {"deviceViewUnitName": unitName}; } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.devices/devices.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.devices/devices.js index 0f3f8066fd..05e041bc24 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.devices/devices.js +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.devices/devices.js @@ -60,17 +60,25 @@ function onRequest(context) { if (data) { for (var i = 0; i < data.length; i++) { var config = utility.getDeviceTypeConfig(data[i]); - if (!config) { - continue; + var category = "iot"; + var label = data[i]; + var analyticsEnabled = "true"; + var groupingEnabled = "true"; + if (config) { + var deviceType = config.deviceType; + category = deviceType.category; + label = deviceType.label; + analyticsEnabled = deviceType.analyticsEnabled; + groupingEnabled = deviceType.groupingEnabled; } - var deviceType = config.deviceType; + deviceTypes.push({ "type": data[i], - "category": deviceType.category, - "label": deviceType.label, + "category": category, + "label": label, "thumb": utility.getDeviceThumb(data[i]), - "analyticsEnabled": deviceType.analyticsEnabled, - "groupingEnabled": deviceType.groupingEnabled + "analyticsEnabled": analyticsEnabled, + "groupingEnabled": groupingEnabled }); } } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.hbs b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.hbs new file mode 100644 index 0000000000..b79c02383e --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.hbs @@ -0,0 +1,77 @@ +{{! + Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + + WSO2 Inc. licenses this file to you 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. +}} +{{#zone "topCss"}} + +{{/zone}} + +{{#zone "device-thumbnail"}} + +{{/zone}} + +{{#zone "device-opetations"}} +

+ Operations +
+
+ operation comes here +
+{{/zone}} + +{{#zone "device-view-tabs"}} +
  • Device + Statistics +
  • +
  • Operations Log
  • +{{/zone}} + +{{#zone "device-view-tab-contents"}} +
    +
    Device Statistics
    + unit "cdmf.unit.device.type.senseme.realtime.analytics-view" device=device +
    +
    +
    Operations Log
    +
    + +
    +
    + Not available yet +
    +
    +
    +
    +
    +{{/zone}} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.js new file mode 100644 index 0000000000..834d8f5d53 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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. + */ + +function onRequest(context) { + var log = new Log("device-view.js"); + var deviceType = context.uriParams.deviceType; + var deviceId = request.getParameter("id"); + var autoCompleteParams = [ + {"name" : "deviceId", "value" : deviceId} + ]; + + if (deviceType != null && deviceType != undefined && deviceId != null && deviceId != undefined) { + var deviceModule = require("/app/modules/business-controllers/device.js")["deviceModule"]; + var device = deviceModule.viewDevice(deviceType, deviceId); + if (device && device.status != "error") { + return {"device": device.content, "autoCompleteParams" : autoCompleteParams, "encodedFeaturePayloads": ""}; + } else { + response.sendError(404, "Device Id " + deviceId + " of type " + deviceType + " cannot be found!"); + exit(); + } + } +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.json b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/public/images/deviceType.png b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/public/images/deviceType.png new file mode 100644 index 0000000000000000000000000000000000000000..ad3f3a5d010fcce3265fd654e83d0815c59cc3ad GIT binary patch literal 7764 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 literal 0 HcmV?d00001 diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/private/config.json b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/private/config.json new file mode 100644 index 0000000000..f5d01d0d77 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/private/config.json @@ -0,0 +1,49 @@ +{ + "deviceType": { + "label": "Windows", + "category": "mobile", + "scopes" : [ + "perm:windows:enroll", + "perm:windows:lock-devices", + "perm:windows:ring", + "perm:windows:wipe", + "perm:windows:lock-reset", + "perm:windows:reboot", + "perm:windows:location", + "perm:windows:disenroll" + ], + "analyticsEnabled": "false", + "groupingEnabled": "false", + "features": { + "DEVICE_RING": { + "icon": "fw-dial-up", + "permission": "/device-mgt/devices/owning-device/operations/android/ring" + }, + "DEVICE_LOCK": { + "icon": "fw-lock", + "permission": "/device-mgt/devices/owning-device/operations/windows/lock" + }, + "DEVICE_LOCATION": { + "icon": "fw-map-location", + "permission": "/device-mgt/devices/owning-device/operations/windows/location" + }, + "DEVICE_REBOOT": { + "icon": "fw-refresh", + "permission": "/device-mgt/devices/owning-device/operations/windows/reboot" + }, + "LOCK_RESET": { + "icon": "fw-security", + "permission": "/device-mgt/devices/owning-device/operations/windows/lock-reset" + + }, + "DISENROLL": { + "icon": "fw-block", + "permission": "/device-mgt/devices/owning-device/operations/windows/disenroll" + }, + "WIPE_DATA": { + "icon": "fw-delete", + "permission": "/device-mgt/devices/owning-device/operations/windows/wipe" + } + } + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/css/styles.css b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/css/styles.css new file mode 100644 index 0000000000..04e2a26115 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/css/styles.css @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) 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. + * 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. + */ +.circle { + background: none repeat scroll 0 0 #191919; + border-radius: 50px; + height: 50px; + padding: 10px; + width: 50px; + color: #fff; +} +.padding-top-double { + padding-top: 20px; +} +.padding-double { + padding: 20px; +} +.grey { + color: #333; +} +hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #7f7f7f; + margin: 1em 0; + padding: 0; + opacity: 0.2; +} +.light-grey { + color: #7c7c7c; +} +.uppercase { + text-transform: uppercase; +} +.grey-bg { + background-color: #f6f4f4; +} + +.doc-link { + background: #11375B; + padding: 20px; + color: white; + margin-top: 0; +} + +.doc-link a { + color: white; +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/images/deviceType.png b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/images/deviceType.png new file mode 100644 index 0000000000000000000000000000000000000000..ad3f3a5d010fcce3265fd654e83d0815c59cc3ad GIT binary patch literal 7764 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 literal 0 HcmV?d00001 diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/images/schematicsGuide.png b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/images/schematicsGuide.png new file mode 100644 index 0000000000000000000000000000000000000000..db4d0adbb760dbff877884209c7f878083b1aa2f GIT binary patch literal 128984 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

    (E+-BQ13%VTKFa^1 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 literal 0 HcmV?d00001 diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/js/type-view.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/js/type-view.js new file mode 100644 index 0000000000..9d6bcd2666 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/public/js/type-view.js @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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. + */ + +var modalPopup = ".modal", + modalPopupContainer = modalPopup + " .modal-content", + modalPopupContent = modalPopup + " .modal-content"; + +var emmAdminBasePath = "/api/device-mgt/v1.0"; + +//function openCollapsedNav() { +// $(".wr-hidden-nav-toggle-btn").addClass("active"); +// $("#hiddenNav").slideToggle("slideDown", function () { +// if ($(this).css("display") == "none") { +// $(".wr-hidden-nav-toggle-btn").removeClass("active"); +// } +// }); +//} + +/* + * set popup maximum height function. + */ +function setPopupMaxHeight() { + var maxHeight = "max-height"; + var marginTop = "margin-top"; + var body = "body"; + $(modalPopupContent).css(maxHeight, ($(body).height() - ($(body).height() / 100 * 30))); + $(modalPopupContainer).css(marginTop, (-($(modalPopupContainer).height() / 2))); +} + +/* + * show popup function. + */ +function showPopup() { + $(modalPopup).modal('show'); +} + +/* + * hide popup function. + */ +function hidePopup() { + $(modalPopupContent).html(""); + $(modalPopupContent).removeClass("operation-data"); + $(modalPopup).modal('hide'); + $('body').removeClass('modal-open').css('padding-right','0px'); + $('.modal-backdrop').remove(); +} + +/* + * QR-code generation function. + */ +function generateQRCode(qrCodeClass) { + var enrollmentURL = $("#qr-code-modal").data("enrollment-url"); + $(qrCodeClass).qrcode({ + text: enrollmentURL, + width: 200, + height: 200 + }); +} + +function toggleEnrollment() { + $(".modal-content").html($("#qr-code-modal").html()); + generateQRCode(".modal-content .qr-code"); + modalDialog.show(); +} + +var updateNotificationCountOnSuccess = function (data, textStatus, jqXHR) { + var notificationBubble = "#notification-bubble"; + if (jqXHR.status == 200 && data) { + var responsePayload = JSON.parse(data); + var newNotificationsCount = responsePayload["count"]; + if (newNotificationsCount > 0) { + $(notificationBubble).html(newNotificationsCount); + $(notificationBubble).show(); + } else { + $(notificationBubble).hide(); + } + } +}; + +function updateNotificationCountOnError() { + var notificationBubble = "#notification-bubble"; + $(notificationBubble).html("Error"); + $(notificationBubble).show(); +} + +function loadNewNotificationsOnSideViewPanel() { + if ($("#right-sidebar").attr("is-authorized") == "false") { + $("#notification-bubble-wrapper").remove(); + } else { + var serviceURL = emmAdminBasePath + "/notifications?status=NEW"; + invokerUtil.get(serviceURL, updateNotificationCountOnSuccess, updateNotificationCountOnError); + loadNewNotifications(); + } +} + +function loadNewNotifications() { + var messageSideBar = ".sidebar-messages"; + if ($("#right-sidebar").attr("is-authorized") == "false") { + $(messageSideBar).html("

    You are not authorized to view notifications.

    "); + } else { + var notifications = $("#notifications"); + var currentUser = notifications.data("currentUser"); + + $.template("notification-listing", notifications.attr("src"), function (template) { + var serviceURL = emmAdminBasePath + "/notifications?offset=0&limit=5&status=NEW"; + invokerUtil.get( + serviceURL, + // on success + function (data, textStatus, jqXHR) { + if (jqXHR.status == 200 && data) { + var viewModel = {}; + var responsePayload = JSON.parse(data); + if (responsePayload["notifications"]) { + if (responsePayload.count > 0) { + viewModel["notifications"] = responsePayload["notifications"]; + viewModel["appContext"] = context; + $(messageSideBar).html(template(viewModel)); + } else { + $(messageSideBar).html("

    No New Notifications

    " + + "
    " + + "Check this section for error notifications
    related to device operations" + + "
    "); + } + } else { + $(messageSideBar).html("

    Unexpected error " + + "occurred while loading new notifications.

    "); + } + } + }, + // on error + function (jqXHR) { + if (jqXHR.status = 500) { + $(messageSideBar).html("

    Unexpected error occurred while trying " + + "to retrieve any new notifications.

    "); + } + } + ); + }); + } +} + +/** + * Toggle function for + * notification listing sidebar. + * @return {Null} + */ +$.sidebar_toggle = function (action, target, container) { + var elem = '[data-toggle=sidebar]', + button, + containerOffsetLeft, + containerOffsetRight, + targetOffsetLeft, + targetOffsetRight, + targetWidth, + targetSide, + relationship, + pushType, + buttonParent; + + var sidebar_window = { + update: function (target, container, button) { + containerOffsetLeft = $(container).data('offset-left') ? $(container).data('offset-left') : 0; + containerOffsetRight = $(container).data('offset-right') ? $(container).data('offset-right') : 0; + targetOffsetLeft = $(target).data('offset-left') ? $(target).data('offset-left') : 0; + targetOffsetRight = $(target).data('offset-right') ? $(target).data('offset-right') : 0; + targetWidth = $(target).data('width'); + targetSide = $(target).data("side"); + pushType = $(container).parent().is('body') == true ? 'padding' : 'margin'; + + if (button !== undefined) { + relationship = button.attr('rel') ? button.attr('rel') : ''; + buttonParent = $(button).parent(); + } + }, + + show: function () { + if ($(target).data('sidebar-fixed') == true) { + $(target).height($(window).height() - $(target).data('fixed-offset')); + } + $(target).trigger('show.sidebar'); + if (targetWidth !== undefined) { + $(target).css('width', targetWidth); + } + $(target).addClass('toggled'); + if (button !== undefined) { + if (relationship !== '') { + // Removing active class from all relative buttons + $(elem + '[rel=' + relationship + ']:not([data-handle=close])').removeClass("active"); + $(elem + '[rel=' + relationship + ']:not([data-handle=close])').attr('aria-expanded', 'false'); + } + // Adding active class to button + if (button.attr('data-handle') !== 'close') { + button.addClass("active"); + button.attr('aria-expanded', 'true'); + } + if (buttonParent.is('li')) { + if (relationship !== '') { + $(elem + '[rel=' + relationship + ']:not([data-handle=close])').parent().removeClass("active"); + $(elem + '[rel=' + relationship + ']:not([data-handle=close])').parent().attr('aria-expanded', 'false'); + } + buttonParent.addClass("active"); + buttonParent.attr('aria-expanded', 'true'); + } + } + // Sidebar open function + if (targetSide == 'left') { + if ((button !== undefined) && (button.attr('data-container-divide'))) { + $(container).css(pushType + '-' + targetSide, targetWidth + targetOffsetLeft); + } + $(target).css(targetSide, targetOffsetLeft); + } else if (targetSide == 'right') { + if ((button !== undefined) && (button.attr('data-container-divide'))) { + $(container).css(pushType + '-' + targetSide, targetWidth + targetOffsetRight); + } + $(target).css(targetSide, targetOffsetRight); + } + $(target).trigger('shown.sidebar'); + }, + + hide: function () { + $(target).trigger('hide.sidebar'); + $(target).removeClass('toggled'); + if (button !== undefined) { + if (relationship !== '') { + // Removing active class from all relative buttons + $(elem + '[rel=' + relationship + ']:not([data-handle=close])').removeClass("active"); + $(elem + '[rel=' + relationship + ']:not([data-handle=close])').attr('aria-expanded', 'false'); + } + // Removing active class from button + if (button.attr('data-handle') !== 'close') { + button.removeClass("active"); + button.attr('aria-expanded', 'false'); + } + if ($(button).parent().is('li')) { + if (relationship !== '') { + $(elem + '[rel=' + relationship + ']:not([data-handle=close])').parent().removeClass("active"); + $(elem + '[rel=' + relationship + ']:not([data-handle=close])').parent().attr('aria-expanded', 'false'); + } + } + } + // Sidebar close function + if (targetSide == 'left') { + if ((button !== undefined) && (button.attr('data-container-divide'))) { + $(container).css(pushType + '-' + targetSide, targetOffsetLeft); + } + $(target).css(targetSide, -Math.abs(targetWidth + targetOffsetLeft)); + } else if (targetSide == 'right') { + if ((button !== undefined) && (button.attr('data-container-divide'))) { + $(container).css(pushType + '-' + targetSide, targetOffsetRight); + } + $(target).css(targetSide, -Math.abs(targetWidth + targetOffsetRight)); + } + $(target).trigger('hidden.sidebar'); + } + }; + if (action === 'show') { + sidebar_window.update(target, container); + sidebar_window.show(); + } + if (action === 'hide') { + sidebar_window.update(target, container); + sidebar_window.hide(); + } + // binding click function + var body = 'body'; + $(body).off('click', elem); + $(body).on('click', elem, function (e) { + e.preventDefault(); + button = $(this); + container = button.data('container'); + target = button.data('target'); + sidebar_window.update(target, container, button); + /** + * Sidebar function on data container divide + * @return {Null} + */ + if (button.attr('aria-expanded') == 'false') { + sidebar_window.show(); + } else if (button.attr('aria-expanded') == 'true') { + sidebar_window.hide(); + } + }); +}; + +$.fn.collapse_nav_sub = function () { + var navSelector = 'ul.nav'; + + if (!$(navSelector).hasClass('collapse-nav-sub')) { + $(navSelector + ' > li', this).each(function () { + var position = $(this).offset().left - $(this).parent().scrollLeft(); + $(this).attr('data-absolute-position', (position + 5)); + }); + + $(navSelector + ' li', this).each(function () { + if ($('ul', this).length !== 0) { + $(this).addClass('has-sub'); + } + }); + + $(navSelector + ' > li', this).each(function () { + $(this).css({ + 'left': $(this).data('absolute-position'), + 'position': 'absolute' + }); + }); + + $(navSelector + ' li.has-sub', this).on('click', function () { + var elem = $(this); + if (elem.attr('aria-expanded') !== 'true') { + elem.siblings().fadeOut(100, function () { + elem.animate({'left': '15'}, 200, function () { + $(elem).first().children('ul').fadeIn(200); + }); + }); + elem.siblings().attr('aria-expanded', 'false'); + elem.attr('aria-expanded', 'true'); + } else { + $(elem).first().children('ul').fadeOut(100, function () { + elem.animate({'left': $(elem).data('absolute-position')}, 200, function () { + elem.siblings().fadeIn(100); + }); + }); + elem.siblings().attr('aria-expanded', 'false'); + elem.attr('aria-expanded', 'false'); + } + }); + + $(navSelector + ' > li.has-sub ul', this).on('click', function (e) { + e.stopPropagation(); + }); + $(navSelector).addClass('collapse-nav-sub'); + } +}; + +$(".download-link").click(function(){ + toggleEnrollment(); +}); + +$(document).ready(function () { + $.sidebar_toggle(); + if (typeof $.fn.collapse == 'function') { + $('.navbar-collapse.tiles').on('shown.bs.collapse', function () { + $(this).collapse_nav_sub(); + }); + } + + loadNewNotificationsOnSideViewPanel(); + $("#right-sidebar").on("click", ".new-notification", function () { + var notificationId = $(this).data("id"); + var redirectUrl = $(this).data("url"); + var markAsReadNotificationsEpr = emmAdminBasePath + "/notifications/" + notificationId + "/mark-checked"; + var messageSideBar = ".sidebar-messages"; + + invokerUtil.put( + markAsReadNotificationsEpr, + null, + // on success + function (data) { + data = JSON.parse(data); + if (data.statusCode == responseCodes["ACCEPTED"]) { + location.href = redirectUrl; + } + }, + // on error + function () { + var content = "
  • Warning

    " + + "

    Unexpected error occurred while loading notification. Please refresh the page and" + + " try again

  • "; + $(messageSideBar).html(content); + } + ); + }); +}); diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/type-view.hbs b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/type-view.hbs new file mode 100644 index 0000000000..aa089d40eb --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/type-view.hbs @@ -0,0 +1,142 @@ +
    +

    +
    +
    + +
    + +
    + +
    + +
    + +
    +

    Description

    +
    +

    Need to get the description from the device

    +
    +
    +
    +
    View API
    + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +

    +
    +
    +

    How To Enroll a Device

    +
    +
      +
    • 01 +    curl -k -X POST https://localhost:8243/api-application-registration/register -H + 'authorization: Basic Base64(username:password)' -H 'content-type: application/json' + -d '{ "applicationName":"testme", "isAllowedToAllDomains":false, "tags":["device_management"]}' +
    • +
    • 02 +    curl -k -d "grant_type=password&username=%username%&password=%password%&scope=perm:devices:add" + -H "Authorization: Basic Base64(client_id:client_secret)" + -H "Content-Type: application/x-www-form-urlencoded" https://localhost:8243/token +
    • +
    • 03 +    curl -X POST http://localhost:8280/api/device-mgt/v1.0/devices -H 'accept: application/json' + -H 'authorization: Bearer %accessToken%' + -H 'content-type: application/json' -d '{ "name": "devicename", "type": "{{deviceType}}", + "description": "descritption", "deviceIdentifier": "1234", "enrolmentInfo": + {"dateOfEnrolment": 0, "dateOfLastUpdate": 0, "ownership": "BYOD", "status": "ACTIVE", "owner": "username"} + ,"properties": [{"name": "propertyName","value": "propertyValue"}]}' +
    • +
    +
    +
    +
    + +{{#zone "topCss"}} + {{css "css/styles.css"}} +{{/zone}} + +{{#zone "bottomJs"}} +{{/zone}} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/type-view.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/type-view.js new file mode 100644 index 0000000000..cac85f1346 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/type-view.js @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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. + */ + +function onRequest(context) { + var deviceType = context.uriParams.deviceType; + return { + "deviceType": deviceType + }; +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/type-view.json b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/type-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.type-view/type-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file From 4ce83526908579f04da2b90158bd94fd6c48651b Mon Sep 17 00:00:00 2001 From: ayyoob Date: Sat, 22 Apr 2017 00:15:58 +0530 Subject: [PATCH 005/358] added device event api --- .../org.wso2.carbon.device.mgt.api/pom.xml | 36 +- .../mgt/jaxrs/beans/analytics/Attribute.java | 60 ++ .../jaxrs/beans/analytics/AttributeType.java | 32 + .../beans/analytics/DeviceTypeEvent.java | 51 ++ .../beans/analytics/EventAttributeList.java | 47 ++ .../jaxrs/beans/analytics/EventRecords.java | 60 ++ .../jaxrs/beans/analytics/TransportType.java | 27 + .../api/DeviceEventManagementService.java | 294 ++++++++ .../DeviceEventManagementServiceImpl.java | 655 ++++++++++++++++++ .../impl/DeviceManagementServiceImpl.java | 6 +- .../src/main/webapp/WEB-INF/cxf-servlet.xml | 6 +- pom.xml | 37 + 12 files changed, 1305 insertions(+), 6 deletions(-) create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/Attribute.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/AttributeType.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/DeviceTypeEvent.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/EventAttributeList.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/EventRecords.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/TransportType.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceEventManagementService.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceEventManagementServiceImpl.java diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/pom.xml b/components/device-mgt/org.wso2.carbon.device.mgt.api/pom.xml index 2141a6f890..a3d71577b8 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/pom.xml +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/pom.xml @@ -297,7 +297,41 @@ ${carbon.identity.framework.version} provided - + + org.wso2.carbon.analytics + org.wso2.carbon.analytics.datasource.commons + provided + + + org.wso2.carbon.analytics + org.wso2.carbon.analytics.api + provided + + + org.wso2.carbon.analytics + org.wso2.carbon.analytics.dataservice.commons + provided + + + org.wso2.carbon.analytics-common + org.wso2.carbon.event.receiver.stub + provided + + + org.wso2.carbon.analytics-common + org.wso2.carbon.event.stream.stub + provided + + + org.wso2.carbon.analytics-common + org.wso2.carbon.event.publisher.stub + provided + + + org.wso2.carbon.analytics-common + org.wso2.carbon.event.stream.persistence.stub + provided + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/Attribute.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/Attribute.java new file mode 100644 index 0000000000..276fea9ee4 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/Attribute.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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.wso2.carbon.device.mgt.jaxrs.beans.analytics; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModelProperty; + +/** + * This hold the attribute definition. + */ +public class Attribute { + + @ApiModelProperty(value = "Event Attribute Name") + @JsonProperty("name") + private String name; + @ApiModelProperty(value = "Event Attribute Type") + @JsonProperty("type") + private AttributeType type; + + public Attribute() { + + } + + public Attribute(String name, AttributeType attributeType) { + this.name = name; + this.type = attributeType; + } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public AttributeType getType() { + return type; + } + + public void setType(AttributeType type) { + this.type = type; + } +} + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/AttributeType.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/AttributeType.java new file mode 100644 index 0000000000..23235612b9 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/AttributeType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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.wso2.carbon.device.mgt.jaxrs.beans.analytics; + +/** + * This hold the definition of the attribute type for the attributes. + */ +public enum AttributeType { + STRING, LONG, BOOL, INT, FLOAT, DOUBLE; + + @Override + public String toString() { + return super.toString().toLowerCase(); + } +} + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/DeviceTypeEvent.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/DeviceTypeEvent.java new file mode 100644 index 0000000000..942c078529 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/DeviceTypeEvent.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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.wso2.carbon.device.mgt.jaxrs.beans.analytics; + +import io.swagger.annotations.ApiModelProperty; + +/** + * This hold stats data record + */ +public class DeviceTypeEvent { + + @ApiModelProperty(value = "Attributes related to device type event") + private EventAttributeList eventAttributeList; + @ApiModelProperty(value = "Transport to be used for device to server communication.") + private TransportType transportType; + + + public EventAttributeList getEventAttributeList() { + return eventAttributeList; + } + + public void setEventAttributeList( + EventAttributeList eventAttributeList) { + this.eventAttributeList = eventAttributeList; + } + + public TransportType getTransportType() { + return transportType; + } + + public void setTransportType(TransportType transportType) { + this.transportType = transportType; + } +} + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/EventAttributeList.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/EventAttributeList.java new file mode 100644 index 0000000000..fe9bd39e03 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/EventAttributeList.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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.wso2.carbon.device.mgt.jaxrs.beans.analytics; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModelProperty; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.jaxrs.beans.BasePaginatedResult; + +import java.util.ArrayList; +import java.util.List; + +/** + * This holds event attributes + */ +public class EventAttributeList { + + private List attributes = new ArrayList<>(); + + @ApiModelProperty(value = "List of Event Attributes") + @JsonProperty("attributes") + public List getList() { + return attributes; + } + + public void setList(List attributes) { + this.attributes = attributes; + } + +} + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/EventRecords.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/EventRecords.java new file mode 100644 index 0000000000..bb3347c1ba --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/EventRecords.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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.wso2.carbon.device.mgt.jaxrs.beans.analytics; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModelProperty; +import org.wso2.carbon.device.mgt.common.Device; + +import java.util.ArrayList; +import java.util.List; +import org.wso2.carbon.analytics.datasource.commons.Record; +import org.wso2.carbon.device.mgt.jaxrs.beans.BasePaginatedResult; + +/** + * This hold stats data record + */ +public class EventRecords extends BasePaginatedResult { + + private List records = new ArrayList<>(); + + @ApiModelProperty(value = "List of records returned") + @JsonProperty("records") + public List getRecord() { + return records; + } + + public void setList(List records) { + this.records = records; + setCount(records.size()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{\n"); + + sb.append(" count: ").append(getCount()).append(",\n"); + sb.append(" records: [").append(records).append("\n"); + sb.append("]}\n"); + return sb.toString(); + } + +} + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/TransportType.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/TransportType.java new file mode 100644 index 0000000000..58eae4e616 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/TransportType.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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.wso2.carbon.device.mgt.jaxrs.beans.analytics; + +/** + * This hold the default transport types support by the server. + */ +public enum TransportType { + HTTP, MQTT; +} + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceEventManagementService.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceEventManagementService.java new file mode 100644 index 0000000000..803c91ffce --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceEventManagementService.java @@ -0,0 +1,294 @@ +package org.wso2.carbon.device.mgt.jaxrs.service.api; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Extension; +import io.swagger.annotations.ExtensionProperty; +import io.swagger.annotations.Info; +import io.swagger.annotations.ResponseHeader; +import io.swagger.annotations.SwaggerDefinition; +import io.swagger.annotations.Tag; +import org.wso2.carbon.apimgt.annotations.api.Scope; +import org.wso2.carbon.apimgt.annotations.api.Scopes; +import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceTypeList; +import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse; +import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.DeviceTypeEvent; +import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.EventAttributeList; +import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.TransportType; +import org.wso2.carbon.device.mgt.jaxrs.util.Constants; + +import javax.validation.Valid; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@SwaggerDefinition( + info = @Info( + version = "1.0.0", + title = "", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = "name", value = "DeviceEventManagement"), + @ExtensionProperty(name = "context", value = "/api/device-mgt/v1.0/device-types/events"), + }) + } + ), + tags = { + @Tag(name = "device_management", description = "") + } +) +@Scopes( + scopes = { + @Scope( + name = "Add or Delete Event Definition for device type", + description = "Add or Delete Event Definition for device type", + key = "perm:device-types:events", + permissions = {"/device-mgt/device-type/add"} + ), + @Scope( + name = "Get Feature Details of a Device Type", + description = "Get Feature Details of a Device Type", + key = "perm:device-types:events:view", + permissions = {"/device-mgt/devices/owning-device/view"} + ) + } +) +@Path("/device-types/events") +@Api(value = "Device Event Management", description = "This API corresponds to all tasks related to device " + + "event management") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public interface DeviceEventManagementService { + + @POST + @Path("/{type}") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "POST", + value = "Add Event Type Defnition", + notes = "Add the event definition for the device.", + tags = "Device Event Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:device-types:events") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully added the event defintion.", + response = DeviceTypeList.class, + responseHeaders = { + @ResponseHeader( + name = "Content-Type", + description = "The content type of the body"), + @ResponseHeader( + name = "ETag", + description = "Entity Tag of the response resource.\n" + + "Used by caches, or in conditional requests."), + @ResponseHeader( + name = "Last-Modified", + description = + "Date and time the resource was last modified.\n" + + "Used by caches, or in conditional requests."), + } + ), + @ApiResponse( + code = 400, + message = + "Bad Request. \n"), + @ApiResponse( + code = 406, + message = "Not Acceptable.\n The requested media type is not supported"), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n Server error occurred while fetching the " + + "list of supported device types.", + response = ErrorResponse.class) + } + ) + Response deployDeviceTypeEventDefinition(@ApiParam(name = "type", value = "name of the device type", required = false) + @PathParam("type")String deviceType, + @ApiParam(name = "deviceTypeEvent", value = "DeviceTypeEvent object with data.", required = true) + @Valid DeviceTypeEvent deviceTypeEvent); + + @DELETE + @Path("/{type}") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "DELETE", + value = "Delete Event Type Defnition", + notes = "Delete the event definition for the device.", + tags = "Device Event Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:device-types:events") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully deleted the event definition.", + response = DeviceTypeList.class, + responseHeaders = { + @ResponseHeader( + name = "Content-Type", + description = "The content type of the body"), + @ResponseHeader( + name = "ETag", + description = "Entity Tag of the response resource.\n" + + "Used by caches, or in conditional requests."), + @ResponseHeader( + name = "Last-Modified", + description = + "Date and time the resource was last modified.\n" + + "Used by caches, or in conditional requests."), + } + ), + @ApiResponse( + code = 400, + message = + "Bad Request. \n"), + @ApiResponse( + code = 406, + message = "Not Acceptable.\n The requested media type is not supported"), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n Server error occurred while fetching the " + + "list of supported device types.", + response = ErrorResponse.class) + } + ) + Response deleteDeviceTypeEventDefinitions(@ApiParam(name = "type", value = "name of the device type", required = false) + @PathParam("type")String deviceType); + + @GET + @Path("/{type}/{deviceId}") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Getting Device Events", + notes = "Get the events for the device.", + tags = "Device Event Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:device-types:events:view") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully fetched the event definition.", + response = DeviceTypeList.class, + responseHeaders = { + @ResponseHeader( + name = "Content-Type", + description = "The content type of the body"), + @ResponseHeader( + name = "ETag", + description = "Entity Tag of the response resource.\n" + + "Used by caches, or in conditional requests."), + @ResponseHeader( + name = "Last-Modified", + description = + "Date and time the resource was last modified.\n" + + "Used by caches, or in conditional requests."), + } + ), + @ApiResponse( + code = 400, + message = + "Bad Request. \n"), + @ApiResponse( + code = 406, + message = "Not Acceptable.\n The requested media type is not supported"), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n Server error occurred while fetching the " + + "list of supported device types.", + response = ErrorResponse.class) + } + ) + Response getData(@ApiParam(name = "deviceId", value = "id of the device ", required = false) + @PathParam("deviceId") String deviceId, + @ApiParam(name = "from", value = "unix timestamp to retrieve", required = false) + @QueryParam("from") long from, + @ApiParam(name = "to", value = "unix time to retrieve", required = false) + @QueryParam("to") long to, + @ApiParam(name = "type", value = "name of the device type", required = false) + @PathParam("type") String deviceType, + @ApiParam(name = "offset", value = "offset of the records that needs to be picked up", required = false) + @QueryParam("offset") int offset, + @ApiParam(name = "limit", value = "limit of the records that needs to be picked up", required = false) + @QueryParam("limit") int limit); + + @GET + @Path("/{type}") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Getting Event Type Defnition", + notes = "Get the event definition for the device.", + tags = "Device Event Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:device-types:events:view") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully fetched the event defintion.", + response = DeviceTypeList.class, + responseHeaders = { + @ResponseHeader( + name = "Content-Type", + description = "The content type of the body"), + @ResponseHeader( + name = "ETag", + description = "Entity Tag of the response resource.\n" + + "Used by caches, or in conditional requests."), + @ResponseHeader( + name = "Last-Modified", + description = + "Date and time the resource was last modified.\n" + + "Used by caches, or in conditional requests."), + } + ), + @ApiResponse( + code = 400, + message = + "Bad Request. \n"), + @ApiResponse( + code = 406, + message = "Not Acceptable.\n The requested media type is not supported"), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n Server error occurred while fetching the " + + "list of supported device types.", + response = ErrorResponse.class) + } + ) + Response getDeviceTypeEventDefinition(@ApiParam(name = "type", value = "name of the device type", required = false) + @PathParam("type")String deviceType) ; + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceEventManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceEventManagementServiceImpl.java new file mode 100644 index 0000000000..69ff1e3e0d --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceEventManagementServiceImpl.java @@ -0,0 +1,655 @@ +package org.wso2.carbon.device.mgt.jaxrs.service.impl; + +import org.apache.axis2.AxisFault; +import org.apache.axis2.client.Options; +import org.apache.axis2.transport.http.HTTPConstants; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.axis2.java.security.SSLProtocolSocketFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.analytics.api.AnalyticsDataAPI; +import org.wso2.carbon.analytics.api.AnalyticsDataAPIUtil; +import org.wso2.carbon.analytics.dataservice.commons.AnalyticsDataResponse; +import org.wso2.carbon.analytics.dataservice.commons.SearchResultEntry; +import org.wso2.carbon.analytics.dataservice.commons.SortByField; +import org.wso2.carbon.analytics.dataservice.commons.SortType; +import org.wso2.carbon.analytics.stream.persistence.stub + .EventStreamPersistenceAdminServiceEventStreamPersistenceAdminServiceExceptionException; +import org.wso2.carbon.analytics.stream.persistence.stub.EventStreamPersistenceAdminServiceStub; +import org.wso2.carbon.analytics.stream.persistence.stub.dto.AnalyticsTable; +import org.wso2.carbon.analytics.stream.persistence.stub.dto.AnalyticsTableRecord; +import org.wso2.carbon.base.MultitenantConstants; +import org.wso2.carbon.base.ServerConfiguration; +import org.wso2.carbon.context.CarbonContext; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.core.util.Utils; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.authorization.DeviceAccessAuthorizationException; +import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.DeviceTypeEvent; +import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.EventRecords; +import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.Attribute; +import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.AttributeType; +import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.EventAttributeList; +import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.TransportType; +import org.wso2.carbon.device.mgt.jaxrs.service.api.DeviceEventManagementService; +import org.wso2.carbon.device.mgt.jaxrs.util.DeviceMgtAPIUtils; +import org.wso2.carbon.event.publisher.stub.EventPublisherAdminServiceStub; +import org.wso2.carbon.event.receiver.stub.EventReceiverAdminServiceStub; +import org.wso2.carbon.event.receiver.stub.types.BasicInputAdapterPropertyDto; +import org.wso2.carbon.event.stream.stub.EventStreamAdminServiceStub; +import org.wso2.carbon.event.stream.stub.types.EventStreamAttributeDto; +import org.wso2.carbon.event.stream.stub.types.EventStreamDefinitionDto; +import org.wso2.carbon.identity.jwt.client.extension.JWTClient; +import org.wso2.carbon.identity.jwt.client.extension.exception.JWTClientException; +import org.wso2.carbon.user.api.UserStoreException; +import org.wso2.carbon.analytics.datasource.commons.Record; +import org.wso2.carbon.analytics.datasource.commons.exception.AnalyticsException; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateException; +import javax.validation.Valid; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.rmi.RemoteException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * This is used for simple analytics purpose, to create streams and receiver dynamically and a common endpoint + * to retrieve data. + */ +@Path("/device-types/events") +public class DeviceEventManagementServiceImpl implements DeviceEventManagementService { + + private static final Log log = LogFactory.getLog(DeviceEventManagementServiceImpl.class); + + private static final String DAS_PORT = "${iot.analytics.https.port}"; + private static final String DAS_HOST_NAME = "${iot.analytics.host}"; + private static final String DEFAULT_HTTP_PROTOCOL = "https"; + private static final String DAS_ADMIN_SERVICE_EP = DEFAULT_HTTP_PROTOCOL + "://" + DAS_HOST_NAME + + ":" + DAS_PORT + "/services/EventReceiverAdminService" + "/"; + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String AUTHORIZATION_HEADER_VALUE = "Bearer"; + private static final String KEY_STORE_TYPE = "JKS"; + private static final String TRUST_STORE_TYPE = "JKS"; + private static final String KEY_MANAGER_TYPE = "SunX509"; //Default Key Manager Type + private static final String TRUST_MANAGER_TYPE = "SunX509"; //Default Trust Manager Type + private static final String SSLV3 = "SSLv3"; + private static final String DEFAULT_STREAM_VERSION = "1.0.0"; + private static final String DEFAULT_EVENT_STORE_NAME = "EVENT_STORE"; + private static final String DEFAULT_WEBSOCKET_PUBLISHER_ADAPTER_TYPE = "secured-websocket"; + + private static KeyStore keyStore; + private static KeyStore trustStore; + private static char[] keyStorePassword; + private static SSLContext sslContext; + static { + String keyStorePassword = ServerConfiguration.getInstance().getFirstProperty("Security.KeyStore.Password"); + String trustStorePassword = ServerConfiguration.getInstance().getFirstProperty( + "Security.TrustStore.Password"); + String keyStoreLocation = ServerConfiguration.getInstance().getFirstProperty("Security.KeyStore.Location"); + String trustStoreLocation = ServerConfiguration.getInstance().getFirstProperty( + "Security.TrustStore.Location"); + + //Call to load the keystore. + try { + loadKeyStore(keyStoreLocation, keyStorePassword); + //Call to load the TrustStore. + loadTrustStore(trustStoreLocation, trustStorePassword); + //Create the SSL context with the loaded TrustStore/keystore. + initSSLConnection(); + } catch (KeyStoreException|IOException|CertificateException|NoSuchAlgorithmException + | UnrecoverableKeyException | KeyManagementException e) { + log.error("publishing dynamic event receiver is failed due to " + e.getMessage(), e); + } + } + + /** + * Deploy Event Stream, Receiver, Publisher and Store Configuration. + */ + @POST + @Path("/{type}") + @Override + public Response deployDeviceTypeEventDefinition(@PathParam("type") String deviceType, @Valid DeviceTypeEvent deviceTypeEvent) { + TransportType transportType = deviceTypeEvent.getTransportType(); + EventAttributeList eventAttributes = deviceTypeEvent.getEventAttributeList(); + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + boolean superTenantMode = false; + try { + if (eventAttributes == null || eventAttributes.getList() == null || eventAttributes.getList().size() == 0 || + deviceType == null || + !DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(deviceType)) { + String errorMessage = "Invalid device type"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } + String eventReceiverName = getReceiverName(deviceType, tenantDomain); + String streamName = getStreamDefinition(deviceType, tenantDomain); + String streamNameWithVersion = streamName + ":" + DEFAULT_STREAM_VERSION; + publishStreamDefinitons(streamName, DEFAULT_STREAM_VERSION, deviceType, eventAttributes); + publishEventReceivers(eventReceiverName, streamNameWithVersion, transportType, tenantDomain, deviceType); + publishEventStore(streamName, DEFAULT_STREAM_VERSION, eventAttributes); + publishWebsocketPublisherDefinition(streamNameWithVersion, deviceType); + superTenantMode = true; + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain( + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, true); + if (!MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { + publishStreamDefinitons(streamName, DEFAULT_STREAM_VERSION, deviceType, eventAttributes); + publishEventReceivers(eventReceiverName, streamNameWithVersion, transportType, tenantDomain, deviceType); + } + return Response.ok().build(); + } catch (AxisFault e) { + log.error("failed to create event definitions for tenantDomain:" + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (RemoteException e) { + log.error("Failed to connect with the remote services:" + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (JWTClientException e) { + log.error("Failed to generate jwt token for tenantDomain:" + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (UserStoreException e) { + log.error("Failed to connect with the user store, tenantDomain: " + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (DeviceManagementException e) { + log.error("Failed to access device management service, tenantDomain: " + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (EventStreamPersistenceAdminServiceEventStreamPersistenceAdminServiceExceptionException e) { + log.error("Failed to create event store for, tenantDomain: " + tenantDomain + " deviceType" + deviceType, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } finally { + if (superTenantMode) { + PrivilegedCarbonContext.endTenantFlow(); + } + } + } + + @DELETE + @Path("/{type}") + @Override + public Response deleteDeviceTypeEventDefinitions(@PathParam("type") String deviceType) { + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + try { + if (deviceType == null || + !DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(deviceType)) { + String errorMessage = "Invalid device type"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } + String eventReceiverName = getReceiverName(deviceType, tenantDomain); + String eventPublisherName = deviceType.trim().toLowerCase() + "_websocket_publisher"; + String streamName = getStreamDefinition(deviceType, tenantDomain); + + getEventStreamAdminServiceStub().removeEventStreamDefinition(streamName, DEFAULT_STREAM_VERSION); + getEventReceiverAdminServiceStub().undeployActiveEventReceiverConfiguration(eventReceiverName); + getEventPublisherAdminServiceStub().undeployActiveEventPublisherConfiguration(eventPublisherName); + return Response.ok().build(); + } catch (AxisFault e) { + log.error("failed to create event definitions for tenantDomain:" + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (RemoteException e) { + log.error("Failed to connect with the remote services:" + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (JWTClientException e) { + log.error("Failed to generate jwt token for tenantDomain:" + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (UserStoreException e) { + log.error("Failed to connect with the user store, tenantDomain: " + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (DeviceManagementException e) { + log.error("Failed to access device management service, tenantDomain: " + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + + @GET + @Path("/{type}/{deviceId}") + @Override + public Response getData(@PathParam("deviceId") String deviceId, @QueryParam("from") long from, + @QueryParam("to") long to,@PathParam("type") String deviceType, @QueryParam("offset") + int offset, @QueryParam("limit") int limit) { + String fromDate = String.valueOf(from); + String toDate = String.valueOf(to); + String query = "deviceId:" + deviceId + " AND _timestamp : [" + fromDate + " TO " + toDate + "]"; + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String sensorTableName = getTableName(getStreamDefinition(deviceType, tenantDomain)); + try { + if (deviceType == null || + !DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(deviceType)) { + String errorMessage = "Invalid device type"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } + if (!DeviceMgtAPIUtils.getDeviceAccessAuthorizationService().isUserAuthorized( + new DeviceIdentifier(deviceId, deviceType))) { + return Response.status(Response.Status.UNAUTHORIZED.getStatusCode()).build(); + } + List sortByFields = new ArrayList<>(); + SortByField sortByField = new SortByField("_timestamp", SortType.ASC); + sortByFields.add(sortByField); + EventRecords eventRecords = getAllEventsForDevice(sensorTableName, query, sortByFields, offset, limit); + return Response.status(Response.Status.OK.getStatusCode()).entity(eventRecords).build(); + } catch (AnalyticsException e) { + String errorMsg = "Error on retrieving stats on table " + sensorTableName + " with query " + query; + log.error(errorMsg); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()).entity(errorMsg).build(); + } catch (DeviceAccessAuthorizationException e) { + log.error(e.getErrorMessage(), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (DeviceManagementException e) { + String errorMsg = "Error on retrieving stats on table " + sensorTableName + " with query " + query; + log.error(errorMsg); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()).entity(errorMsg).build(); + } + } + + @GET + @Path("/{type}") + @Override + public Response getDeviceTypeEventDefinition(@PathParam("type") String deviceType) { + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + try { + if (deviceType == null || + !DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(deviceType)) { + String errorMessage = "Invalid device type"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } + String streamName = getStreamDefinition(deviceType, tenantDomain); + EventStreamDefinitionDto eventStreamDefinitionDto = getEventStreamAdminServiceStub().getStreamDefinitionDto( + streamName + ":" + DEFAULT_STREAM_VERSION); + if (eventStreamDefinitionDto == null) { + return Response.status(Response.Status.NO_CONTENT).build(); + } + EventStreamAttributeDto[] eventStreamAttributeDtos = eventStreamDefinitionDto.getPayloadData(); + EventAttributeList eventAttributeList = new EventAttributeList(); + List attributes = new ArrayList<>(); + for (EventStreamAttributeDto eventStreamAttributeDto : eventStreamAttributeDtos) { + attributes.add(new Attribute(eventStreamAttributeDto.getAttributeName() + , AttributeType.valueOf(eventStreamAttributeDto.getAttributeType().toUpperCase()))); + } + eventAttributeList.setList(attributes); + return Response.ok().entity(eventAttributeList).build(); + } catch (AxisFault e) { + log.error("failed to create event definitions for tenantDomain:" + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (RemoteException e) { + log.error("Failed to connect with the remote services:" + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (JWTClientException e) { + log.error("Failed to generate jwt token for tenantDomain:" + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (UserStoreException e) { + log.error("Failed to connect with the user store, tenantDomain: " + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (DeviceManagementException e) { + log.error("Failed to access device management service, tenantDomain: " + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + + + private void publishEventReceivers(String eventRecieverName, String streamNameWithVersion, TransportType transportType + , String requestedTenantDomain, String deviceType) + throws RemoteException, UserStoreException, JWTClientException { + EventReceiverAdminServiceStub receiverAdminServiceStub = getEventReceiverAdminServiceStub(); + String adapterType = "oauth-mqtt"; + BasicInputAdapterPropertyDto basicInputAdapterPropertyDtos[]; + if (transportType == TransportType.MQTT) { + basicInputAdapterPropertyDtos = new BasicInputAdapterPropertyDto[4]; + basicInputAdapterPropertyDtos[0] = getBasicInputAdapterPropertyDto("topic", requestedTenantDomain + + "/" + deviceType + "/+/events"); + basicInputAdapterPropertyDtos[1] = getBasicInputAdapterPropertyDto("contentValidator", "iot-mqtt"); + basicInputAdapterPropertyDtos[2] = getBasicInputAdapterPropertyDto("cleanSession", "true"); + basicInputAdapterPropertyDtos[3] = getBasicInputAdapterPropertyDto("clientId", generateUUID()); + } else { + adapterType = "oauth-http"; + basicInputAdapterPropertyDtos = new BasicInputAdapterPropertyDto[1]; + basicInputAdapterPropertyDtos[0] = getBasicInputAdapterPropertyDto("contentValidator", "iot-mqtt"); + } + receiverAdminServiceStub.deployJsonEventReceiverConfiguration(eventRecieverName, streamNameWithVersion + , adapterType, null, basicInputAdapterPropertyDtos, false); + } + + private void publishStreamDefinitons(String streamName, String version, String deviceType + , EventAttributeList eventAttributes) + throws RemoteException, UserStoreException, JWTClientException { + EventStreamAdminServiceStub eventStreamAdminServiceStub = getEventStreamAdminServiceStub(); + EventStreamDefinitionDto eventStreamDefinitionDto = new EventStreamDefinitionDto(); + eventStreamDefinitionDto.setName(streamName); + eventStreamDefinitionDto.setVersion(version); + EventStreamAttributeDto eventStreamAttributeDtos[] = new EventStreamAttributeDto[eventAttributes.getList().size()]; + int i = 0; + for (Attribute attribute : eventAttributes.getList()) { + EventStreamAttributeDto eventStreamAttributeDto = new EventStreamAttributeDto(); + eventStreamAttributeDto.setAttributeName(attribute.getName()); + eventStreamAttributeDto.setAttributeType(attribute.getType().toString()); + eventStreamAttributeDtos[i] = eventStreamAttributeDto; + i++; + } + EventStreamAttributeDto metaData[] = new EventStreamAttributeDto[1]; + EventStreamAttributeDto eventStreamAttributeDto = new EventStreamAttributeDto(); + eventStreamAttributeDto.setAttributeName("deviceId"); + eventStreamAttributeDto.setAttributeType(AttributeType.STRING.toString()); + metaData[0] = eventStreamAttributeDto; + eventStreamDefinitionDto.setMetaData(metaData); + eventStreamDefinitionDto.setPayloadData(eventStreamAttributeDtos); + String streamId = streamName + ":" + version; + if (eventStreamAdminServiceStub.getStreamDefinitionAsString(streamId) != null) { + eventStreamAdminServiceStub.editEventStreamDefinitionAsDto(eventStreamDefinitionDto, streamId); + } else { + eventStreamAdminServiceStub.addEventStreamDefinitionAsDto(eventStreamDefinitionDto); + } + + } + + private void publishEventStore(String streamName, String version, EventAttributeList eventAttributes) + throws RemoteException, UserStoreException, JWTClientException, + EventStreamPersistenceAdminServiceEventStreamPersistenceAdminServiceExceptionException { + EventStreamPersistenceAdminServiceStub eventStreamAdminServiceStub = getEventStreamPersistenceAdminServiceStub(); + AnalyticsTable analyticsTable = new AnalyticsTable(); + analyticsTable.setRecordStoreName(DEFAULT_EVENT_STORE_NAME); + analyticsTable.setStreamVersion(version); + analyticsTable.setTableName(getTableName(streamName)); + AnalyticsTableRecord analyticsTableRecords[] = new AnalyticsTableRecord[eventAttributes.getList().size() + 1]; + int i = 0; + for (Attribute attribute : eventAttributes.getList()) { + AnalyticsTableRecord analyticsTableRecord = new AnalyticsTableRecord(); + analyticsTableRecord.setColumnName(attribute.getName()); + analyticsTableRecord.setColumnType(attribute.getType().toString().toUpperCase()); + analyticsTableRecord.setFacet(false); + analyticsTableRecord.setIndexed(true); + analyticsTableRecord.setPersist(true); + analyticsTableRecord.setPrimaryKey(false); + analyticsTableRecord.setScoreParam(false); + analyticsTableRecords[i] = analyticsTableRecord; + i++; + } + AnalyticsTableRecord analyticsTableRecord = new AnalyticsTableRecord(); + analyticsTableRecord.setColumnName("meta_deviceId"); + analyticsTableRecord.setColumnType(AttributeType.STRING.toString().toUpperCase()); + analyticsTableRecord.setFacet(false); + analyticsTableRecord.setIndexed(true); + analyticsTableRecord.setPersist(true); + analyticsTableRecord.setPrimaryKey(false); + analyticsTableRecord.setScoreParam(false); + analyticsTableRecords[i] = analyticsTableRecord; + analyticsTable.setAnalyticsTableRecords(analyticsTableRecords); + eventStreamAdminServiceStub.addAnalyticsTable(analyticsTable); + + } + + private void publishWebsocketPublisherDefinition(String streamNameWithVersion, String deviceType) + throws RemoteException, UserStoreException, JWTClientException { + EventPublisherAdminServiceStub eventPublisherAdminServiceStub = getEventPublisherAdminServiceStub(); + String eventPublisherName = deviceType.trim().toLowerCase() + "_websocket_publisher"; + eventPublisherAdminServiceStub.startdeployJsonEventPublisherConfiguration(eventPublisherName + , streamNameWithVersion, DEFAULT_WEBSOCKET_PUBLISHER_ADAPTER_TYPE, null, null,null, false, null); + } + + private EventStreamAdminServiceStub getEventStreamAdminServiceStub() + throws AxisFault, UserStoreException, JWTClientException { + EventStreamAdminServiceStub eventStreamAdminServiceStub = new EventStreamAdminServiceStub( + Utils.replaceSystemProperty(DAS_ADMIN_SERVICE_EP)); + Options streamOptions = eventStreamAdminServiceStub._getServiceClient().getOptions(); + if (streamOptions == null) { + streamOptions = new Options(); + } + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String username = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm() + .getRealmConfiguration().getAdminUserName() + "@" + tenantDomain; + JWTClient jwtClient = DeviceMgtAPIUtils.getJWTClientManagerService().getJWTClient(); + + String authValue = AUTHORIZATION_HEADER_VALUE + " " + new String(Base64.encodeBase64( + jwtClient.getJwtToken(username).getBytes())); + + List
    list = new ArrayList<>(); + Header httpHeader = new Header(); + httpHeader.setName(AUTHORIZATION_HEADER); + httpHeader.setValue(authValue); + list.add(httpHeader);//"https" + streamOptions.setProperty(HTTPConstants.HTTP_HEADERS, list); + streamOptions.setProperty(HTTPConstants.CUSTOM_PROTOCOL_HANDLER + , new Protocol(DEFAULT_HTTP_PROTOCOL + , (ProtocolSocketFactory) new SSLProtocolSocketFactory(sslContext) + , Integer.parseInt(Utils.replaceSystemProperty(DAS_PORT)))); + eventStreamAdminServiceStub._getServiceClient().setOptions(streamOptions); + return eventStreamAdminServiceStub; + } + + private EventReceiverAdminServiceStub getEventReceiverAdminServiceStub() + throws AxisFault, UserStoreException, JWTClientException { + EventReceiverAdminServiceStub receiverAdminServiceStub = new EventReceiverAdminServiceStub( + Utils.replaceSystemProperty(DAS_ADMIN_SERVICE_EP)); + Options eventReciverOptions = receiverAdminServiceStub._getServiceClient().getOptions(); + if (eventReciverOptions == null) { + eventReciverOptions = new Options(); + } + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String username = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm() + .getRealmConfiguration().getAdminUserName() + "@" + tenantDomain; + JWTClient jwtClient = DeviceMgtAPIUtils.getJWTClientManagerService().getJWTClient(); + + String authValue = AUTHORIZATION_HEADER_VALUE + " " + new String(Base64.encodeBase64( + jwtClient.getJwtToken(username).getBytes())); + + List
    list = new ArrayList<>(); + Header httpHeader = new Header(); + httpHeader.setName(AUTHORIZATION_HEADER); + httpHeader.setValue(authValue); + list.add(httpHeader);//"https" + + eventReciverOptions.setProperty(HTTPConstants.HTTP_HEADERS, list); + eventReciverOptions.setProperty(HTTPConstants.CUSTOM_PROTOCOL_HANDLER + , new Protocol(DEFAULT_HTTP_PROTOCOL + , (ProtocolSocketFactory) new SSLProtocolSocketFactory(sslContext) + , Integer.parseInt(Utils.replaceSystemProperty(DAS_PORT)))); + receiverAdminServiceStub._getServiceClient().setOptions(eventReciverOptions); + return receiverAdminServiceStub; + } + + private EventPublisherAdminServiceStub getEventPublisherAdminServiceStub() + throws AxisFault, UserStoreException, JWTClientException { + EventPublisherAdminServiceStub eventPublisherAdminServiceStub = new EventPublisherAdminServiceStub( + Utils.replaceSystemProperty(DAS_ADMIN_SERVICE_EP)); + Options eventReciverOptions = eventPublisherAdminServiceStub._getServiceClient().getOptions(); + if (eventReciverOptions == null) { + eventReciverOptions = new Options(); + } + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String username = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm() + .getRealmConfiguration().getAdminUserName() + "@" + tenantDomain; + JWTClient jwtClient = DeviceMgtAPIUtils.getJWTClientManagerService().getJWTClient(); + + String authValue = AUTHORIZATION_HEADER_VALUE + " " + new String(Base64.encodeBase64( + jwtClient.getJwtToken(username).getBytes())); + + List
    list = new ArrayList<>(); + Header httpHeader = new Header(); + httpHeader.setName(AUTHORIZATION_HEADER); + httpHeader.setValue(authValue); + list.add(httpHeader);//"https" + + eventReciverOptions.setProperty(HTTPConstants.HTTP_HEADERS, list); + eventReciverOptions.setProperty(HTTPConstants.CUSTOM_PROTOCOL_HANDLER + , new Protocol(DEFAULT_HTTP_PROTOCOL + , (ProtocolSocketFactory) new SSLProtocolSocketFactory(sslContext) + , Integer.parseInt(Utils.replaceSystemProperty(DAS_PORT)))); + eventPublisherAdminServiceStub._getServiceClient().setOptions(eventReciverOptions); + return eventPublisherAdminServiceStub; + } + + private EventStreamPersistenceAdminServiceStub getEventStreamPersistenceAdminServiceStub() + throws AxisFault, UserStoreException, JWTClientException { + EventStreamPersistenceAdminServiceStub eventStreamPersistenceAdminServiceStub + = new EventStreamPersistenceAdminServiceStub( + Utils.replaceSystemProperty(DAS_ADMIN_SERVICE_EP)); + Options eventReciverOptions = eventStreamPersistenceAdminServiceStub._getServiceClient().getOptions(); + if (eventReciverOptions == null) { + eventReciverOptions = new Options(); + } + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String username = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm() + .getRealmConfiguration().getAdminUserName() + "@" + tenantDomain; + JWTClient jwtClient = DeviceMgtAPIUtils.getJWTClientManagerService().getJWTClient(); + + String authValue = AUTHORIZATION_HEADER_VALUE + " " + new String(Base64.encodeBase64( + jwtClient.getJwtToken(username).getBytes())); + + List
    list = new ArrayList<>(); + Header httpHeader = new Header(); + httpHeader.setName(AUTHORIZATION_HEADER); + httpHeader.setValue(authValue); + list.add(httpHeader);//"https" + + eventReciverOptions.setProperty(HTTPConstants.HTTP_HEADERS, list); + eventReciverOptions.setProperty(HTTPConstants.CUSTOM_PROTOCOL_HANDLER + , new Protocol(DEFAULT_HTTP_PROTOCOL + , (ProtocolSocketFactory) new SSLProtocolSocketFactory(sslContext) + , Integer.parseInt(Utils.replaceSystemProperty(DAS_PORT)))); + eventStreamPersistenceAdminServiceStub._getServiceClient().setOptions(eventReciverOptions); + return eventStreamPersistenceAdminServiceStub; + } + + /** + * Loads the keystore. + * + * @param keyStorePath - the path of the keystore + * @param ksPassword - the keystore password + */ + private static void loadKeyStore(String keyStorePath, String ksPassword) + throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { + InputStream fis = null; + try { + keyStorePassword = ksPassword.toCharArray(); + keyStore = KeyStore.getInstance(KEY_STORE_TYPE); + fis = new FileInputStream(keyStorePath); + keyStore.load(fis, keyStorePassword); + } finally { + if (fis != null) { + fis.close(); + } + } + } + + /** + * Loads the trustore + * + * @param trustStorePath - the trustore path in the filesystem. + * @param tsPassword - the truststore password + */ + private static void loadTrustStore(String trustStorePath, String tsPassword) + throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { + + InputStream fis = null; + try { + trustStore = KeyStore.getInstance(TRUST_STORE_TYPE); + fis = new FileInputStream(trustStorePath); + trustStore.load(fis, tsPassword.toCharArray()); + } finally { + if (fis != null) { + fis.close(); + } + } + } + + /** + * Initializes the SSL Context + */ + private static void initSSLConnection() throws NoSuchAlgorithmException, UnrecoverableKeyException, + KeyStoreException, KeyManagementException { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KEY_MANAGER_TYPE); + keyManagerFactory.init(keyStore, keyStorePassword); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TRUST_MANAGER_TYPE); + trustManagerFactory.init(trustStore); + + // Create and initialize SSLContext for HTTPS communication + sslContext = SSLContext.getInstance(SSLV3); + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + SSLContext.setDefault(sslContext); + } + + private BasicInputAdapterPropertyDto getBasicInputAdapterPropertyDto(String key, String value) { + BasicInputAdapterPropertyDto basicInputAdapterPropertyDto = new BasicInputAdapterPropertyDto(); + basicInputAdapterPropertyDto.setKey(key); + basicInputAdapterPropertyDto.setValue(value); + return basicInputAdapterPropertyDto; + } + + private static String generateUUID() { + UUID uuid = UUID.randomUUID(); + long l = ByteBuffer.wrap(uuid.toString().getBytes(StandardCharsets.UTF_8)).getLong(); + return Long.toString(l, Character.MAX_RADIX); + } + + private String getStreamDefinition(String deviceType, String tenantDomain) { + return tenantDomain.toLowerCase() + "." + deviceType.toLowerCase(); + } + + private String getTableName(String streamName) { + return streamName.toUpperCase().replace('.', '_'); + } + + private String getReceiverName(String deviceType, String tenantDomain) { + return deviceType.trim().toLowerCase() + "-" + tenantDomain.toLowerCase() + "-receiver"; + } + + public static AnalyticsDataAPI getAnalyticsDataAPI() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + AnalyticsDataAPI analyticsDataAPI = + (AnalyticsDataAPI) ctx.getOSGiService(AnalyticsDataAPI.class, null); + if (analyticsDataAPI == null) { + String msg = "Analytics api service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return analyticsDataAPI; + } + + protected static EventRecords getAllEventsForDevice(String tableName, String query, List sortByFields + , int offset, int limit) throws AnalyticsException { + int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId(); + AnalyticsDataAPI analyticsDataAPI = getAnalyticsDataAPI(); + int eventCount = analyticsDataAPI.searchCount(tenantId, tableName, query); + if (eventCount == 0) { + return null; + } + List resultEntries = analyticsDataAPI.search(tenantId, tableName, query, offset, limit, + sortByFields); + List recordIds = getRecordIds(resultEntries); + AnalyticsDataResponse response = analyticsDataAPI.get(tenantId, tableName, 1, null, recordIds); + EventRecords eventRecords = new EventRecords(); + eventRecords.setList(AnalyticsDataAPIUtil.listRecords(analyticsDataAPI, response)); + return eventRecords; + } + + private static List getRecordIds(List searchResults) { + List ids = new ArrayList<>(); + for (SearchResultEntry searchResult : searchResults) { + ids.add(searchResult.getId()); + } + return ids; + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java index 4bbbfdb130..d34a8ee3e4 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java @@ -91,7 +91,7 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { @POST @Override - public Response addDevice(Device device) { + public Response addDevice(@Valid Device device) { if (device == null) { String errorMessage = "The payload of the device enrollment is incorrect."; return Response.status(Response.Status.BAD_REQUEST).entity(errorMessage).build(); @@ -728,7 +728,7 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { @POST @Path("/{type}/operations") - public Response addOperation(@PathParam("type") String type, OperationRequest operationRequest) { + public Response addOperation(@PathParam("type") String type, @Valid OperationRequest operationRequest) { try { if (operationRequest == null || operationRequest.getDeviceIdentifiers() == null) { String errorMessage = "Device identifier list is empty"; @@ -794,7 +794,7 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { @PUT @Path("/{type}/{id}/operations") - public Response updateOperation(@PathParam("type") String type, @PathParam("id") String deviceId, Operation operation) { + public Response updateOperation(@PathParam("type") String type, @PathParam("id") String deviceId, @Valid Operation operation) { try { if (operation == null) { String errorMessage = "Device identifier list is empty"; diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/webapp/WEB-INF/cxf-servlet.xml index fd6c5e0309..77e7023cb3 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/webapp/WEB-INF/cxf-servlet.xml +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -40,7 +40,8 @@ - + + @@ -82,7 +83,8 @@ - + + diff --git a/pom.xml b/pom.xml index 6afe10b8ee..494c985a01 100644 --- a/pom.xml +++ b/pom.xml @@ -1396,6 +1396,41 @@ org.wso2.carbon.event.output.adapter.core ${carbon.analytics.common.version} + + org.wso2.carbon.analytics-common + org.wso2.carbon.event.receiver.stub + ${carbon.analytics.common.version} + + + org.wso2.carbon.analytics + org.wso2.carbon.analytics.datasource.commons + ${carbon.analytics.version} + + + org.wso2.carbon.analytics + org.wso2.carbon.analytics.dataservice.commons + ${carbon.analytics.version} + + + org.wso2.carbon.analytics-common + org.wso2.carbon.event.stream.stub + ${carbon.analytics.common.version} + + + org.wso2.carbon.analytics-common + org.wso2.carbon.event.publisher.stub + ${carbon.analytics.common.version} + + + org.wso2.carbon.analytics + org.wso2.carbon.analytics.api + ${carbon.analytics.version} + + + org.wso2.carbon.analytics-common + org.wso2.carbon.event.stream.persistence.stub + ${carbon.analytics.common.version} + org.wso2.carbon.devicemgt org.wso2.carbon.device.mgt.extensions.device.type.deployer @@ -1820,6 +1855,8 @@ 5.1.3 [5.1.3,6.0.0) + 1.3.3 + [1.3.0,2.0.0) 4.6.0 From 2a89a2fb2380d0c1c3c41843f5a094206195a6c6 Mon Sep 17 00:00:00 2001 From: ayyoob Date: Sat, 22 Apr 2017 18:31:13 +0530 Subject: [PATCH 006/358] resolved issues in event management service --- .../beans/analytics/DeviceTypeEvent.java | 24 +- .../api/DeviceEventManagementService.java | 7 +- .../DeviceEventManagementServiceImpl.java | 222 +++++++++++------- 3 files changed, 159 insertions(+), 94 deletions(-) diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/DeviceTypeEvent.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/DeviceTypeEvent.java index 942c078529..7ff162b357 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/DeviceTypeEvent.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/DeviceTypeEvent.java @@ -18,6 +18,7 @@ */ package org.wso2.carbon.device.mgt.jaxrs.beans.analytics; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.annotations.ApiModelProperty; /** @@ -25,27 +26,28 @@ import io.swagger.annotations.ApiModelProperty; */ public class DeviceTypeEvent { - @ApiModelProperty(value = "Attributes related to device type event") - private EventAttributeList eventAttributeList; - @ApiModelProperty(value = "Transport to be used for device to server communication.") - private TransportType transportType; - + private EventAttributeList eventAttributes; + private TransportType transport; + @ApiModelProperty(value = "Attributes related to device type event") + @JsonProperty("eventAttributes") public EventAttributeList getEventAttributeList() { - return eventAttributeList; + return eventAttributes; } public void setEventAttributeList( - EventAttributeList eventAttributeList) { - this.eventAttributeList = eventAttributeList; + EventAttributeList eventAttributes) { + this.eventAttributes = eventAttributes; } + @ApiModelProperty(value = "Transport to be used for device to server communication.") + @JsonProperty("transport") public TransportType getTransportType() { - return transportType; + return transport; } - public void setTransportType(TransportType transportType) { - this.transportType = transportType; + public void setTransportType(TransportType transport) { + this.transport = transport; } } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceEventManagementService.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceEventManagementService.java index 803c91ffce..73a816b7a4 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceEventManagementService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceEventManagementService.java @@ -17,6 +17,7 @@ import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceTypeList; import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse; import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.DeviceTypeEvent; import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.EventAttributeList; +import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.EventRecords; import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.TransportType; import org.wso2.carbon.device.mgt.jaxrs.util.Constants; @@ -90,7 +91,6 @@ public interface DeviceEventManagementService { @ApiResponse( code = 200, message = "OK. \n Successfully added the event defintion.", - response = DeviceTypeList.class, responseHeaders = { @ResponseHeader( name = "Content-Type", @@ -144,7 +144,6 @@ public interface DeviceEventManagementService { @ApiResponse( code = 200, message = "OK. \n Successfully deleted the event definition.", - response = DeviceTypeList.class, responseHeaders = { @ResponseHeader( name = "Content-Type", @@ -196,7 +195,7 @@ public interface DeviceEventManagementService { @ApiResponse( code = 200, message = "OK. \n Successfully fetched the event definition.", - response = DeviceTypeList.class, + response = EventRecords.class, responseHeaders = { @ResponseHeader( name = "Content-Type", @@ -258,7 +257,7 @@ public interface DeviceEventManagementService { @ApiResponse( code = 200, message = "OK. \n Successfully fetched the event defintion.", - response = DeviceTypeList.class, + response = EventAttributeList.class, responseHeaders = { @ResponseHeader( name = "Content-Type", diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceEventManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceEventManagementServiceImpl.java index 69ff1e3e0d..14b1fd6776 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceEventManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceEventManagementServiceImpl.java @@ -37,7 +37,9 @@ import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.EventAttributeList; import org.wso2.carbon.device.mgt.jaxrs.beans.analytics.TransportType; import org.wso2.carbon.device.mgt.jaxrs.service.api.DeviceEventManagementService; import org.wso2.carbon.device.mgt.jaxrs.util.DeviceMgtAPIUtils; +import org.wso2.carbon.event.publisher.stub.EventPublisherAdminServiceCallbackHandler; import org.wso2.carbon.event.publisher.stub.EventPublisherAdminServiceStub; +import org.wso2.carbon.event.receiver.stub.EventReceiverAdminServiceCallbackHandler; import org.wso2.carbon.event.receiver.stub.EventReceiverAdminServiceStub; import org.wso2.carbon.event.receiver.stub.types.BasicInputAdapterPropertyDto; import org.wso2.carbon.event.stream.stub.EventStreamAdminServiceStub; @@ -48,6 +50,7 @@ import org.wso2.carbon.identity.jwt.client.extension.exception.JWTClientExceptio import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.analytics.datasource.commons.Record; import org.wso2.carbon.analytics.datasource.commons.exception.AnalyticsException; + import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; @@ -88,7 +91,11 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe private static final String DAS_HOST_NAME = "${iot.analytics.host}"; private static final String DEFAULT_HTTP_PROTOCOL = "https"; private static final String DAS_ADMIN_SERVICE_EP = DEFAULT_HTTP_PROTOCOL + "://" + DAS_HOST_NAME - + ":" + DAS_PORT + "/services/EventReceiverAdminService" + "/"; + + ":" + DAS_PORT + "/services/"; + private static final String EVENT_RECIEVER_CONTEXT = "EventReceiverAdminService/"; + private static final String EVENT_PUBLISHER_CONTEXT = "EventPublisherAdminService/"; + private static final String EVENT_STREAM_CONTEXT = "EventStreamAdminService/"; + private static final String EVENT_PERSISTENCE_CONTEXT = "EventStreamPersistenceAdminService/"; private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String AUTHORIZATION_HEADER_VALUE = "Bearer"; private static final String KEY_STORE_TYPE = "JKS"; @@ -104,6 +111,7 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe private static KeyStore trustStore; private static char[] keyStorePassword; private static SSLContext sslContext; + static { String keyStorePassword = ServerConfiguration.getInstance().getFirstProperty("Security.KeyStore.Password"); String trustStorePassword = ServerConfiguration.getInstance().getFirstProperty( @@ -119,28 +127,77 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe loadTrustStore(trustStoreLocation, trustStorePassword); //Create the SSL context with the loaded TrustStore/keystore. initSSLConnection(); - } catch (KeyStoreException|IOException|CertificateException|NoSuchAlgorithmException + } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException | KeyManagementException e) { log.error("publishing dynamic event receiver is failed due to " + e.getMessage(), e); } } + @GET + @Path("/{type}") + @Override + public Response getDeviceTypeEventDefinition(@PathParam("type") String deviceType) { + String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + try { + if (deviceType == null || + !DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(deviceType)) { + String errorMessage = "Invalid device type"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } + String streamName = getStreamDefinition(deviceType, tenantDomain); + EventStreamAdminServiceStub eventStreamAdminServiceStub = getEventStreamAdminServiceStub(); + EventStreamDefinitionDto eventStreamDefinitionDto = eventStreamAdminServiceStub.getStreamDefinitionDto( + streamName + ":" + DEFAULT_STREAM_VERSION); + if (eventStreamDefinitionDto == null) { + eventStreamAdminServiceStub.cleanup(); + return Response.status(Response.Status.NO_CONTENT).build(); + } + EventStreamAttributeDto[] eventStreamAttributeDtos = eventStreamDefinitionDto.getPayloadData(); + EventAttributeList eventAttributeList = new EventAttributeList(); + List attributes = new ArrayList<>(); + for (EventStreamAttributeDto eventStreamAttributeDto : eventStreamAttributeDtos) { + attributes.add(new Attribute(eventStreamAttributeDto.getAttributeName() + , AttributeType.valueOf(eventStreamAttributeDto.getAttributeType().toUpperCase()))); + } + eventAttributeList.setList(attributes); + eventStreamAdminServiceStub.cleanup(); + return Response.ok().entity(eventAttributeList).build(); + } catch (AxisFault e) { + log.error("failed to retrieve event definitions for tenantDomain:" + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (RemoteException e) { + log.error("Failed to connect with the remote services:" + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (JWTClientException e) { + log.error("Failed to generate jwt token for tenantDomain:" + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (UserStoreException e) { + log.error("Failed to connect with the user store, tenantDomain: " + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } catch (DeviceManagementException e) { + log.error("Failed to access device management service, tenantDomain: " + tenantDomain, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + } + /** * Deploy Event Stream, Receiver, Publisher and Store Configuration. */ @POST @Path("/{type}") @Override - public Response deployDeviceTypeEventDefinition(@PathParam("type") String deviceType, @Valid DeviceTypeEvent deviceTypeEvent) { + public Response deployDeviceTypeEventDefinition(@PathParam("type") String deviceType, + @Valid DeviceTypeEvent deviceTypeEvent) { TransportType transportType = deviceTypeEvent.getTransportType(); EventAttributeList eventAttributes = deviceTypeEvent.getEventAttributeList(); String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); boolean superTenantMode = false; try { if (eventAttributes == null || eventAttributes.getList() == null || eventAttributes.getList().size() == 0 || - deviceType == null || + deviceType == null || transportType == null || !DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(deviceType)) { - String errorMessage = "Invalid device type"; + String errorMessage = "Invalid Payload"; log.error(errorMessage); return Response.status(Response.Status.BAD_REQUEST).build(); } @@ -157,7 +214,8 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, true); if (!MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { publishStreamDefinitons(streamName, DEFAULT_STREAM_VERSION, deviceType, eventAttributes); - publishEventReceivers(eventReceiverName, streamNameWithVersion, transportType, tenantDomain, deviceType); + publishEventReceivers(eventReceiverName, streamNameWithVersion, transportType, tenantDomain, + deviceType); } return Response.ok().build(); } catch (AxisFault e) { @@ -176,7 +234,8 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe log.error("Failed to access device management service, tenantDomain: " + tenantDomain, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } catch (EventStreamPersistenceAdminServiceEventStreamPersistenceAdminServiceExceptionException e) { - log.error("Failed to create event store for, tenantDomain: " + tenantDomain + " deviceType" + deviceType, e); + log.error("Failed to create event store for, tenantDomain: " + tenantDomain + " deviceType" + deviceType, + e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } finally { if (superTenantMode) { @@ -190,6 +249,7 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe @Override public Response deleteDeviceTypeEventDefinitions(@PathParam("type") String deviceType) { String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + boolean superTenantMode = false; try { if (deviceType == null || !DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(deviceType)) { @@ -200,13 +260,47 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe String eventReceiverName = getReceiverName(deviceType, tenantDomain); String eventPublisherName = deviceType.trim().toLowerCase() + "_websocket_publisher"; String streamName = getStreamDefinition(deviceType, tenantDomain); + EventStreamAdminServiceStub eventStreamAdminServiceStub = getEventStreamAdminServiceStub(); + if (eventStreamAdminServiceStub.getStreamDefinitionDto(streamName + ":" + DEFAULT_STREAM_VERSION) == null) { + return Response.status(Response.Status.NO_CONTENT).build(); + } + EventReceiverAdminServiceCallbackHandler eventReceiverAdminServiceCallbackHandler = + new EventReceiverAdminServiceCallbackHandler() {}; + EventPublisherAdminServiceCallbackHandler eventPublisherAdminServiceCallbackHandler = + new EventPublisherAdminServiceCallbackHandler() {}; + + EventReceiverAdminServiceStub eventReceiverAdminServiceStub = getEventReceiverAdminServiceStub(); + eventReceiverAdminServiceStub.startundeployActiveEventReceiverConfiguration(eventReceiverName + , eventReceiverAdminServiceCallbackHandler); + + eventStreamAdminServiceStub.removeEventStreamDefinition(streamName, DEFAULT_STREAM_VERSION); - getEventStreamAdminServiceStub().removeEventStreamDefinition(streamName, DEFAULT_STREAM_VERSION); - getEventReceiverAdminServiceStub().undeployActiveEventReceiverConfiguration(eventReceiverName); - getEventPublisherAdminServiceStub().undeployActiveEventPublisherConfiguration(eventPublisherName); + EventPublisherAdminServiceStub eventPublisherAdminServiceStub = getEventPublisherAdminServiceStub(); + eventPublisherAdminServiceStub.startundeployActiveEventPublisherConfiguration(eventPublisherName + , eventPublisherAdminServiceCallbackHandler); + + eventStreamAdminServiceStub.cleanup(); + eventPublisherAdminServiceStub.cleanup(); + eventReceiverAdminServiceStub.cleanup(); + + superTenantMode = true; + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain( + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, true); + if (!MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { + eventReceiverAdminServiceStub = getEventReceiverAdminServiceStub(); + eventStreamAdminServiceStub = getEventStreamAdminServiceStub(); + + eventReceiverAdminServiceStub.startundeployActiveEventReceiverConfiguration(eventReceiverName + , eventReceiverAdminServiceCallbackHandler); + eventStreamAdminServiceStub.removeEventStreamDefinition(streamName, DEFAULT_STREAM_VERSION); + + eventReceiverAdminServiceStub.cleanup(); + eventStreamAdminServiceStub.cleanup(); + } return Response.ok().build(); } catch (AxisFault e) { - log.error("failed to create event definitions for tenantDomain:" + tenantDomain, e); + log.error("failed to delete event definitions for tenantDomain:" + tenantDomain, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } catch (RemoteException e) { log.error("Failed to connect with the remote services:" + tenantDomain, e); @@ -220,6 +314,10 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe } catch (DeviceManagementException e) { log.error("Failed to access device management service, tenantDomain: " + tenantDomain, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } finally { + if (superTenantMode) { + PrivilegedCarbonContext.endTenantFlow(); + } } } @@ -227,8 +325,8 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe @Path("/{type}/{deviceId}") @Override public Response getData(@PathParam("deviceId") String deviceId, @QueryParam("from") long from, - @QueryParam("to") long to,@PathParam("type") String deviceType, @QueryParam("offset") - int offset, @QueryParam("limit") int limit) { + @QueryParam("to") long to, @PathParam("type") String deviceType, @QueryParam("offset") + int offset, @QueryParam("limit") int limit) { String fromDate = String.valueOf(from); String toDate = String.valueOf(to); String query = "deviceId:" + deviceId + " AND _timestamp : [" + fromDate + " TO " + toDate + "]"; @@ -264,53 +362,8 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe } } - @GET - @Path("/{type}") - @Override - public Response getDeviceTypeEventDefinition(@PathParam("type") String deviceType) { - String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); - try { - if (deviceType == null || - !DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(deviceType)) { - String errorMessage = "Invalid device type"; - log.error(errorMessage); - return Response.status(Response.Status.BAD_REQUEST).build(); - } - String streamName = getStreamDefinition(deviceType, tenantDomain); - EventStreamDefinitionDto eventStreamDefinitionDto = getEventStreamAdminServiceStub().getStreamDefinitionDto( - streamName + ":" + DEFAULT_STREAM_VERSION); - if (eventStreamDefinitionDto == null) { - return Response.status(Response.Status.NO_CONTENT).build(); - } - EventStreamAttributeDto[] eventStreamAttributeDtos = eventStreamDefinitionDto.getPayloadData(); - EventAttributeList eventAttributeList = new EventAttributeList(); - List attributes = new ArrayList<>(); - for (EventStreamAttributeDto eventStreamAttributeDto : eventStreamAttributeDtos) { - attributes.add(new Attribute(eventStreamAttributeDto.getAttributeName() - , AttributeType.valueOf(eventStreamAttributeDto.getAttributeType().toUpperCase()))); - } - eventAttributeList.setList(attributes); - return Response.ok().entity(eventAttributeList).build(); - } catch (AxisFault e) { - log.error("failed to create event definitions for tenantDomain:" + tenantDomain, e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); - } catch (RemoteException e) { - log.error("Failed to connect with the remote services:" + tenantDomain, e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); - } catch (JWTClientException e) { - log.error("Failed to generate jwt token for tenantDomain:" + tenantDomain, e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); - } catch (UserStoreException e) { - log.error("Failed to connect with the user store, tenantDomain: " + tenantDomain, e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); - } catch (DeviceManagementException e) { - log.error("Failed to access device management service, tenantDomain: " + tenantDomain, e); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); - } - } - - - private void publishEventReceivers(String eventRecieverName, String streamNameWithVersion, TransportType transportType + private void publishEventReceivers(String eventRecieverName, String streamNameWithVersion, + TransportType transportType , String requestedTenantDomain, String deviceType) throws RemoteException, UserStoreException, JWTClientException { EventReceiverAdminServiceStub receiverAdminServiceStub = getEventReceiverAdminServiceStub(); @@ -328,8 +381,11 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe basicInputAdapterPropertyDtos = new BasicInputAdapterPropertyDto[1]; basicInputAdapterPropertyDtos[0] = getBasicInputAdapterPropertyDto("contentValidator", "iot-mqtt"); } - receiverAdminServiceStub.deployJsonEventReceiverConfiguration(eventRecieverName, streamNameWithVersion - , adapterType, null, basicInputAdapterPropertyDtos, false); + if (receiverAdminServiceStub.getActiveEventReceiverConfiguration(eventRecieverName) == null) { + receiverAdminServiceStub.deployJsonEventReceiverConfiguration(eventRecieverName, streamNameWithVersion + , adapterType, null, basicInputAdapterPropertyDtos, false); + } + receiverAdminServiceStub.cleanup(); } private void publishStreamDefinitons(String streamName, String version, String deviceType @@ -339,7 +395,8 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe EventStreamDefinitionDto eventStreamDefinitionDto = new EventStreamDefinitionDto(); eventStreamDefinitionDto.setName(streamName); eventStreamDefinitionDto.setVersion(version); - EventStreamAttributeDto eventStreamAttributeDtos[] = new EventStreamAttributeDto[eventAttributes.getList().size()]; + EventStreamAttributeDto eventStreamAttributeDtos[] = + new EventStreamAttributeDto[eventAttributes.getList().size()]; int i = 0; for (Attribute attribute : eventAttributes.getList()) { EventStreamAttributeDto eventStreamAttributeDto = new EventStreamAttributeDto(); @@ -356,22 +413,25 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe eventStreamDefinitionDto.setMetaData(metaData); eventStreamDefinitionDto.setPayloadData(eventStreamAttributeDtos); String streamId = streamName + ":" + version; - if (eventStreamAdminServiceStub.getStreamDefinitionAsString(streamId) != null) { + if (eventStreamAdminServiceStub.getStreamDefinitionDto(streamId) != null) { eventStreamAdminServiceStub.editEventStreamDefinitionAsDto(eventStreamDefinitionDto, streamId); } else { eventStreamAdminServiceStub.addEventStreamDefinitionAsDto(eventStreamDefinitionDto); } - + eventStreamAdminServiceStub.cleanup(); } private void publishEventStore(String streamName, String version, EventAttributeList eventAttributes) throws RemoteException, UserStoreException, JWTClientException, EventStreamPersistenceAdminServiceEventStreamPersistenceAdminServiceExceptionException { - EventStreamPersistenceAdminServiceStub eventStreamAdminServiceStub = getEventStreamPersistenceAdminServiceStub(); + EventStreamPersistenceAdminServiceStub eventStreamAdminServiceStub = + getEventStreamPersistenceAdminServiceStub(); AnalyticsTable analyticsTable = new AnalyticsTable(); analyticsTable.setRecordStoreName(DEFAULT_EVENT_STORE_NAME); analyticsTable.setStreamVersion(version); - analyticsTable.setTableName(getTableName(streamName)); + analyticsTable.setTableName(streamName); + analyticsTable.setMergeSchema(false); + analyticsTable.setPersist(true); AnalyticsTableRecord analyticsTableRecords[] = new AnalyticsTableRecord[eventAttributes.getList().size() + 1]; int i = 0; for (Attribute attribute : eventAttributes.getList()) { @@ -397,21 +457,25 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe analyticsTableRecords[i] = analyticsTableRecord; analyticsTable.setAnalyticsTableRecords(analyticsTableRecords); eventStreamAdminServiceStub.addAnalyticsTable(analyticsTable); - + eventStreamAdminServiceStub.cleanup(); } private void publishWebsocketPublisherDefinition(String streamNameWithVersion, String deviceType) throws RemoteException, UserStoreException, JWTClientException { EventPublisherAdminServiceStub eventPublisherAdminServiceStub = getEventPublisherAdminServiceStub(); String eventPublisherName = deviceType.trim().toLowerCase() + "_websocket_publisher"; - eventPublisherAdminServiceStub.startdeployJsonEventPublisherConfiguration(eventPublisherName - , streamNameWithVersion, DEFAULT_WEBSOCKET_PUBLISHER_ADAPTER_TYPE, null, null,null, false, null); + if (eventPublisherAdminServiceStub.getActiveEventPublisherConfiguration(eventPublisherName) == null) { + eventPublisherAdminServiceStub.deployJsonEventPublisherConfiguration(eventPublisherName + , streamNameWithVersion, DEFAULT_WEBSOCKET_PUBLISHER_ADAPTER_TYPE, null, null + , null, false); + } + eventPublisherAdminServiceStub.cleanup(); } private EventStreamAdminServiceStub getEventStreamAdminServiceStub() throws AxisFault, UserStoreException, JWTClientException { EventStreamAdminServiceStub eventStreamAdminServiceStub = new EventStreamAdminServiceStub( - Utils.replaceSystemProperty(DAS_ADMIN_SERVICE_EP)); + Utils.replaceSystemProperty(DAS_ADMIN_SERVICE_EP + EVENT_STREAM_CONTEXT)); Options streamOptions = eventStreamAdminServiceStub._getServiceClient().getOptions(); if (streamOptions == null) { streamOptions = new Options(); @@ -421,7 +485,7 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe .getRealmConfiguration().getAdminUserName() + "@" + tenantDomain; JWTClient jwtClient = DeviceMgtAPIUtils.getJWTClientManagerService().getJWTClient(); - String authValue = AUTHORIZATION_HEADER_VALUE + " " + new String(Base64.encodeBase64( + String authValue = AUTHORIZATION_HEADER_VALUE + " " + new String(Base64.encodeBase64( jwtClient.getJwtToken(username).getBytes())); List
    list = new ArrayList<>(); @@ -441,7 +505,7 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe private EventReceiverAdminServiceStub getEventReceiverAdminServiceStub() throws AxisFault, UserStoreException, JWTClientException { EventReceiverAdminServiceStub receiverAdminServiceStub = new EventReceiverAdminServiceStub( - Utils.replaceSystemProperty(DAS_ADMIN_SERVICE_EP)); + Utils.replaceSystemProperty(DAS_ADMIN_SERVICE_EP + EVENT_RECIEVER_CONTEXT)); Options eventReciverOptions = receiverAdminServiceStub._getServiceClient().getOptions(); if (eventReciverOptions == null) { eventReciverOptions = new Options(); @@ -451,7 +515,7 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe .getRealmConfiguration().getAdminUserName() + "@" + tenantDomain; JWTClient jwtClient = DeviceMgtAPIUtils.getJWTClientManagerService().getJWTClient(); - String authValue = AUTHORIZATION_HEADER_VALUE + " " + new String(Base64.encodeBase64( + String authValue = AUTHORIZATION_HEADER_VALUE + " " + new String(Base64.encodeBase64( jwtClient.getJwtToken(username).getBytes())); List
    list = new ArrayList<>(); @@ -472,7 +536,7 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe private EventPublisherAdminServiceStub getEventPublisherAdminServiceStub() throws AxisFault, UserStoreException, JWTClientException { EventPublisherAdminServiceStub eventPublisherAdminServiceStub = new EventPublisherAdminServiceStub( - Utils.replaceSystemProperty(DAS_ADMIN_SERVICE_EP)); + Utils.replaceSystemProperty(DAS_ADMIN_SERVICE_EP + EVENT_PUBLISHER_CONTEXT)); Options eventReciverOptions = eventPublisherAdminServiceStub._getServiceClient().getOptions(); if (eventReciverOptions == null) { eventReciverOptions = new Options(); @@ -482,7 +546,7 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe .getRealmConfiguration().getAdminUserName() + "@" + tenantDomain; JWTClient jwtClient = DeviceMgtAPIUtils.getJWTClientManagerService().getJWTClient(); - String authValue = AUTHORIZATION_HEADER_VALUE + " " + new String(Base64.encodeBase64( + String authValue = AUTHORIZATION_HEADER_VALUE + " " + new String(Base64.encodeBase64( jwtClient.getJwtToken(username).getBytes())); List
    list = new ArrayList<>(); @@ -504,7 +568,7 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe throws AxisFault, UserStoreException, JWTClientException { EventStreamPersistenceAdminServiceStub eventStreamPersistenceAdminServiceStub = new EventStreamPersistenceAdminServiceStub( - Utils.replaceSystemProperty(DAS_ADMIN_SERVICE_EP)); + Utils.replaceSystemProperty(DAS_ADMIN_SERVICE_EP + EVENT_PERSISTENCE_CONTEXT)); Options eventReciverOptions = eventStreamPersistenceAdminServiceStub._getServiceClient().getOptions(); if (eventReciverOptions == null) { eventReciverOptions = new Options(); @@ -514,7 +578,7 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe .getRealmConfiguration().getAdminUserName() + "@" + tenantDomain; JWTClient jwtClient = DeviceMgtAPIUtils.getJWTClientManagerService().getJWTClient(); - String authValue = AUTHORIZATION_HEADER_VALUE + " " + new String(Base64.encodeBase64( + String authValue = AUTHORIZATION_HEADER_VALUE + " " + new String(Base64.encodeBase64( jwtClient.getJwtToken(username).getBytes())); List
    list = new ArrayList<>(); @@ -578,7 +642,7 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe * Initializes the SSL Context */ private static void initSSLConnection() throws NoSuchAlgorithmException, UnrecoverableKeyException, - KeyStoreException, KeyManagementException { + KeyStoreException, KeyManagementException { KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KEY_MANAGER_TYPE); keyManagerFactory.init(keyStore, keyStorePassword); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TRUST_MANAGER_TYPE); From 3df2912a6a29a39eb09acac184dc6a1bf2fbbe8e Mon Sep 17 00:00:00 2001 From: ayyoob Date: Mon, 24 Apr 2017 02:51:03 +0530 Subject: [PATCH 007/358] added default UI for realtime,batch, device view and type view --- .../mqtt/MQTTNotificationStrategy.java | 9 +- .../jaxrs/beans/analytics/EventRecords.java | 1 - .../api/DeviceEventManagementService.java | 4 +- .../DeviceEventManagementServiceImpl.java | 221 ++++++++++-------- .../impl/DeviceManagementServiceImpl.java | 72 +++++- .../jaggeryapps/devicemgt/api/stats-api.jag | 77 ++++++ .../devicemgt/app/conf/config.json | 4 +- .../cdmf.page.device.analytics/analytics.js | 6 +- .../public/js/date-picker.js | 9 +- .../operation-bar.hbs | 114 +++++++++ .../operation-bar.js | 29 +++ .../operation-bar.json | 3 + .../public/js/operation-bar.js | 116 +++++++++ .../overview-section.hbs | 65 ++++++ .../overview-section.js | 23 ++ .../overview-section.json | 3 + .../analytics-view.hbs | 47 ++++ .../analytics-view.js | 71 ++++++ .../analytics-view.json | 3 + .../public/js/device.js | 74 ++++++ .../device-view.hbs | 23 +- .../device-view.js | 50 +++- .../analytics-view.hbs | 44 ++++ .../analytics-view.js | 54 +++++ .../analytics-view.json | 3 + .../public/js/device-stats.js | 57 +++++ .../public/js/moment.min.js | 7 + .../public/js/socket.io.min.js | 2 + .../type-view.hbs | 26 +++ .../type-view.js | 6 +- .../jaggeryapps/devicemgt/jaggery.conf | 4 + 31 files changed, 1104 insertions(+), 123 deletions(-) create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/api/stats-api.jag create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/operation-bar.hbs create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/operation-bar.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/operation-bar.json create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/public/js/operation-bar.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.overview-section/overview-section.hbs create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.overview-section/overview-section.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.overview-section/overview-section.json create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/analytics-view.hbs create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/analytics-view.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/analytics-view.json create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/public/js/device.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/analytics-view.hbs create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/analytics-view.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/analytics-view.json create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/public/js/device-stats.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/public/js/moment.min.js create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/public/js/socket.io.min.js diff --git a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt/src/main/java/org/wso2/carbon/device/mgt/extensions/push/notification/provider/mqtt/MQTTNotificationStrategy.java b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt/src/main/java/org/wso2/carbon/device/mgt/extensions/push/notification/provider/mqtt/MQTTNotificationStrategy.java index 93be818999..39e1bc3796 100644 --- a/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt/src/main/java/org/wso2/carbon/device/mgt/extensions/push/notification/provider/mqtt/MQTTNotificationStrategy.java +++ b/components/device-mgt-extensions/org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt/src/main/java/org/wso2/carbon/device/mgt/extensions/push/notification/provider/mqtt/MQTTNotificationStrategy.java @@ -22,7 +22,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; -import org.wso2.carbon.device.mgt.common.policy.mgt.Profile; import org.wso2.carbon.device.mgt.common.push.notification.NotificationContext; import org.wso2.carbon.device.mgt.common.push.notification.NotificationStrategy; import org.wso2.carbon.device.mgt.common.push.notification.PushNotificationConfig; @@ -101,7 +100,7 @@ public class MQTTNotificationStrategy implements NotificationStrategy { for (ProfileOperation profileOperation : profileOperations) { Map dynamicProperties = new HashMap<>(); String topic = tenantDomain + "/" - + deviceType + "/" + deviceId + "/" + profileOperation.getType() + + deviceType + "/" + deviceId + "/operation/" + profileOperation.getType() .toString().toLowerCase() + "/" + profileOperation.getCode().toLowerCase(); dynamicProperties.put("topic", topic); MQTTDataHolder.getInstance().getOutputEventAdapterService().publish(mqttAdapterName, dynamicProperties, @@ -111,11 +110,11 @@ public class MQTTNotificationStrategy implements NotificationStrategy { } else { Map dynamicProperties = new HashMap<>(); String topic = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(true) + "/" - + ctx.getDeviceId().getType() + "/" + ctx.getDeviceId().getId() + "/" + operation.getType() - .toString().toLowerCase() + "/" + operation.getCode(); + + ctx.getDeviceId().getType() + "/" + ctx.getDeviceId().getId() + "/operation/" + + operation.getType().toString().toLowerCase() + "/" + operation.getCode(); dynamicProperties.put("topic", topic); if (operation.getPayLoad() == null) { - operation.setPayLoad(""); + operation.setPayLoad(operation.getCode()); } MQTTDataHolder.getInstance().getOutputEventAdapterService().publish(mqttAdapterName, dynamicProperties, operation.getPayLoad()); diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/EventRecords.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/EventRecords.java index bb3347c1ba..0dacde3833 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/EventRecords.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/analytics/EventRecords.java @@ -42,7 +42,6 @@ public class EventRecords extends BasePaginatedResult { public void setList(List records) { this.records = records; - setCount(records.size()); } @Override diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceEventManagementService.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceEventManagementService.java index 73a816b7a4..5a77c51074 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceEventManagementService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceEventManagementService.java @@ -41,7 +41,7 @@ import javax.ws.rs.core.Response; extensions = { @Extension(properties = { @ExtensionProperty(name = "name", value = "DeviceEventManagement"), - @ExtensionProperty(name = "context", value = "/api/device-mgt/v1.0/device-types/events"), + @ExtensionProperty(name = "context", value = "/api/device-mgt/v1.0/events"), }) } ), @@ -65,7 +65,7 @@ import javax.ws.rs.core.Response; ) } ) -@Path("/device-types/events") +@Path("/events") @Api(value = "Device Event Management", description = "This API corresponds to all tasks related to device " + "event management") @Produces(MediaType.APPLICATION_JSON) diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceEventManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceEventManagementServiceImpl.java index 14b1fd6776..2d26538ec4 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceEventManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceEventManagementServiceImpl.java @@ -2,6 +2,7 @@ package org.wso2.carbon.device.mgt.jaxrs.service.impl; import org.apache.axis2.AxisFault; import org.apache.axis2.client.Options; +import org.apache.axis2.client.Stub; import org.apache.axis2.transport.http.HTTPConstants; import org.apache.commons.codec.binary.Base64; import org.apache.commons.httpclient.Header; @@ -82,7 +83,7 @@ import java.util.UUID; * This is used for simple analytics purpose, to create streams and receiver dynamically and a common endpoint * to retrieve data. */ -@Path("/device-types/events") +@Path("/events") public class DeviceEventManagementServiceImpl implements DeviceEventManagementService { private static final Log log = LogFactory.getLog(DeviceEventManagementServiceImpl.class); @@ -138,6 +139,7 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe @Override public Response getDeviceTypeEventDefinition(@PathParam("type") String deviceType) { String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + EventStreamAdminServiceStub eventStreamAdminServiceStub = null; try { if (deviceType == null || !DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(deviceType)) { @@ -146,11 +148,10 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe return Response.status(Response.Status.BAD_REQUEST).build(); } String streamName = getStreamDefinition(deviceType, tenantDomain); - EventStreamAdminServiceStub eventStreamAdminServiceStub = getEventStreamAdminServiceStub(); + eventStreamAdminServiceStub = getEventStreamAdminServiceStub(); EventStreamDefinitionDto eventStreamDefinitionDto = eventStreamAdminServiceStub.getStreamDefinitionDto( streamName + ":" + DEFAULT_STREAM_VERSION); if (eventStreamDefinitionDto == null) { - eventStreamAdminServiceStub.cleanup(); return Response.status(Response.Status.NO_CONTENT).build(); } EventStreamAttributeDto[] eventStreamAttributeDtos = eventStreamDefinitionDto.getPayloadData(); @@ -161,7 +162,6 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe , AttributeType.valueOf(eventStreamAttributeDto.getAttributeType().toUpperCase()))); } eventAttributeList.setList(attributes); - eventStreamAdminServiceStub.cleanup(); return Response.ok().entity(eventAttributeList).build(); } catch (AxisFault e) { log.error("failed to retrieve event definitions for tenantDomain:" + tenantDomain, e); @@ -178,6 +178,8 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe } catch (DeviceManagementException e) { log.error("Failed to access device management service, tenantDomain: " + tenantDomain, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } finally { + cleanup(eventStreamAdminServiceStub); } } @@ -250,6 +252,12 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe public Response deleteDeviceTypeEventDefinitions(@PathParam("type") String deviceType) { String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); boolean superTenantMode = false; + EventReceiverAdminServiceStub eventReceiverAdminServiceStub = null; + EventPublisherAdminServiceStub eventPublisherAdminServiceStub = null; + EventStreamAdminServiceStub eventStreamAdminServiceStub = null; + + EventReceiverAdminServiceStub tenantBasedEventReceiverAdminServiceStub = null; + EventStreamAdminServiceStub tenantBasedEventStreamAdminServiceStub = null; try { if (deviceType == null || !DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(deviceType)) { @@ -260,43 +268,35 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe String eventReceiverName = getReceiverName(deviceType, tenantDomain); String eventPublisherName = deviceType.trim().toLowerCase() + "_websocket_publisher"; String streamName = getStreamDefinition(deviceType, tenantDomain); - EventStreamAdminServiceStub eventStreamAdminServiceStub = getEventStreamAdminServiceStub(); + eventStreamAdminServiceStub = getEventStreamAdminServiceStub(); if (eventStreamAdminServiceStub.getStreamDefinitionDto(streamName + ":" + DEFAULT_STREAM_VERSION) == null) { return Response.status(Response.Status.NO_CONTENT).build(); } + eventStreamAdminServiceStub.removeEventStreamDefinition(streamName, DEFAULT_STREAM_VERSION); EventReceiverAdminServiceCallbackHandler eventReceiverAdminServiceCallbackHandler = new EventReceiverAdminServiceCallbackHandler() {}; EventPublisherAdminServiceCallbackHandler eventPublisherAdminServiceCallbackHandler = new EventPublisherAdminServiceCallbackHandler() {}; - EventReceiverAdminServiceStub eventReceiverAdminServiceStub = getEventReceiverAdminServiceStub(); - eventReceiverAdminServiceStub.startundeployActiveEventReceiverConfiguration(eventReceiverName + eventReceiverAdminServiceStub = getEventReceiverAdminServiceStub(); + eventReceiverAdminServiceStub.startundeployInactiveEventReceiverConfiguration(eventReceiverName , eventReceiverAdminServiceCallbackHandler); - eventStreamAdminServiceStub.removeEventStreamDefinition(streamName, DEFAULT_STREAM_VERSION); - - EventPublisherAdminServiceStub eventPublisherAdminServiceStub = getEventPublisherAdminServiceStub(); - eventPublisherAdminServiceStub.startundeployActiveEventPublisherConfiguration(eventPublisherName + eventPublisherAdminServiceStub = getEventPublisherAdminServiceStub(); + eventPublisherAdminServiceStub.startundeployInactiveEventPublisherConfiguration(eventPublisherName , eventPublisherAdminServiceCallbackHandler); - eventStreamAdminServiceStub.cleanup(); - eventPublisherAdminServiceStub.cleanup(); - eventReceiverAdminServiceStub.cleanup(); - superTenantMode = true; PrivilegedCarbonContext.startTenantFlow(); PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain( MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, true); if (!MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { - eventReceiverAdminServiceStub = getEventReceiverAdminServiceStub(); - eventStreamAdminServiceStub = getEventStreamAdminServiceStub(); - - eventReceiverAdminServiceStub.startundeployActiveEventReceiverConfiguration(eventReceiverName - , eventReceiverAdminServiceCallbackHandler); + tenantBasedEventReceiverAdminServiceStub = getEventReceiverAdminServiceStub(); + tenantBasedEventStreamAdminServiceStub = getEventStreamAdminServiceStub(); eventStreamAdminServiceStub.removeEventStreamDefinition(streamName, DEFAULT_STREAM_VERSION); + eventReceiverAdminServiceStub.startundeployInactiveEventReceiverConfiguration(eventReceiverName + , eventReceiverAdminServiceCallbackHandler); - eventReceiverAdminServiceStub.cleanup(); - eventStreamAdminServiceStub.cleanup(); } return Response.ok().build(); } catch (AxisFault e) { @@ -318,6 +318,11 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe if (superTenantMode) { PrivilegedCarbonContext.endTenantFlow(); } + cleanup(eventStreamAdminServiceStub); + cleanup(eventPublisherAdminServiceStub); + cleanup(eventReceiverAdminServiceStub); + cleanup(eventReceiverAdminServiceStub); + cleanup(eventStreamAdminServiceStub); } } @@ -367,109 +372,120 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe , String requestedTenantDomain, String deviceType) throws RemoteException, UserStoreException, JWTClientException { EventReceiverAdminServiceStub receiverAdminServiceStub = getEventReceiverAdminServiceStub(); - String adapterType = "oauth-mqtt"; - BasicInputAdapterPropertyDto basicInputAdapterPropertyDtos[]; - if (transportType == TransportType.MQTT) { - basicInputAdapterPropertyDtos = new BasicInputAdapterPropertyDto[4]; - basicInputAdapterPropertyDtos[0] = getBasicInputAdapterPropertyDto("topic", requestedTenantDomain - + "/" + deviceType + "/+/events"); - basicInputAdapterPropertyDtos[1] = getBasicInputAdapterPropertyDto("contentValidator", "iot-mqtt"); - basicInputAdapterPropertyDtos[2] = getBasicInputAdapterPropertyDto("cleanSession", "true"); - basicInputAdapterPropertyDtos[3] = getBasicInputAdapterPropertyDto("clientId", generateUUID()); - } else { - adapterType = "oauth-http"; - basicInputAdapterPropertyDtos = new BasicInputAdapterPropertyDto[1]; - basicInputAdapterPropertyDtos[0] = getBasicInputAdapterPropertyDto("contentValidator", "iot-mqtt"); - } - if (receiverAdminServiceStub.getActiveEventReceiverConfiguration(eventRecieverName) == null) { - receiverAdminServiceStub.deployJsonEventReceiverConfiguration(eventRecieverName, streamNameWithVersion - , adapterType, null, basicInputAdapterPropertyDtos, false); + try { + String adapterType = "oauth-mqtt"; + BasicInputAdapterPropertyDto basicInputAdapterPropertyDtos[]; + if (transportType == TransportType.MQTT) { + basicInputAdapterPropertyDtos = new BasicInputAdapterPropertyDto[4]; + basicInputAdapterPropertyDtos[0] = getBasicInputAdapterPropertyDto("topic", requestedTenantDomain + + "/" + deviceType + "/+/events"); + basicInputAdapterPropertyDtos[1] = getBasicInputAdapterPropertyDto("contentValidator", "iot-mqtt"); + basicInputAdapterPropertyDtos[2] = getBasicInputAdapterPropertyDto("cleanSession", "true"); + basicInputAdapterPropertyDtos[3] = getBasicInputAdapterPropertyDto("clientId", generateUUID()); + } else { + adapterType = "oauth-http"; + basicInputAdapterPropertyDtos = new BasicInputAdapterPropertyDto[1]; + basicInputAdapterPropertyDtos[0] = getBasicInputAdapterPropertyDto("contentValidator", "iot-mqtt"); + } + if (receiverAdminServiceStub.getActiveEventReceiverConfiguration(eventRecieverName) == null) { + receiverAdminServiceStub.deployJsonEventReceiverConfiguration(eventRecieverName, streamNameWithVersion + , adapterType, null, basicInputAdapterPropertyDtos, false); + } + } finally { + cleanup(receiverAdminServiceStub); } - receiverAdminServiceStub.cleanup(); } private void publishStreamDefinitons(String streamName, String version, String deviceType , EventAttributeList eventAttributes) throws RemoteException, UserStoreException, JWTClientException { EventStreamAdminServiceStub eventStreamAdminServiceStub = getEventStreamAdminServiceStub(); - EventStreamDefinitionDto eventStreamDefinitionDto = new EventStreamDefinitionDto(); - eventStreamDefinitionDto.setName(streamName); - eventStreamDefinitionDto.setVersion(version); - EventStreamAttributeDto eventStreamAttributeDtos[] = - new EventStreamAttributeDto[eventAttributes.getList().size()]; - int i = 0; - for (Attribute attribute : eventAttributes.getList()) { + try { + EventStreamDefinitionDto eventStreamDefinitionDto = new EventStreamDefinitionDto(); + eventStreamDefinitionDto.setName(streamName); + eventStreamDefinitionDto.setVersion(version); + EventStreamAttributeDto eventStreamAttributeDtos[] = + new EventStreamAttributeDto[eventAttributes.getList().size() + 1]; + int i = 0; + for (Attribute attribute : eventAttributes.getList()) { + EventStreamAttributeDto eventStreamAttributeDto = new EventStreamAttributeDto(); + eventStreamAttributeDto.setAttributeName(attribute.getName()); + eventStreamAttributeDto.setAttributeType(attribute.getType().toString()); + eventStreamAttributeDtos[i] = eventStreamAttributeDto; + i++; + } + EventStreamAttributeDto eventStreamAttributeDto = new EventStreamAttributeDto(); - eventStreamAttributeDto.setAttributeName(attribute.getName()); - eventStreamAttributeDto.setAttributeType(attribute.getType().toString()); + eventStreamAttributeDto.setAttributeName("deviceId"); + eventStreamAttributeDto.setAttributeType(AttributeType.STRING.toString()); eventStreamAttributeDtos[i] = eventStreamAttributeDto; - i++; - } - EventStreamAttributeDto metaData[] = new EventStreamAttributeDto[1]; - EventStreamAttributeDto eventStreamAttributeDto = new EventStreamAttributeDto(); - eventStreamAttributeDto.setAttributeName("deviceId"); - eventStreamAttributeDto.setAttributeType(AttributeType.STRING.toString()); - metaData[0] = eventStreamAttributeDto; - eventStreamDefinitionDto.setMetaData(metaData); - eventStreamDefinitionDto.setPayloadData(eventStreamAttributeDtos); - String streamId = streamName + ":" + version; - if (eventStreamAdminServiceStub.getStreamDefinitionDto(streamId) != null) { - eventStreamAdminServiceStub.editEventStreamDefinitionAsDto(eventStreamDefinitionDto, streamId); - } else { - eventStreamAdminServiceStub.addEventStreamDefinitionAsDto(eventStreamDefinitionDto); + eventStreamDefinitionDto.setPayloadData(eventStreamAttributeDtos); + String streamId = streamName + ":" + version; + if (eventStreamAdminServiceStub.getStreamDefinitionDto(streamId) != null) { + eventStreamAdminServiceStub.editEventStreamDefinitionAsDto(eventStreamDefinitionDto, streamId); + } else { + eventStreamAdminServiceStub.addEventStreamDefinitionAsDto(eventStreamDefinitionDto); + } + } finally { + cleanup(eventStreamAdminServiceStub); } - eventStreamAdminServiceStub.cleanup(); } private void publishEventStore(String streamName, String version, EventAttributeList eventAttributes) throws RemoteException, UserStoreException, JWTClientException, EventStreamPersistenceAdminServiceEventStreamPersistenceAdminServiceExceptionException { - EventStreamPersistenceAdminServiceStub eventStreamAdminServiceStub = + EventStreamPersistenceAdminServiceStub eventStreamPersistenceAdminServiceStub = getEventStreamPersistenceAdminServiceStub(); - AnalyticsTable analyticsTable = new AnalyticsTable(); - analyticsTable.setRecordStoreName(DEFAULT_EVENT_STORE_NAME); - analyticsTable.setStreamVersion(version); - analyticsTable.setTableName(streamName); - analyticsTable.setMergeSchema(false); - analyticsTable.setPersist(true); - AnalyticsTableRecord analyticsTableRecords[] = new AnalyticsTableRecord[eventAttributes.getList().size() + 1]; - int i = 0; - for (Attribute attribute : eventAttributes.getList()) { + try { + AnalyticsTable analyticsTable = new AnalyticsTable(); + analyticsTable.setRecordStoreName(DEFAULT_EVENT_STORE_NAME); + analyticsTable.setStreamVersion(version); + analyticsTable.setTableName(streamName); + analyticsTable.setMergeSchema(false); + analyticsTable.setPersist(true); + AnalyticsTableRecord analyticsTableRecords[] = new AnalyticsTableRecord[eventAttributes.getList().size() + 1]; + int i = 0; + for (Attribute attribute : eventAttributes.getList()) { + AnalyticsTableRecord analyticsTableRecord = new AnalyticsTableRecord(); + analyticsTableRecord.setColumnName(attribute.getName()); + analyticsTableRecord.setColumnType(attribute.getType().toString().toUpperCase()); + analyticsTableRecord.setFacet(false); + analyticsTableRecord.setIndexed(true); + analyticsTableRecord.setPersist(true); + analyticsTableRecord.setPrimaryKey(false); + analyticsTableRecord.setScoreParam(false); + analyticsTableRecords[i] = analyticsTableRecord; + i++; + } AnalyticsTableRecord analyticsTableRecord = new AnalyticsTableRecord(); - analyticsTableRecord.setColumnName(attribute.getName()); - analyticsTableRecord.setColumnType(attribute.getType().toString().toUpperCase()); + analyticsTableRecord.setColumnName("deviceId"); + analyticsTableRecord.setColumnType(AttributeType.STRING.toString().toUpperCase()); analyticsTableRecord.setFacet(false); analyticsTableRecord.setIndexed(true); analyticsTableRecord.setPersist(true); analyticsTableRecord.setPrimaryKey(false); analyticsTableRecord.setScoreParam(false); analyticsTableRecords[i] = analyticsTableRecord; - i++; + analyticsTable.setAnalyticsTableRecords(analyticsTableRecords); + eventStreamPersistenceAdminServiceStub.addAnalyticsTable(analyticsTable); + } finally { + cleanup(eventStreamPersistenceAdminServiceStub); } - AnalyticsTableRecord analyticsTableRecord = new AnalyticsTableRecord(); - analyticsTableRecord.setColumnName("meta_deviceId"); - analyticsTableRecord.setColumnType(AttributeType.STRING.toString().toUpperCase()); - analyticsTableRecord.setFacet(false); - analyticsTableRecord.setIndexed(true); - analyticsTableRecord.setPersist(true); - analyticsTableRecord.setPrimaryKey(false); - analyticsTableRecord.setScoreParam(false); - analyticsTableRecords[i] = analyticsTableRecord; - analyticsTable.setAnalyticsTableRecords(analyticsTableRecords); - eventStreamAdminServiceStub.addAnalyticsTable(analyticsTable); - eventStreamAdminServiceStub.cleanup(); } private void publishWebsocketPublisherDefinition(String streamNameWithVersion, String deviceType) throws RemoteException, UserStoreException, JWTClientException { EventPublisherAdminServiceStub eventPublisherAdminServiceStub = getEventPublisherAdminServiceStub(); - String eventPublisherName = deviceType.trim().toLowerCase() + "_websocket_publisher"; - if (eventPublisherAdminServiceStub.getActiveEventPublisherConfiguration(eventPublisherName) == null) { - eventPublisherAdminServiceStub.deployJsonEventPublisherConfiguration(eventPublisherName - , streamNameWithVersion, DEFAULT_WEBSOCKET_PUBLISHER_ADAPTER_TYPE, null, null - , null, false); + try { + String eventPublisherName = deviceType.trim().toLowerCase() + "_websocket_publisher"; + if (eventPublisherAdminServiceStub.getActiveEventPublisherConfiguration(eventPublisherName) == null) { + eventPublisherAdminServiceStub.deployJsonEventPublisherConfiguration(eventPublisherName + , streamNameWithVersion, DEFAULT_WEBSOCKET_PUBLISHER_ADAPTER_TYPE, null, null + , null, false); + } + } finally { + cleanup(eventPublisherAdminServiceStub); } - eventPublisherAdminServiceStub.cleanup(); } private EventStreamAdminServiceStub getEventStreamAdminServiceStub() @@ -695,15 +711,16 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe , int offset, int limit) throws AnalyticsException { int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId(); AnalyticsDataAPI analyticsDataAPI = getAnalyticsDataAPI(); + EventRecords eventRecords = new EventRecords(); int eventCount = analyticsDataAPI.searchCount(tenantId, tableName, query); if (eventCount == 0) { - return null; + eventRecords.setCount(0); } List resultEntries = analyticsDataAPI.search(tenantId, tableName, query, offset, limit, sortByFields); List recordIds = getRecordIds(resultEntries); AnalyticsDataResponse response = analyticsDataAPI.get(tenantId, tableName, 1, null, recordIds); - EventRecords eventRecords = new EventRecords(); + eventRecords.setCount(eventCount); eventRecords.setList(AnalyticsDataAPIUtil.listRecords(analyticsDataAPI, response)); return eventRecords; } @@ -716,4 +733,14 @@ public class DeviceEventManagementServiceImpl implements DeviceEventManagementSe return ids; } + private void cleanup(Stub stub) { + if (stub != null) { + try { + stub.cleanup(); + } catch (AxisFault axisFault) { + // do nothing + } + } + } + } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java index 1f63b8069a..540f87c856 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java @@ -736,17 +736,29 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { log.error(errorMessage); return Response.status(Response.Status.BAD_REQUEST).build(); } - DeviceIdentifier deviceIdentifier; - List deviceIdentifiers = new ArrayList<>(); - for (String deviceId : operationRequest.getDeviceIdentifiers()) { - deviceIdentifier = new DeviceIdentifier(); - deviceIdentifier.setId(deviceId); - deviceIdentifier.setType(type); - deviceIdentifiers.add(deviceIdentifier); + if (!DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(type)) { + String errorMessage = "Device identifier list is empty"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } + Operation.Type operationType = operationRequest.getOperation().getType(); + if (operationType == Operation.Type.COMMAND || operationType == Operation.Type.CONFIG) { + DeviceIdentifier deviceIdentifier; + List deviceIdentifiers = new ArrayList<>(); + for (String deviceId : operationRequest.getDeviceIdentifiers()) { + deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(type); + deviceIdentifiers.add(deviceIdentifier); + } + Activity activity = DeviceMgtAPIUtils.getDeviceManagementService().addOperation(type + , operationRequest.getOperation(), deviceIdentifiers); + return Response.status(Response.Status.CREATED).entity(activity).build(); + } else { + String message = "Only Command and Config operation is supported through this api"; + return Response.status(Response.Status.NOT_ACCEPTABLE).entity(message).build(); } - Activity activity = DeviceMgtAPIUtils.getDeviceManagementService().addOperation(type - , operationRequest.getOperation(), deviceIdentifiers); - return Response.status(Response.Status.CREATED).entity(activity).build(); + } catch (InvalidDeviceException e) { String errorMessage = "Invalid Device Identifiers found."; log.error(errorMessage, e); @@ -757,6 +769,11 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { log.error(errorMessage, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving deivce management service instance"; + log.error(errorMessage, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); } } @@ -764,6 +781,11 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { @Path("/{type}/{id}/pending/operations") public Response getPendingOperations(@PathParam("type") String type, @PathParam("id") String deviceId) { try { + if (!DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(type)) { + String errorMessage = "Device identifier list is empty"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } List operations = DeviceMgtAPIUtils.getDeviceManagementService().getPendingOperations( new DeviceIdentifier(deviceId, type)); OperationList operationsList = new OperationList(); @@ -775,6 +797,11 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { log.error(errorMessage, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving deivce management service instance"; + log.error(errorMessage, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); } } @@ -782,6 +809,11 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { @Path("/{type}/{id}/last-pending/operation") public Response getNextPendingOperation(@PathParam("type") String type, @PathParam("id") String deviceId) { try { + if (!DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(type)) { + String errorMessage = "Device identifier list is empty"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } Operation operation = DeviceMgtAPIUtils.getDeviceManagementService().getNextPendingOperation( new DeviceIdentifier(deviceId, type)); return Response.status(Response.Status.OK).entity(operation).build(); @@ -790,6 +822,11 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { log.error(errorMessage, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving deivce management service instance"; + log.error(errorMessage, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); } } @@ -797,6 +834,11 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { @Path("/{type}/{id}/operations") public Response updateOperation(@PathParam("type") String type, @PathParam("id") String deviceId, @Valid Operation operation) { try { + if (!DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(type)) { + String errorMessage = "Device identifier list is empty"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } if (operation == null) { String errorMessage = "Device identifier list is empty"; log.error(errorMessage); @@ -810,6 +852,11 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { log.error(errorMessage, e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving deivce management service instance"; + log.error(errorMessage, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build(); } } @@ -824,6 +871,11 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { } try { + if (!DeviceMgtAPIUtils.getDeviceManagementService().getAvailableDeviceTypes().contains(type)) { + String errorMessage = "Device identifier list is empty"; + log.error(errorMessage); + return Response.status(Response.Status.BAD_REQUEST).build(); + } List operations = DeviceMgtAPIUtils.getDeviceManagementService() .getOperationsByDeviceAndStatus(new DeviceIdentifier(deviceId, type), status); OperationList operationsList = new OperationList(); diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/api/stats-api.jag b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/api/stats-api.jag new file mode 100644 index 0000000000..6919a492a3 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/api/stats-api.jag @@ -0,0 +1,77 @@ +<% +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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. + */ + +var uri = request.getRequestURI(); +var uriMatcher = new URIMatcher(String(uri)); + +var log = new Log("api/stats-api.jag"); + +var serviceInvokers = require("/app/modules/oauth/token-protected-service-invokers.js")["invokers"]; +var devicemgtProps = require("/app/modules/conf-reader/main.js")["conf"]; + +if (uriMatcher.match("/{context}/api/stats/paginate")) { + var deviceType = request.getParameter("deviceType"); + var deviceId = request.getParameter("deviceId"); + var from = request.getParameter("from"); + var to = request.getParameter("to"); + var index = request.getParameter("start"); + var length = request.getParameter("length"); + var keys = request.getParameter("attributes"); + keys = JSON.parse(keys); + var restAPIEndpoint = devicemgtProps["httpsURL"] + devicemgtProps["backendRestEndpoints"]["deviceMgt"] + "/events/" + + deviceType + "/" + deviceId + "?offset=" + index +"&limit=" + length + "&from="+ from + "&to=" + to; + serviceInvokers.XMLHttp.get( + restAPIEndpoint, + function (restAPIResponse) { + if (restAPIResponse["status"] == 200 && restAPIResponse["responseText"]) { + var responsePayload = parse(restAPIResponse["responseText"]); + + var paginatedResult = {}; + paginatedResult["recordsTotal"] = responsePayload["count"]; + paginatedResult["recordsFiltered"] = responsePayload["count"]; + var records = responsePayload["records"]; + var dataSet = []; + for (var i = 0; i < records.length; i++){ + var record = records[i]; + var timestamp = record["timestamp"]; + var dataRow = []; + dataRow.push(timestamp); + for (var j = 0; j < keys.length; j++) { + var key = keys[j]; + dataRow.push(record.values[key]); + } + //dataSet.push(dataRow); + dataSet.push(dataRow); + } + paginatedResult["data"] = dataSet; + response["status"] = restAPIResponse["status"]; + response["content"] = paginatedResult; + } else { + response["status"] = 204; + var paginatedResult = {}; + var dataSet = []; + paginatedResult["recordsTotal"] = 0; + paginatedResult["recordsFiltered"] = 0; + paginatedResult["data"] = dataSet; + response["content"] = paginatedResult; + } + } + ); +} +%> \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/conf/config.json b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/conf/config.json index 5caf20f31f..b0becff9d7 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/conf/config.json +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/conf/config.json @@ -176,7 +176,9 @@ "perm:ios:get-restrictions", "perm:ios:wipe-data", "perm:admin", - "perm:devicetype:deployment" + "perm:devicetype:deployment", + "perm:device-types:events", + "perm:device-types:events:view" ], "isOAuthEnabled": true, "backendRestEndpoints": { diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.device.analytics/analytics.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.device.analytics/analytics.js index f77e3d14ce..54234b86db 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.device.analytics/analytics.js +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.device.analytics/analytics.js @@ -21,8 +21,12 @@ function onRequest(context) { var deviceType = context.uriParams.deviceType; var deviceName = request.getParameter("deviceName"); var deviceId = request.getParameter("deviceId"); + var unitName = utility.getTenantedDeviceUnitName(deviceType, "analytics-view"); + if (!unitName) { + unitName = "cdmf.unit.default.device.type.analytics-view"; + } return { - "deviceAnalyticsViewUnitName": utility.getTenantedDeviceUnitName(deviceType, "analytics-view"), + "deviceAnalyticsViewUnitName": unitName, "deviceType": deviceType, "deviceName": deviceName, "deviceId": deviceId diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.analytics.date-range-picker/public/js/date-picker.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.analytics.date-range-picker/public/js/date-picker.js index 3f89797692..5a8ac270be 100755 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.analytics.date-range-picker/public/js/date-picker.js +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.analytics.date-range-picker/public/js/date-picker.js @@ -103,7 +103,14 @@ function setDateTime(from, to) { // Implement drawGraph_ method in your UI unit for analytics. var deviceTypes = $("#device-type-details").data("devicetypes"); for (var i = 0; i < deviceTypes.length; i++){ - window["drawGraph_" + deviceTypes](parseInt(from / 1000), parseInt(to / 1000)); + try{ + window["drawGraph_" + deviceTypes](parseInt(from / 1000), parseInt(to / 1000)); + }catch(e){ + } + try{ + window["drawTable"](parseInt(from / 1000), parseInt(to / 1000)); + }catch(e){ + } } } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/operation-bar.hbs b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/operation-bar.hbs new file mode 100644 index 0000000000..04030eb5e8 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/operation-bar.hbs @@ -0,0 +1,114 @@ +{{! + Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + + WSO2 Inc. licenses this file to you 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. +}} + +{{#if control_operations}} +
    + + {{#each control_operations}} + + + {{name}} + + +
    +
    +
    +
    +

    + + + + + {{name}} +
    +

    +

    + {{description}} +
    +

    + +
    + +
    + + +
    + + + + +
    +
    +
    +
    +
    + {{/each}} +
    +{{else}} +
    +

    + Operations Loading Failed!

    +
    +{{/if}} + + + +{{#zone "bottomJs"}} + {{js "js/operation-bar.js"}} +{{/zone}} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/operation-bar.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/operation-bar.js new file mode 100644 index 0000000000..1f9ef9fc07 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/operation-bar.js @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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. + */ + +function onRequest(context) { + var log = new Log("operation.js"); + var deviceType = context.uriParams.deviceType; + var operationModule = require("/app/modules/business-controllers/operation.js")["operationModule"]; + var devicemgtProps = require("/app/modules/conf-reader/main.js")["conf"]; + var restAPIEndpoint = devicemgtProps["backendRestEndpoints"]["deviceMgt"] + + "/devices/" + deviceType + "/operations"; + var device = context.unit.params.device; + var features = context.unit.params.features; + return {"control_operations": features, "device": device, "operationEndpoint": restAPIEndpoint}; +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/operation-bar.json b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/operation-bar.json new file mode 100644 index 0000000000..688e939808 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/operation-bar.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/public/js/operation-bar.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/public/js/operation-bar.js new file mode 100644 index 0000000000..79b0b31f0d --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.operation-bar/public/js/operation-bar.js @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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. + */ + +/* + * On operation click function. + * @param selection: Selected operation + */ +function operationSelect(selection) { + $(modalPopupContent).addClass("operation-data"); + $(modalPopupContent).html($(" .operation[data-operation-code=" + selection + "]").html()); + $(modalPopupContent).data("operation-code", selection); + showPopup(); +} + +function submitForm(formId) { + var form = $("#" + formId); + var operationDetails = $("#operation-details"); + var deviceId = operationDetails.data("deviceid"); + var operationEndpoint = operationDetails.data("endpoint"); + console.log(deviceId); + console.log(operationEndpoint); + + var contentType = "application/json"; + + var payload = {}; + var devices=[]; + devices.push(deviceId); + payload["deviceIdentifiers"] = devices; + var operation = {}; + operation["code"] = form.find("#operation-code").val(); + operation["type"]= form.find("#operation-type").val(); + operation["status"] = "IN_PROGRESS"; + operation["control"] = "REPEAT"; + operation["payLoad"] = form.find("#operation-payload").val(); + operation["enabled"] = true; + payload["operation"] = operation; + + //setting responses callbacks + var defaultStatusClasses = "fw fw-stack-1x"; + var content = $("#operation-response-template").find(".content"); + var title = content.find("#title"); + var statusIcon = content.find("#status-icon"); + var description = content.find("#description"); + var successCallBack = function (response) { + var res = response; + try { + res = JSON.parse(response).messageFromServer; + } catch (err) { + //do nothing + } + title.html("Operation Triggered!"); + statusIcon.attr("class", defaultStatusClasses + " fw-check"); + description.html(res); + $(modalPopupContent).html(content.html()); + }; + var errorCallBack = function (response) { + console.log(response); + title.html("An Error Occurred!"); + statusIcon.attr("class", defaultStatusClasses + " fw-error"); + var reason = (response.responseText == "null")?response.statusText:response.responseText; + description.html(reason); + $(modalPopupContent).html(content.html()); + }; + invokerUtil.post(operationEndpoint, payload, successCallBack, errorCallBack, contentType); +} + +$(document).on('submit', 'form', function (e) { + e.preventDefault(); + var postOperationRequest = $.ajax({ + url: $(this).attr("action") + '&' + $(this).serialize(), + method: "post" + }); + + var btnSubmit = $('#btnSend', this); + btnSubmit.addClass('hidden'); + + var lblSending = $('#lblSending', this); + lblSending.removeClass('hidden'); + + var lblSent = $('#lblSent', this); + postOperationRequest.done(function (data) { + lblSending.addClass('hidden'); + lblSent.removeClass('hidden'); + setTimeout(function () { + hidePopup(); + }, 3000); + }); + + postOperationRequest.fail(function (jqXHR, textStatus) { + lblSending.addClass('hidden'); + lblSent.addClass('hidden'); + }); +}); + +function operationTypeChage(selectElement) { + if (selectElement.value == "COMMAND") { + $("#operation-payload").hide(); + } else { + $("#operation-payload").show(); + } +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.overview-section/overview-section.hbs b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.overview-section/overview-section.hbs new file mode 100644 index 0000000000..e06d6a258d --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.overview-section/overview-section.hbs @@ -0,0 +1,65 @@ +{{! + Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + + WSO2 Inc. licenses this file to you 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. +}} + + + + + + + + + + + +
    ModelTODO
    Status + {{#if permissions.CHANGE_DEVICE_STATUS}} + {{#equal device.status "ACTIVE"}} + + {{/equal}} + {{#equal device.status "INACTIVE"}} + + {{/equal}} + {{#equal device.status "BLOCKED"}} + + {{/equal}} + {{#equal device.status "REMOVED"}} + + {{/equal}} + {{else}} + {{#equal device.status "ACTIVE"}} Active{{/equal}} + {{#equal device.status "INACTIVE"}} Inactive{{/equal}} + {{#equal device.status "BLOCKED"}} Blocked{{/equal}} + {{#equal device.status "REMOVED"}} Removed{{/equal}} + {{/if}} +
    diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.overview-section/overview-section.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.overview-section/overview-section.js new file mode 100644 index 0000000000..c7e4efae8f --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.overview-section/overview-section.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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. + */ + +function onRequest (context) { + var log = new Log("overview-section.js"); + var device = context.unit.params.device; + return {"device" : device}; +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.overview-section/overview-section.json b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.overview-section/overview-section.json new file mode 100644 index 0000000000..688e939808 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.overview-section/overview-section.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/analytics-view.hbs b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/analytics-view.hbs new file mode 100644 index 0000000000..456b895631 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/analytics-view.hbs @@ -0,0 +1,47 @@ +{{! + Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + + WSO2 Inc. licenses this file to you 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. +}} + +{{unit "cdmf.unit.data-tables-extended"}} + + +
    +{{#if attributes}} + + + + + {{#each attributes}} + + {{/each}} + + + + +
    Timestamp{{this}}
    +{{else}} +

    Analytics Not Configured

    +{{/if}} +
    + +{{#zone "bottomJs"}} + {{js "js/device.js"}} +{{/zone}} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/analytics-view.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/analytics-view.js new file mode 100644 index 0000000000..20dede515e --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/analytics-view.js @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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. + */ + +function onRequest(context) { + + var deviceType = context.uriParams.deviceType; + var deviceId = request.getParameter("deviceId"); + var keys = []; + var serviceInvokers = require("/app/modules/oauth/token-protected-service-invokers.js")["invokers"]; + var devicemgtProps = require("/app/modules/conf-reader/main.js")["conf"]; + + if (deviceType != null && deviceType != undefined && deviceId != null && deviceId != undefined) { + var deviceModule = require("/app/modules/business-controllers/device.js")["deviceModule"]; + var device = deviceModule.viewDevice(deviceType, deviceId); + + var restAPIEndpoint = devicemgtProps["httpsURL"] + devicemgtProps["backendRestEndpoints"]["deviceMgt"] + + "/events/" + deviceType; + serviceInvokers.XMLHttp.get( + restAPIEndpoint, + function (restAPIResponse) { + if (restAPIResponse["status"] == 200 && restAPIResponse["responseText"]) { + var eventAttributes = parse(restAPIResponse["responseText"]); + if (eventAttributes.attributes.length > 0) { + for (var i = 0; i < eventAttributes.attributes.length; i++) { + var attribute = eventAttributes.attributes[i]; + if (attribute['name'] == "deviceId") { + continue; + } + keys.push(attribute['name']); + } + } + + } + } + ); + + if (device && device.status != "error") { + if (keys.length === 0 || keys.length === undefined) { + return { + "device": device.content, + "backendApiUri": "/api/device-mgt/v1.0/events/" + deviceType + }; + } else { + + return { + "device": device.content, + "backendApiUri": "/api/device-mgt/v1.0/events/" + deviceType, + "attributes": keys + }; + } + } else { + response.sendError(404, "Device Id " + deviceId + " of type " + deviceType + " cannot be found!"); + exit(); + } + } +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/analytics-view.json b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/analytics-view.json new file mode 100644 index 0000000000..688e939808 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/analytics-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/public/js/device.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/public/js/device.js new file mode 100644 index 0000000000..e7a216dd3e --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.analytics-view/public/js/device.js @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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. + */ + +var InitiateViewOption = null; +var deviceId = null; +var deviceType = null; +var fromTime = null; +var toTime = null; +var keys = null; + +function drawTable(from, to) { + var device = $("#device-details"); + deviceId = device.data("deviceid"); + deviceType = device.data("devicetype"); + keys = device.data("attributes").split(","); + fromTime = from * 1000; + toTime = to * 1000; + if ( $.fn.dataTable.isDataTable( '#stats-table' ) ) { + var table = $('#stats-table').DataTable(); + table.clear().draw(); + table.ajax.reload(); + } + else { + $("#stats-table").datatables_extended({ + serverSide: true, + processing: false, + searching: false, + ordering: false, + pageLength: 100, + order: [], + ajax: { + url: "/devicemgt/api/stats/paginate", + data: buildAjaxData + } + }); + } +} + +function buildAjaxData (){ + var settings = $("#stats-table").dataTable().fnSettings(); + + var obj = { + //default params + "draw" : settings.iDraw, + "start" : settings._iDisplayStart, + "length" : settings._iDisplayLength, + "columns" : "", + "order": "", + "deviceType" : deviceType, + "deviceId" : deviceId, + "from": fromTime, + "to" : toTime, + "attributes" : JSON.stringify(keys) + }; + + return obj; + + +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.hbs b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.hbs index b79c02383e..399d1ccf59 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.hbs +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.hbs @@ -23,34 +23,50 @@ {{/zone}} +{{#zone "overview-section"}} +
    + Device Overview - {{label}}
    + {{unit "cdmf.unit.default.device.overview-section" device=device}} +{{/zone}} + {{#zone "device-thumbnail"}} {{/zone}} + {{#zone "device-opetations"}} + {{#if features}}
    Operations
    - operation comes here + {{unit "cdmf.unit.default.device.operation-bar" device=device features=features}}
    + {{/if}} {{/zone}} {{#zone "device-view-tabs"}} + {{#if attributes}}
  • Device Statistics
  • + {{/if}} + {{#if features}}
  • Operations Log
  • + {{/if}} {{/zone}} {{#zone "device-view-tab-contents"}} + {{#if attributes}}
    -
    Device Statistics
    - unit "cdmf.unit.device.type.senseme.realtime.analytics-view" device=device +
    Realtime Statistics
    + {{unit "cdmf.unit.default.device.type.realtime.analytics-view" device=device attributes=attributes}}
    + {{/if}} + {{#if features}}
    Operations Log
    + {{/if}} {{/zone}} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.js index 834d8f5d53..36d4aeeadd 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.js +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.device-view/device-view.js @@ -15,11 +15,53 @@ * specific language governing permissions and limitations * under the License. */ +var serviceInvokers = require("/app/modules/oauth/token-protected-service-invokers.js")["invokers"]; +var devicemgtProps = require("/app/modules/conf-reader/main.js")["conf"]; function onRequest(context) { var log = new Log("device-view.js"); var deviceType = context.uriParams.deviceType; var deviceId = request.getParameter("id"); + var attributes = []; + var featureList = []; + log.error(featureList); + var restAPIEndpoint = devicemgtProps["httpsURL"] + devicemgtProps["backendRestEndpoints"]["deviceMgt"] + + "/events/" + deviceType; + serviceInvokers.XMLHttp.get( + restAPIEndpoint, + function (restAPIResponse) { + if (restAPIResponse["status"] == 200 && restAPIResponse["responseText"]) { + var eventAttributes = parse(restAPIResponse["responseText"]); + if (eventAttributes.attributes.length > 0) { + for (var i = 0; i < eventAttributes.attributes.length; i++) { + var attribute = eventAttributes.attributes[i]; + if (attribute['name'] == "deviceId") { + continue; + } + attributes.push(attribute['name']); + } + } + + } + } + ); + + var featureEndpoint = devicemgtProps["httpsURL"] + devicemgtProps["backendRestEndpoints"]["deviceMgt"] + + "/device-types/" + deviceType + "/features"; + var featuresList = serviceInvokers.XMLHttp.get(featureEndpoint, function (responsePayload) { + var features = JSON.parse(responsePayload.responseText); + var feature; + for (var i = 0; i < features.length; i++) { + feature = {}; + feature["operation"] = features[i].code; + feature["name"] = features[i].name; + feature["description"] = features[i].description; + } + featureList.push(feature); + }, function (responsePayload) { + featureList = null; + } + ); var autoCompleteParams = [ {"name" : "deviceId", "value" : deviceId} ]; @@ -28,7 +70,13 @@ function onRequest(context) { var deviceModule = require("/app/modules/business-controllers/device.js")["deviceModule"]; var device = deviceModule.viewDevice(deviceType, deviceId); if (device && device.status != "error") { - return {"device": device.content, "autoCompleteParams" : autoCompleteParams, "encodedFeaturePayloads": ""}; + if (attributes.length === 0 || attributes.length === undefined) { + return {"device": device.content, "autoCompleteParams" : autoCompleteParams + , "encodedFeaturePayloads": "", "features":featureList}; + } else { + return {"device": device.content, "autoCompleteParams" : autoCompleteParams + , "encodedFeaturePayloads": "", "attributes": attributes, "features":featureList}; + } } else { response.sendError(404, "Device Id " + deviceId + " of type " + deviceType + " cannot be found!"); exit(); diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/analytics-view.hbs b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/analytics-view.hbs new file mode 100644 index 0000000000..aa128d1630 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/analytics-view.hbs @@ -0,0 +1,44 @@ +{{! + Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + + WSO2 Inc. licenses this file to you 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. +}} +{{unit "cdmf.unit.lib.rickshaw-graph"}} + +
    + + + {{#each attributes}} + + + + + {{/each}} + +
    {{this}}-
    +
    +
    + + + + View Device Analytics + + +{{#zone "bottomJs"}} + {{js "js/moment.min.js"}} + {{js "js/socket.io.min.js"}} + {{js "js/device-stats.js"}} +{{/zone}} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/analytics-view.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/analytics-view.js new file mode 100644 index 0000000000..cec05e10b3 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/analytics-view.js @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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. + */ + +function onRequest(context) { + var log = new Log("stats.js"); + var carbonServer = require("carbon").server; + var device = context.unit.params.device; + var attributes = context.unit.params.attributes; + var devicemgtProps = require("/app/modules/conf-reader/main.js")["conf"]; + var userModule = require("/app/modules/business-controllers/user.js")["userModule"]; + var constants = require("/app/modules/constants.js"); + var websocketEndpoint = devicemgtProps["wssURL"].replace("https", "wss"); + var jwtService = carbonServer.osgiService( + 'org.wso2.carbon.identity.jwt.client.extension.service.JWTClientManagerService'); + var jwtClient = jwtService.getJWTClient(); + var encodedClientKeys = session.get(constants["ENCODED_TENANT_BASED_WEB_SOCKET_CLIENT_CREDENTIALS"]); + var token = ""; + var user = userModule.getCarbonUser(); + var tenantDomain = user.domain; + if (encodedClientKeys) { + var tokenUtil = require("/app/modules/oauth/token-handler-utils.js")["utils"]; + var resp = tokenUtil.decode(encodedClientKeys).split(":"); + var tokenPair = jwtClient.getAccessToken(resp[0], resp[1], context.user.username,"default", {}); + if (tokenPair) { + token = tokenPair.accessToken; + } + if (tenantDomain == "carbon.super") { + websocketEndpoint = websocketEndpoint + "/secured-websocket/" + tenantDomain + "." + device.type + "/1.0.0?" + + "deviceId=" + device.deviceIdentifier + "&deviceType=" + device.type + "&websocketToken=" + token; + } else { + websocketEndpoint = websocketEndpoint + "/t/" + tenantDomain + "/secured-websocket/" + tenantDomain + + "." + device.type + "/1.0.0?" + "deviceId=" + device.deviceIdentifier + "&deviceType=" + + device.type + "&websocketToken=" + token; + } + + } + + return {"device": device, "websocketEndpoint": websocketEndpoint, "attributes": attributes}; +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/analytics-view.json b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/analytics-view.json new file mode 100644 index 0000000000..688e939808 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/analytics-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/public/js/device-stats.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/public/js/device-stats.js new file mode 100644 index 0000000000..6eddd09ff6 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/public/js/device-stats.js @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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. + */ + +var ws; +var attributes = null; +$(window).load(function () { + var div = $("#div-chart"); + var websocketUrl = div.data("websocketurl"); + attributes = div.data("attributes").split(","); + connect(websocketUrl) +}); + +$(window).unload(function () { + disconnect(); +}); + +//websocket connection +function connect(target) { + if ('WebSocket' in window) { + ws = new WebSocket(target); + } else if ('MozWebSocket' in window) { + ws = new MozWebSocket(target); + } else { + console.log('WebSocket is not supported by this browser.'); + } + if (ws) { + ws.onmessage = function (webSocketData) { + var data = JSON.parse(webSocketData.data); + var payloadData = data["event"]["payloadData"]; + for (var i = 0; i < attributes.length; i++){ + $("#" + attributes[i] +"-value").text(payloadData[attributes[i]]); + } + }; + } +} + +function disconnect() { + if (ws != null) { + ws.close(); + ws = null; + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/public/js/moment.min.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/public/js/moment.min.js new file mode 100644 index 0000000000..d0b48f73e9 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/public/js/moment.min.js @@ -0,0 +1,7 @@ +//! moment.js +//! version : 2.10.2 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Ac.apply(null,arguments)}function b(a){Ac=a}function c(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function d(a){return"[object Array]"===Object.prototype.toString.call(a)}function e(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function f(a,b){var c,d=[];for(c=0;c0)for(c in Cc)d=Cc[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function m(b){l(this,b),this._d=new Date(+b._d),Dc===!1&&(Dc=!0,a.updateOffset(this),Dc=!1)}function n(a){return a instanceof m||null!=a&&g(a,"_isAMomentObject")}function o(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function p(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&o(a[d])!==o(b[d]))&&g++;return g+f}function q(){}function r(a){return a?a.toLowerCase().replace("_","-"):a}function s(a){for(var b,c,d,e,f=0;f0;){if(d=t(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&p(e,c,!0)>=b-1)break;b--}f++}return null}function t(a){var b=null;if(!Ec[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Bc._abbr,require("./locale/"+a),u(b)}catch(c){}return Ec[a]}function u(a,b){var c;return a&&(c="undefined"==typeof b?w(a):v(a,b),c&&(Bc=c)),Bc._abbr}function v(a,b){return null!==b?(b.abbr=a,Ec[a]||(Ec[a]=new q),Ec[a].set(b),u(a),Ec[a]):(delete Ec[a],null)}function w(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Bc;if(!d(a)){if(b=t(a))return b;a=[a]}return s(a)}function x(a,b){var c=a.toLowerCase();Fc[c]=Fc[c+"s"]=Fc[b]=a}function y(a){return"string"==typeof a?Fc[a]||Fc[a.toLowerCase()]:void 0}function z(a){var b,c,d={};for(c in a)g(a,c)&&(b=y(c),b&&(d[b]=a[c]));return d}function A(b,c){return function(d){return null!=d?(C(this,b,d),a.updateOffset(this,c),this):B(this,b)}}function B(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function C(a,b,c){return a._d["set"+(a._isUTC?"UTC":"")+b](c)}function D(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=y(a),"function"==typeof this[a])return this[a](b);return this}function E(a,b,c){for(var d=""+Math.abs(a),e=a>=0;d.lengthb;b++)d[b]=Jc[d[b]]?Jc[d[b]]:G(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function I(a,b){return a.isValid()?(b=J(b,a.localeData()),Ic[b]||(Ic[b]=H(b)),Ic[b](a)):a.localeData().invalidDate()}function J(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Hc.lastIndex=0;d>=0&&Hc.test(a);)a=a.replace(Hc,c),Hc.lastIndex=0,d-=1;return a}function K(a,b,c){Yc[a]="function"==typeof b?b:function(a){return a&&c?c:b}}function L(a,b){return g(Yc,a)?Yc[a](b._strict,b._locale):new RegExp(M(a))}function M(a){return a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function N(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=o(a)}),c=0;cd;d++){if(e=i([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function U(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),Q(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function V(b){return null!=b?(U(this,b),a.updateOffset(this,!0),this):B(this,"Month")}function W(){return Q(this.year(),this.month())}function X(a){var b,c=a._a;return c&&-2===a._pf.overflow&&(b=c[_c]<0||c[_c]>11?_c:c[ad]<1||c[ad]>Q(c[$c],c[_c])?ad:c[bd]<0||c[bd]>24||24===c[bd]&&(0!==c[cd]||0!==c[dd]||0!==c[ed])?bd:c[cd]<0||c[cd]>59?cd:c[dd]<0||c[dd]>59?dd:c[ed]<0||c[ed]>999?ed:-1,a._pf._overflowDayOfYear&&($c>b||b>ad)&&(b=ad),a._pf.overflow=b),a}function Y(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function Z(a,b){var c=!0;return h(function(){return c&&(Y(a),c=!1),b.apply(this,arguments)},b)}function $(a,b){hd[a]||(Y(b),hd[a]=!0)}function _(a){var b,c,d=a._i,e=id.exec(d);if(e){for(a._pf.iso=!0,b=0,c=jd.length;c>b;b++)if(jd[b][1].exec(d)){a._f=jd[b][0]+(e[6]||" ");break}for(b=0,c=kd.length;c>b;b++)if(kd[b][1].exec(d)){a._f+=kd[b][0];break}d.match(Vc)&&(a._f+="Z"),sa(a)}else a._isValid=!1}function aa(b){var c=ld.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(_(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function ba(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function ca(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function da(a){return ea(a)?366:365}function ea(a){return a%4===0&&a%100!==0||a%400===0}function fa(){return ea(this.year())}function ga(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=za(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function ha(a){return ga(a,this._week.dow,this._week.doy).week}function ia(){return this._week.dow}function ja(){return this._week.doy}function ka(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function la(a){var b=ga(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function ma(a,b,c,d,e){var f,g,h=ca(a,0,1).getUTCDay();return h=0===h?7:h,c=null!=c?c:e,f=e-h+(h>d?7:0)-(e>h?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:da(a-1)+g}}function na(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function oa(a,b,c){return null!=a?a:null!=b?b:c}function pa(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function qa(a){var b,c,d,e,f=[];if(!a._d){for(d=pa(a),a._w&&null==a._a[ad]&&null==a._a[_c]&&ra(a),a._dayOfYear&&(e=oa(a._a[$c],d[$c]),a._dayOfYear>da(e)&&(a._pf._overflowDayOfYear=!0),c=ca(e,0,a._dayOfYear),a._a[_c]=c.getUTCMonth(),a._a[ad]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[bd]&&0===a._a[cd]&&0===a._a[dd]&&0===a._a[ed]&&(a._nextDay=!0,a._a[bd]=0),a._d=(a._useUTC?ca:ba).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[bd]=24)}}function ra(a){var b,c,d,e,f,g,h;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=oa(b.GG,a._a[$c],ga(za(),1,4).year),d=oa(b.W,1),e=oa(b.E,1)):(f=a._locale._week.dow,g=a._locale._week.doy,c=oa(b.gg,a._a[$c],ga(za(),f,g).year),d=oa(b.w,1),null!=b.d?(e=b.d,f>e&&++d):e=null!=b.e?b.e+f:f),h=ma(c,d,e,g,f),a._a[$c]=h.year,a._dayOfYear=h.dayOfYear}function sa(b){if(b._f===a.ISO_8601)return void _(b);b._a=[],b._pf.empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=J(b._f,b._locale).match(Gc)||[],c=0;c0&&b._pf.unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length),Jc[f]?(d?b._pf.empty=!1:b._pf.unusedTokens.push(f),P(f,d,b)):b._strict&&!d&&b._pf.unusedTokens.push(f);b._pf.charsLeftOver=i-j,h.length>0&&b._pf.unusedInput.push(h),b._pf.bigHour===!0&&b._a[bd]<=12&&(b._pf.bigHour=void 0),b._a[bd]=ta(b._locale,b._a[bd],b._meridiem),qa(b),X(b)}function ta(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function ua(a){var b,d,e,f,g;if(0===a._f.length)return a._pf.invalidFormat=!0,void(a._d=new Date(0/0));for(f=0;fg)&&(e=g,d=b));h(a,d||b)}function va(a){if(!a._d){var b=z(a._i);a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],qa(a)}}function wa(a){var b,c=a._i,e=a._f;return a._locale=a._locale||w(a._l),null===c||void 0===e&&""===c?k({nullInput:!0}):("string"==typeof c&&(a._i=c=a._locale.preparse(c)),n(c)?new m(X(c)):(d(e)?ua(a):e?sa(a):xa(a),b=new m(X(a)),b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b))}function xa(b){var c=b._i;void 0===c?b._d=new Date:e(c)?b._d=new Date(+c):"string"==typeof c?aa(b):d(c)?(b._a=f(c.slice(0),function(a){return parseInt(a,10)}),qa(b)):"object"==typeof c?va(b):"number"==typeof c?b._d=new Date(c):a.createFromInputFallback(b)}function ya(a,b,d,e,f){var g={};return"boolean"==typeof d&&(e=d,d=void 0),g._isAMomentObject=!0,g._useUTC=g._isUTC=f,g._l=d,g._i=a,g._f=b,g._strict=e,g._pf=c(),wa(g)}function za(a,b,c,d){return ya(a,b,c,d,!1)}function Aa(a,b){var c,e;if(1===b.length&&d(b[0])&&(b=b[0]),!b.length)return za();for(c=b[0],e=1;ea&&(a=-a,c="-"),c+E(~~(a/60),2)+b+E(~~a%60,2)})}function Ga(a){var b=(a||"").match(Vc)||[],c=b[b.length-1]||[],d=(c+"").match(qd)||["-",0,0],e=+(60*d[1])+o(d[2]);return"+"===d[0]?e:-e}function Ha(b,c){var d,f;return c._isUTC?(d=c.clone(),f=(n(b)||e(b)?+b:+za(b))-+d,d._d.setTime(+d._d+f),a.updateOffset(d,!1),d):za(b).local();return c._isUTC?za(b).zone(c._offset||0):za(b).local()}function Ia(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Ja(b,c){var d,e=this._offset||0;return null!=b?("string"==typeof b&&(b=Ga(b)),Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ia(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?Za(this,Ua(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ia(this)}function Ka(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function La(a){return this.utcOffset(0,a)}function Ma(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ia(this),"m")),this}function Na(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ga(this._i)),this}function Oa(a){return a=a?za(a).utcOffset():0,(this.utcOffset()-a)%60===0}function Pa(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Qa(){if(this._a){var a=this._isUTC?i(this._a):za(this._a);return this.isValid()&&p(this._a,a.toArray())>0}return!1}function Ra(){return!this._isUTC}function Sa(){return this._isUTC}function Ta(){return this._isUTC&&0===this._offset}function Ua(a,b){var c,d,e,f=a,h=null;return Ea(a)?f={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(f={},b?f[b]=a:f.milliseconds=a):(h=rd.exec(a))?(c="-"===h[1]?-1:1,f={y:0,d:o(h[ad])*c,h:o(h[bd])*c,m:o(h[cd])*c,s:o(h[dd])*c,ms:o(h[ed])*c}):(h=sd.exec(a))?(c="-"===h[1]?-1:1,f={y:Va(h[2],c),M:Va(h[3],c),d:Va(h[4],c),h:Va(h[5],c),m:Va(h[6],c),s:Va(h[7],c),w:Va(h[8],c)}):null==f?f={}:"object"==typeof f&&("from"in f||"to"in f)&&(e=Xa(za(f.from),za(f.to)),f={},f.ms=e.milliseconds,f.M=e.months),d=new Da(f),Ea(a)&&g(a,"_locale")&&(d._locale=a._locale),d}function Va(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function Wa(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Xa(a,b){var c;return b=Ha(b,a),a.isBefore(b)?c=Wa(a,b):(c=Wa(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function Ya(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||($(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Ua(c,d),Za(this,e,a),this}}function Za(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&C(b,"Date",B(b,"Date")+g*d),h&&U(b,B(b,"Month")+h*d),e&&a.updateOffset(b,g||h)}function $a(a){var b=a||za(),c=Ha(b,this).startOf("day"),d=this.diff(c,"days",!0),e=-6>d?"sameElse":-1>d?"lastWeek":0>d?"lastDay":1>d?"sameDay":2>d?"nextDay":7>d?"nextWeek":"sameElse";return this.format(this.localeData().calendar(e,this,za(b)))}function _a(){return new m(this)}function ab(a,b){var c;return b=y("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=n(a)?a:za(a),+this>+a):(c=n(a)?+a:+za(a),c<+this.clone().startOf(b))}function bb(a,b){var c;return b=y("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=n(a)?a:za(a),+a>+this):(c=n(a)?+a:+za(a),+this.clone().endOf(b)a?Math.ceil(a):Math.floor(a)}function fb(a,b,c){var d,e,f=Ha(a,this),g=6e4*(f.utcOffset()-this.utcOffset());return b=y(b),"year"===b||"month"===b||"quarter"===b?(e=gb(this,f),"quarter"===b?e/=3:"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:eb(e)}function gb(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function hb(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function ib(){var a=this.clone().utc();return 0b;b++)if(this._weekdaysParse[b]||(c=za([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b}function Jb(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Eb(a,this.localeData()),this.add(a-b,"d")):b}function Kb(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Lb(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)}function Mb(a,b){F(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Nb(a,b){return b._meridiemParse}function Ob(a){return"p"===(a+"").toLowerCase().charAt(0)}function Pb(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Qb(a){F(0,[a,3],0,"millisecond")}function Rb(){return this._isUTC?"UTC":""}function Sb(){return this._isUTC?"Coordinated Universal Time":""}function Tb(a){return za(1e3*a)}function Ub(){return za.apply(null,arguments).parseZone()}function Vb(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.call(b,c):d}function Wb(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b}function Xb(){return this._invalidDate}function Yb(a){return this._ordinal.replace("%d",a)}function Zb(a){return a}function $b(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)}function _b(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)}function ac(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function bc(a,b,c,d){var e=w(),f=i().set(d,b);return e[c](f,a)}function cc(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return bc(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=bc(a,f,c,e);return g}function dc(a,b){return cc(a,b,"months",12,"month")}function ec(a,b){return cc(a,b,"monthsShort",12,"month")}function fc(a,b){return cc(a,b,"weekdays",7,"day")}function gc(a,b){return cc(a,b,"weekdaysShort",7,"day")}function hc(a,b){return cc(a,b,"weekdaysMin",7,"day")}function ic(){var a=this._data;return this._milliseconds=Od(this._milliseconds),this._days=Od(this._days),this._months=Od(this._months),a.milliseconds=Od(a.milliseconds),a.seconds=Od(a.seconds),a.minutes=Od(a.minutes),a.hours=Od(a.hours),a.months=Od(a.months),a.years=Od(a.years),this}function jc(a,b,c,d){var e=Ua(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function kc(a,b){return jc(this,a,b,1)}function lc(a,b){return jc(this,a,b,-1)}function mc(){var a,b,c,d=this._milliseconds,e=this._days,f=this._months,g=this._data,h=0;return g.milliseconds=d%1e3,a=eb(d/1e3),g.seconds=a%60,b=eb(a/60),g.minutes=b%60,c=eb(b/60),g.hours=c%24,e+=eb(c/24),h=eb(nc(e)),e-=eb(oc(h)),f+=eb(e/30),e%=30,h+=eb(f/12),f%=12,g.days=e,g.months=f,g.years=h,this}function nc(a){return 400*a/146097}function oc(a){return 146097*a/400}function pc(a){var b,c,d=this._milliseconds;if(a=y(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+12*nc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(oc(this._months/12)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 24*b*60+d/6e4;case"second":return 24*b*60*60+d/1e3;case"millisecond":return Math.floor(24*b*60*60*1e3)+d;default:throw new Error("Unknown unit "+a)}}function qc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*o(this._months/12)}function rc(a){return function(){return this.as(a)}}function sc(a){return a=y(a),this[a+"s"]()}function tc(a){return function(){return this._data[a]}}function uc(){return eb(this.days()/7)}function vc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function wc(a,b,c){var d=Ua(a).abs(),e=ce(d.as("s")),f=ce(d.as("m")),g=ce(d.as("h")),h=ce(d.as("d")),i=ce(d.as("M")),j=ce(d.as("y")),k=e0,k[4]=c,vc.apply(null,k)}function xc(a,b){return void 0===de[a]?!1:void 0===b?de[a]:(de[a]=b,!0)}function yc(a){var b=this.localeData(),c=wc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function zc(){var a=ee(this.years()),b=ee(this.months()),c=ee(this.days()),d=ee(this.hours()),e=ee(this.minutes()),f=ee(this.seconds()+this.milliseconds()/1e3),g=this.asSeconds();return g?(0>g?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"}var Ac,Bc,Cc=a.momentProperties=[],Dc=!1,Ec={},Fc={},Gc=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g,Hc=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Ic={},Jc={},Kc=/\d/,Lc=/\d\d/,Mc=/\d{3}/,Nc=/\d{4}/,Oc=/[+-]?\d{6}/,Pc=/\d\d?/,Qc=/\d{1,3}/,Rc=/\d{1,4}/,Sc=/[+-]?\d{1,6}/,Tc=/\d+/,Uc=/[+-]?\d+/,Vc=/Z|[+-]\d\d:?\d\d/gi,Wc=/[+-]?\d+(\.\d{1,3})?/,Xc=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Yc={},Zc={},$c=0,_c=1,ad=2,bd=3,cd=4,dd=5,ed=6;F("M",["MM",2],"Mo",function(){return this.month()+1}),F("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),F("MMMM",0,0,function(a){return this.localeData().months(this,a)}),x("month","M"),K("M",Pc),K("MM",Pc,Lc),K("MMM",Xc),K("MMMM",Xc),N(["M","MM"],function(a,b){b[_c]=o(a)-1}),N(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[_c]=e:c._pf.invalidMonth=a});var fd="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),gd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),hd={};a.suppressDeprecationWarnings=!1;var id=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,jd=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],kd=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],ld=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=Z("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),F(0,["YY",2],0,function(){return this.year()%100}),F(0,["YYYY",4],0,"year"),F(0,["YYYYY",5],0,"year"),F(0,["YYYYYY",6,!0],0,"year"),x("year","y"),K("Y",Uc),K("YY",Pc,Lc),K("YYYY",Rc,Nc),K("YYYYY",Sc,Oc),K("YYYYYY",Sc,Oc),N(["YYYY","YYYYY","YYYYYY"],$c),N("YY",function(b,c){c[$c]=a.parseTwoDigitYear(b)}),a.parseTwoDigitYear=function(a){return o(a)+(o(a)>68?1900:2e3)};var md=A("FullYear",!1);F("w",["ww",2],"wo","week"),F("W",["WW",2],"Wo","isoWeek"),x("week","w"),x("isoWeek","W"),K("w",Pc),K("ww",Pc,Lc),K("W",Pc),K("WW",Pc,Lc),O(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=o(a)});var nd={dow:0,doy:6};F("DDD",["DDDD",3],"DDDo","dayOfYear"),x("dayOfYear","DDD"),K("DDD",Qc),K("DDDD",Mc),N(["DDD","DDDD"],function(a,b,c){c._dayOfYear=o(a)}),a.ISO_8601=function(){};var od=Z("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=za.apply(null,arguments);return this>a?this:a}),pd=Z("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=za.apply(null,arguments);return a>this?this:a});Fa("Z",":"),Fa("ZZ",""),K("Z",Vc),K("ZZ",Vc),N(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ga(a)});var qd=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var rd=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,sd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Ua.fn=Da.prototype;var td=Ya(1,"add"),ud=Ya(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var vd=Z("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});F(0,["gg",2],0,function(){return this.weekYear()%100}),F(0,["GG",2],0,function(){return this.isoWeekYear()%100}),xb("gggg","weekYear"),xb("ggggg","weekYear"),xb("GGGG","isoWeekYear"),xb("GGGGG","isoWeekYear"),x("weekYear","gg"),x("isoWeekYear","GG"),K("G",Uc),K("g",Uc),K("GG",Pc,Lc),K("gg",Pc,Lc),K("GGGG",Rc,Nc),K("gggg",Rc,Nc),K("GGGGG",Sc,Oc),K("ggggg",Sc,Oc),O(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=o(a)}),O(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),F("Q",0,0,"quarter"),x("quarter","Q"),K("Q",Kc),N("Q",function(a,b){b[_c]=3*(o(a)-1)}),F("D",["DD",2],"Do","date"),x("date","D"),K("D",Pc),K("DD",Pc,Lc),K("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),N(["D","DD"],ad),N("Do",function(a,b){b[ad]=o(a.match(Pc)[0],10)});var wd=A("Date",!0);F("d",0,"do","day"),F("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),F("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),F("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),F("e",0,0,"weekday"),F("E",0,0,"isoWeekday"),x("day","d"),x("weekday","e"),x("isoWeekday","E"),K("d",Pc),K("e",Pc),K("E",Pc),K("dd",Xc),K("ddd",Xc),K("dddd",Xc),O(["dd","ddd","dddd"],function(a,b,c){var d=c._locale.weekdaysParse(a);null!=d?b.d=d:c._pf.invalidWeekday=a}),O(["d","e","E"],function(a,b,c,d){b[d]=o(a)});var xd="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),yd="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),zd="Su_Mo_Tu_We_Th_Fr_Sa".split("_");F("H",["HH",2],0,"hour"),F("h",["hh",2],0,function(){return this.hours()%12||12}),Mb("a",!0),Mb("A",!1),x("hour","h"),K("a",Nb),K("A",Nb),K("H",Pc),K("h",Pc),K("HH",Pc,Lc),K("hh",Pc,Lc),N(["H","HH"],bd),N(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),N(["h","hh"],function(a,b,c){b[bd]=o(a),c._pf.bigHour=!0});var Ad=/[ap]\.?m?\.?/i,Bd=A("Hours",!0);F("m",["mm",2],0,"minute"),x("minute","m"),K("m",Pc),K("mm",Pc,Lc),N(["m","mm"],cd);var Cd=A("Minutes",!1);F("s",["ss",2],0,"second"),x("second","s"),K("s",Pc),K("ss",Pc,Lc),N(["s","ss"],dd);var Dd=A("Seconds",!1);F("S",0,0,function(){return~~(this.millisecond()/100)}),F(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),Qb("SSS"),Qb("SSSS"),x("millisecond","ms"),K("S",Qc,Kc),K("SS",Qc,Lc),K("SSS",Qc,Mc),K("SSSS",Tc),N(["S","SS","SSS","SSSS"],function(a,b){b[ed]=o(1e3*("0."+a))});var Ed=A("Milliseconds",!1);F("z",0,0,"zoneAbbr"),F("zz",0,0,"zoneName");var Fd=m.prototype;Fd.add=td,Fd.calendar=$a,Fd.clone=_a,Fd.diff=fb,Fd.endOf=pb,Fd.format=jb,Fd.from=kb,Fd.fromNow=lb,Fd.get=D,Fd.invalidAt=wb,Fd.isAfter=ab,Fd.isBefore=bb,Fd.isBetween=cb,Fd.isSame=db,Fd.isValid=ub,Fd.lang=vd,Fd.locale=mb,Fd.localeData=nb,Fd.max=pd,Fd.min=od,Fd.parsingFlags=vb,Fd.set=D,Fd.startOf=ob,Fd.subtract=ud,Fd.toArray=tb,Fd.toDate=sb,Fd.toISOString=ib,Fd.toJSON=ib,Fd.toString=hb,Fd.unix=rb,Fd.valueOf=qb,Fd.year=md,Fd.isLeapYear=fa,Fd.weekYear=zb,Fd.isoWeekYear=Ab,Fd.quarter=Fd.quarters=Db,Fd.month=V,Fd.daysInMonth=W,Fd.week=Fd.weeks=ka,Fd.isoWeek=Fd.isoWeeks=la,Fd.weeksInYear=Cb,Fd.isoWeeksInYear=Bb,Fd.date=wd,Fd.day=Fd.days=Jb,Fd.weekday=Kb,Fd.isoWeekday=Lb,Fd.dayOfYear=na,Fd.hour=Fd.hours=Bd,Fd.minute=Fd.minutes=Cd,Fd.second=Fd.seconds=Dd,Fd.millisecond=Fd.milliseconds=Ed,Fd.utcOffset=Ja,Fd.utc=La,Fd.local=Ma,Fd.parseZone=Na,Fd.hasAlignedHourOffset=Oa,Fd.isDST=Pa,Fd.isDSTShifted=Qa,Fd.isLocal=Ra,Fd.isUtcOffset=Sa,Fd.isUtc=Ta,Fd.isUTC=Ta,Fd.zoneAbbr=Rb,Fd.zoneName=Sb,Fd.dates=Z("dates accessor is deprecated. Use date instead.",wd),Fd.months=Z("months accessor is deprecated. Use month instead",V),Fd.years=Z("years accessor is deprecated. Use year instead",md),Fd.zone=Z("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Ka);var Gd=Fd,Hd={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Id={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY LT",LLLL:"dddd, MMMM D, YYYY LT"},Jd="Invalid date",Kd="%d",Ld=/\d{1,2}/,Md={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Nd=q.prototype;Nd._calendar=Hd,Nd.calendar=Vb,Nd._longDateFormat=Id,Nd.longDateFormat=Wb,Nd._invalidDate=Jd,Nd.invalidDate=Xb,Nd._ordinal=Kd,Nd.ordinal=Yb,Nd._ordinalParse=Ld, +Nd.preparse=Zb,Nd.postformat=Zb,Nd._relativeTime=Md,Nd.relativeTime=$b,Nd.pastFuture=_b,Nd.set=ac,Nd.months=R,Nd._months=fd,Nd.monthsShort=S,Nd._monthsShort=gd,Nd.monthsParse=T,Nd.week=ha,Nd._week=nd,Nd.firstDayOfYear=ja,Nd.firstDayOfWeek=ia,Nd.weekdays=Fb,Nd._weekdays=xd,Nd.weekdaysMin=Hb,Nd._weekdaysMin=zd,Nd.weekdaysShort=Gb,Nd._weekdaysShort=yd,Nd.weekdaysParse=Ib,Nd.isPM=Ob,Nd._meridiemParse=Ad,Nd.meridiem=Pb,u("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===o(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=Z("moment.lang is deprecated. Use moment.locale instead.",u),a.langData=Z("moment.langData is deprecated. Use moment.localeData instead.",w);var Od=Math.abs,Pd=rc("ms"),Qd=rc("s"),Rd=rc("m"),Sd=rc("h"),Td=rc("d"),Ud=rc("w"),Vd=rc("M"),Wd=rc("y"),Xd=tc("milliseconds"),Yd=tc("seconds"),Zd=tc("minutes"),$d=tc("hours"),_d=tc("days"),ae=tc("months"),be=tc("years"),ce=Math.round,de={s:45,m:45,h:22,d:26,M:11},ee=Math.abs,fe=Da.prototype;fe.abs=ic,fe.add=kc,fe.subtract=lc,fe.as=pc,fe.asMilliseconds=Pd,fe.asSeconds=Qd,fe.asMinutes=Rd,fe.asHours=Sd,fe.asDays=Td,fe.asWeeks=Ud,fe.asMonths=Vd,fe.asYears=Wd,fe.valueOf=qc,fe._bubble=mc,fe.get=sc,fe.milliseconds=Xd,fe.seconds=Yd,fe.minutes=Zd,fe.hours=$d,fe.days=_d,fe.weeks=uc,fe.months=ae,fe.years=be,fe.humanize=yc,fe.toISOString=zc,fe.toString=zc,fe.toJSON=zc,fe.locale=mb,fe.localeData=nb,fe.toIsoString=Z("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",zc),fe.lang=vd,F("X",0,0,"unix"),F("x",0,0,"valueOf"),K("x",Uc),K("X",Wc),N("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),N("x",function(a,b,c){c._d=new Date(o(a))}),a.version="2.10.2",b(za),a.fn=Gd,a.min=Ba,a.max=Ca,a.utc=i,a.unix=Tb,a.months=dc,a.isDate=e,a.locale=u,a.invalid=k,a.duration=Ua,a.isMoment=n,a.weekdays=fc,a.parseZone=Ub,a.localeData=w,a.isDuration=Ea,a.monthsShort=ec,a.weekdaysMin=hc,a.defineLocale=v,a.weekdaysShort=gc,a.normalizeUnits=y,a.relativeTimeThreshold=xc;var ge=a;return ge}); \ No newline at end of file diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/public/js/socket.io.min.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/public/js/socket.io.min.js new file mode 100644 index 0000000000..7e870c9864 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.default.device.type.realtime.analytics-view/public/js/socket.io.min.js @@ -0,0 +1,2 @@ +/*! Socket.IO.min.js build:0.9.16, production. Copyright(c) 2011 LearnBoost MIT Licensed */ +var io="undefined"==typeof module?{}:module.exports;(function(){(function(a,b){var c=a;c.version="0.9.16",c.protocol=1,c.transports=[],c.j=[],c.sockets={},c.connect=function(a,d){var e=c.util.parseUri(a),f,g;b&&b.location&&(e.protocol=e.protocol||b.location.protocol.slice(0,-1),e.host=e.host||(b.document?b.document.domain:b.location.hostname),e.port=e.port||b.location.port),f=c.util.uniqueUri(e);var h={host:e.host,secure:"https"==e.protocol,port:e.port||("https"==e.protocol?443:80),query:e.query||""};c.util.merge(h,d);if(h["force new connection"]||!c.sockets[f])g=new c.Socket(h);return!h["force new connection"]&&g&&(c.sockets[f]=g),g=g||c.sockets[f],g.of(e.path.length>1?e.path:"")}})("object"==typeof module?module.exports:this.io={},this),function(a,b){var c=a.util={},d=/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,e=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];c.parseUri=function(a){var b=d.exec(a||""),c={},f=14;while(f--)c[e[f]]=b[f]||"";return c},c.uniqueUri=function(a){var c=a.protocol,d=a.host,e=a.port;return"document"in b?(d=d||document.domain,e=e||(c=="https"&&document.location.protocol!=="https:"?443:document.location.port)):(d=d||"localhost",!e&&c=="https"&&(e=443)),(c||"http")+"://"+d+":"+(e||80)},c.query=function(a,b){var d=c.chunkQuery(a||""),e=[];c.merge(d,c.chunkQuery(b||""));for(var f in d)d.hasOwnProperty(f)&&e.push(f+"="+d[f]);return e.length?"?"+e.join("&"):""},c.chunkQuery=function(a){var b={},c=a.split("&"),d=0,e=c.length,f;for(;db.length?a:b,f=a.length>b.length?b:a;for(var g=0,h=f.length;g0&&a.splice(0,1)[0]!=c.transport.name);a.length?h(a):c.publish("connect_failed")}}},c.options["connect timeout"]))})}c.sessionid=d,c.closeTimeout=f*1e3,c.heartbeatTimeout=e*1e3,c.transports||(c.transports=c.origTransports=g?b.util.intersect(g.split(","),c.options.transports):c.options.transports),c.setHeartbeatTimeout(),h(c.transports),c.once("connect",function(){clearTimeout(c.connectTimeoutTimer),a&&typeof a=="function"&&a()})}),this},d.prototype.setHeartbeatTimeout=function(){clearTimeout(this.heartbeatTimeoutTimer);if(this.transport&&!this.transport.heartbeats())return;var a=this;this.heartbeatTimeoutTimer=setTimeout(function(){a.transport.onClose()},this.heartbeatTimeout)},d.prototype.packet=function(a){return this.connected&&!this.doBuffer?this.transport.packet(a):this.buffer.push(a),this},d.prototype.setBuffer=function(a){this.doBuffer=a,!a&&this.connected&&this.buffer.length&&(this.options.manualFlush||this.flushBuffer())},d.prototype.flushBuffer=function(){this.transport.payload(this.buffer),this.buffer=[]},d.prototype.disconnect=function(){if(this.connected||this.connecting)this.open&&this.of("").packet({type:"disconnect"}),this.onDisconnect("booted");return this},d.prototype.disconnectSync=function(){var a=b.util.request(),c=["http"+(this.options.secure?"s":"")+":/",this.options.host+":"+this.options.port,this.options.resource,b.protocol,"",this.sessionid].join("/")+"/?disconnect=1";a.open("GET",c,!1),a.send(null),this.onDisconnect("booted")},d.prototype.isXDomain=function(){var a=c.location.port||("https:"==c.location.protocol?443:80);return this.options.host!==c.location.hostname||this.options.port!=a},d.prototype.onConnect=function(){this.connected||(this.connected=!0,this.connecting=!1,this.doBuffer||this.setBuffer(!1),this.emit("connect"))},d.prototype.onOpen=function(){this.open=!0},d.prototype.onClose=function(){this.open=!1,clearTimeout(this.heartbeatTimeoutTimer)},d.prototype.onPacket=function(a){this.of(a.endpoint).onPacket(a)},d.prototype.onError=function(a){a&&a.advice&&a.advice==="reconnect"&&(this.connected||this.connecting)&&(this.disconnect(),this.options.reconnect&&this.reconnect()),this.publish("error",a&&a.reason?a.reason:a)},d.prototype.onDisconnect=function(a){var b=this.connected,c=this.connecting;this.connected=!1,this.connecting=!1,this.open=!1;if(b||c)this.transport.close(),this.transport.clearTimeouts(),b&&(this.publish("disconnect",a),"booted"!=a&&this.options.reconnect&&!this.reconnecting&&this.reconnect())},d.prototype.reconnect=function(){function e(){if(a.connected){for(var b in a.namespaces)a.namespaces.hasOwnProperty(b)&&""!==b&&a.namespaces[b].packet({type:"connect"});a.publish("reconnect",a.transport.name,a.reconnectionAttempts)}clearTimeout(a.reconnectionTimer),a.removeListener("connect_failed",f),a.removeListener("connect",f),a.reconnecting=!1,delete a.reconnectionAttempts,delete a.reconnectionDelay,delete a.reconnectionTimer,delete a.redoTransports,a.options["try multiple transports"]=c}function f(){if(!a.reconnecting)return;if(a.connected)return e();if(a.connecting&&a.reconnecting)return a.reconnectionTimer=setTimeout(f,1e3);a.reconnectionAttempts++>=b?a.redoTransports?(a.publish("reconnect_failed"),e()):(a.on("connect_failed",f),a.options["try multiple transports"]=!0,a.transports=a.origTransports,a.transport=a.getTransport(),a.redoTransports=!0,a.connect()):(a.reconnectionDelay=10:!1},c.xdomainCheck=function(){return!0},typeof window!="undefined"&&(WEB_SOCKET_DISABLE_AUTO_INITIALIZATION=!0),b.transports.push("flashsocket")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports);if("undefined"!=typeof window)var swfobject=function(){function A(){if(t)return;try{var a=i.getElementsByTagName("body")[0].appendChild(Q("span"));a.parentNode.removeChild(a)}catch(b){return}t=!0;var c=l.length;for(var d=0;d0)for(var c=0;c0){var g=P(d);if(g)if(S(m[c].swfVersion)&&!(y.wk&&y.wk<312))U(d,!0),e&&(f.success=!0,f.ref=G(d),e(f));else if(m[c].expressInstall&&H()){var h={};h.data=m[c].expressInstall,h.width=g.getAttribute("width")||"0",h.height=g.getAttribute("height")||"0",g.getAttribute("class")&&(h.styleclass=g.getAttribute("class")),g.getAttribute("align")&&(h.align=g.getAttribute("align"));var i={},j=g.getElementsByTagName("param"),k=j.length;for(var l=0;l');h.outerHTML='"+k+"",n[n.length]=c.id,g=P(c.id)}else{var m=Q(b);m.setAttribute("type",e);for(var o in c)c[o]!=Object.prototype[o]&&(o.toLowerCase()=="styleclass"?m.setAttribute("class",c[o]):o.toLowerCase()!="classid"&&m.setAttribute(o,c[o]));for(var p in d)d[p]!=Object.prototype[p]&&p.toLowerCase()!="movie"&&M(m,p,d[p]);h.parentNode.replaceChild(m,h),g=m}}return g}function M(a,b,c){var d=Q("param");d.setAttribute("name",b),d.setAttribute("value",c),a.appendChild(d)}function N(a){var b=P(a);b&&b.nodeName=="OBJECT"&&(y.ie&&y.win?(b.style.display="none",function(){b.readyState==4?O(a):setTimeout(arguments.callee,10)}()):b.parentNode.removeChild(b))}function O(a){var b=P(a);if(b){for(var c in b)typeof b[c]=="function"&&(b[c]=null);b.parentNode.removeChild(b)}}function P(a){var b=null;try{b=i.getElementById(a)}catch(c){}return b}function Q(a){return i.createElement(a)}function R(a,b,c){a.attachEvent(b,c),o[o.length]=[a,b,c]}function S(a){var b=y.pv,c=a.split(".");return c[0]=parseInt(c[0],10),c[1]=parseInt(c[1],10)||0,c[2]=parseInt(c[2],10)||0,b[0]>c[0]||b[0]==c[0]&&b[1]>c[1]||b[0]==c[0]&&b[1]==c[1]&&b[2]>=c[2]?!0:!1}function T(c,d,e,f){if(y.ie&&y.mac)return;var g=i.getElementsByTagName("head")[0];if(!g)return;var h=e&&typeof e=="string"?e:"screen";f&&(v=null,w=null);if(!v||w!=h){var j=Q("style");j.setAttribute("type","text/css"),j.setAttribute("media",h),v=g.appendChild(j),y.ie&&y.win&&typeof i.styleSheets!=a&&i.styleSheets.length>0&&(v=i.styleSheets[i.styleSheets.length-1]),w=h}y.ie&&y.win?v&&typeof v.addRule==b&&v.addRule(c,d):v&&typeof i.createTextNode!=a&&v.appendChild(i.createTextNode(c+" {"+d+"}"))}function U(a,b){if(!x)return;var c=b?"visible":"hidden";t&&P(a)?P(a).style.visibility=c:T("#"+a,"visibility:"+c)}function V(b){var c=/[\\\"<>\.;]/,d=c.exec(b)!=null;return d&&typeof encodeURIComponent!=a?encodeURIComponent(b):b}var a="undefined",b="object",c="Shockwave Flash",d="ShockwaveFlash.ShockwaveFlash",e="application/x-shockwave-flash",f="SWFObjectExprInst",g="onreadystatechange",h=window,i=document,j=navigator,k=!1,l=[D],m=[],n=[],o=[],p,q,r,s,t=!1,u=!1,v,w,x=!0,y=function(){var f=typeof i.getElementById!=a&&typeof i.getElementsByTagName!=a&&typeof i.createElement!=a,g=j.userAgent.toLowerCase(),l=j.platform.toLowerCase(),m=l?/win/.test(l):/win/.test(g),n=l?/mac/.test(l):/mac/.test(g),o=/webkit/.test(g)?parseFloat(g.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):!1,p=!1,q=[0,0,0],r=null;if(typeof j.plugins!=a&&typeof j.plugins[c]==b)r=j.plugins[c].description,r&&(typeof j.mimeTypes==a||!j.mimeTypes[e]||!!j.mimeTypes[e].enabledPlugin)&&(k=!0,p=!1,r=r.replace(/^.*\s+(\S+\s+\S+$)/,"$1"),q[0]=parseInt(r.replace(/^(.*)\..*$/,"$1"),10),q[1]=parseInt(r.replace(/^.*\.(.*)\s.*$/,"$1"),10),q[2]=/[a-zA-Z]/.test(r)?parseInt(r.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0);else if(typeof h[["Active"].concat("Object").join("X")]!=a)try{var s=new(window[["Active"].concat("Object").join("X")])(d);s&&(r=s.GetVariable("$version"),r&&(p=!0,r=r.split(" ")[1].split(","),q=[parseInt(r[0],10),parseInt(r[1],10),parseInt(r[2],10)]))}catch(t){}return{w3:f,pv:q,wk:o,ie:p,win:m,mac:n}}(),z=function(){if(!y.w3)return;(typeof i.readyState!=a&&i.readyState=="complete"||typeof i.readyState==a&&(i.getElementsByTagName("body")[0]||i.body))&&A(),t||(typeof i.addEventListener!=a&&i.addEventListener("DOMContentLoaded",A,!1),y.ie&&y.win&&(i.attachEvent(g,function(){i.readyState=="complete"&&(i.detachEvent(g,arguments.callee),A())}),h==top&&function(){if(t)return;try{i.documentElement.doScroll("left")}catch(a){setTimeout(arguments.callee,0);return}A()}()),y.wk&&function(){if(t)return;if(!/loaded|complete/.test(i.readyState)){setTimeout(arguments.callee,0);return}A()}(),C(A))}(),W=function(){y.ie&&y.win&&window.attachEvent("onunload",function(){var a=o.length;for(var b=0;b= 10.0.0 is required.");return}location.protocol=="file:"&&a.error("WARNING: web-socket-js doesn't work in file:///... URL unless you set Flash Security Settings properly. Open the page via Web server i.e. http://..."),WebSocket=function(a,b,c,d,e){var f=this;f.__id=WebSocket.__nextId++,WebSocket.__instances[f.__id]=f,f.readyState=WebSocket.CONNECTING,f.bufferedAmount=0,f.__events={},b?typeof b=="string"&&(b=[b]):b=[],setTimeout(function(){WebSocket.__addTask(function(){WebSocket.__flash.create(f.__id,a,b,c||null,d||0,e||null)})},0)},WebSocket.prototype.send=function(a){if(this.readyState==WebSocket.CONNECTING)throw"INVALID_STATE_ERR: Web Socket connection has not been established";var b=WebSocket.__flash.send(this.__id,encodeURIComponent(a));return b<0?!0:(this.bufferedAmount+=b,!1)},WebSocket.prototype.close=function(){if(this.readyState==WebSocket.CLOSED||this.readyState==WebSocket.CLOSING)return;this.readyState=WebSocket.CLOSING,WebSocket.__flash.close(this.__id)},WebSocket.prototype.addEventListener=function(a,b,c){a in this.__events||(this.__events[a]=[]),this.__events[a].push(b)},WebSocket.prototype.removeEventListener=function(a,b,c){if(!(a in this.__events))return;var d=this.__events[a];for(var e=d.length-1;e>=0;--e)if(d[e]===b){d.splice(e,1);break}},WebSocket.prototype.dispatchEvent=function(a){var b=this.__events[a.type]||[];for(var c=0;c"),this.doc.close(),this.doc.parentWindow.s=this;var a=this.doc.createElement("div");a.className="socketio",this.doc.body.appendChild(a),this.iframe=this.doc.createElement("iframe"),a.appendChild(this.iframe);var c=this,d=b.util.query(this.socket.options.query,"t="+ +(new Date));this.iframe.src=this.prepareUrl()+d,b.util.on(window,"unload",function(){c.destroy()})},c.prototype._=function(a,b){a=a.replace(/\\\//g,"/"),this.onData(a);try{var c=b.getElementsByTagName("script")[0];c.parentNode.removeChild(c)}catch(d){}},c.prototype.destroy=function(){if(this.iframe){try{this.iframe.src="about:blank"}catch(a){}this.doc=null,this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,CollectGarbage()}},c.prototype.close=function(){return this.destroy(),b.Transport.XHR.prototype.close.call(this)},c.check=function(a){if(typeof window!="undefined"&&["Active"].concat("Object").join("X")in window)try{var c=new(window[["Active"].concat("Object").join("X")])("htmlfile");return c&&b.Transport.XHR.check(a)}catch(d){}return!1},c.xdomainCheck=function(){return!1},b.transports.push("htmlfile")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b,c){function d(){b.Transport.XHR.apply(this,arguments)}function e(){}a["xhr-polling"]=d,b.util.inherit(d,b.Transport.XHR),b.util.merge(d,b.Transport.XHR),d.prototype.name="xhr-polling",d.prototype.heartbeats=function(){return!1},d.prototype.open=function(){var a=this;return b.Transport.XHR.prototype.open.call(a),!1},d.prototype.get=function(){function b(){this.readyState==4&&(this.onreadystatechange=e,this.status==200?(a.onData(this.responseText),a.get()):a.onClose())}function d(){this.onload=e,this.onerror=e,a.retryCounter=1,a.onData(this.responseText),a.get()}function f(){a.retryCounter++,!a.retryCounter||a.retryCounter>3?a.onClose():a.get()}if(!this.isOpen)return;var a=this;this.xhr=this.request(),c.XDomainRequest&&this.xhr instanceof XDomainRequest?(this.xhr.onload=d,this.xhr.onerror=f):this.xhr.onreadystatechange=b,this.xhr.send(null)},d.prototype.onClose=function(){b.Transport.XHR.prototype.onClose.call(this);if(this.xhr){this.xhr.onreadystatechange=this.xhr.onload=this.xhr.onerror=e;try{this.xhr.abort()}catch(a){}this.xhr=null}},d.prototype.ready=function(a,c){var d=this;b.util.defer(function(){c.call(d)})},b.transports.push("xhr-polling")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b,c){function e(a){b.Transport["xhr-polling"].apply(this,arguments),this.index=b.j.length;var c=this;b.j.push(function(a){c._(a)})}var d=c.document&&"MozAppearance"in c.document.documentElement.style;a["jsonp-polling"]=e,b.util.inherit(e,b.Transport["xhr-polling"]),e.prototype.name="jsonp-polling",e.prototype.post=function(a){function i(){j(),c.socket.setBuffer(!1)}function j(){c.iframe&&c.form.removeChild(c.iframe);try{h=document.createElement('