Fix merge conflicts

feature/appm-store/pbac
lasanthaDLPDS 5 years ago
commit 298c58e8b8

@ -0,0 +1,38 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://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.application.mgt.common;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
public class BasePaginatedResult {
@ApiModelProperty(
value = "Number of total resources.",
example = "1")
@JsonProperty("count")
private long count;
public long getCount() {
return count;
}
public void setCount(long count) {
this.count = count;
}
}

@ -0,0 +1,87 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://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.application.mgt.common;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(value = "BasicUserInfo", description = "Basic user information and the roles of the user.")
public class BasicUserInfo {
@ApiModelProperty(name = "username", value = "The login name of the user.", required = true )
private String username;
@ApiModelProperty(name = "firstname", value = "The first name of the user.", required = true )
private String firstname;
@ApiModelProperty(name = "lastname", value = "The last name of the user.", required = true )
private String lastname;
@ApiModelProperty(name = "emailAddress", value = "The email address of the user.", required = true )
private String emailAddress;
@ApiModelProperty(name = "createdDate", value = "User creation date." )
private String createdDate;
@ApiModelProperty(name = "modifiedDate", value = "User modifiedDate date." )
private String modifiedDate;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getCreatedDate() {
return createdDate;
}
public void setCreatedDate(String createdDate) {
this.createdDate = createdDate;
}
public String getModifiedDate() {
return modifiedDate;
}
public void setModifiedDate(String modifiedDate) {
this.modifiedDate = modifiedDate;
}
}

@ -0,0 +1,54 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://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.application.mgt.common;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.ArrayList;
import java.util.List;
@ApiModel(value = "BasicUserInfoList", description = "This contains basic details of a set of users that matches " +
"a given criteria as a collection")
public class BasicUserInfoList extends BasePaginatedResult {
private List<BasicUserInfo> users = new ArrayList<>();
@ApiModelProperty(value = "List of devices returned")
@JsonProperty("users")
public List<BasicUserInfo> getList() {
return users;
}
public void setList(List<BasicUserInfo> users) {
this.users = users;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{\n");
sb.append(" count: ").append(getCount()).append(",\n");
sb.append(" users: [").append(users).append("\n");
sb.append("]}\n");
return sb.toString();
}
}

@ -0,0 +1,50 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://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.application.mgt.common;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import java.util.ArrayList;
import java.util.List;
public class DeviceGroupList extends BasePaginatedResult {
@ApiModelProperty(value = "List of device groups returned")
@JsonProperty("groups")
private List<?> deviceGroups = new ArrayList<>();
public List<?> getList() {
return deviceGroups;
}
public void setList(List<?> deviceGroups) {
this.deviceGroups = deviceGroups;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{\n");
sb.append(" count: ").append(getCount()).append(",\n");
sb.append(" groups: [").append(deviceGroups).append("\n");
sb.append("]}\n");
return sb.toString();
}
}

@ -0,0 +1,52 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://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.application.mgt.common;
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;
public class DeviceList extends BasePaginatedResult {
private List<Device> devices = new ArrayList<>();
@ApiModelProperty(value = "List of devices returned")
@JsonProperty("devices")
public List<Device> getList() {
return devices;
}
public void setList(List<Device> devices) {
this.devices = devices;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{\n");
sb.append(" count: ").append(getCount()).append(",\n");
sb.append(" devices: [").append(devices).append("\n");
sb.append("]}\n");
return sb.toString();
}
}

@ -0,0 +1,54 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://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.application.mgt.common;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
@ApiModel(value = "Role List")
public class RoleList extends BasePaginatedResult {
private List<?> roles;
@ApiModelProperty(value = "Returns the list of roles that match the offset and limit parameter values "
+ "that were specified.")
@JsonProperty("roles")
public List<?> getList() {
return roles;
}
public void setList(List<?> roles) {
this.roles = roles;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{\n");
sb.append(" count: ").append(getCount()).append(",\n");
sb.append(" roles: [").append(roles).append("\n");
sb.append("]}\n");
return sb.toString();
}
}

@ -23,6 +23,7 @@ import org.wso2.carbon.device.application.mgt.common.ExecutionStatus;
import org.wso2.carbon.device.application.mgt.common.dto.ScheduledSubscriptionDTO; import org.wso2.carbon.device.application.mgt.common.dto.ScheduledSubscriptionDTO;
import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException; import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException;
import org.wso2.carbon.device.application.mgt.common.exception.SubscriptionManagementException; import org.wso2.carbon.device.application.mgt.common.exception.SubscriptionManagementException;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import java.util.List; import java.util.List;
@ -83,4 +84,30 @@ public interface SubscriptionManager {
* @throws SubscriptionManagementException if error occurred while updating the status of the subscription * @throws SubscriptionManagementException if error occurred while updating the status of the subscription
*/ */
void updateScheduledSubscriptionStatus(int id, ExecutionStatus status) throws SubscriptionManagementException; void updateScheduledSubscriptionStatus(int id, ExecutionStatus status) throws SubscriptionManagementException;
/***
* This method used to get the app id ,device ids and pass them to DM service method.
*
* @param appUUID UUID of the application release.
* @param offsetValue offset value for get paginated request.
* @param limitValue limit value for get paginated request.
* @param status status of the devices.
* @return deviceDetails - device details for given application release.
* @throws {@link ApplicationManagementException} Exception of the application management
*/
PaginationResult getAppInstalledDevices(int offsetValue, int limitValue, String appUUID,
String status) throws ApplicationManagementException;
/***
* This method used to get category details.
*
* @param appUUID UUID of the application release.
* @param subType subscription type of the application.
* @param offsetValue offset value for get paginated request.
* @param limitValue limit value for get paginated request.
* @return {@link PaginationResult} pagination result of the category details.
* @throws {@link ApplicationManagementException} Exception of the application management
*/
PaginationResult getAppInstalledCategories(int offsetValue, int limitValue, String appUUID,
String subType) throws ApplicationManagementException;
} }

@ -168,6 +168,46 @@ public interface SubscriptionDAO {
* @return {@link ScheduledSubscriptionDTO} * @return {@link ScheduledSubscriptionDTO}
* @throws ApplicationManagementDAOException if error occurred while retrieving the subscription * @throws ApplicationManagementDAOException if error occurred while retrieving the subscription
*/ */
ScheduledSubscriptionDTO getPendingScheduledSubscriptionByTaskName(String taskName) ScheduledSubscriptionDTO getPendingScheduledSubscriptionByTaskName(String taskName) throws ApplicationManagementDAOException;
/**
* This method is used to get the details of users
*
* @param tenantId id of the current tenant
* @param offsetValue offset value for get paginated result
* @param limitValue limit value for get paginated result
* @param appReleaseId id of the application release.
* @return subscribedUsers - list of app subscribed users.
* @throws {@link ApplicationManagementDAOException} if connections establishment fails.
*/
List<String> getAppSubscribedUsers(int offsetValue, int limitValue, int appReleaseId,
int tenantId)
throws ApplicationManagementDAOException;
/**
* This method is used to get the details of roles
*
* @param tenantId id of the current tenant
* @param offsetValue offset value for get paginated request.
* @param limitValue limit value for get paginated request.
* @param appReleaseId id of the application release.
* @return subscribedRoles - list of app subscribed roles.
* @throws {@link ApplicationManagementDAOException} if connections establishment fails.
*/
List<String> getAppSubscribedRoles(int offsetValue, int limitValue, int appReleaseId,
int tenantId)
throws ApplicationManagementDAOException;
/**
* This method is used to get the details of subscribed groups
*
* @param tenantId id of the current tenant
* @param offsetValue offset value for get paginated request.
* @param limitValue limit value for get paginated request.
* @param appReleaseId id of the application release.
* @return subscribedGroups - list of app subscribed groups.
* @throws {@link ApplicationManagementDAOException} if connections establishment fails.
*/
List<String> getAppSubscribedGroups(int offsetValue, int limitValue, int appReleaseId, int tenantId)
throws ApplicationManagementDAOException; throws ApplicationManagementDAOException;
} }

@ -356,6 +356,7 @@ public class GenericSubscriptionDAOImpl extends AbstractDAOImpl implements Subsc
+ "DS.UNSUBSCRIBED_BY AS UNSUBSCRIBED_BY, " + "DS.UNSUBSCRIBED_BY AS UNSUBSCRIBED_BY, "
+ "DS.UNSUBSCRIBED_TIMESTAMP AS UNSUBSCRIBED_AT, " + "DS.UNSUBSCRIBED_TIMESTAMP AS UNSUBSCRIBED_AT, "
+ "DS.ACTION_TRIGGERED_FROM AS ACTION_TRIGGERED_FROM, " + "DS.ACTION_TRIGGERED_FROM AS ACTION_TRIGGERED_FROM, "
+ "DS.STATUS AS STATUS,"
+ "DS.DM_DEVICE_ID AS DEVICE_ID " + "DS.DM_DEVICE_ID AS DEVICE_ID "
+ "FROM AP_DEVICE_SUBSCRIPTION DS " + "FROM AP_DEVICE_SUBSCRIPTION DS "
+ "WHERE DS.AP_APP_RELEASE_ID = ? AND DS.TENANT_ID=?"; + "WHERE DS.AP_APP_RELEASE_ID = ? AND DS.TENANT_ID=?";
@ -849,6 +850,45 @@ public class GenericSubscriptionDAOImpl extends AbstractDAOImpl implements Subsc
} }
} }
public List<String> getAppSubscribedUsers(int offsetValue, int limitValue, int appReleaseId,
int tenantId)
throws ApplicationManagementDAOException {
if (log.isDebugEnabled()) {
log.debug("Request received in DAO Layer to get already subscribed users for " +
"given app release id.");
}
try {
Connection conn = this.getDBConnection();
List<String> subscribedUsers = new ArrayList<>();
String sql = "SELECT "
+ "US.USER_NAME AS USER "
+ "FROM AP_USER_SUBSCRIPTION US "
+ "WHERE "
+ "AP_APP_RELEASE_ID = ? AND TENANT_ID = ? LIMIT ?,?";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, appReleaseId);
stmt.setInt(2, tenantId);
stmt.setInt(3, offsetValue);
stmt.setInt(4, limitValue);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
subscribedUsers.add(rs.getString("USER"));
}
}
return subscribedUsers;
}
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the DB connection to get already " +
"subscribed users for given app release id.";
log.error(msg, e);
throw new ApplicationManagementDAOException(msg, e);
} catch (SQLException e) {
String msg = "SQL Error occurred while getting subscribed users for given app release id.";
log.error(msg, e);
throw new ApplicationManagementDAOException(msg, e);
}
}
@Override @Override
public List<ScheduledSubscriptionDTO> getScheduledSubscriptionByStatus(ExecutionStatus status, boolean deleted) public List<ScheduledSubscriptionDTO> getScheduledSubscriptionByStatus(ExecutionStatus status, boolean deleted)
throws ApplicationManagementDAOException { throws ApplicationManagementDAOException {
@ -918,6 +958,45 @@ public class GenericSubscriptionDAOImpl extends AbstractDAOImpl implements Subsc
} }
} }
public List<String> getAppSubscribedRoles(int offsetValue, int limitValue, int appReleaseId,
int tenantId)
throws ApplicationManagementDAOException {
if (log.isDebugEnabled()) {
log.debug("Request received in DAO Layer to get already subscribed roles for " +
"given app release id.");
}
try {
Connection conn = this.getDBConnection();
List<String> subscribedRoles = new ArrayList<>();
String sql = "SELECT "
+ "US.ROLE_NAME AS ROLE "
+ "FROM AP_ROLE_SUBSCRIPTION US "
+ "WHERE "
+ "AP_APP_RELEASE_ID = ? AND TENANT_ID = ? LIMIT ?,?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, appReleaseId);
ps.setInt(2, tenantId);
ps.setInt(3, offsetValue);
ps.setInt(4, limitValue);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
subscribedRoles.add(rs.getString("ROLE"));
}
}
return subscribedRoles;
}
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the DB connection to get already " +
"subscribed roles for given app release id.";
log.error(msg, e);
throw new ApplicationManagementDAOException(msg, e);
} catch (SQLException e) {
String msg = "SQL Error occurred while getting subscribed roles for given app release id.";
log.error(msg, e);
throw new ApplicationManagementDAOException(msg, e);
}
}
@Override @Override
public ScheduledSubscriptionDTO getPendingScheduledSubscriptionByTaskName(String taskName) public ScheduledSubscriptionDTO getPendingScheduledSubscriptionByTaskName(String taskName)
throws ApplicationManagementDAOException { throws ApplicationManagementDAOException {
@ -957,4 +1036,45 @@ public class GenericSubscriptionDAOImpl extends AbstractDAOImpl implements Subsc
throw new ApplicationManagementDAOException(msg, e); throw new ApplicationManagementDAOException(msg, e);
} }
} }
@Override
public List<String> getAppSubscribedGroups(int offsetValue, int limitValue, int appReleaseId,
int tenantId)
throws ApplicationManagementDAOException {
if (log.isDebugEnabled()) {
log.debug("Request received in DAO Layer to get already subscribed groups for " +
"given app release id.");
}
try {
Connection conn = this.getDBConnection();
List<String> subscribedGroups = new ArrayList<>();
String sql = "SELECT "
+ "GS.GROUP_NAME AS GROUPS "
+ "FROM AP_GROUP_SUBSCRIPTION GS "
+ "WHERE "
+ "AP_APP_RELEASE_ID = ? AND TENANT_ID = ? LIMIT ?,?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, appReleaseId);
ps.setInt(2, tenantId);
ps.setInt(3, offsetValue);
ps.setInt(4, limitValue);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
subscribedGroups.add(rs.getString("GROUPS"));
}
}
return subscribedGroups;
}
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the DB connection to get already " +
"subscribed groups for given app release id.";
log.error(msg, e);
throw new ApplicationManagementDAOException(msg, e);
} catch (SQLException e) {
String msg = "SQL Error occurred while getting subscribed groups for given " +
"app release id.";
log.error(msg, e);
throw new ApplicationManagementDAOException(msg, e);
}
}
} }

