diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/AbstractGadgetDataServiceDAO.java b/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/AbstractGadgetDataServiceDAO.java index bfc8af3f621..964eb02dd88 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/AbstractGadgetDataServiceDAO.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/AbstractGadgetDataServiceDAO.java @@ -298,8 +298,9 @@ public abstract class AbstractGadgetDataServiceDAO implements GadgetDataServiceD } @Override - public List getFeatureNonCompliantDeviceCountsByPlatforms(String nonCompliantFeatureCode, - FilterSet filterSet) throws InvalidParameterValueException, SQLException { + public List + getFeatureNonCompliantDeviceCountsByPlatforms(String nonCompliantFeatureCode, + FilterSet filterSet) throws InvalidParameterValueException, SQLException { if (nonCompliantFeatureCode == null || "".equals(nonCompliantFeatureCode)) { throw new InvalidParameterValueException("Non-compliant feature code should not be either null or empty."); @@ -413,7 +414,7 @@ public abstract class AbstractGadgetDataServiceDAO implements GadgetDataServiceD @Override public List getFeatureNonCompliantDeviceCountsByOwnershipTypes(String nonCompliantFeatureCode, - FilterSet filterSet) throws InvalidParameterValueException, SQLException { + FilterSet filterSet) throws InvalidParameterValueException, SQLException { if (nonCompliantFeatureCode == null || "".equals(nonCompliantFeatureCode)) { throw new InvalidParameterValueException("Non-compliant feature code should not be either null or empty."); @@ -472,7 +473,7 @@ public abstract class AbstractGadgetDataServiceDAO implements GadgetDataServiceD @Override public List getDevicesWithDetails(FilterSet filterSet) - throws InvalidParameterValueException, SQLException { + throws InvalidParameterValueException, SQLException { Map filters = this.extractDatabaseFiltersFromBean(filterSet); @@ -526,7 +527,7 @@ public abstract class AbstractGadgetDataServiceDAO implements GadgetDataServiceD @Override public List getFeatureNonCompliantDevicesWithDetails(String nonCompliantFeatureCode, - FilterSet filterSet) throws InvalidParameterValueException, SQLException { + FilterSet filterSet) throws InvalidParameterValueException, SQLException { if (nonCompliantFeatureCode == null || "".equals(nonCompliantFeatureCode)) { throw new InvalidParameterValueException("Non-compliant feature code should not be either null or empty."); @@ -585,7 +586,7 @@ public abstract class AbstractGadgetDataServiceDAO implements GadgetDataServiceD } protected Map extractDatabaseFiltersFromBean(FilterSet filterSet) - throws InvalidParameterValueException { + throws InvalidParameterValueException { if (filterSet == null) { return null; } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/GadgetDataServiceDAOFactory.java b/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/GadgetDataServiceDAOFactory.java index 5ee595424ff..43999fc46ab 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/GadgetDataServiceDAOFactory.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/GadgetDataServiceDAOFactory.java @@ -21,6 +21,7 @@ package org.wso2.carbon.device.mgt.analytics.dashboard.dao; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.device.mgt.analytics.dashboard.dao.impl.GenericGadgetDataServiceDAOImpl; +import org.wso2.carbon.device.mgt.analytics.dashboard.dao.impl.MSSQLGadgetDataServiceDAOImpl; import org.wso2.carbon.device.mgt.analytics.dashboard.dao.impl.OracleGadgetDataServiceDAOImpl; import org.wso2.carbon.device.mgt.analytics.dashboard.dao.impl.PostgreSQLGadgetDataServiceDAOImpl; import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; @@ -52,7 +53,7 @@ public class GadgetDataServiceDAOFactory { case DeviceManagementConstants.DataBaseTypes.DB_TYPE_MYSQL: return new GenericGadgetDataServiceDAOImpl(); case DeviceManagementConstants.DataBaseTypes.DB_TYPE_MSSQL: - // to be added + return new MSSQLGadgetDataServiceDAOImpl(); case DeviceManagementConstants.DataBaseTypes.DB_TYPE_POSTGRESQL: return new PostgreSQLGadgetDataServiceDAOImpl(); case DeviceManagementConstants.DataBaseTypes.DB_TYPE_ORACLE: diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/GenericGadgetDataServiceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/GenericGadgetDataServiceDAOImpl.java index caa6f9bb252..3126d7736ad 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/GenericGadgetDataServiceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/GenericGadgetDataServiceDAOImpl.java @@ -39,7 +39,7 @@ public class GenericGadgetDataServiceDAOImpl extends AbstractGadgetDataServiceDA @Override public PaginationResult getNonCompliantDeviceCountsByFeatures(int startIndex, int resultCount) - throws InvalidParameterValueException, SQLException { + throws InvalidParameterValueException, SQLException { if (startIndex < 0) { throw new InvalidParameterValueException("Start index should be equal to 0 or greater than that."); @@ -99,7 +99,7 @@ public class GenericGadgetDataServiceDAOImpl extends AbstractGadgetDataServiceDA @Override public PaginationResult getDevicesWithDetails(FilterSet filterSet, int startIndex, int resultCount) - throws InvalidParameterValueException, SQLException { + throws InvalidParameterValueException, SQLException { if (startIndex < 0) { throw new InvalidParameterValueException("Start index should be equal to 0 or greater than that."); diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/MSSQLGadgetDataServiceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/MSSQLGadgetDataServiceDAOImpl.java new file mode 100644 index 00000000000..4e5bef4d45d --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/MSSQLGadgetDataServiceDAOImpl.java @@ -0,0 +1,281 @@ +/* + * 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.analytics.dashboard.dao.impl; + +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.analytics.dashboard.dao.AbstractGadgetDataServiceDAO; +import org.wso2.carbon.device.mgt.analytics.dashboard.dao.bean.DetailedDeviceEntry; +import org.wso2.carbon.device.mgt.analytics.dashboard.dao.bean.DeviceCountByGroupEntry; +import org.wso2.carbon.device.mgt.analytics.dashboard.dao.bean.FilterSet; +import org.wso2.carbon.device.mgt.analytics.dashboard.dao.exception.InvalidParameterValueException; +import org.wso2.carbon.device.mgt.common.PaginationResult; +import org.wso2.carbon.device.mgt.core.dao.util.DeviceManagementDAOUtil; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class MSSQLGadgetDataServiceDAOImpl extends AbstractGadgetDataServiceDAO { + + @Override + public PaginationResult getNonCompliantDeviceCountsByFeatures(int startIndex, int resultCount) + throws InvalidParameterValueException, SQLException { + + if (startIndex < 0) { + throw new InvalidParameterValueException("Start index should be equal to 0 or greater than that."); + } + + if (resultCount < 5) { + throw new InvalidParameterValueException("Result count should be equal to 5 or greater than that."); + } + + Connection con; + PreparedStatement stmt = null; + ResultSet rs = null; + int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); + List filteredNonCompliantDeviceCountsByFeatures = new ArrayList<>(); + int totalRecordsCount = 0; + try { + con = this.getConnection(); + String sql = "SELECT FEATURE_CODE, COUNT(DEVICE_ID) AS DEVICE_COUNT FROM DEVICES_VIEW_2 " + + "WHERE TENANT_ID = ? GROUP BY FEATURE_CODE ORDER BY DEVICE_COUNT DESC " + + "OFFSET ? ROWS FETCH NEXT ? ROWS ONLY"; + stmt = con.prepareStatement(sql); + stmt.setInt(1, tenantId); + stmt.setInt(2, startIndex); + stmt.setInt(3, resultCount); + + // executing query + rs = stmt.executeQuery(); + // fetching query results + DeviceCountByGroupEntry filteredNonCompliantDeviceCountByFeature; + while (rs.next()) { + filteredNonCompliantDeviceCountByFeature = new DeviceCountByGroupEntry(); + filteredNonCompliantDeviceCountByFeature.setGroup(rs.getString("FEATURE_CODE")); + filteredNonCompliantDeviceCountByFeature.setDisplayNameForGroup(rs.getString("FEATURE_CODE")); + filteredNonCompliantDeviceCountByFeature.setDeviceCount(rs.getInt("DEVICE_COUNT")); + filteredNonCompliantDeviceCountsByFeatures.add(filteredNonCompliantDeviceCountByFeature); + } + // fetching total records count + sql = "SELECT COUNT(FEATURE_CODE) AS NON_COMPLIANT_FEATURE_COUNT FROM " + + "(SELECT DISTINCT FEATURE_CODE FROM DEVICES_VIEW_2 WHERE TENANT_ID = ?) NON_COMPLIANT_FEATURE_CODE"; + + stmt = con.prepareStatement(sql); + stmt.setInt(1, tenantId); + + // executing query + rs = stmt.executeQuery(); + // fetching query results + while (rs.next()) { + totalRecordsCount = rs.getInt("NON_COMPLIANT_FEATURE_COUNT"); + } + } finally { + DeviceManagementDAOUtil.cleanupResources(stmt, rs); + } + PaginationResult paginationResult = new PaginationResult(); + paginationResult.setData(filteredNonCompliantDeviceCountsByFeatures); + paginationResult.setRecordsTotal(totalRecordsCount); + return paginationResult; + } + + @Override + public PaginationResult getDevicesWithDetails(FilterSet filterSet, int startIndex, int resultCount) + throws InvalidParameterValueException, SQLException { + + if (startIndex < 0) { + throw new InvalidParameterValueException("Start index should be equal to 0 or greater than that."); + } + + if (resultCount < 5) { + throw new InvalidParameterValueException("Result count should be equal to 5 or greater than that."); + } + + Map filters = this.extractDatabaseFiltersFromBean(filterSet); + + Connection con; + PreparedStatement stmt = null; + ResultSet rs = null; + int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); + List filteredDevicesWithDetails = new ArrayList<>(); + int totalRecordsCount = 0; + try { + con = this.getConnection(); + String sql, advancedSqlFiltering = ""; + // appending filters if exist, to support advanced filtering options + // [1] appending filter columns, if exist + if (filters != null && filters.size() > 0) { + for (String column : filters.keySet()) { + advancedSqlFiltering = advancedSqlFiltering + "AND " + column + " = ? "; + } + } + sql = "SELECT DEVICE_ID, PLATFORM, OWNERSHIP, CONNECTIVITY_STATUS FROM DEVICES_VIEW_1 " + + "WHERE TENANT_ID = ? " + advancedSqlFiltering + "ORDER BY DEVICE_ID ASC " + + "OFFSET ? ROWS FETCH NEXT ? ROWS ONLY"; + stmt = con.prepareStatement(sql); + // [2] appending filter column values, if exist + stmt.setInt(1, tenantId); + if (filters != null && filters.values().size() > 0) { + int i = 2; + for (Object value : filters.values()) { + if (value instanceof Integer) { + stmt.setInt(i, (Integer) value); + } else if (value instanceof String) { + stmt.setString(i, (String) value); + } + i++; + } + stmt.setInt(i, startIndex); + stmt.setInt(++i, resultCount); + } else { + stmt.setInt(2, startIndex); + stmt.setInt(3, resultCount); + } + // executing query + rs = stmt.executeQuery(); + // fetching query results + DetailedDeviceEntry filteredDeviceWithDetails; + while (rs.next()) { + filteredDeviceWithDetails = new DetailedDeviceEntry(); + filteredDeviceWithDetails.setDeviceId(rs.getInt("DEVICE_ID")); + filteredDeviceWithDetails.setPlatform(rs.getString("PLATFORM")); + filteredDeviceWithDetails.setOwnershipType(rs.getString("OWNERSHIP")); + filteredDeviceWithDetails.setConnectivityStatus(rs.getString("CONNECTIVITY_STATUS")); + filteredDevicesWithDetails.add(filteredDeviceWithDetails); + } + + // fetching total records count + sql = "SELECT COUNT(DEVICE_ID) AS DEVICE_COUNT FROM DEVICES_VIEW_1 WHERE TENANT_ID = ?"; + + stmt = con.prepareStatement(sql); + stmt.setInt(1, tenantId); + + // executing query + rs = stmt.executeQuery(); + // fetching query results + while (rs.next()) { + totalRecordsCount = rs.getInt("DEVICE_COUNT"); + } + } finally { + DeviceManagementDAOUtil.cleanupResources(stmt, rs); + } + PaginationResult paginationResult = new PaginationResult(); + paginationResult.setData(filteredDevicesWithDetails); + paginationResult.setRecordsTotal(totalRecordsCount); + return paginationResult; + } + + @Override + public PaginationResult getFeatureNonCompliantDevicesWithDetails(String nonCompliantFeatureCode, + FilterSet filterSet, int startIndex, int resultCount) + throws InvalidParameterValueException, SQLException { + + if (nonCompliantFeatureCode == null || "".equals(nonCompliantFeatureCode)) { + throw new InvalidParameterValueException("Non-compliant feature code should not be either null or empty."); + } + + if (startIndex < 0) { + throw new InvalidParameterValueException("Start index should be equal to 0 or greater than that."); + } + + if (resultCount < 5) { + throw new InvalidParameterValueException("Result count should be equal to 5 or greater than that."); + } + + Map filters = this.extractDatabaseFiltersFromBean(filterSet); + + Connection con; + PreparedStatement stmt = null; + ResultSet rs = null; + int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); + List filteredDevicesWithDetails = new ArrayList<>(); + int totalRecordsCount = 0; + try { + con = this.getConnection(); + String sql, advancedSqlFiltering = ""; + // appending filters if exist, to support advanced filtering options + // [1] appending filter columns, if exist + if (filters != null && filters.size() > 0) { + for (String column : filters.keySet()) { + advancedSqlFiltering = advancedSqlFiltering + "AND " + column + " = ? "; + } + } + sql = "SELECT DEVICE_ID, PLATFORM, OWNERSHIP, CONNECTIVITY_STATUS FROM DEVICES_VIEW_2 " + + "WHERE TENANT_ID = ? AND FEATURE_CODE = ? " + advancedSqlFiltering + + "ORDER BY DEVICE_ID ASC OFFSET ? ROWS FETCH NEXT ? ROWS ONLY"; + stmt = con.prepareStatement(sql); + // [2] appending filter column values, if exist + stmt.setInt(1, tenantId); + stmt.setString(2, nonCompliantFeatureCode); + if (filters != null && filters.values().size() > 0) { + int i = 3; + for (Object value : filters.values()) { + if (value instanceof Integer) { + stmt.setInt(i, (Integer) value); + } else if (value instanceof String) { + stmt.setString(i, (String) value); + } + i++; + } + stmt.setInt(i, startIndex); + stmt.setInt(++i, resultCount); + } else { + stmt.setInt(3, startIndex); + stmt.setInt(4, resultCount); + } + // executing query + rs = stmt.executeQuery(); + // fetching query results + DetailedDeviceEntry filteredDeviceWithDetails; + while (rs.next()) { + filteredDeviceWithDetails = new DetailedDeviceEntry(); + filteredDeviceWithDetails.setDeviceId(rs.getInt("DEVICE_ID")); + filteredDeviceWithDetails.setPlatform(rs.getString("PLATFORM")); + filteredDeviceWithDetails.setOwnershipType(rs.getString("OWNERSHIP")); + filteredDeviceWithDetails.setConnectivityStatus(rs.getString("CONNECTIVITY_STATUS")); + filteredDevicesWithDetails.add(filteredDeviceWithDetails); + } + + // fetching total records count + sql = "SELECT COUNT(DEVICE_ID) AS DEVICE_COUNT FROM DEVICES_VIEW_2 " + + "WHERE TENANT_ID = ? AND FEATURE_CODE = ?"; + + stmt = con.prepareStatement(sql); + stmt.setInt(1, tenantId); + stmt.setString(2, nonCompliantFeatureCode); + + // executing query + rs = stmt.executeQuery(); + // fetching query results + while (rs.next()) { + totalRecordsCount = rs.getInt("DEVICE_COUNT"); + } + } finally { + DeviceManagementDAOUtil.cleanupResources(stmt, rs); + } + PaginationResult paginationResult = new PaginationResult(); + paginationResult.setData(filteredDevicesWithDetails); + paginationResult.setRecordsTotal(totalRecordsCount); + return paginationResult; + } + +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/OracleGadgetDataServiceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/OracleGadgetDataServiceDAOImpl.java index b82ea38a307..a433bca813a 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/OracleGadgetDataServiceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/OracleGadgetDataServiceDAOImpl.java @@ -39,7 +39,7 @@ public class OracleGadgetDataServiceDAOImpl extends AbstractGadgetDataServiceDAO @Override public PaginationResult getNonCompliantDeviceCountsByFeatures(int startIndex, int resultCount) - throws InvalidParameterValueException, SQLException { + throws InvalidParameterValueException, SQLException { if (startIndex < 0) { throw new InvalidParameterValueException("Start index should be equal to 0 or greater than that."); @@ -100,7 +100,7 @@ public class OracleGadgetDataServiceDAOImpl extends AbstractGadgetDataServiceDAO @Override public PaginationResult getDevicesWithDetails(FilterSet filterSet, int startIndex, int resultCount) - throws InvalidParameterValueException, SQLException { + throws InvalidParameterValueException, SQLException { if (startIndex < 0) { throw new InvalidParameterValueException("Start index should be equal to 0 or greater than that."); @@ -186,8 +186,8 @@ public class OracleGadgetDataServiceDAOImpl extends AbstractGadgetDataServiceDAO @Override public PaginationResult getFeatureNonCompliantDevicesWithDetails(String nonCompliantFeatureCode, - FilterSet filterSet, int startIndex, int resultCount) - throws InvalidParameterValueException, SQLException { + FilterSet filterSet, int startIndex, int resultCount) + throws InvalidParameterValueException, SQLException { if (nonCompliantFeatureCode == null || "".equals(nonCompliantFeatureCode)) { throw new InvalidParameterValueException("Non-compliant feature code should not be either null or empty."); diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/PostgreSQLGadgetDataServiceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/PostgreSQLGadgetDataServiceDAOImpl.java index 778f4b453e4..d28a5e5b50d 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/PostgreSQLGadgetDataServiceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.analytics.dashboard/src/main/java/org/wso2/carbon/device/mgt/analytics/dashboard/dao/impl/PostgreSQLGadgetDataServiceDAOImpl.java @@ -39,7 +39,7 @@ public class PostgreSQLGadgetDataServiceDAOImpl extends AbstractGadgetDataServic @Override public PaginationResult getNonCompliantDeviceCountsByFeatures(int startIndex, int resultCount) - throws InvalidParameterValueException, SQLException { + throws InvalidParameterValueException, SQLException { if (startIndex < 0) { throw new InvalidParameterValueException("Start index should be equal to 0 or greater than that."); @@ -99,7 +99,7 @@ public class PostgreSQLGadgetDataServiceDAOImpl extends AbstractGadgetDataServic @Override public PaginationResult getDevicesWithDetails(FilterSet filterSet, int startIndex, int resultCount) - throws InvalidParameterValueException, SQLException { + throws InvalidParameterValueException, SQLException { if (startIndex < 0) { throw new InvalidParameterValueException("Start index should be equal to 0 or greater than that."); @@ -185,7 +185,7 @@ public class PostgreSQLGadgetDataServiceDAOImpl extends AbstractGadgetDataServic @Override public PaginationResult getFeatureNonCompliantDevicesWithDetails(String nonCompliantFeatureCode, FilterSet filterSet, int startIndex, int resultCount) - throws InvalidParameterValueException, SQLException { + throws InvalidParameterValueException, SQLException { if (nonCompliantFeatureCode == null || "".equals(nonCompliantFeatureCode)) { throw new InvalidParameterValueException("Non-compliant feature code should not be either null or empty.");