From 1e191cdf8b12b44827b3b879a23264ddbda731b4 Mon Sep 17 00:00:00 2001
From: amalhub <amalg@wso2.com>
Date: Mon, 11 Sep 2017 14:27:33 +0530
Subject: [PATCH 1/2] Adding Device-Application mapping implementation

---
 .../services/SubscriptionManagementAPI.java   | 88 +++++++++++++++++--
 .../impl/SubscriptionManagementAPIImpl.java   | 15 +++-
 .../src/main/webapp/META-INF/permissions.xml  | 12 +++
 .../mgt/common/DeviceIdentifier.java          |  2 +-
 .../mgt/core/dao/SubscriptionDAO.java         |  3 +
 .../mgt/core/dao/common/DAOFactory.java       | 20 +++++
 .../GenericSubscriptionDAOImpl.java           | 59 +++++++++++++
 .../core/impl/SubscriptionManagerImpl.java    | 33 +++++--
 .../dbscripts/cdm/application-mgt/mysql.sql   | 13 +++
 9 files changed, 233 insertions(+), 12 deletions(-)
 create mode 100644 components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/impl/subscription/GenericSubscriptionDAOImpl.java

diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.api/src/main/java/org/wso2/carbon/device/application/mgt/api/services/SubscriptionManagementAPI.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.api/src/main/java/org/wso2/carbon/device/application/mgt/api/services/SubscriptionManagementAPI.java
index d68cfb92353..570acb449cc 100644
--- a/components/application-mgt/org.wso2.carbon.device.application.mgt.api/src/main/java/org/wso2/carbon/device/application/mgt/api/services/SubscriptionManagementAPI.java
+++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.api/src/main/java/org/wso2/carbon/device/application/mgt/api/services/SubscriptionManagementAPI.java
@@ -24,10 +24,7 @@ import org.wso2.carbon.device.application.mgt.common.Application;
 import org.wso2.carbon.device.application.mgt.common.InstallationDetails;
 
 import javax.validation.Valid;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
+import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
@@ -111,7 +108,88 @@ public interface SubscriptionManagementAPI {
     Response installApplication(
             @ApiParam(
                     name = "installationDetails",
-                    value = "The application ID and list the devices/users/roles",
+                    value = "The application ID and list of devices/users/roles",
                     required = true)
             @Valid InstallationDetails installationDetails);
+
+    @POST
+    @Path("/uninstall-application")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @ApiOperation(
+            consumes = MediaType.APPLICATION_JSON,
+            produces = MediaType.APPLICATION_JSON,
+            httpMethod = "POST",
+            value = "Uninstall an application",
+            notes = "This will uninstall an application to a given list of devices/users/roles",
+            tags = "Subscription Management",
+            extensions = {
+                    @Extension(properties = {
+                            @ExtensionProperty(name = SCOPE, value = "perm:subscription:uninstall")
+                    })
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(
+                            code = 200,
+                            message = "OK. \n Successfully uninstalled the application.",
+                            response = Application.class),
+                    @ApiResponse(
+                            code = 304,
+                            message = "Not Modified. \n " +
+                                    "Empty body because the application is already uninstalled."),
+                    @ApiResponse(
+                            code = 500,
+                            message = "Internal Server Error. \n Error occurred while installing the application.",
+                            response = ErrorResponse.class)
+            })
+    Response uninstallApplication(
+            @ApiParam(
+                    name = "installationDetails",
+                    value = "The application ID and list of devices/users/roles",
+                    required = true)
+            @Valid InstallationDetails installationDetails);
+
+    @GET
+    @Path("/application/{applicationUUID}/device/{deviceId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @ApiOperation(
+            consumes = MediaType.APPLICATION_JSON,
+            produces = MediaType.APPLICATION_JSON,
+            httpMethod = "GET",
+            value = "Get an application",
+            notes = "This will return an application to a given valid token",
+            tags = "Subscription Management",
+            extensions = {
+                    @Extension(properties = {
+                            @ExtensionProperty(name = SCOPE, value = "perm:subscription:getApplication")
+                    })
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(
+                            code = 200,
+                            message = "OK. \n Successfully installed the application.",
+                            response = Application.class),
+                    @ApiResponse(
+                            code = 304,
+                            message = "Not Modified. \n " +
+                                    "Empty body because the application is already installed."),
+                    @ApiResponse(
+                            code = 500,
+                            message = "Internal Server Error. \n Error occurred while fetching the application.",
+                            response = ErrorResponse.class)
+            })
+    Response getApplication(
+            @ApiParam(
+                    name = "applicationUUID",
+                    value = "Application ID")
+            @QueryParam("applicationUUID") String applicationUUID,
+            @ApiParam(
+                    name = "deviceId",
+                    value = "The device ID")
+            @QueryParam("deviceId") String deviceId);
 }
diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.api/src/main/java/org/wso2/carbon/device/application/mgt/api/services/impl/SubscriptionManagementAPIImpl.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.api/src/main/java/org/wso2/carbon/device/application/mgt/api/services/impl/SubscriptionManagementAPIImpl.java
index 5c521487de2..881da552bc4 100644
--- a/components/application-mgt/org.wso2.carbon.device.application.mgt.api/src/main/java/org/wso2/carbon/device/application/mgt/api/services/impl/SubscriptionManagementAPIImpl.java
+++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.api/src/main/java/org/wso2/carbon/device/application/mgt/api/services/impl/SubscriptionManagementAPIImpl.java
@@ -64,9 +64,22 @@ public class SubscriptionManagementAPIImpl implements SubscriptionManagementAPI{
             }
             return Response.status(Response.Status.OK).entity(response).build();
         } catch (ApplicationManagementException e) {
-            String msg = "Error occurred while creating the application";
+            String msg = "Error occurred while installing the application";
             log.error(msg, e);
             return Response.status(Response.Status.BAD_REQUEST).build();
         }
     }
+
+    @Override
+    public Response uninstallApplication(@ApiParam(name = "installationDetails", value = "The application ID and list" +
+            " of devices/users/roles", required = true) @Valid InstallationDetails installationDetails) {
+        return null;
+    }
+
+    @Override
+    public Response getApplication(@ApiParam(name = "applicationUUID", value = "Application ID") String
+                                               applicationUUID, @ApiParam(name = "deviceId", value = "The device ID")
+            String deviceId) {
+        return null;
+    }
 }
diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.api/src/main/webapp/META-INF/permissions.xml b/components/application-mgt/org.wso2.carbon.device.application.mgt.api/src/main/webapp/META-INF/permissions.xml
index 27df5059a3d..df2d1cf5b58 100644
--- a/components/application-mgt/org.wso2.carbon.device.application.mgt.api/src/main/webapp/META-INF/permissions.xml
+++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.api/src/main/webapp/META-INF/permissions.xml
@@ -95,4 +95,16 @@
         <url>/application-mgt/subscription</url>
         <method>POST</method>
     </Permission>
+    <Permission>
+        <name>Uninstall Application</name>
+        <path>/device-mgt/subscription/uninstall</path>
+        <url>/application-mgt/subscription</url>
+        <method>POST</method>
+    </Permission>
+    <Permission>
+        <name>Get Application</name>
+        <path>/device-mgt/subscription/getApplication</path>
+        <url>/application-mgt/subscription</url>
+        <method>GET</method>
+    </Permission>
 </PermissionConfiguration>
diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/DeviceIdentifier.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/DeviceIdentifier.java
index 3da016bf04a..8f7e656319e 100644
--- a/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/DeviceIdentifier.java
+++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.common/src/main/java/org/wso2/carbon/device/application/mgt/common/DeviceIdentifier.java
@@ -29,7 +29,7 @@ public class DeviceIdentifier {
             name = "id",
             value = "Identity of the device.",
             required = true,
-            example = "123456")
+            example = "d24f870f390352a4")
     private String id;
 
     @ApiModelProperty(
diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/SubscriptionDAO.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/SubscriptionDAO.java
index 010e50203be..59f3b532f10 100644
--- a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/SubscriptionDAO.java
+++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/SubscriptionDAO.java
@@ -22,5 +22,8 @@ package org.wso2.carbon.device.application.mgt.core.dao;
  * This interface provides the list of operations that are supported with subscription database.
  *
  */
+import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException;
+
 public interface SubscriptionDAO {
+    int addDeviceApplicationMapping(String deviceIdentifier, String applicationUUID, boolean installed) throws ApplicationManagementException;
 }
diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/common/DAOFactory.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/common/DAOFactory.java
index 51a4b393473..4016192d141 100644
--- a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/common/DAOFactory.java
+++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/common/DAOFactory.java
@@ -26,6 +26,7 @@ import org.wso2.carbon.device.application.mgt.core.dao.ApplicationDAO;
 import org.wso2.carbon.device.application.mgt.core.dao.ApplicationReleaseDAO;
 import org.wso2.carbon.device.application.mgt.core.dao.LifecycleStateDAO;
 import org.wso2.carbon.device.application.mgt.core.dao.PlatformDAO;
+import org.wso2.carbon.device.application.mgt.core.dao.SubscriptionDAO;
 import org.wso2.carbon.device.application.mgt.core.dao.VisibilityDAO;
 import org.wso2.carbon.device.application.mgt.core.dao.impl.application.GenericApplicationDAOImpl;
 import org.wso2.carbon.device.application.mgt.core.dao.impl.application.release.GenericApplicationReleaseDAOImpl;
@@ -34,6 +35,7 @@ import org.wso2.carbon.device.application.mgt.core.dao.impl.lifecyclestate.Gener
 import org.wso2.carbon.device.application.mgt.core.dao.impl.platform.GenericPlatformDAOImpl;
 import org.wso2.carbon.device.application.mgt.core.dao.impl.platform.OracleMsSQLPlatformDAOImpl;
 import org.wso2.carbon.device.application.mgt.core.dao.impl.visibility.GenericVisibilityDAOImpl;
+import org.wso2.carbon.device.application.mgt.core.dao.impl.subscription.GenericSubscriptionDAOImpl;
 import org.wso2.carbon.device.application.mgt.core.exception.ApplicationManagementDAOException;
 import org.wso2.carbon.device.application.mgt.core.util.ApplicationMgtDatabaseCreator;
 import org.wso2.carbon.device.application.mgt.core.util.ConnectionManagerUtil;
@@ -144,6 +146,24 @@ public class DAOFactory {
         throw new IllegalStateException("Database engine has not initialized properly.");
     }
 
+    /**
+     * To get the instance of SubscriptionDAOImplementation of the particular database engine.
+     * @return GenericSubscriptionDAOImpl
+     */
+    public static SubscriptionDAO getSubscriptionDAO() {
+        if (databaseEngine != null) {
+            switch (databaseEngine) {
+                case Constants.DataBaseTypes.DB_TYPE_H2:
+                case Constants.DataBaseTypes.DB_TYPE_MYSQL:
+                case Constants.DataBaseTypes.DB_TYPE_POSTGRESQL:
+                    return new GenericSubscriptionDAOImpl();
+                default:
+                    throw new UnsupportedDatabaseEngineException("Unsupported database engine : " + databaseEngine);
+            }
+        }
+        throw new IllegalStateException("Database engine has not initialized properly.");
+    }
+
     /**
      * This method initializes the databases by creating the database.
      *
diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/impl/subscription/GenericSubscriptionDAOImpl.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/impl/subscription/GenericSubscriptionDAOImpl.java
new file mode 100644
index 00000000000..b00b0499af1
--- /dev/null
+++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/impl/subscription/GenericSubscriptionDAOImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
+ *
+ * WSO2 Inc. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.wso2.carbon.device.application.mgt.core.dao.impl.subscription;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException;
+import org.wso2.carbon.device.application.mgt.core.dao.SubscriptionDAO;
+import org.wso2.carbon.device.application.mgt.core.dao.common.Util;
+import org.wso2.carbon.device.application.mgt.core.dao.impl.AbstractDAOImpl;
+
+import java.sql.*;
+
+public class GenericSubscriptionDAOImpl extends AbstractDAOImpl implements SubscriptionDAO {
+    private static Log log = LogFactory.getLog(GenericSubscriptionDAOImpl.class);
+
+    @Override
+    public int addDeviceApplicationMapping(String deviceIdentifier, String applicationUUID, boolean installed) throws ApplicationManagementException {
+        Connection conn;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        int mappingId = -1;
+        try {
+            conn = this.getDBConnection();
+            String sql = "INSERT INTO APPM_DEVICE_APPLICATION_MAPPING (DEVICE_IDENTIFIER, APPLICATION_UUID, " +
+                    "INSTALLED) VALUES (?, ?, ?)";
+            stmt = conn.prepareStatement(sql, new String[] {"id"});
+            stmt.setString(1, deviceIdentifier);
+            stmt.setString(2, applicationUUID);
+            stmt.setBoolean(3, installed);
+            stmt.executeUpdate();
+
+            rs = stmt.getGeneratedKeys();
+            if (rs.next()) {
+                mappingId = rs.getInt(1);
+            }
+            return mappingId;
+        } catch (SQLException e) {
+            throw new ApplicationManagementException("Error occurred while adding device application mapping to DB", e);
+        } finally {
+            Util.cleanupResources(stmt, rs);
+        }
+    }
+}
diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/impl/SubscriptionManagerImpl.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/impl/SubscriptionManagerImpl.java
index 0c89f631149..07eedb19806 100644
--- a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/impl/SubscriptionManagerImpl.java
+++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/impl/SubscriptionManagerImpl.java
@@ -22,7 +22,12 @@ import org.apache.commons.logging.LogFactory;
 import org.wso2.carbon.device.application.mgt.common.DeviceIdentifier;
 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.core.dao.common.DAOFactory;
+import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
+import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOFactory;
 
+import java.sql.SQLException;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -37,13 +42,29 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
                                                                List<DeviceIdentifier> deviceList)
             throws ApplicationManagementException {
         log.info("Install application: " + applicationUUID + " to: " + deviceList.size() + " devices.");
+        List<DeviceIdentifier> failedDeviceList = new ArrayList<>(deviceList);
         for (DeviceIdentifier device : deviceList) {
-            String deviceId = device.getId();
-            //Todo: implementation, validations
-            //Todo: generating one time download link for the application and put install operation to device.
-            //Todo: Store the mappings in DB.
+            org.wso2.carbon.device.mgt.common.DeviceIdentifier deviceIdentifier = new org.wso2.carbon.device.mgt
+                    .common.DeviceIdentifier(device.getId(), device.getType());
+            try {
+                DeviceManagementDAOFactory.openConnection();
+                if (DeviceManagementDAOFactory.getDeviceDAO().getDevice(deviceIdentifier).isEmpty()) {
+                    log.error("Device with ID: " + device.getId() + " not found to install the application.");
+                } else {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Installing application to : " + device.getId());
+                    }
+                    //Todo: generating one time download link for the application and put install operation to device.
+                    DAOFactory.getSubscriptionDAO().addDeviceApplicationMapping(device.getId(), applicationUUID, false);
+                    failedDeviceList.remove(device);
+                }
+            } catch (DeviceManagementDAOException | SQLException e) {
+                throw new ApplicationManagementException("Error locating device.", e);
+            } finally {
+                DeviceManagementDAOFactory.closeConnection();
+            }
         }
-        return deviceList;
+        return failedDeviceList;
     }
 
     @Override
@@ -52,6 +73,7 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
         log.info("Install application: " + applicationUUID + " to: " + userList.size() + " users.");
         for (String user : userList) {
             //Todo: implementation
+            //Todo: get the device list and call installApplicationForDevices
         }
         return userList;
     }
@@ -62,6 +84,7 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
         log.info("Install application: " + applicationUUID + " to: " + roleList.size() + " users.");
         for (String role : roleList) {
             //Todo: implementation
+            //Todo: get the device list and call installApplicationForDevices
         }
         return roleList;
     }
diff --git a/features/application-mgt/org.wso2.carbon.device.application.mgt.server.feature/src/main/resources/dbscripts/cdm/application-mgt/mysql.sql b/features/application-mgt/org.wso2.carbon.device.application.mgt.server.feature/src/main/resources/dbscripts/cdm/application-mgt/mysql.sql
index c8ecd3c81a5..1d840f1e2a7 100644
--- a/features/application-mgt/org.wso2.carbon.device.application.mgt.server.feature/src/main/resources/dbscripts/cdm/application-mgt/mysql.sql
+++ b/features/application-mgt/org.wso2.carbon.device.application.mgt.server.feature/src/main/resources/dbscripts/cdm/application-mgt/mysql.sql
@@ -390,6 +390,19 @@ CREATE TABLE IF NOT EXISTS `APPM_SUBSCRIPTION_PROPERTIES` (
     ON UPDATE NO ACTION)
   ENGINE = InnoDB;
 
+-- -----------------------------------------------------
+-- Table `APPM_DEVICE_APPLICATION_MAPPING`
+-- -----------------------------------------------------
+CREATE TABLE IF NOT EXISTS `APPM_DEVICE_APPLICATION_MAPPING` (
+  `ID` INT AUTO_INCREMENT NOT NULL,
+  `DEVICE_IDENTIFIER`  VARCHAR(255) NOT NULL,
+  `APPLICATION_UUID` VARCHAR(100) NOT NULL,
+  `INSTALLED` BOOLEAN NOT NULL,
+  `SENT_AT` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (ID),
+  CONSTRAINT fk_appm_application FOREIGN KEY (`APPLICATION_UUID`) REFERENCES
+  APPM_APPLICATION (`UUID`) ON DELETE NO ACTION ON UPDATE NO ACTION
+) ENGINE = InnoDB;
 
 SET SQL_MODE=@OLD_SQL_MODE;
 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;

From ae80bd8ba8a1ad94de736596becd9077fad25721 Mon Sep 17 00:00:00 2001
From: amalhub <amalg@wso2.com>
Date: Mon, 11 Sep 2017 15:01:36 +0530
Subject: [PATCH 2/2] App-mgt store mapping validation

---
 .../GenericSubscriptionDAOImpl.java           | 34 +++++++++++++------
 .../dbscripts/cdm/application-mgt/mysql.sql   |  5 +--
 2 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/impl/subscription/GenericSubscriptionDAOImpl.java b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/impl/subscription/GenericSubscriptionDAOImpl.java
index b00b0499af1..b2d2b520d11 100644
--- a/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/impl/subscription/GenericSubscriptionDAOImpl.java
+++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.core/src/main/java/org/wso2/carbon/device/application/mgt/core/dao/impl/subscription/GenericSubscriptionDAOImpl.java
@@ -30,26 +30,40 @@ public class GenericSubscriptionDAOImpl extends AbstractDAOImpl implements Subsc
     private static Log log = LogFactory.getLog(GenericSubscriptionDAOImpl.class);
 
     @Override
-    public int addDeviceApplicationMapping(String deviceIdentifier, String applicationUUID, boolean installed) throws ApplicationManagementException {
+    public int addDeviceApplicationMapping(String deviceIdentifier, String applicationUUID, boolean installed) throws
+            ApplicationManagementException {
         Connection conn;
         PreparedStatement stmt = null;
         ResultSet rs = null;
         int mappingId = -1;
         try {
             conn = this.getDBConnection();
-            String sql = "INSERT INTO APPM_DEVICE_APPLICATION_MAPPING (DEVICE_IDENTIFIER, APPLICATION_UUID, " +
-                    "INSTALLED) VALUES (?, ?, ?)";
-            stmt = conn.prepareStatement(sql, new String[] {"id"});
+            String sql = "SELECT ID FROM APPM_DEVICE_APPLICATION_MAPPING WHERE DEVICE_IDENTIFIER = ? AND " +
+                    "APPLICATION_UUID = ?";
+            stmt = conn.prepareStatement(sql);
             stmt.setString(1, deviceIdentifier);
             stmt.setString(2, applicationUUID);
-            stmt.setBoolean(3, installed);
-            stmt.executeUpdate();
+            rs = stmt.executeQuery();
 
-            rs = stmt.getGeneratedKeys();
-            if (rs.next()) {
-                mappingId = rs.getInt(1);
+            if (!rs.next()) {
+                sql = "INSERT INTO APPM_DEVICE_APPLICATION_MAPPING (DEVICE_IDENTIFIER, APPLICATION_UUID, " +
+                        "INSTALLED) VALUES (?, ?, ?)";
+                stmt = conn.prepareStatement(sql, new String[]{"id"});
+                stmt.setString(1, deviceIdentifier);
+                stmt.setString(2, applicationUUID);
+                stmt.setBoolean(3, installed);
+                stmt.executeUpdate();
+
+                rs = stmt.getGeneratedKeys();
+                if (rs.next()) {
+                    mappingId = rs.getInt(1);
+                }
+                return mappingId;
+            } else {
+                log.warn("Device[" + deviceIdentifier + "] application[" + applicationUUID + "] mapping already " +
+                        "exists in the DB");
+                return -1;
             }
-            return mappingId;
         } catch (SQLException e) {
             throw new ApplicationManagementException("Error occurred while adding device application mapping to DB", e);
         } finally {
diff --git a/features/application-mgt/org.wso2.carbon.device.application.mgt.server.feature/src/main/resources/dbscripts/cdm/application-mgt/mysql.sql b/features/application-mgt/org.wso2.carbon.device.application.mgt.server.feature/src/main/resources/dbscripts/cdm/application-mgt/mysql.sql
index 1d840f1e2a7..a5f5bbd5a51 100644
--- a/features/application-mgt/org.wso2.carbon.device.application.mgt.server.feature/src/main/resources/dbscripts/cdm/application-mgt/mysql.sql
+++ b/features/application-mgt/org.wso2.carbon.device.application.mgt.server.feature/src/main/resources/dbscripts/cdm/application-mgt/mysql.sql
@@ -400,8 +400,9 @@ CREATE TABLE IF NOT EXISTS `APPM_DEVICE_APPLICATION_MAPPING` (
   `INSTALLED` BOOLEAN NOT NULL,
   `SENT_AT` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
   PRIMARY KEY (ID),
-  CONSTRAINT fk_appm_application FOREIGN KEY (`APPLICATION_UUID`) REFERENCES
-  APPM_APPLICATION (`UUID`) ON DELETE NO ACTION ON UPDATE NO ACTION
+  CONSTRAINT `fk_appm_application` FOREIGN KEY (`APPLICATION_UUID`) REFERENCES
+  APPM_APPLICATION (`UUID`) ON DELETE NO ACTION ON UPDATE NO ACTION,
+  UNIQUE KEY `device_app_mapping` (`DEVICE_IDENTIFIER`, `APPLICATION_UUID`)
 ) ENGINE = InnoDB;
 
 SET SQL_MODE=@OLD_SQL_MODE;