@ -82,6 +82,7 @@ import org.wso2.carbon.device.mgt.core.util.MDMAndroidOperationUtil;
import org.wso2.carbon.device.mgt.core.util.MDMIOSOperationUtil; import org.wso2.carbon.device.mgt.core.util.MDMIOSOperationUtil;
import org.wso2.carbon.identity.jwt.client.extension.dto.AccessTokenInfo; import org.wso2.carbon.identity.jwt.client.extension.dto.AccessTokenInfo;
import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import java.io.IOException; import java.io.IOException;
@ -684,6 +685,7 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
app.setLocation(application.getApplicationReleases().get(0).getInstallerPath()); app.setLocation(application.getApplicationReleases().get(0).getInstallerPath());
return MDMAndroidOperationUtil.createInstallAppOperation(app); return MDMAndroidOperationUtil.createInstallAppOperation(app);
} else if (SubAction.UNINSTALL.toString().equalsIgnoreCase(action)) { } else if (SubAction.UNINSTALL.toString().equalsIgnoreCase(action)) {
app.setType(mobileAppType);
return MDMAndroidOperationUtil.createAppUninstallOperation(app); return MDMAndroidOperationUtil.createAppUninstallOperation(app);
} else { } else {
String msg = "Invalid Action is found. Action: " + action; String msg = "Invalid Action is found. Action: " + action;
@ -793,10 +795,117 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
log.error(msg, e); log.error(msg, e);
throw new ApplicationManagementException(msg, e); throw new ApplicationManagementException(msg, e);
} catch (IOException e) { } catch (IOException e) {
String msg = "Error while installing the enrollment with id: " + applicationPolicyDTO.getApplicationDTO() String msg = "Error while installing the enrollment with id: " + applicationPolicyDTO.getApplicationDTO().getId()
.getId() + " on device"; + " on device";
log.error(msg, e); log.error(msg, e);
throw new ApplicationManagementException(msg, e); throw new ApplicationManagementException(msg, e);
} }
} }
@Override
public PaginationResult getAppInstalledDevices(int offsetValue, int limitValue, String appUUID,
String status)
throws ApplicationManagementException {
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
DeviceManagementProviderService deviceManagementProviderService = HelperUtil
.getDeviceManagementProviderService();
try {
ConnectionManagerUtil.openDBConnection();
ApplicationDTO applicationDTO = this.applicationDAO.getAppWithRelatedRelease(appUUID, tenantId);
int applicationReleaseId = applicationDTO.getApplicationReleaseDTOs().get(0).getId();
List<DeviceSubscriptionDTO> deviceSubscriptionDTOS = subscriptionDAO
.getDeviceSubscriptions(applicationReleaseId, tenantId);
if (deviceSubscriptionDTOS.isEmpty()) {
String msg = "Couldn't found an subscribed devices for application release id: "
+ applicationReleaseId;
log.info(msg);
}
List<Integer> deviceIdList = new ArrayList<>();
for (DeviceSubscriptionDTO deviceIds : deviceSubscriptionDTOS) {
deviceIdList.add(deviceIds.getDeviceId());
}
//pass the device id list to device manager service method
try {
PaginationResult deviceDetails = deviceManagementProviderService
.getAppSubscribedDevices(offsetValue ,limitValue, deviceIdList, status);
if (deviceDetails == null) {
String msg = "Couldn't found an subscribed devices details for device ids: "
+ deviceIdList;
log.error(msg);
throw new NotFoundException(msg);
}
return deviceDetails;
} catch (DeviceManagementException e) {
String msg = "service error occurred while getting data from the service";
log.error(msg, e);
throw new ApplicationManagementException(msg, e);
}
} catch (ApplicationManagementDAOException e) {
ConnectionManagerUtil.rollbackDBTransaction();
String msg = "Error occurred when get application release data for application" +
" release UUID: " + appUUID;
log.error(msg, e);
throw new ApplicationManagementException(msg, e);
} catch (DBConnectionException e) {
String msg = "DB Connection error occurred while getting device details that " +
"given application id";
log.error(msg, e);
throw new ApplicationManagementException(msg, e);
} finally {
ConnectionManagerUtil.closeDBConnection();
}
}
@Override
public PaginationResult getAppInstalledCategories(int offsetValue, int limitValue,
String appUUID, String subType)
throws ApplicationManagementException {
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
PaginationResult paginationResult = new PaginationResult();
try {
ConnectionManagerUtil.openDBConnection();
ApplicationDTO applicationDTO = this.applicationDAO
.getAppWithRelatedRelease(appUUID, tenantId);
int applicationReleaseId = applicationDTO.getApplicationReleaseDTOs().get(0).getId();
int count = 0;
List<String> SubscriptionList = new ArrayList<>();
if (SubscriptionType.USER.toString().equalsIgnoreCase(subType)) {
SubscriptionList = subscriptionDAO
.getAppSubscribedUsers(offsetValue, limitValue, applicationReleaseId, tenantId);
} else if (SubscriptionType.ROLE.toString().equalsIgnoreCase(subType)) {
SubscriptionList = subscriptionDAO
.getAppSubscribedRoles(offsetValue, limitValue, applicationReleaseId, tenantId);
} else if (SubscriptionType.GROUP.toString().equalsIgnoreCase(subType)) {
SubscriptionList = subscriptionDAO
.getAppSubscribedGroups(offsetValue, limitValue, applicationReleaseId, tenantId);
}
count = SubscriptionList.size();
paginationResult.setData(SubscriptionList);
paginationResult.setRecordsFiltered(count);
paginationResult.setRecordsTotal(count);
return paginationResult;
} catch (ApplicationManagementDAOException e) {
ConnectionManagerUtil.rollbackDBTransaction();
String msg = "Error occurred when get application release data for application" +
" release UUID: " + appUUID;
log.error(msg, e);
throw new ApplicationManagementException(msg, e);
} catch (DBConnectionException e) {
String msg = "DB Connection error occurred while getting category details that " +
"given application id";
log.error(msg, e);
throw new ApplicationManagementException(msg, e);
} finally {
ConnectionManagerUtil.closeDBConnection();
}
}
} }

