From d01aa2026d3d745e6b0d5e98507aefe16f97577d Mon Sep 17 00:00:00 2001 From: osh Date: Thu, 17 Feb 2022 13:58:15 +0530 Subject: [PATCH] fixes https://gitlab.com/entgra/product-iots/-/issues/1226 Adding the new billing API --- .../device/mgt/jaxrs/beans/DeviceList.java | 11 + .../service/api/DeviceManagementService.java | 74 ++++++ .../impl/DeviceManagementServiceImpl.java | 45 ++++ .../device/mgt/common/DeviceBilling.java | 225 ++++++++++++++++++ .../device/mgt/common/PaginationResult.java | 11 + .../device/mgt/common/cost/mgt/Costdata.java | 76 ++++++ .../carbon/device/mgt/core/dao/DeviceDAO.java | 27 ++- .../dao/impl/device/GenericDeviceDAOImpl.java | 62 ++++- .../dao/impl/device/OracleDeviceDAOImpl.java | 11 + .../impl/device/PostgreSQLDeviceDAOImpl.java | 11 + .../impl/device/SQLServerDeviceDAOImpl.java | 11 + .../dao/util/DeviceManagementDAOUtil.java | 25 ++ .../DeviceManagementProviderService.java | 36 ++- .../DeviceManagementProviderServiceImpl.java | 139 ++++++++--- 14 files changed, 713 insertions(+), 51 deletions(-) create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/DeviceBilling.java create mode 100644 components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/cost/mgt/Costdata.java diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/DeviceList.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/DeviceList.java index 5adb83af03..770ab690c4 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/DeviceList.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/DeviceList.java @@ -35,10 +35,21 @@ public class DeviceList extends BasePaginatedResult { return devices; } + @ApiModelProperty(name = "totalCost", value = "Total cost of all devices per tenant", required = false) + private double totalCost; + public void setList(List devices) { this.devices = devices; } + public double getTotalCost() { + return totalCost; + } + + public void setTotalCost(double totalCost) { + this.totalCost = totalCost; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); 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 eb1d952491..c19f050ffd 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 @@ -343,6 +343,80 @@ public interface DeviceManagementService { @QueryParam("limit") int limit); + + @GET + @Path("/billing") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Getting Cost details of devices in a tenant", + notes = "Provides individual cost and total cost of all devices per tenant.", + tags = "Device Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:view") + }) + } + ) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "OK. \n Successfully fetched the list of devices.", + 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 = "The incoming request has more than one selection criteria defined via the query parameters.", + response = ErrorResponse.class), + @ApiResponse( + code = 404, + message = "The search criteria did not match any device registered with the server.", + response = ErrorResponse.class), + @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 device list.", + response = ErrorResponse.class) + }) + Response getDevicesBilling( + @ApiParam( + name = "tenantDomain", + value = "The tenant domain.", + required = false, + defaultValue = "nita") + String tenantDomain, + @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 device details you require from the starting pagination index/offset.", + required = false, + defaultValue = "10") + @QueryParam("limit") + int limit); + @GET @Produces(MediaType.APPLICATION_JSON) @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 804029a13c..e1c9e8fb15 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 @@ -340,6 +340,51 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { } } + @GET + @Override + @Path("/billing") + public Response getDevicesBilling( + @DefaultValue("nita") + @QueryParam ("tenantDomain") String tenantDomain, + @QueryParam("offset") int offset, + @DefaultValue("10") + @QueryParam("limit") int limit) { + try { + RequestValidationUtil.validatePaginationParameters(offset, limit); + DeviceManagementProviderService dms = DeviceMgtAPIUtils.getDeviceManagementService(); +// DeviceAccessAuthorizationService deviceAccessAuthorizationService = +// DeviceMgtAPIUtils.getDeviceAccessAuthorizationService(); + PaginationRequest request = new PaginationRequest(offset, limit); + PaginationResult result; + DeviceList devices = new DeviceList(); + + try { + result = dms.getAllDevicesBillings(request, true, tenantDomain); + } catch (Exception exception) { + String msg = "------------------TEST ERROR-----------------------"; + log.error(msg, exception); + return Response.serverError().entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); + } + + int resultCount = result.getRecordsTotal(); + if (resultCount == 0) { + Response.status(Response.Status.OK).entity(devices).build(); + } + + devices.setList((List) result.getData()); + devices.setCount(result.getRecordsTotal()); + devices.setTotalCost(result.getTotalCost()); + return Response.status(Response.Status.OK).entity(devices).build(); + } + catch (Exception e) { + String msg = "Error occurred while fetching all enrolled devices"; + log.error(msg, e); + return Response.serverError().entity( + new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); + } + } + @GET @Override @Path("/user-devices") diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/DeviceBilling.java b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/DeviceBilling.java new file mode 100644 index 0000000000..4eaa3fa7dd --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/DeviceBilling.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2014, 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; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.Gson; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.device.details.DeviceInfo; +import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistorySnapshotWrapper; + +import java.io.Serializable; +import java.util.List; + +@ApiModel(value = "DeviceBilling", description = "This class carries all information related to a managed device.") +public class DeviceBilling implements Serializable { + + private static final long serialVersionUID = 1998101711L; + + @ApiModelProperty(name = "id", value = "ID of the device in the device information database.", + required = false) + private int id; + + @ApiModelProperty(name = "name", value = "The device name that can be set on the device by the device user.", + required = true) + private String name; +// +// @ApiModelProperty(name = "type", value = "The OS type of the device.", required = true) +// private String type; + + @ApiModelProperty(name = "description", value = "Additional information on the device.", required = false) + private String description; + + @ApiModelProperty(name = "cost", value = "Cost charged per device.", required = false) + private Double cost; + + @ApiModelProperty(name = "deviceIdentifier", value = "This is a 64-bit number (as a hex string) that is randomly" + + " generated when the user first sets up the device and should" + + " remain constant for the lifetime of the user's device." + + " The value may change if a factory reset is performed on " + + "the device.", + required = false) + private String deviceIdentifier; + + @ApiModelProperty(name = "daysSinceEnrolled", value = "Number of days gone since device enrollment date to current date.", + required = false) + private int daysSinceEnrolled; + + @ApiModelProperty(name = "daysUsed", value = "Number of days gone since device enrollment date to date device was removed.", + required = false) + private int daysUsed; + + @ApiModelProperty(name = "enrolmentInfo", value = "This defines the device registration related information. " + + "It is mandatory to define this information.", required = false) + private EnrolmentInfo enrolmentInfo; + +// @ApiModelProperty(name = "features", value = "List of features.", required = false) +// private List features; + +// private List properties; + + @ApiModelProperty(name = "advanceInfo", value = "This defines the device registration related information. " + + "It is mandatory to define this information.", required = false) + private DeviceInfo deviceInfo; + +// @ApiModelProperty(name = "applications", value = "This represents the application list installed into the device", +// required = false) +// private List applications; + +// @ApiModelProperty( +// name = "historySnapshot", +// value = "device history snapshots") +// @JsonProperty(value = "historySnapshot") +// private DeviceLocationHistorySnapshotWrapper historySnapshot; + + public DeviceBilling() { + } + + public DeviceBilling(String name, String description, EnrolmentInfo enrolmentInfo) { + this.name = name; + this.description = description; + this.enrolmentInfo = enrolmentInfo; + } + + public DeviceBilling(String name, String description, String deviceId, EnrolmentInfo enrolmentInfo) { + this.name = name; + this.description = description; + this.deviceIdentifier = deviceId; + this.enrolmentInfo = enrolmentInfo; + } + + public int getDaysUsed() { + return daysUsed; + } + + public void setDaysUsed(int daysUsed) { + this.daysUsed = daysUsed; + } + + public int getDaysSinceEnrolled() { + return daysSinceEnrolled; + } + + public void setDaysSinceEnrolled(int daysSinceEnrolled) { + this.daysSinceEnrolled = daysSinceEnrolled; + } + + public Double getCost() { + return cost; + } + + public void setCost(Double cost) { + this.cost = cost; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDeviceIdentifier() { + return deviceIdentifier; + } + + public void setDeviceIdentifier(String deviceIdentifier) { + this.deviceIdentifier = deviceIdentifier; + } + + public EnrolmentInfo getEnrolmentInfo() { + return enrolmentInfo; + } + + public void setEnrolmentInfo(EnrolmentInfo enrolmentInfo) { + this.enrolmentInfo = enrolmentInfo; + } + + public DeviceInfo getDeviceInfo() { + return deviceInfo; + } + + public void setDeviceInfo(DeviceInfo deviceInfo) { + this.deviceInfo = deviceInfo; + } + + public static class Property { + + private String name; + private String value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + @Override + public String toString() { + return new Gson().toJson(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof DeviceBilling)) + return false; + + DeviceBilling device = (DeviceBilling) o; + + return getDeviceIdentifier().equals(device.getDeviceIdentifier()); + + } + + @Override + public int hashCode() { + return getDeviceIdentifier().hashCode(); + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/PaginationResult.java b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/PaginationResult.java index 148cc1781a..9bb9fc5920 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/PaginationResult.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/PaginationResult.java @@ -44,6 +44,17 @@ public class PaginationResult implements Serializable { @ApiModelProperty(name = "data", value = "This holds the database records that matches given criteria", required = true) private List data; + @ApiModelProperty(name = "totalCost", value = "Total cost of all devices per tenant", required = false) + private double totalCost; + + public double getTotalCost() { + return totalCost; + } + + public void setTotalCost(double totalCost) { + this.totalCost = totalCost; + } + public int getRecordsTotal() { return recordsTotal; } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/cost/mgt/Costdata.java b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/cost/mgt/Costdata.java new file mode 100644 index 0000000000..28d4fe0eed --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/cost/mgt/Costdata.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. 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.cost.mgt; + +import com.google.gson.Gson; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.sql.Timestamp; +import java.util.Date; + +@XmlRootElement(name = "Cost") +public class Costdata { + + private String tenantDomain; + private Double cost; + private Timestamp subscriptionBeginning; + private Timestamp subscriptionEnd; + + @XmlElement(name = "tenantDomain", required = true) + public String getTenantDomain() { + return tenantDomain; + } + + public void setTenantDomain(String tenantDomain) { + this.tenantDomain = tenantDomain; + } + + @XmlElement(name = "cost", required = true) + public Double getCost() { + return cost; + } + + public void setCost(Double cost) { + this.cost = cost; + } + + @XmlElement(name = "subscriptionBeginning", required = true) + public Timestamp getSubscriptionBeginning() { + return subscriptionBeginning; + } + + public void setSubscriptionBeginning(Timestamp subscriptionBeginning) { + this.subscriptionBeginning = subscriptionBeginning; + } + + @XmlElement(name = "subscriptionEnd", required = true) + public Timestamp getSubscriptionEnd() { + return subscriptionEnd; + } + + public void setSubscriptionEnd(Timestamp subscriptionEnd) { + this.subscriptionEnd = subscriptionEnd; + } + + @Override + public String toString() { + return new Gson().toJson(this); + } +} 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 40eefe935e..e34c208077 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 @@ -36,12 +36,8 @@ package org.wso2.carbon.device.mgt.core.dao; import org.apache.commons.collections.map.SingletonMap; -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.*; import org.wso2.carbon.device.mgt.common.EnrolmentInfo.Status; -import org.wso2.carbon.device.mgt.common.PaginationRequest; -import org.wso2.carbon.device.mgt.common.Count; import org.wso2.carbon.device.mgt.common.configuration.mgt.DevicePropertyInfo; import org.wso2.carbon.device.mgt.common.device.details.DeviceData; import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistorySnapshot; @@ -49,7 +45,6 @@ import org.wso2.carbon.device.mgt.common.device.details.DeviceMonitoringData; import org.wso2.carbon.device.mgt.common.geo.service.GeoQuery; import org.wso2.carbon.device.mgt.core.dto.DeviceType; import org.wso2.carbon.device.mgt.common.geo.service.GeoCluster; -import org.wso2.carbon.device.mgt.common.geo.service.GeoCoordinate; import java.sql.SQLException; import java.util.Date; @@ -297,6 +292,26 @@ public interface DeviceDAO { */ List getDevices(PaginationRequest request, int tenantId) throws DeviceManagementDAOException; + /** + * This method is used to retrieve the not removed devices of a given tenant as a paginated result. + * + * @param request PaginationRequest object holding the data for pagination + * @param tenantId tenant id. + * @return returns paginated list of not removed devices of tenant. + * @throws DeviceManagementDAOException + */ + List getDeviceBillList(PaginationRequest request, int tenantId) throws DeviceManagementDAOException; + + /** + * This method is used to retrieve the removed devices of a given tenant as a paginated result. + * + * @param request PaginationRequest object holding the data for pagination + * @param tenantId tenant id. + * @return rreturns paginated list of removed devices of tenant. + * @throws DeviceManagementDAOException + */ + List getRemovedDeviceBillList(PaginationRequest request, int tenantId) throws DeviceManagementDAOException; + /** * This method is used to retrieve the devices of a given tenant as a paginated result, along the lines of * activeServerCount and serverIndex diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/GenericDeviceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/GenericDeviceDAOImpl.java index eb3fa87f54..c79e46ebc8 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/GenericDeviceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/GenericDeviceDAOImpl.java @@ -18,13 +18,9 @@ package org.wso2.carbon.device.mgt.core.dao.impl.device; -import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.wso2.carbon.device.mgt.common.Count; -import org.wso2.carbon.device.mgt.common.Device; -import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; -import org.wso2.carbon.device.mgt.common.PaginationRequest; +import org.wso2.carbon.device.mgt.common.*; import org.wso2.carbon.device.mgt.common.device.details.DeviceInfo; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOFactory; @@ -187,6 +183,62 @@ public class GenericDeviceDAOImpl extends AbstractDeviceDAOImpl { } } + @Override + public List getDeviceBillList(PaginationRequest request, int tenantId) + throws DeviceManagementDAOException { + Connection conn; + PreparedStatement stmt = null; + List devices = new ArrayList<>(); + try { + conn = this.getConnection(); + String sql ="select DEVICE_IDENTIFICATION, DESCRIPTION, NAME AS DEVICE_NAME, DATE_OF_ENROLMENT, STATUS,\n" + + "TIMESTAMPDIFF('DAY', CURDATE(), DATE_OF_ENROLMENT) as DAYS_SINCE_ENROLLED from DM_DEVICE d, DM_ENROLMENT e\n" + + "where e.TENANT_ID= ? and d.ID=e.DEVICE_ID and STATUS !='REMOVED' LIMIT 10"; + stmt = conn.prepareStatement(sql); + stmt.setInt(1, tenantId); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + DeviceBilling device = DeviceManagementDAOUtil.loadDeviceBilling(rs, false); + devices.add(device); + } + } catch (SQLException e) { + throw new DeviceManagementDAOException("Error occurred while fetching the list of devices belongs to '" + + request.getOwner() + "'", e); + } finally { + DeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return devices; + } + + @Override + public List getRemovedDeviceBillList(PaginationRequest request, int tenantId) + throws DeviceManagementDAOException { + Connection conn; + PreparedStatement stmt = null; + List devices = new ArrayList<>(); + try { + conn = this.getConnection(); + String sql = "select DEVICE_IDENTIFICATION, DESCRIPTION, NAME AS DEVICE_NAME, DATE_OF_ENROLMENT, DATE_OF_LAST_UPDATE, STATUS, " + + "TIMESTAMPDIFF('DAY', DATE_OF_ENROLMENT, DATE_OF_LAST_UPDATE) AS DAYS_USED from DM_DEVICE d, DM_ENROLMENT e" + + " where e.TENANT_ID=? and d.ID=e.DEVICE_ID and STATUS ='REMOVED'\n"; + stmt = conn.prepareStatement(sql); + stmt.setInt(1, tenantId); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + DeviceBilling device = DeviceManagementDAOUtil.loadDeviceBilling(rs, true); + devices.add(device); + } + } catch (SQLException e) { + throw new DeviceManagementDAOException("Error occurred while fetching the list of devices belongs to '" + + request.getOwner() + "'", e); + } finally { + DeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return devices; + } + @Override public List getAllocatedDevices(PaginationRequest request, int tenantId, int activeServerCount, int serverIndex) throws DeviceManagementDAOException { diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/OracleDeviceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/OracleDeviceDAOImpl.java index f2a91cae45..76f0ed7c5a 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/OracleDeviceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/OracleDeviceDAOImpl.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.device.mgt.common.Count; import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceBilling; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.device.details.DeviceInfo; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; @@ -187,6 +188,16 @@ public class OracleDeviceDAOImpl extends AbstractDeviceDAOImpl { } } + @Override + public List getRemovedDeviceBillList(PaginationRequest request, int tenantId) throws DeviceManagementDAOException { + return null; + } + + @Override + public List getDeviceBillList(PaginationRequest request, int tenantId) throws DeviceManagementDAOException { + return null; + } + @Override public List getAllocatedDevices(PaginationRequest request, int tenantId, int activeServerCount, int serverIndex) diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/PostgreSQLDeviceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/PostgreSQLDeviceDAOImpl.java index 66313603cb..f25319a9b0 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/PostgreSQLDeviceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/PostgreSQLDeviceDAOImpl.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.device.mgt.common.Count; import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceBilling; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.device.details.DeviceInfo; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; @@ -178,6 +179,16 @@ public class PostgreSQLDeviceDAOImpl extends AbstractDeviceDAOImpl { } } + @Override + public List getRemovedDeviceBillList(PaginationRequest request, int tenantId) throws DeviceManagementDAOException { + return null; + } + + @Override + public List getDeviceBillList(PaginationRequest request, int tenantId) throws DeviceManagementDAOException { + return null; + } + @Override public List getAllocatedDevices(PaginationRequest request, int tenantId, int activeServerCount, int serverIndex) diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/SQLServerDeviceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/SQLServerDeviceDAOImpl.java index ae4b25b431..3c87242653 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/SQLServerDeviceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/device/SQLServerDeviceDAOImpl.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.device.mgt.common.Count; import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceBilling; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.device.details.DeviceInfo; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; @@ -188,6 +189,16 @@ public class SQLServerDeviceDAOImpl extends AbstractDeviceDAOImpl { } } + @Override + public List getRemovedDeviceBillList(PaginationRequest request, int tenantId) throws DeviceManagementDAOException { + return null; + } + + @Override + public List getDeviceBillList(PaginationRequest request, int tenantId) throws DeviceManagementDAOException { + return null; + } + @Override public List getAllocatedDevices(PaginationRequest request, int tenantId, int activeServerCount, int serverIndex) diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/util/DeviceManagementDAOUtil.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/util/DeviceManagementDAOUtil.java index 8beb17b5a0..bd43651a80 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/util/DeviceManagementDAOUtil.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/util/DeviceManagementDAOUtil.java @@ -26,6 +26,7 @@ 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.DeviceBilling; import org.wso2.carbon.device.mgt.common.DeviceIdentifier; import org.wso2.carbon.device.mgt.common.EnrolmentInfo; import org.wso2.carbon.device.mgt.common.device.details.DeviceInfo; @@ -152,6 +153,16 @@ public final class DeviceManagementDAOUtil { return enrolmentInfo; } + public static EnrolmentInfo loadEnrolmentBilling(ResultSet rs, Boolean removedDevices) throws SQLException { + EnrolmentInfo enrolmentInfo = new EnrolmentInfo(); + enrolmentInfo.setDateOfEnrolment(rs.getTimestamp("DATE_OF_ENROLMENT").getTime()); + enrolmentInfo.setStatus(EnrolmentInfo.Status.valueOf(rs.getString("STATUS"))); + if (removedDevices) { + enrolmentInfo.setDateOfLastUpdate(rs.getTimestamp("DATE_OF_LAST_UPDATE").getTime()); + } + return enrolmentInfo; + } + public static EnrolmentInfo loadMatchingEnrolment(ResultSet rs) throws SQLException { Map enrolmentInfos = new HashMap<>(); EnrolmentInfo enrolmentInfo = loadEnrolment(rs); @@ -197,6 +208,20 @@ public final class DeviceManagementDAOUtil { return device; } + public static DeviceBilling loadDeviceBilling(ResultSet rs, Boolean removedDevices) throws SQLException { + DeviceBilling device = new DeviceBilling(); + device.setName(rs.getString("DEVICE_NAME")); + device.setDescription(rs.getString("DESCRIPTION")); + device.setDeviceIdentifier(rs.getString("DEVICE_IDENTIFICATION")); + if (removedDevices) { + device.setDaysUsed((int) rs.getLong("DAYS_USED")); + } else { + device.setDaysSinceEnrolled((int) rs.getLong("DAYS_SINCE_ENROLLED")); + } + device.setEnrolmentInfo(loadEnrolmentBilling(rs, removedDevices)); + return device; + } + public static DeviceMonitoringData loadDevice(ResultSet rs, String deviceTypeName) throws SQLException { Device device = new Device(); device.setId(rs.getInt("DEVICE_ID")); 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 6265384aad..15d0fc2c4c 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 @@ -36,23 +36,13 @@ package org.wso2.carbon.device.mgt.core.service; import org.apache.commons.collections.map.SingletonMap; -import org.wso2.carbon.device.mgt.common.ActivityPaginationRequest; -import org.wso2.carbon.device.mgt.common.Device; -import org.wso2.carbon.device.mgt.common.DeviceIdentifier; -import org.wso2.carbon.device.mgt.common.DeviceTransferRequest; -import org.wso2.carbon.device.mgt.common.DynamicTaskContext; -import org.wso2.carbon.device.mgt.common.EnrolmentInfo; -import org.wso2.carbon.device.mgt.common.FeatureManager; -import org.wso2.carbon.device.mgt.common.MonitoringOperation; -import org.wso2.carbon.device.mgt.common.OperationMonitoringTaskConfig; -import org.wso2.carbon.device.mgt.common.PaginationRequest; -import org.wso2.carbon.device.mgt.common.PaginationResult; -import org.wso2.carbon.device.mgt.common.StartupOperationConfig; +import org.wso2.carbon.device.mgt.common.*; import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; import org.wso2.carbon.device.mgt.common.configuration.mgt.AmbiguousConfigurationException; import org.wso2.carbon.device.mgt.common.configuration.mgt.ConfigurationManagementException; import org.wso2.carbon.device.mgt.common.configuration.mgt.DeviceConfiguration; import org.wso2.carbon.device.mgt.common.configuration.mgt.PlatformConfiguration; +import org.wso2.carbon.device.mgt.common.cost.mgt.Costdata; import org.wso2.carbon.device.mgt.common.device.details.DeviceData; import org.wso2.carbon.device.mgt.common.configuration.mgt.ConfigurationEntry; import org.wso2.carbon.device.mgt.common.device.details.DeviceLocationHistorySnapshot; @@ -79,6 +69,7 @@ import org.wso2.carbon.device.mgt.common.geo.service.GeoCluster; import org.wso2.carbon.device.mgt.common.geo.service.GeoCoordinate; import java.sql.SQLException; +import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; @@ -207,6 +198,27 @@ public interface DeviceManagementProviderService { */ PaginationResult getAllDevices(PaginationRequest request, boolean requireDeviceInfo) throws DeviceManagementException; + /** + * Method to retrieve all the devices with pagination support. + * + * @param request PaginationRequest object holding the data for pagination + * @return List - Result includes the cost of removed device list. + * @throws DeviceManagementException If some unusual behaviour is observed while fetching the + * devices. + */ + List getRemovedDeviceListWithCost(PaginationResult paginationResult, PaginationRequest request, Collection costdata, String tenantDomain, Double totalCost) throws DeviceManagementException; + /** + * Method to retrieve all the devices with pagination support. + * + * @param request PaginationRequest object holding the data for pagination + * @param requireDeviceInfo - A boolean indicating whether the device-info (location, app-info etc) is also required + * along with the device data. + * @return PaginationResult - Result including the required parameters necessary to do pagination. + * @throws DeviceManagementException If some unusual behaviour is observed while fetching the + * devices. + */ + PaginationResult getAllDevicesBillings(PaginationRequest request, boolean requireDeviceInfo, String tenantDomain) 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 77b7ac0e08..30d39b0ea1 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 @@ -34,6 +34,7 @@ */ package org.wso2.carbon.device.mgt.core.service; +import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import org.apache.commons.collections.map.SingletonMap; import org.apache.commons.lang.StringUtils; @@ -49,23 +50,7 @@ import org.apache.http.protocol.HTTP; import org.wso2.carbon.CarbonConstants; import org.wso2.carbon.context.CarbonContext; import org.wso2.carbon.context.PrivilegedCarbonContext; -import org.wso2.carbon.device.mgt.common.ActivityPaginationRequest; -import org.wso2.carbon.device.mgt.common.Device; -import org.wso2.carbon.device.mgt.common.DeviceEnrollmentInfoNotification; -import org.wso2.carbon.device.mgt.common.DeviceIdentifier; -import org.wso2.carbon.device.mgt.common.DeviceManager; -import org.wso2.carbon.device.mgt.common.DeviceNotification; -import org.wso2.carbon.device.mgt.common.DevicePropertyNotification; -import org.wso2.carbon.device.mgt.common.DeviceTransferRequest; -import org.wso2.carbon.device.mgt.common.DynamicTaskContext; -import org.wso2.carbon.device.mgt.common.EnrolmentInfo; -import org.wso2.carbon.device.mgt.common.FeatureManager; -import org.wso2.carbon.device.mgt.common.InitialOperationConfig; -import org.wso2.carbon.device.mgt.common.MonitoringOperation; -import org.wso2.carbon.device.mgt.common.OperationMonitoringTaskConfig; -import org.wso2.carbon.device.mgt.common.PaginationRequest; -import org.wso2.carbon.device.mgt.common.PaginationResult; -import org.wso2.carbon.device.mgt.common.StartupOperationConfig; +import org.wso2.carbon.device.mgt.common.*; import org.wso2.carbon.device.mgt.common.app.mgt.Application; import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; import org.wso2.carbon.device.mgt.common.configuration.mgt.AmbiguousConfigurationException; @@ -76,6 +61,7 @@ import org.wso2.carbon.device.mgt.common.configuration.mgt.DeviceConfiguration; import org.wso2.carbon.device.mgt.common.configuration.mgt.DevicePropertyInfo; import org.wso2.carbon.device.mgt.common.configuration.mgt.EnrollmentConfiguration; import org.wso2.carbon.device.mgt.common.configuration.mgt.PlatformConfiguration; +import org.wso2.carbon.device.mgt.common.cost.mgt.Costdata; import org.wso2.carbon.device.mgt.common.device.details.DeviceData; import org.wso2.carbon.device.mgt.common.device.details.DeviceInfo; import org.wso2.carbon.device.mgt.common.device.details.DeviceLocation; @@ -99,6 +85,7 @@ import org.wso2.carbon.device.mgt.common.group.mgt.GroupManagementException; import org.wso2.carbon.device.mgt.common.invitation.mgt.DeviceEnrollmentInvitationDetails; import org.wso2.carbon.device.mgt.common.license.mgt.License; import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManagementException; +import org.wso2.carbon.device.mgt.common.metadata.mgt.Metadata; 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; @@ -133,6 +120,8 @@ import org.wso2.carbon.device.mgt.common.geo.service.GeoCoordinate; import org.wso2.carbon.device.mgt.core.internal.DeviceManagementDataHolder; import org.wso2.carbon.device.mgt.core.internal.DeviceManagementServiceComponent; import org.wso2.carbon.device.mgt.core.internal.PluginInitializationListener; +import org.wso2.carbon.device.mgt.core.metadata.mgt.dao.MetadataDAO; +import org.wso2.carbon.device.mgt.core.metadata.mgt.dao.MetadataManagementDAOFactory; import org.wso2.carbon.device.mgt.core.operation.mgt.CommandOperation; import org.wso2.carbon.device.mgt.core.util.DeviceManagerUtil; import org.wso2.carbon.email.sender.core.ContentProviderInfo; @@ -150,17 +139,9 @@ import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import java.io.IOException; import java.io.StringWriter; +import java.lang.reflect.Type; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; +import java.util.*; import java.util.stream.Collectors; //import org.wso2.carbon.device.mgt.analytics.data.publisher.exception.DataPublisherConfigurationException; @@ -176,6 +157,7 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv private final DeviceTypeDAO deviceTypeDAO; private final EnrollmentDAO enrollmentDAO; private final ApplicationDAO applicationDAO; + private MetadataDAO metadataDAO; public DeviceManagementProviderServiceImpl() { this.pluginRepository = new DeviceManagementPluginRepository(); @@ -183,6 +165,7 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv this.applicationDAO = DeviceManagementDAOFactory.getApplicationDAO(); this.deviceTypeDAO = DeviceManagementDAOFactory.getDeviceTypeDAO(); this.enrollmentDAO = DeviceManagementDAOFactory.getEnrollmentDAO(); + this.metadataDAO = MetadataManagementDAOFactory.getMetadataDAO(); /* Registering a listener to retrieve events when some device management service plugin is installed after * the component is done getting initialized */ @@ -954,6 +937,106 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv return this.getAllDevices(request, true); } + @Override + public List getRemovedDeviceListWithCost(PaginationResult paginationResult, PaginationRequest request, Collection costdata, String tenantDomain, Double totalCost) throws DeviceManagementException { + List allRemovedDevices; + int tenantId = this.getTenantId(); + try { + allRemovedDevices = deviceDAO.getRemovedDeviceBillList(request,tenantId); + for (Costdata test: costdata) { + if (test.getTenantDomain().equals(tenantDomain)) { + for (DeviceBilling device: allRemovedDevices) { + long dateDiff = device.getEnrolmentInfo().getDateOfLastUpdate()-device.getEnrolmentInfo().getDateOfEnrolment(); + long dateInDays = dateDiff / (1000*60*60*24); + double cost = (test.getCost()/365)*dateInDays; + totalCost = cost + totalCost; + device.setCost(cost); + } + } + } + paginationResult.setTotalCost(totalCost); + } catch (DeviceManagementDAOException e) { + String msg = "Error occurred while retrieving device list pertaining to the current tenant"; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + catch (Exception e) { + String msg = "Error occurred get devices"; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + + return allRemovedDevices; + } + + @Override + public PaginationResult getAllDevicesBillings(PaginationRequest request, boolean requireDeviceInfo, String tenantDomain) throws DeviceManagementException { + if (request == null) { + String msg = "Received incomplete pagination request for method getAllDeviceBillings"; + log.error(msg); + throw new DeviceManagementException(msg); + } + if (log.isDebugEnabled()) { + log.debug("Get devices with pagination " + request.toString() + " and requiredDeviceInfo: " + requireDeviceInfo); + } + + PaginationResult paginationResult = new PaginationResult(); + Double totalCost = 0.0; + List allDevices; + List allRemovedDevices; + int count = 0; + int tenantId = this.getTenantId(); + DeviceManagerUtil.validateDeviceListPageSize(request); + try { + DeviceManagementDAOFactory.openConnection(); + allDevices = deviceDAO.getDeviceBillList(request, tenantId); + count = deviceDAO.getDeviceCount(request, tenantId); + + String metaKey = "PER_DEVICE_COST"; + MetadataManagementDAOFactory.openConnection(); + Metadata metadata = metadataDAO.getMetadata(tenantId, metaKey); + + Gson g = new Gson(); + + Type collectionType = new TypeToken>(){}.getType(); + Collection costdata = g.fromJson(metadata.getMetaValue(), collectionType); + + for (Costdata test: costdata) { + if (test.getTenantDomain().equals(tenantDomain)) { + for (DeviceBilling device: allDevices) { + long dateDiff = test.getSubscriptionEnd().getTime()-device.getEnrolmentInfo().getDateOfEnrolment(); + long dateInDays = dateDiff / (1000*60*60*24); + double cost = (test.getCost()/365)*dateInDays; + totalCost = cost + totalCost; + device.setCost(cost); + } + } + } + + allRemovedDevices = this.getRemovedDeviceListWithCost(paginationResult, request, costdata, tenantDomain, totalCost); + allDevices.addAll(allRemovedDevices); + + } catch (DeviceManagementDAOException e) { + String msg = "Error occurred while retrieving device list pertaining to the current tenant"; + 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 in getAllDevices"; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } finally { + DeviceManagementDAOFactory.closeConnection(); + } + paginationResult.setData(allDevices); + paginationResult.setRecordsFiltered(count); + paginationResult.setRecordsTotal(count); + return paginationResult; + } + @Override public PaginationResult getAllDevices(PaginationRequest request, boolean requireDeviceInfo) throws DeviceManagementException { if (request == null) { @@ -4344,7 +4427,7 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv log.debug("Triggering Corrective action. Device Identifier: " + deviceIdentifier); } - if (StringUtils.isBlank(featureCode)){ + if (StringUtils.isBlank(featureCode)) { String msg = "Found a Blan feature code: " + featureCode; log.error(msg); throw new BadRequestException(msg);