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 96991ed2ce8..4cf332c0b8f 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 @@ -58,6 +58,7 @@ 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.PropertyMap; 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; @@ -79,6 +80,7 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.List; +import java.util.Map; /** * Device related REST-API. This can be used to manipulated device related details. @@ -1074,6 +1076,84 @@ public interface DeviceManagementService { required = true) SearchContext searchContext); + @POST + @Path("/query-devices") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + consumes = MediaType.APPLICATION_JSON, + httpMethod = "POST", + value = "Property based Search for Devices", + notes = "Search for devices based on properties", + tags = "Device Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:search") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully retrieved the device information.", + response = DeviceList.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 = 304, + message = "Not Modified. \n " + + "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 Acceptable.\n The existing device did not match the values specified in the device search.", + response = ErrorResponse.class), + @ApiResponse( + code = 406, + message = "Not Acceptable.\n The requested media type is not supported"), + @ApiResponse( + code = 415, + message = "Unsupported media type. \n The format of the requested entity was not supported."), + @ApiResponse( + code = 500, + message = "Internal Server Error. \n " + + "Server error occurred while getting the device details.", + response = ErrorResponse.class) + }) + Response queryDevicesByProperties( + @ApiParam( + name = "offset", + value = "The starting pagination index for the complete list of qualified items.", + required = false, + defaultValue = "0") + @QueryParam("offset") + int offset, + @ApiParam( + name = "limit", + value = "Provide how many activity details you require from the starting pagination index/offset.", + required = false, + defaultValue = "5") + @QueryParam("limit") + int limit, + @ApiParam( + name = "device property map", + value = "properties by which devices need filtered", + required = true) + PropertyMap map); + @GET @Path("/{type}/{id}/applications") @ApiOperation( 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 9175580ce93..2e7aeb9e53f 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 @@ -63,6 +63,7 @@ import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementExcept 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.policy.mgt.monitor.PolicyComplianceException; +import org.wso2.carbon.device.mgt.common.search.PropertyMap; import org.wso2.carbon.device.mgt.common.search.SearchContext; import org.wso2.carbon.device.mgt.core.app.mgt.ApplicationManagementProviderService; import org.wso2.carbon.device.mgt.core.device.details.mgt.DeviceDetailsMgtException; @@ -104,6 +105,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Map; @Path("/devices") @Produces(MediaType.APPLICATION_JSON) @@ -652,6 +654,30 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { return Response.status(Response.Status.OK).entity(deviceList).build(); } + @POST + @Path("/query-devices") + @Override + public Response queryDevicesByProperties(@QueryParam("offset") int offset, + @QueryParam("limit") int limit, PropertyMap map) { + List devices; + DeviceList deviceList = new DeviceList(); + try { + DeviceManagementProviderService dms = DeviceMgtAPIUtils.getDeviceManagementService(); + devices = dms.getDevicesBasedOnProperties(map.getProperties()); + if(devices == null || devices.isEmpty()){ + return Response.status(Response.Status.OK).entity("No device found matching query criteria.").build(); + } + } catch (DeviceManagementException e) { + String msg = "Error occurred while searching for devices that matches the provided device properties"; + log.error(msg, e); + return Response.serverError().entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); + } + deviceList.setList(devices); + deviceList.setCount(devices.size()); + return Response.status(Response.Status.OK).entity(deviceList).build(); + } + @GET @Path("/{type}/{id}/applications") @Override diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/search/PropertyMap.java b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/search/PropertyMap.java new file mode 100644 index 00000000000..68df7c7bdc9 --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/search/PropertyMap.java @@ -0,0 +1,43 @@ +/* + * 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. + */ + + +package org.wso2.carbon.device.mgt.common.search; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.util.Map; + + +@ApiModel(value = "PropertyMap", description = "Search properties based on which devices need to be found.") +public class PropertyMap { + + @ApiModelProperty(name = "properties", value = "Contains the properties for search.", + required = true) + private Map properties; + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } +} + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java index b089477f06f..5ea2a87b5b6 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java @@ -47,6 +47,7 @@ import org.wso2.carbon.device.mgt.core.geo.geoHash.GeoCoordinate; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * This class represents the key operations associated with persisting device related information. @@ -180,6 +181,26 @@ public interface DeviceDAO { Device getDevice(DeviceIdentifier deviceIdentifier, Date ifModifiedSince, int tenantId) throws DeviceManagementDAOException; + /** + * Retrieves a list of devices based on a given criteria of properties + * @param deviceProps properties by which devices need to be filtered + * @param tenantId tenant id + * @return list of devices with properties + * @throws DeviceManagementDAOException + */ + List getDeviceBasedOnDeviceProperties(Map deviceProps, int tenantId) throws DeviceManagementDAOException; + + + /** + * Retrieves properties of given device identifier + * + * @param deviceId identifier of device that need to be retrieved + * @param tenantId tenant ID + * @return list of devices with properties + * @throws DeviceManagementDAOException + */ + Device getDeviceProps(String deviceId, int tenantId) throws DeviceManagementDAOException; + /** * This method is used to retrieve a device of a given device-identifier and tenant-id which modified * later than the ifModifiedSince param. diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractDeviceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractDeviceDAOImpl.java index 866b230ed20..1cd83aa7948 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractDeviceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractDeviceDAOImpl.java @@ -56,15 +56,22 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.StringJoiner; public abstract class AbstractDeviceDAOImpl implements DeviceDAO { private static final org.apache.commons.logging.Log log = LogFactory.getLog(AbstractDeviceDAOImpl.class); + private static final String PROPERTY_KEY_COLUMN_NAME = "PROPERTY_NAME"; + private static final String PROPERTY_VALUE_COLUMN_NAME = "PROPERTY_VALUE"; + private static final String PROPERTY_DEVICE_TYPE_NAME = "DEVICE_TYPE_NAME"; + private static final String PROPERTY_DEVICE_IDENTIFICATION = "DEVICE_IDENTIFICATION"; + @Override public int addDevice(int typeId, Device device, int tenantId) throws DeviceManagementDAOException { Connection conn; @@ -282,6 +289,103 @@ public abstract class AbstractDeviceDAOImpl implements DeviceDAO { return device; } + @Override + public List getDeviceBasedOnDeviceProperties(Map deviceProps, int tenantId) + throws DeviceManagementDAOException { + Connection conn = null; + PreparedStatement stmt = null; + ResultSet resultSet = null; + List devices = new ArrayList<>(); + try { + List> outputLists = new ArrayList<>(); + List deviceList = null; + conn = this.getConnection(); + for (Map.Entry entry : deviceProps.entrySet()) { + + stmt = conn.prepareStatement("SELECT DEVICE_IDENTIFICATION FROM DM_DEVICE_PROPERTIES " + + "WHERE (PROPERTY_NAME , PROPERTY_VALUE) IN " + + "((? , ?)) AND TENANT_ID = ?"); + stmt.setString(1, entry.getKey()); + stmt.setString(2, entry.getValue()); + stmt.setInt(3, tenantId); + resultSet = stmt.executeQuery(); + + deviceList = new ArrayList<>(); + while (resultSet.next()) { + deviceList.add(resultSet.getString(PROPERTY_DEVICE_IDENTIFICATION)); + } + outputLists.add(deviceList); + } + List deviceIds = findIntersection(outputLists); + for(String deviceId : deviceIds){ + devices.add(getDeviceProps(deviceId, tenantId)); + } + } catch (SQLException e) { + String msg = "Error occurred while fetching devices against criteria : '" + deviceProps; + log.error(msg, e); + throw new DeviceManagementDAOException(msg, e); + } finally { + DeviceManagementDAOUtil.cleanupResources(stmt, resultSet); + } + return devices; + } + + @Override + public Device getDeviceProps(String deviceId, int tenantId) throws DeviceManagementDAOException { + Connection conn = null; + PreparedStatement stmt = null; + Device device = null; + ResultSet resultSet = null; + String deviceType = null; + try { + conn = this.getConnection(); + stmt = conn.prepareStatement( + "SELECT * FROM DM_DEVICE_PROPERTIES WHERE DEVICE_IDENTIFICATION = ? AND TENANT_ID = ?"); + stmt.setString(1, deviceId); + stmt.setInt(2, tenantId); + resultSet = stmt.executeQuery(); + 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); + //We are repeatedly assigning device type here. Yes. This was done intentionally, as there would be + //No other efficient/simple/inexpensive way of retrieving device type of a particular device from the database + //Note that device-identification will be unique across device types + deviceType = resultSet.getString(PROPERTY_DEVICE_TYPE_NAME); + } + device = new Device(); + device.setDeviceIdentifier(deviceId); + device.setType(deviceType); + device.setProperties(properties); + + } catch (SQLException e) { + String msg = "Error occurred while fetching properties for device : '" + deviceId; + log.error(msg, e); + throw new DeviceManagementDAOException(msg, e); + } finally { + DeviceManagementDAOUtil.cleanupResources(stmt, resultSet); + } + + return device; + } + + private List findIntersection(List> collections) { + boolean first = true; + List intersectedResult = new ArrayList<>(); + for (Collection collection : collections) { + if (first) { + intersectedResult.addAll(collection); + first = false; + } else { + intersectedResult.retainAll(collection); + } + } + return intersectedResult; + } + + @Override public Device getDevice(String deviceIdentifier, Date since, int tenantId) throws DeviceManagementDAOException { diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderService.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderService.java index 43ac2c8567f..b32ed7ed124 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderService.java @@ -65,6 +65,7 @@ import org.wso2.carbon.device.mgt.core.geo.geoHash.GeoCoordinate; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Proxy class for all Device Management related operations that take the corresponding plugin type in @@ -254,6 +255,15 @@ public interface DeviceManagementProviderService { */ Device getDevice(DeviceIdentifier deviceId, Date since, boolean requireDeviceInfo) throws DeviceManagementException; + /** + * Retrieves a list of devices based on a given criteria of properties + * + * @param deviceProps properties by which devices need to be drawn + * @return list of devices + * @throws DeviceManagementException + */ + List getDevicesBasedOnProperties(Map deviceProps) throws DeviceManagementException; + /** * Returns the device of specified id. * diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderServiceImpl.java index 76e86420d3c..11b6866433f 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderServiceImpl.java @@ -1206,6 +1206,46 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv return device; } + + @Override + public List getDevicesBasedOnProperties(Map deviceProps) throws DeviceManagementException { + if (deviceProps == null || deviceProps.isEmpty()) { + String msg = "Devices retrieval criteria cannot be null or empty."; + log.error(msg); + throw new DeviceManagementException(msg); + } + if (log.isDebugEnabled()) { + log.debug("Attempting to get devices based on criteria : " + deviceProps); + } + List devices; + try { + int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); + DeviceManagementDAOFactory.openConnection(); + devices = deviceDAO.getDeviceBasedOnDeviceProperties(deviceProps, tenantId); + if (devices == null) { + if (log.isDebugEnabled()) { + log.debug("No device is found against criteria : " + deviceProps + " and tenantId "+ tenantId); + } + return null; + } + } catch (DeviceManagementDAOException e) { + String msg = "Error occurred while obtaining devices based on criteria : " + deviceProps; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } catch (SQLException e) { + String msg = "Error occurred while opening a connection to the data source"; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } catch (Exception e) { + String msg = "Error occurred while obtaining devices based on criteria : " + deviceProps; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } finally { + DeviceManagementDAOFactory.closeConnection(); + } + return devices; + } + @Override public Device getDevice(String deviceId, Date since, boolean requireDeviceInfo) throws DeviceManagementException { if (deviceId == null || since == null) { diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/dao/PluginDAO.java b/components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/dao/PluginDAO.java index bd1747d4549..a6bdbb79dfe 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/dao/PluginDAO.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/dao/PluginDAO.java @@ -37,6 +37,7 @@ package org.wso2.carbon.device.mgt.extensions.device.type.template.dao; import org.wso2.carbon.device.mgt.common.Device; import org.wso2.carbon.device.mgt.extensions.device.type.template.exception.DeviceTypeMgtPluginException; + import java.util.List; public interface PluginDAO { diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/dao/PropertyBasedPluginDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/dao/PropertyBasedPluginDAOImpl.java index 1c38884ca79..9b01e290d39 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/dao/PropertyBasedPluginDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.extensions/src/main/java/org/wso2/carbon/device/mgt/extensions/device/type/template/dao/PropertyBasedPluginDAOImpl.java @@ -48,7 +48,6 @@ 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;