@ -58,7 +58,7 @@
} }
.mobile-menu-button{ .mobile-menu-button{
margin-top: 4%; margin-top: 15px;
} }
Header{ Header{
@ -68,7 +68,7 @@
} }
.dashboard-body{ .dashboard-body{
margin-top: 14%; margin-top: 50px;
} }
.logo-image { .logo-image {
@ -80,7 +80,7 @@
@media screen and (max-height: 350px) { @media screen and (max-height: 350px) {
.mobile-menu-button{ .mobile-menu-button{
margin-top: 2%; margin-top: 15px;
} }
.dashboard-body{ .dashboard-body{
margin-top: 9%; margin-top: 9%;

@ -40,14 +40,14 @@ class Dashboard extends React.Component {
this.Logo = this.config.theme.logo; this.Logo = this.config.theme.logo;
} }
showDrawer = () => { showMobileNavigationBar = () => {
this.setState({ this.setState({
visible: true, visible: true,
collapsed: !this.state.collapsed collapsed: !this.state.collapsed
}); });
}; };
onClose = () => { onCloseMobileNavigationBar = () => {
this.setState({ this.setState({
visible: false, visible: false,
}); });
@ -131,7 +131,7 @@ class Dashboard extends React.Component {
<Layout className="mobile-layout"> <Layout className="mobile-layout">
<div className="mobile-menu-button"> <div className="mobile-menu-button">
<Button type="link" onClick={this.showDrawer}> <Button type="link" onClick={this.showMobileNavigationBar}>
<Icon type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'} className="bar-icon"/> <Icon type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'} className="bar-icon"/>
</Button> </Button>
</div> </div>
@ -146,7 +146,7 @@ class Dashboard extends React.Component {
} }
placement="left" placement="left"
closable={false} closable={false}
onClose={this.onClose} onClose={this.onCloseMobileNavigationBar}
visible={this.state.visible} visible={this.state.visible}
getContainer={false} getContainer={false}
style={{position: 'absolute'}}> style={{position: 'absolute'}}>
@ -214,6 +214,6 @@ class Dashboard extends React.Component {
</div> </div>
); );
} }
} };
export default withConfigContext(Dashboard); export default withConfigContext(Dashboard);

@ -20,6 +20,7 @@ package org.wso2.carbon.device.application.mgt.store.api.services;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses; import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Extension; import io.swagger.annotations.Extension;
import io.swagger.annotations.ExtensionProperty; import io.swagger.annotations.ExtensionProperty;
@ -27,15 +28,17 @@ import io.swagger.annotations.Info;
import io.swagger.annotations.SwaggerDefinition; import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag; import io.swagger.annotations.Tag;
import org.wso2.carbon.apimgt.annotations.api.Scopes; import org.wso2.carbon.apimgt.annotations.api.Scopes;
import org.wso2.carbon.device.application.mgt.common.ErrorResponse;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier; import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import javax.validation.Valid; import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.Consumes;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.List; import java.util.List;
@ -181,4 +184,133 @@ public interface SubscriptionManagementAPI {
) )
@QueryParam("timestamp") String timestamp @QueryParam("timestamp") String timestamp
); );
@GET
@Path("/{uuid}/devices")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ApiOperation(
consumes = MediaType.APPLICATION_JSON,
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Get device details that have a given application install",
notes = "This will get the device details that have a given application install, if exists",
tags = "Subscription Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = SCOPE, value = "perm:app:subscription:uninstall")
})
}
)
@ApiResponses(
value = {
@ApiResponse(
code = 200,
message = "OK. \n Successfully retrieved device details.",
response = List.class,
responseContainer = "List"),
@ApiResponse(
code = 404,
message = "Not Found. \n No Application found which has application release of UUID.",
response = ErrorResponse.class),
@ApiResponse(
code = 400,
message = "Bad Request. \n Found invalid payload with the request.",
response = List.class),
@ApiResponse(
code = 403,
message = "Forbidden. \n Don't have permission to get the details.",
response = List.class),
@ApiResponse(
code = 500,
message = "Internal Server Error. \n Error occurred while getting data",
response = ErrorResponse.class)
})
Response getAppInstalledDevices(
@ApiParam(
name="uuid",
value="uuid of the application release.",
required = true)
@PathParam("uuid") String uuid,
@ApiParam(
name = "offset",
value = "The starting pagination index for the complete list of qualified items.",
defaultValue = "0")
@QueryParam("offset") int offset,
@ApiParam(
name = "limit",
value = "Provide how many device details you require from the starting pagination index/offset.",
defaultValue = "5")
@QueryParam("limit") int limit,
@ApiParam(
name = "status",
value = "Provide the device status details, such as active or inactive.")
@QueryParam("status") String status
);
@GET
@Path("/{uuid}/{subType}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ApiOperation(
consumes = MediaType.APPLICATION_JSON,
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Get category details that have a given application install",
notes = "This will get the category details that have a given application install, if exists",
tags = "Subscription Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = SCOPE, value = "perm:app:subscription:uninstall")
})
}
)
@ApiResponses(
value = {
@ApiResponse(
code = 200,
message = "OK. \n Successfully retrieved categories details.",
response = List.class,
responseContainer = "List"),
@ApiResponse(
code = 404,
message = "Not Found. \n No Application found which has application " +
"release of UUID.",
response = ErrorResponse.class),
@ApiResponse(
code = 400,
message = "Bad Request. \n Found invalid payload with the request.",
response = List.class),
@ApiResponse(
code = 403,
message = "Forbidden. \n Don't have permission to get the details.",
response = List.class),
@ApiResponse(
code = 500,
message = "Internal Server Error. \n Error occurred while getting data",
response = ErrorResponse.class)
})
Response getAppInstalledCategories(
@ApiParam(
name="uuid",
value="uuid of the application release.",
required = true)
@PathParam("uuid") String uuid,
@ApiParam(
name="subType",
value="Subscription type of the application release.",
required = true)
@PathParam("subType") String subType,
@ApiParam(
name = "offset",
value = "The starting pagination index for the complete list of qualified items.",
defaultValue = "0")
@QueryParam("offset") int offset,
@ApiParam(
name = "limit",
value = "Provide how many device details you require from the starting " +
"pagination index/offset.",
defaultValue = "5")
@QueryParam("limit") int limit
);
} }

@ -1,21 +1,3 @@
/*
* 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.
*/
/* /*
* Copyright (c) 2019, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. * Copyright (c) 2019, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
* *
@ -46,20 +28,31 @@ import org.wso2.carbon.device.application.mgt.common.SubscriptionType;
import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException; import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException;
import org.wso2.carbon.device.application.mgt.common.services.SubscriptionManager; import org.wso2.carbon.device.application.mgt.common.services.SubscriptionManager;
import org.wso2.carbon.device.application.mgt.core.exception.ApplicationOperationTaskException; import org.wso2.carbon.device.application.mgt.core.exception.ApplicationOperationTaskException;
import org.wso2.carbon.device.application.mgt.common.DeviceList;
import org.wso2.carbon.device.application.mgt.common.BasicUserInfo;
import org.wso2.carbon.device.application.mgt.common.BasicUserInfoList;
import org.wso2.carbon.device.application.mgt.common.RoleList;
import org.wso2.carbon.device.application.mgt.common.DeviceGroupList;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.application.mgt.common.SubsciptionType;
import org.wso2.carbon.device.application.mgt.core.exception.BadRequestException; import org.wso2.carbon.device.application.mgt.core.exception.BadRequestException;
import org.wso2.carbon.device.application.mgt.core.exception.ForbiddenException; import org.wso2.carbon.device.application.mgt.core.exception.ForbiddenException;
import org.wso2.carbon.device.application.mgt.core.exception.NotFoundException; import org.wso2.carbon.device.application.mgt.core.exception.NotFoundException;
import org.wso2.carbon.device.application.mgt.core.task.ScheduledAppSubscriptionTaskManager; import org.wso2.carbon.device.application.mgt.core.task.ScheduledAppSubscriptionTaskManager;
import org.wso2.carbon.device.application.mgt.core.util.APIUtil; import org.wso2.carbon.device.application.mgt.core.util.APIUtil;
import org.wso2.carbon.device.application.mgt.store.api.services.SubscriptionManagementAPI; import org.wso2.carbon.device.application.mgt.store.api.services.SubscriptionManagementAPI;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier; import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import javax.validation.Valid; import javax.validation.Valid;
import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.Consumes;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@ -114,6 +107,7 @@ public class SubscriptionManagementAPIImpl implements SubscriptionManagementAPI{
} }
} }
@Override @Override
@POST @POST
@Path("/{uuid}/{subType}/{action}") @Path("/{uuid}/{subType}/{action}")
@ -185,4 +179,114 @@ public class SubscriptionManagementAPIImpl implements SubscriptionManagementAPI{
} }
return Response.status(Response.Status.CREATED).build(); return Response.status(Response.Status.CREATED).build();
} }
@GET
@Consumes("application/json")
@Produces("application/json")
@Path("/{uuid}/devices")
public Response getAppInstalledDevices(@PathParam("uuid") String uuid,
@DefaultValue("0")
@QueryParam("offset") int offset,
@DefaultValue("5")
@QueryParam("limit") int limit,
@QueryParam("status") String status) {
try {
SubscriptionManager subscriptionManager = APIUtil.getSubscriptionManager();
PaginationResult subscribedDeviceDetails = subscriptionManager
.getAppInstalledDevices(offset, limit, uuid, status);
DeviceList devices = new DeviceList();
devices.setList((List<Device>) subscribedDeviceDetails.getData());
devices.setCount(subscribedDeviceDetails.getRecordsTotal());
return Response.status(Response.Status.OK).entity(devices).build();
} catch (NotFoundException e) {
String msg = "Application with application release UUID: " + uuid + " is not found";
log.error(msg, e);
return Response.status(Response.Status.NOT_FOUND).entity(msg).build();
} catch (BadRequestException e) {
String msg = "Found invalid payload for getting application which has UUID: " + uuid
+ ". Hence verify the payload";
log.error(msg, e);
return Response.status(Response.Status.BAD_REQUEST).entity(msg).build();
} catch (ForbiddenException e) {
String msg = "Application release is not in the installable state."
+ "Hence you are not permitted to get the devices details.";
log.error(msg, e);
return Response.status(Response.Status.FORBIDDEN).entity(msg).build();
} catch (ApplicationManagementException e) {
String msg = "Error occurred while getting application with the application release uuid: "
+ uuid;
log.error(msg, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
}
}
@GET
@Consumes("application/json")
@Produces("application/json")
@Path("/{uuid}/{subType}")
public Response getAppInstalledCategories(@PathParam("uuid") String uuid,
@PathParam("subType") String subType,
@DefaultValue("0")
@QueryParam("offset") int offset,
@DefaultValue("5")
@QueryParam("limit") int limit) {
try {
SubscriptionManager subscriptionManager = APIUtil.getSubscriptionManager();
PaginationResult subscribedCategoryDetails = subscriptionManager
.getAppInstalledCategories(offset, limit, uuid, subType);
if (SubsciptionType.USER.toString().equalsIgnoreCase(subType)) {
BasicUserInfoList users = new BasicUserInfoList();
users.setList((List<BasicUserInfo>) subscribedCategoryDetails.getData());
users.setCount(subscribedCategoryDetails.getRecordsTotal());
return Response.status(Response.Status.OK).entity(users).build();
} else if (SubsciptionType.ROLE.toString().equalsIgnoreCase(subType)) {
RoleList roles = new RoleList();
roles.setList(subscribedCategoryDetails.getData());
roles.setCount(subscribedCategoryDetails.getRecordsTotal());
return Response.status(Response.Status.OK).entity(roles).build();
} else if (SubsciptionType.GROUP.toString().equalsIgnoreCase(subType)) {
DeviceGroupList groups = new DeviceGroupList();
groups.setList(subscribedCategoryDetails.getData());
groups.setCount(subscribedCategoryDetails.getRecordsTotal());
return Response.status(Response.Status.OK).entity(groups).build();
} else {
String msg = "Found invalid sub type ";
log.error(msg);
return Response.status(Response.Status.NOT_FOUND).entity(msg).build();
}
} catch (NotFoundException e) {
String msg = "Application with application release UUID: " + uuid + " is not found";
log.error(msg, e);
return Response.status(Response.Status.NOT_FOUND).entity(msg).build();
} catch (BadRequestException e) {
String msg = "Found invalid payload for getting application which has UUID: " + uuid
+ ". Hence verify the payload";
log.error(msg, e);
return Response.status(Response.Status.BAD_REQUEST).entity(msg).build();
} catch (ForbiddenException e) {
String msg = "Application release is not in the installable state."
+ "Hence you are not permitted to get the devices details.";
log.error(msg, e);
return Response.status(Response.Status.FORBIDDEN).entity(msg).build();
} catch (ApplicationManagementException e) {
String msg = "Error occurred while getting application with the application " +
"release uuid: " + uuid;
log.error(msg, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
}
}
} }

@ -25,6 +25,7 @@ import DetailedRating from "./DetailedRating";
import Reviews from "./review/Reviews"; import Reviews from "./review/Reviews";
import axios from "axios"; import axios from "axios";
import AppInstallModal from "./install/AppInstallModal"; import AppInstallModal from "./install/AppInstallModal";
import AppUninstallModal from "./install/AppUninstallModal";
import CurrentUsersReview from "./review/CurrentUsersReview"; import CurrentUsersReview from "./review/CurrentUsersReview";
import {withConfigContext} from "../../../context/ConfigContext"; import {withConfigContext} from "../../../context/ConfigContext";
import {handleApiError} from "../../../js/Utils"; import {handleApiError} from "../../../js/Utils";
@ -36,11 +37,12 @@ class ReleaseView extends React.Component {
super(props); super(props);
this.state = { this.state = {
loading: false, loading: false,
appInstallModalVisible: false appInstallModalVisible: false,
appUninstallModalVisible: false
} }
} }
installApp = (type, payload) => { appOperation = (type, payload, operation) => {
const config = this.props.context; const config = this.props.context;
const release = this.props.app.applicationReleases[0]; const release = this.props.app.applicationReleases[0];
const {uuid} = release; const {uuid} = release;
@ -48,7 +50,7 @@ class ReleaseView extends React.Component {
this.setState({ this.setState({
loading: true, loading: true,
}); });
const url = window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" + type + "/install"; const url = window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/" + type + "/" + operation;
axios.post( axios.post(
url, url,
payload, payload,
@ -64,7 +66,7 @@ class ReleaseView extends React.Component {
notification["success"]({ notification["success"]({
message: 'Done!', message: 'Done!',
description: description:
'App installed triggered.', 'App '+operation+'ed triggered.',
}); });
} else { } else {
this.setState({ this.setState({
@ -74,24 +76,31 @@ class ReleaseView extends React.Component {
message: "There was a problem", message: "There was a problem",
duration: 0, duration: 0,
description: description:
"Error occurred while installing app", "Error occurred while "+operation+"ing app",
}); });
} }
}).catch((error) => { }).catch((error) => {
handleApiError(error,"Error occurred while installing the app."); handleApiError(error,"Error occurred while "+operation+"ing the app.");
}); });
}; };
showAppInstallModal = () => { showAppInstallModal = () => {
this.setState({ this.setState({
appInstallModalVisible: true appInstallModalVisible: true
}); });
}; };
closeAppInstallModal = () => { closeAppOperationModal = () => {
this.setState({ this.setState({
appInstallModalVisible: false appInstallModalVisible: false,
appUninstallModalVisible: false
});
};
showAppUninstallModal = () => {
this.setState({
appUninstallModalVisible: true
}); });
}; };
@ -112,8 +121,14 @@ class ReleaseView extends React.Component {
uuid={release.uuid} uuid={release.uuid}
visible={this.state.appInstallModalVisible} visible={this.state.appInstallModalVisible}
deviceType = {deviceType} deviceType = {deviceType}
onClose={this.closeAppInstallModal} onClose={this.closeAppOperationModal}
onInstall={this.installApp}/> onInstall={this.appOperation}/>
<AppUninstallModal
uuid={release.uuid}
visible={this.state.appUninstallModalVisible}
deviceType = {deviceType}
onClose={this.closeAppOperationModal}
onUninstall={this.appOperation}/>
<div className="release"> <div className="release">
<Row> <Row>
<Col xl={4} sm={6} xs={8} className="release-icon"> <Col xl={4} sm={6} xs={8} className="release-icon">
@ -138,6 +153,12 @@ class ReleaseView extends React.Component {
htmlType="button" type="primary" icon="download">Install</Button> htmlType="button" type="primary" icon="download">Install</Button>
</Button.Group> </Button.Group>
</div> </div>
<div>
<Button.Group style={{float: "right",marginRight:'3%'}}>
<Button onClick={this.showAppUninstallModal} loading={this.state.loading}
htmlType="button" type="primary" icon="delete">UnInstall</Button>
</Button.Group>
</div>
</Col> </Col>
</Row> </Row>
<Divider/> <Divider/>

@ -0,0 +1,61 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://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.
*/
import React from "react";
import {Modal, Tabs} from "antd";
import DeviceUninstall from "./DeviceUninstall";
import UserUninstall from "./UserUninstall";
import RoleUninstall from "./RoleUninstall";
import GroupUninstall from "./GroupUninstall";
const { TabPane } = Tabs;
class AppUninstallModal extends React.Component{
state={
data:[]
};
render() {
const {deviceType} = this.props;
return (
<div>
<Modal
title="Uninstall App"
visible={this.props.visible}
onCancel={this.props.onClose}
footer={null}
>
<Tabs defaultActiveKey="device">
<TabPane tab="Device" key="device">
<DeviceUninstall deviceType={deviceType} onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
</TabPane>
<TabPane tab="User" key="user">
<UserUninstall onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
</TabPane>
<TabPane tab="Role" key="role">
<RoleUninstall onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
</TabPane>
<TabPane tab="Group" key="group">
<GroupUninstall onUninstall={this.props.onUninstall} uuid={this.props.uuid}/>
</TabPane>
</Tabs>
</Modal>
</div>
);
}
}
export default AppUninstallModal;

@ -196,7 +196,7 @@ class DeviceInstall extends React.Component {
type: device.type type: device.type
}); });
}); });
this.props.onInstall("devices", payload); this.props.onInstall("devices", payload, "install");
}; };
render() { render() {

@ -0,0 +1,232 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://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.
*/
import React from "react";
import axios from "axios";
import {Button,Table, Typography} from "antd";
import TimeAgo from 'javascript-time-ago'
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en'
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
const {Text} = Typography;
const columns = [
{
title: 'Device',
dataIndex: 'name',
fixed: 'left',
width: 100,
},
{
title: 'Modal',
dataIndex: 'deviceInfo',
key: 'modal',
render: deviceInfo => `${deviceInfo.vendor} ${deviceInfo.deviceModel}`
// todo add filtering options
},
{
title: 'Owner',
dataIndex: 'enrolmentInfo',
key: 'owner',
render: enrolmentInfo => enrolmentInfo.owner
// todo add filtering options
},
{
title: 'Last Updated',
dataIndex: 'enrolmentInfo',
key: 'dateOfLastUpdate',
render: (data) => {
return (getTimeAgo(data.dateOfLastUpdate));
}
// todo add filtering options
},
{
title: 'Status',
dataIndex: 'enrolmentInfo',
key: 'status',
render: enrolmentInfo => enrolmentInfo.status
// todo add filtering options
},
{
title: 'Ownership',
dataIndex: 'enrolmentInfo',
key: 'ownership',
render: enrolmentInfo => enrolmentInfo.ownership
// todo add filtering options
},
{
title: 'OS Version',
dataIndex: 'deviceInfo',
key: 'osVersion',
render: deviceInfo => deviceInfo.osVersion
// todo add filtering options
},
{
title: 'IMEI',
dataIndex: 'properties',
key: 'imei',
render: properties => {
let imei = "not-found";
for (let i = 0; i < properties.length; i++) {
if (properties[i].name === "IMEI") {
imei = properties[i].value;
}
}
return imei;
}
// todo add filtering options
},
];
const getTimeAgo = (time) => {
const timeAgo = new TimeAgo('en-US');
return timeAgo.format(time);
};
class DeviceUninstall extends React.Component {
constructor(props) {
super(props);
TimeAgo.addLocale(en);
this.state = {
data: [],
pagination: {},
loading: false,
selectedRows: []
};
}
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRows: selectedRows
})
},
getCheckboxProps: record => ({
disabled: record.name === 'Disabled User', // Column configuration not to be checked
name: record.name,
}),
};
componentDidMount() {
this.fetch();
}
//fetch data from api
fetch = (params = {}) => {
const config = this.props.context;
this.setState({loading: true});
const {deviceType} = this.props;
// get current page
const currentPage = (params.hasOwnProperty("page")) ? params.page : 1;
const extraParams = {
offset: 10 * (currentPage - 1), //calculate the offset
limit: 10,
status: "ACTIVE",
};
if (deviceType !== 'ANY') {
extraParams.type = deviceType;
}
// note: encode with '%26' not '&'
const encodedExtraParams = Object.keys(extraParams).map(key => key + '=' + extraParams[key]).join('&');
const uuid = this.props.uuid;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/subscription/" + uuid + "/"+
"/devices?" + encodedExtraParams,
).then(res => {
if (res.status === 200) {
const pagination = {...this.state.pagination};
this.setState({
loading: false,
data: res.data.data.devices,
pagination,
});
}
}).catch((error) => {
handleApiError(error,"Error occurred while trying to load devices.");
this.setState({loading: false});
});
};
handleTableChange = (pagination, filters, sorter) => {
const pager = {...this.state.pagination};
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
};
uninstall = () => {
const {selectedRows} = this.state;
const payload = [];
selectedRows.map(device => {
payload.push({
id: device.deviceIdentifier,
type: device.type
});
});
this.props.onUninstall("devices", payload, "uninstall");
};
render() {
const {data, pagination, loading, selectedRows} = this.state;
return (
<div>
<Text>
Start uninstalling the application for devices by selecting the corresponding devices.
Select uninstall to automatically start uninstalling the application for the respective devices.
</Text>
<Table
style={{paddingTop: 20}}
columns={columns}
rowKey={record => record.deviceIdentifier}
dataSource={data}
pagination={{
...pagination,
size: "small",
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} devices`
}}
loading={loading}
onChange={this.handleTableChange}
rowSelection={this.rowSelection}
scroll={{x: 1000}}
/>
<div style={{paddingTop: 10, textAlign: "right"}}>
<Button disabled={selectedRows.length === 0} htmlType="button" type="primary"
onClick={this.uninstall}>
Uninstall
</Button>
</div>
</div>
);
}
}
export default withConfigContext(DeviceUninstall);

@ -47,7 +47,7 @@ class GroupInstall extends React.Component {
const config = this.props.context; const config = this.props.context;
this.setState({data: [], fetching: true}); this.setState({data: [], fetching: true});
axios.post( axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/groups?name=" + value, window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt+"/groups?name=" + value,
).then(res => { ).then(res => {
@ -85,7 +85,7 @@ class GroupInstall extends React.Component {
value.map(val=>{ value.map(val=>{
data.push(val.key); data.push(val.key);
}); });
this.props.onInstall("group",data); this.props.onInstall("group", data, "install");
}; };
render() { render() {

@ -0,0 +1,125 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://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.
*/
import React from "react";
import {Typography, Select, Spin, message, notification, Button} from "antd";
import debounce from 'lodash.debounce';
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
const {Text} = Typography;
const {Option} = Select;
class GroupUninstall extends React.Component {
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
state = {
data: [],
value: [],
fetching: false,
};
fetchUser = value => {
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
const config = this.props.context;
this.setState({data: [], fetching: true});
const uuid = this.props.uuid;
axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store+ "/subscription/" + uuid + "/"+
"/GROUP?",
).then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.deviceGroups.map(group => ({
text: group,
value: group,
}));
this.setState({data, fetching: false});
}
}).catch((error) => {
handleApiError(error,"Error occurred while trying to load groups.");
this.setState({fetching: false});
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
install = () =>{
const {value} = this.state;
const data = [];
value.map(val=>{
data.push(val.key);
});
this.props.onInstall("group", data, "uninstall");
};
render() {
const {fetching, data, value} = this.state;
return (
<div>
<Text>Start uninstalling the application for one or more groups by entering the corresponding group name. Select uninstall to automatically start uninstalling the application for the respective device group/ groups.</Text>
<br/>
<br/>
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Search groups"
notFoundContent={fetching ? <Spin size="small"/> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{width: '100%'}}
>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<div style={{paddingTop:10, textAlign:"right"}}>
<Button disabled={value.length===0} htmlType="button" type="primary" onClick={this.install}>Install</Button>
</div>
</div>
);
}
}
export default withConfigContext(GroupUninstall);

@ -85,7 +85,7 @@ class RoleInstall extends React.Component {
value.map(val=>{ value.map(val=>{
data.push(val.key); data.push(val.key);
}); });
this.props.onInstall("role",data); this.props.onInstall("role", data, "install");
}; };
render() { render() {

@ -0,0 +1,124 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://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.
*/
import React from "react";
import {Typography, Select, Spin, message, notification, Button} from "antd";
import debounce from 'lodash.debounce';
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
const {Text} = Typography;
const {Option} = Select;
class RoleUninstall extends React.Component {
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
state = {
data: [],
value: [],
fetching: false,
};
fetchUser = value => {
const config = this.props.context;
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
this.setState({data: [], fetching: true});
const uuid = this.props.uuid;
axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store+ "/subscription/" + uuid + "/"+
"/ROLE?",
).then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.roles.map(role => ({
text: role,
value: role,
}));
this.setState({data, fetching: false});
}
}).catch((error) => {
handleApiError(error,"Error occurred while trying to load roles.");
this.setState({fetching: false});
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
install = () =>{
const {value} = this.state;
const data = [];
value.map(val=>{
data.push(val.key);
});
this.props.onInstall("role", data, "uninstall");
};
render() {
const {fetching, data, value} = this.state;
return (
<div>
<Text>Start uninstalling the application for one or more roles by entering the corresponding role name. Select uninstall to automatically start uninstalling the application for the respective user role/roles.</Text>
<br/>
<br/>
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Search roles"
notFoundContent={fetching ? <Spin size="small"/> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{width: '100%'}}
>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<div style={{paddingTop:10, textAlign:"right"}}>
<Button disabled={value.length===0} htmlType="button" type="primary" onClick={this.install}>Install</Button>
</div>
</div>
);
}
}
export default withConfigContext(RoleUninstall);

@ -87,7 +87,7 @@ class UserInstall extends React.Component {
value.map(val => { value.map(val => {
data.push(val.key); data.push(val.key);
}); });
this.props.onInstall("user", data); this.props.onInstall("user", data, "install");
}; };
render() { render() {

@ -0,0 +1,122 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://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.
*/
import React from "react";
import {Typography, Select, Spin, message, notification, Button} from "antd";
import debounce from 'lodash.debounce';
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
import {handleApiError} from "../../../../js/Utils";
const {Text} = Typography;
const {Option} = Select;
class UserUninstall extends React.Component {
constructor(props) {
super(props);
this.lastFetchId = 0;
this.fetchUser = debounce(this.fetchUser, 800);
}
state = {
data: [],
value: [],
fetching: false,
};
fetchUser = (value) => {
const config = this.props.context;
this.lastFetchId += 1;
const fetchId = this.lastFetchId;
this.setState({data: [], fetching: true});
const uuid = this.props.uuid;
axios.get(
window.location.origin+ config.serverConfig.invoker.uri + config.serverConfig.invoker.store+ "/subscription/" + uuid + "/"+
"/USER?",
).then(res => {
if (res.status === 200) {
if (fetchId !== this.lastFetchId) {
// for fetch callback order
return;
}
const data = res.data.data.users.map(user => ({
text: user,
value: user,
}));
this.setState({data, fetching: false});
}
}).catch((error) => {
handleApiError(error,"Error occurred while trying to load users.");
this.setState({fetching: false});
});
};
handleChange = value => {
this.setState({
value,
data: [],
fetching: false,
});
};
uninstall = () => {
const {value} = this.state;
const data = [];
value.map(val => {
data.push(val.key);
});
this.props.onUninstall("user", data, "uninstall");
};
render() {
const {fetching, data, value} = this.state;
return (
<div>
<Text>Start uninstalling the application for one or more users by entering the corresponding user name. Select uninstall to automatically start uninstalling the application for the respective user/users. </Text>
<p>Select users</p>
<Select
mode="multiple"
labelInValue
value={value}
placeholder="Enter the username"
notFoundContent={fetching ? <Spin size="small"/> : null}
filterOption={false}
onSearch={this.fetchUser}
onChange={this.handleChange}
style={{width: '100%'}}
>
{data.map(d => (
<Option key={d.value}>{d.text}</Option>
))}
</Select>
<div style={{paddingTop: 10, textAlign: "right"}}>
<Button disabled={value.length===0} htmlType="button" type="primary" onClick={this.uninstall}>Uninstall</Button>
</div>
</div>
);
}
}
export default withConfigContext(UserUninstall);

@ -58,7 +58,7 @@
} }
.mobile-menu-button { .mobile-menu-button {
margin-top: 4%; margin-top: 15px;
} }
Header{ Header{
@ -68,7 +68,7 @@
} }
.dashboard-body{ .dashboard-body{
margin-top: 15%; margin-top: 50px;
} }
.logo-image { .logo-image {
@ -80,7 +80,7 @@
@media only screen and (max-height: 350px) { @media only screen and (max-height: 350px) {
.mobile-menu-button{ .mobile-menu-button{
margin-top: 2%; margin-top: 15px;
} }
.dashboard-body{ .dashboard-body{

@ -36,7 +36,9 @@ class Dashboard extends React.Component {
this.state = { this.state = {
routes: props.routes, routes: props.routes,
selectedKeys: [], selectedKeys: [],
deviceTypes: [] deviceTypes: [],
visible: false,
collapsed: false
}; };
this.logo = this.props.context.theme.logo; this.logo = this.props.context.theme.logo;
} }
@ -72,20 +74,14 @@ class Dashboard extends React.Component {
}) })
}; };
//functions for show the drawer showMobileNavigationBar = () => {
state = {
visible: false,
collapsed: false
};
showDrawer = () => {
this.setState({ this.setState({
visible: true, visible: true,
collapsed: !this.state.collapsed, collapsed: !this.state.collapsed,
}); });
}; };
onClose = () => { onCloseMobileNavigationBar = () => {
this.setState({ this.setState({
visible: false, visible: false,
}); });
@ -153,7 +149,7 @@ class Dashboard extends React.Component {
<Layout className="mobile-layout"> <Layout className="mobile-layout">
<div className="mobile-menu-button"> <div className="mobile-menu-button">
<Button type="link" onClick={this.showDrawer}> <Button type="link" onClick={this.showMobileNavigationBar}>
<Icon type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'} className="bar-icon"/> <Icon type={this.state.collapsed ? 'menu-fold' : 'menu-unfold'} className="bar-icon"/>
</Button> </Button>
</div> </div>
@ -163,7 +159,7 @@ class Dashboard extends React.Component {
</Link>} </Link>}
placement="left" placement="left"
closable={false} closable={false}
onClose={this.onClose} onClose={this.onCloseMobileNavigationBar}
visible={this.state.visible} visible={this.state.visible}
getContainer={false} getContainer={false}
style={{position: 'absolute'}} style={{position: 'absolute'}}

@ -29,8 +29,8 @@
<artifactId>io.entgra.device.mgt.ui</artifactId> <artifactId>io.entgra.device.mgt.ui</artifactId>
<version>3.2.9-SNAPSHOT</version> <version>3.2.9-SNAPSHOT</version>
<packaging>war</packaging> <packaging>war</packaging>
<name>WSO2 Carbon - Device Management UI Component</name> <name>Entgra - Device Management UI Component</name>
<url>http://wso2.org</url> <url>https://entgra.io</url>
<description>This Component contains Device Management UI</description> <description>This Component contains Device Management UI</description>
<dependencies> <dependencies>
</dependencies> </dependencies>

@ -562,4 +562,18 @@ public interface DeviceDAO {
*/ */
List<DeviceLocationHistory> getDeviceLocationInfo(DeviceIdentifier deviceIdentifier, long from, long to) List<DeviceLocationHistory> getDeviceLocationInfo(DeviceIdentifier deviceIdentifier, long from, long to)
throws DeviceManagementDAOException; throws DeviceManagementDAOException;
/**
* This method is used to get the details of subscribed devices.
*
* @param deviceIds device ids of the subscribed devices.
* @param tenantId Id of the current tenant.
* @param offsetValue offset value for get paginated request.
* @param limitValue limit value for get paginated request.
* @param status status of the devices.
* @return devices - subscribed device details list
* @throws {@link DeviceManagementDAOException} if connections establishment fails.
*/
List<Device> getSubscribedDevices(int offsetValue, int limitValue, List<Integer> deviceIds,
int tenantId, String status) throws DeviceManagementDAOException;
} }

@ -65,7 +65,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.StringJoiner; import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class AbstractDeviceDAOImpl implements DeviceDAO { public abstract class AbstractDeviceDAOImpl implements DeviceDAO {

@ -34,6 +34,7 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.StringJoiner;
/** /**
* This class holds the generic implementation of DeviceDAO which can be used to support ANSI db syntax. * This class holds the generic implementation of DeviceDAO which can be used to support ANSI db syntax.
@ -583,6 +584,67 @@ public class GenericDeviceDAOImpl extends AbstractDeviceDAOImpl {
return devices; return devices;
} }
@Override
public List<Device> getSubscribedDevices(int offsetValue, int limitValue,
List<Integer> deviceIds, int tenantId, String status)
throws DeviceManagementDAOException {
Connection conn;
try {
conn = this.getConnection();
int index = 1;
boolean isStatusProvided = false;
StringJoiner joiner = new StringJoiner(",",
"SELECT " +
"f.ID AS DEVICE_ID, f.NAME AS DEVICE_NAME, f.DESCRIPTION AS DESCRIPTION, " +
"f.DEVICE_TYPE_ID, f.DEVICE_IDENTIFICATION AS DEVICE_IDENTIFICATION, " +
"e.ID AS ENROLMENT_ID, e.OWNER, e.OWNERSHIP, e.DATE_OF_ENROLMENT, " +
"e.DATE_OF_LAST_UPDATE, e.STATUS, t.NAME AS DEVICE_TYPE " +
"FROM DM_ENROLMENT AS e,DM_DEVICE AS f, DM_DEVICE_TYPE t "+
"WHERE " +
"e.DEVICE_ID=f.ID AND " +
"e.DEVICE_ID IN (", ") AND e.TENANT_ID=?");
deviceIds.stream().map(ignored -> "?").forEach(joiner::add);
String query = joiner.toString();
if (status != null && !status.isEmpty()) {
query = query + " AND e.STATUS=?";
isStatusProvided = true;
}
query = query + " LIMIT ?,?";
try (PreparedStatement ps = conn.prepareStatement(query)) {
for (Integer deviceId : deviceIds) {
ps.setObject(index++, deviceId);
}
ps.setInt(index++, tenantId);
if (isStatusProvided) {
ps.setString(index++, status);
}
ps.setInt(index++, offsetValue);
ps.setInt(index, limitValue);
try (ResultSet rs = ps.executeQuery()) {
List<Device> devices = new ArrayList<>();
while (rs.next()) {
devices.add(DeviceManagementDAOUtil.loadDevice(rs));
}
return devices;
}
}
} catch (SQLException e) {
String msg = "Error occurred while retrieving information of all registered devices " +
"according to device ids and the limit area.";
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
}
private Connection getConnection() throws SQLException { private Connection getConnection() throws SQLException {
return DeviceManagementDAOFactory.getConnection(); return DeviceManagementDAOFactory.getConnection();
} }

@ -37,6 +37,7 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.StringJoiner;
/** /**
* This class holds the generic implementation of DeviceDAO which can be used to support ANSI db syntax. * This class holds the generic implementation of DeviceDAO which can be used to support ANSI db syntax.
@ -588,6 +589,67 @@ public class OracleDeviceDAOImpl extends AbstractDeviceDAOImpl {
return devices; return devices;
} }
@Override
public List<Device> getSubscribedDevices(int offsetValue, int limitValue,
List<Integer> deviceIds, int tenantId, String status)
throws DeviceManagementDAOException {
Connection conn;
try {
conn = this.getConnection();
int index = 1;
boolean isStatusProvided = false;
StringJoiner joiner = new StringJoiner(",",
"SELECT " +
"f.ID AS DEVICE_ID, f.NAME AS DEVICE_NAME, f.DESCRIPTION AS DESCRIPTION, " +
"f.DEVICE_TYPE_ID, f.DEVICE_IDENTIFICATION AS DEVICE_IDENTIFICATION, " +
"e.ID AS ENROLMENT_ID, e.OWNER, e.OWNERSHIP, e.DATE_OF_ENROLMENT, " +
"e.DATE_OF_LAST_UPDATE, e.STATUS, t.NAME AS DEVICE_TYPE " +
"FROM DM_ENROLMENT AS e,DM_DEVICE AS f, DM_DEVICE_TYPE t " +
"WHERE " +
"e.DEVICE_ID=f.ID AND " +
"e.DEVICE_ID IN (", ") AND e.TENANT_ID=?");
deviceIds.stream().map(ignored -> "?").forEach(joiner::add);
String query = joiner.toString();
if (status != null && !status.isEmpty()) {
query = query + " AND e.STATUS=?";
isStatusProvided = true;
}
query = query + " ORDER BY ENROLMENT_ID OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
try (PreparedStatement ps = conn.prepareStatement(query)) {
for (Integer deviceId : deviceIds) {
ps.setObject(index++, deviceId);
}
ps.setInt(index++, tenantId);
if (isStatusProvided) {
ps.setString(index++, status);
}
ps.setInt(index++, offsetValue);
ps.setInt(index, limitValue);
try (ResultSet rs = ps.executeQuery()) {
List<Device> devices = new ArrayList<>();
while (rs.next()) {
devices.add(DeviceManagementDAOUtil.loadDevice(rs));
}
return devices;
}
}
} catch (SQLException e) {
String msg = "Error occurred while retrieving information of all registered devices " +
"according to device ids and the limit area.";
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
}
private Connection getConnection() throws SQLException { private Connection getConnection() throws SQLException {
return DeviceManagementDAOFactory.getConnection(); return DeviceManagementDAOFactory.getConnection();
} }

@ -34,6 +34,7 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.StringJoiner;
/** /**
* This class holds the generic implementation of DeviceDAO which can be used to support ANSI db syntax. * This class holds the generic implementation of DeviceDAO which can be used to support ANSI db syntax.
@ -566,6 +567,67 @@ public class PostgreSQLDeviceDAOImpl extends AbstractDeviceDAOImpl {
return devices; return devices;
} }
@Override
public List<Device> getSubscribedDevices(int offsetValue, int limitValue,
List<Integer> deviceIds, int tenantId, String status)
throws DeviceManagementDAOException {
Connection conn;
try {
conn = this.getConnection();
int index = 1;
boolean isStatusProvided = false;
StringJoiner joiner = new StringJoiner(",",
"SELECT " +
"f.ID AS DEVICE_ID, f.NAME AS DEVICE_NAME, f.DESCRIPTION AS DESCRIPTION, " +
"f.DEVICE_TYPE_ID, f.DEVICE_IDENTIFICATION AS DEVICE_IDENTIFICATION, " +
"e.ID AS ENROLMENT_ID, e.OWNER, e.OWNERSHIP, e.DATE_OF_ENROLMENT, " +
"e.DATE_OF_LAST_UPDATE, e.STATUS, t.NAME AS DEVICE_TYPE " +
"FROM DM_ENROLMENT AS e,DM_DEVICE AS f, DM_DEVICE_TYPE t "+
"WHERE " +
"e.DEVICE_ID=f.ID AND " +
"e.DEVICE_ID IN (", ") AND e.TENANT_ID=?");
deviceIds.stream().map(ignored -> "?").forEach(joiner::add);
String query = joiner.toString();
if (status != null && !status.isEmpty()) {
query = query + " AND e.STATUS=?";
isStatusProvided = true;
}
query = query + " LIMIT ? OFFSET ?";
try (PreparedStatement ps = conn.prepareStatement(query)) {
for (Integer deviceId : deviceIds) {
ps.setObject(index++, deviceId);
}
ps.setInt(index++, tenantId);
if (isStatusProvided) {
ps.setString(index++, status);
}
ps.setInt(index++, offsetValue);
ps.setInt(index, limitValue);
try (ResultSet rs = ps.executeQuery()) {
List<Device> devices = new ArrayList<>();
while (rs.next()) {
devices.add(DeviceManagementDAOUtil.loadDevice(rs));
}
return devices;
}
}
} catch (SQLException e) {
String msg = "Error occurred while retrieving information of all registered devices " +
"according to device ids and the limit area.";
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
}
private Connection getConnection() throws SQLException { private Connection getConnection() throws SQLException {
return DeviceManagementDAOFactory.getConnection(); return DeviceManagementDAOFactory.getConnection();
} }

@ -34,6 +34,7 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.StringJoiner;
/** /**
* This class holds the generic implementation of DeviceDAO which can be used to support ANSI db syntax. * This class holds the generic implementation of DeviceDAO which can be used to support ANSI db syntax.
@ -521,6 +522,67 @@ public class SQLServerDeviceDAOImpl extends AbstractDeviceDAOImpl {
return devices; return devices;
} }
@Override
public List<Device> getSubscribedDevices(int offsetValue, int limitValue,
List<Integer> deviceIds, int tenantId, String status)
throws DeviceManagementDAOException {
Connection conn;
try {
conn = this.getConnection();
int index = 1;
boolean isStatusProvided = false;
StringJoiner joiner = new StringJoiner(",",
"SELECT " +
"f.ID AS DEVICE_ID, f.NAME AS DEVICE_NAME, f.DESCRIPTION AS DESCRIPTION, " +
"f.DEVICE_TYPE_ID, f.DEVICE_IDENTIFICATION AS DEVICE_IDENTIFICATION, " +
"e.ID AS ENROLMENT_ID, e.OWNER, e.OWNERSHIP, e.DATE_OF_ENROLMENT, " +
"e.DATE_OF_LAST_UPDATE, e.STATUS, t.NAME AS DEVICE_TYPE " +
"FROM DM_ENROLMENT AS e,DM_DEVICE AS f, DM_DEVICE_TYPE t "+
"WHERE " +
"e.DEVICE_ID=f.ID AND " +
"e.DEVICE_ID IN (", ") AND e.TENANT_ID=?");
deviceIds.stream().map(ignored -> "?").forEach(joiner::add);
String query = joiner.toString();
if (status != null && !status.isEmpty()) {
query = query + " AND e.STATUS=?";
isStatusProvided = true;
}
query = query + " LIMIT ?,?";
try (PreparedStatement ps = conn.prepareStatement(query)) {
for (Integer deviceId : deviceIds) {
ps.setObject(index++, deviceId);
}
ps.setInt(index++, tenantId);
if (isStatusProvided) {
ps.setString(index++, status);
}
ps.setInt(index++, offsetValue);
ps.setInt(index, limitValue);
try (ResultSet rs = ps.executeQuery()) {
List<Device> devices = new ArrayList<>();
while (rs.next()) {
devices.add(DeviceManagementDAOUtil.loadDevice(rs));
}
return devices;
}
}
} catch (SQLException e) {
String msg = "Error occurred while retrieving information of all registered devices " +
"according to device ids and the limit area.";
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
}
private Connection getConnection() throws SQLException { private Connection getConnection() throws SQLException {
return DeviceManagementDAOFactory.getConnection(); return DeviceManagementDAOFactory.getConnection();
} }

@ -759,7 +759,6 @@ public interface DeviceManagementProviderService {
DeviceTypeVersion getDeviceTypeVersion(String deviceTypeName, String version) throws DeviceTypeVersion getDeviceTypeVersion(String deviceTypeName, String version) throws
DeviceManagementException; DeviceManagementException;
/** /**
* Retrieves a list of configurations of a specific device * Retrieves a list of configurations of a specific device
* using the device's properties * using the device's properties
@ -782,4 +781,18 @@ public interface DeviceManagementProviderService {
* @return tru if device transferee, otherwise false * @return tru if device transferee, otherwise false
*/ */
List<String> transferDeviceToTenant(DeviceTransferRequest deviceTransferRequest) throws DeviceManagementException, DeviceNotFoundException; List<String> transferDeviceToTenant(DeviceTransferRequest deviceTransferRequest) throws DeviceManagementException, DeviceNotFoundException;
/**
* This method retrieves a list of subscribed devices.
*
* @param devicesIds devices ids of the subscribed devices.
* @param offsetValue offset value for get paginated request.
* @param limitValue limit value for get paginated request.
* @param status status of the devices.
* @return {@link PaginationResult}
* @throws {@link DeviceManagementException} if any service level or DAO level error occurs.
*/
PaginationResult getAppSubscribedDevices(int offsetValue, int limitValue,
List<Integer> devicesIds, String status)
throws DeviceManagementException;
} }

@ -3665,4 +3665,39 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv
deviceConfiguration.setDeviceOwner(deviceOwner); deviceConfiguration.setDeviceOwner(deviceOwner);
return deviceConfiguration; return deviceConfiguration;
} }
@Override
public PaginationResult getAppSubscribedDevices(int offsetValue, int limitValue,
List<Integer> devicesIds, String status)
throws DeviceManagementException {
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
if (log.isDebugEnabled()) {
log.debug("Getting all devices details for device ids: " + devicesIds);
}
PaginationResult paginationResult = new PaginationResult();
int count;
List<Device> SubscribedDeviceDetails;
try {
DeviceManagementDAOFactory.openConnection();
SubscribedDeviceDetails = deviceDAO
.getSubscribedDevices(offsetValue, limitValue, devicesIds, tenantId, status);
count = SubscribedDeviceDetails.size();
} catch (DeviceManagementDAOException e) {
String msg = "Error occurred while retrieving device list for device ids " + devicesIds;
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);
} finally {
DeviceManagementDAOFactory.closeConnection();
}
paginationResult.setData(getAllDeviceInfo(SubscribedDeviceDetails));
paginationResult.setRecordsFiltered(count);
paginationResult.setRecordsTotal(count);
return paginationResult;
}
} }

Loading…
Cancel
Save