Application list saving method and device operation response saving

revert-70aa11f8
manoj 10 years ago
parent 0e18ebcf4e
commit 82cbbef83b

@ -46,4 +46,11 @@ public class DeviceIdentifier implements Serializable{
this.id = id;
}
@Override
public String toString() {
return "DeviceIdentifier{" +
"id='" + id + '\'' +
", type='" + type + '\'' +
'}';
}
}

@ -41,7 +41,8 @@ public interface ApplicationManager {
* @throws ApplicationManagementException
*/
Application[] getApplications(String domain, int pageNumber, int size) throws ApplicationManagementException;
Application[] getApplications(String domain, int pageNumber, int size)
throws ApplicationManagementException;
/**
@ -51,8 +52,8 @@ public interface ApplicationManager {
* @param application Application details of the app being updated.
* @param status Installed/Uninstalled
*/
void updateApplicationStatus(DeviceIdentifier deviceId, Application application,
String status) throws ApplicationManagementException;
void updateApplicationStatus(DeviceIdentifier deviceId, Application application, String status)
throws ApplicationManagementException;
/**
* Retrieve the status of an application on a device. Whether it is installed or not.
@ -61,11 +62,13 @@ public interface ApplicationManager {
* @param application Application details of the app being searched.
* @return Status of the application on the device.
*/
String getApplicationStatus(DeviceIdentifier deviceId,
Application application) throws ApplicationManagementException;
String getApplicationStatus(DeviceIdentifier deviceId, Application application)
throws ApplicationManagementException;
void installApplication(Operation operation,
List<DeviceIdentifier> deviceIdentifiers) throws ApplicationManagementException;
void installApplication(Operation operation, List<DeviceIdentifier> deviceIdentifiers)
throws ApplicationManagementException;
void updateApplicationsForDevice(DeviceIdentifier deviceIdentifier, List<Application> applications)
throws ApplicationManagementException;
}

@ -43,6 +43,7 @@ public class Operation implements Serializable {
private String createdTimeStamp;
private boolean isEnabled;
private Object payLoad;
private Object operationResponse;
@Override
public boolean equals(Object o) {
@ -73,6 +74,10 @@ public class Operation implements Serializable {
if (payLoad != null ? !payLoad.equals(operation.payLoad) : operation.payLoad != null) {
return false;
}
if (operationResponse != null ? !operationResponse
.equals(operation.operationResponse) : operation.operationResponse != null) {
return false;
}
if (properties != null ? !properties.equals(operation.properties) : operation.properties != null) {
return false;
}
@ -85,7 +90,6 @@ public class Operation implements Serializable {
if (type != operation.type) {
return false;
}
return true;
}
@ -100,6 +104,7 @@ public class Operation implements Serializable {
result = 31 * result + (createdTimeStamp != null ? createdTimeStamp.hashCode() : 0);
result = 31 * result + (isEnabled ? 1 : 0);
result = 31 * result + (payLoad != null ? payLoad.hashCode() : 0);
result = 31 * result + (operationResponse != null ? operationResponse.hashCode() : 0);
return result;
}
@ -178,6 +183,14 @@ public class Operation implements Serializable {
this.payLoad = payLoad;
}
public Object getOperationResponse() {
return operationResponse;
}
public void setOperationResponse(Object operationResponse) {
this.operationResponse = operationResponse;
}
@Override
public String toString() {
return "Operation{" +

@ -59,8 +59,7 @@ public interface OperationManager {
public Operation getNextPendingOperation(DeviceIdentifier deviceId) throws OperationManagementException;
public void updateOperation(DeviceIdentifier deviceId, int operationId, Operation.Status operationStatus) throws
OperationManagementException;
public void updateOperation(DeviceIdentifier deviceId, Operation operation) throws OperationManagementException;
public void deleteOperation(int operationId) throws OperationManagementException;

@ -65,4 +65,10 @@ public class ApplicationManagementServiceImpl implements ApplicationManager {
DeviceManagementDataHolder.getInstance().getAppManager().installApplication(operation, deviceIdentifiers);
}
@Override
public void updateApplicationsForDevice(DeviceIdentifier deviceIdentifier, List<Application> applications)
throws ApplicationManagementException {
DeviceManagementDataHolder.getInstance().getAppManager().updateApplicationsForDevice(deviceIdentifier,applications);
}
}

@ -31,7 +31,7 @@ public class ApplicationManagerFactory {
}
public static ApplicationManager getConnector(AppManagementConfig config) {
return new RemoteApplicationManager(config, pluginRepository);
return new ApplicationManagerProviderServiceImpl(config, pluginRepository);
}
}

@ -23,8 +23,10 @@ import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.app.mgt.Application;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.app.mgt.Application;
import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException;
import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager;
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
@ -35,6 +37,9 @@ import org.wso2.carbon.device.mgt.core.app.mgt.config.AppManagementConfig;
import org.wso2.carbon.device.mgt.core.app.mgt.oauth.ServiceAuthenticator;
import org.wso2.carbon.device.mgt.core.config.DeviceConfigurationManager;
import org.wso2.carbon.device.mgt.core.config.identity.IdentityConfigurations;
import org.wso2.carbon.device.mgt.core.dao.DeviceDAO;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOFactory;
import org.wso2.carbon.identity.oauth.stub.OAuthAdminServiceException;
import org.wso2.carbon.identity.oauth.stub.OAuthAdminServiceStub;
import org.wso2.carbon.identity.oauth.stub.dto.OAuthConsumerAppDTO;
@ -43,21 +48,23 @@ import java.rmi.RemoteException;
import java.util.List;
/**
* Implements AppManagerConnector interface
* Implements Application Manager interface
*
*/
public class RemoteApplicationManager implements ApplicationManager {
public class ApplicationManagerProviderServiceImpl implements ApplicationManager {
private ConfigurationContext configCtx;
private ServiceAuthenticator authenticator;
private String oAuthAdminServiceUrl;
private DeviceManagementPluginRepository pluginRepository;
private DeviceDAO deviceDAO;
private static final String GET_APP_LIST_URL = "store/apis/assets/mobileapp?domain=carbon.super&page=1";
private static final Log log = LogFactory.getLog(RemoteApplicationManager.class);
private static final Log log = LogFactory.getLog(ApplicationManagerProviderServiceImpl.class);
public RemoteApplicationManager(AppManagementConfig appManagementConfig,
DeviceManagementPluginRepository pluginRepository) {
public ApplicationManagerProviderServiceImpl(AppManagementConfig appManagementConfig,
DeviceManagementPluginRepository pluginRepository) {
IdentityConfigurations identityConfig = DeviceConfigurationManager.getInstance().getDeviceManagementConfig().
getDeviceManagementConfigRepository().getIdentityConfigurations();
@ -72,11 +79,12 @@ public class RemoteApplicationManager implements ApplicationManager {
"Please check if an appropriate axis2.xml is provided", e);
}
this.pluginRepository = pluginRepository;
this.deviceDAO = DeviceManagementDAOFactory.getDeviceDAO();
}
@Override
public Application[] getApplications(String domain, int pageNumber,
int size) throws ApplicationManagementException {
public Application[] getApplications(String domain, int pageNumber, int size)
throws ApplicationManagementException {
return new Application[0];
}
@ -88,7 +96,7 @@ public class RemoteApplicationManager implements ApplicationManager {
@Override
public String getApplicationStatus(DeviceIdentifier deviceId,
Application application) throws ApplicationManagementException {
Application application) throws ApplicationManagementException {
return null;
}
@ -96,11 +104,26 @@ public class RemoteApplicationManager implements ApplicationManager {
public void installApplication(Operation operation, List<DeviceIdentifier> deviceIds)
throws ApplicationManagementException {
for(DeviceIdentifier deviceId: deviceIds){
DeviceManagementService dms =
this.getPluginRepository().getDeviceManagementService(deviceId.getType());
dms.installApplication(operation, deviceIds);
}
for (DeviceIdentifier deviceId : deviceIds) {
DeviceManagementService dms =
this.getPluginRepository().getDeviceManagementService(deviceId.getType());
dms.installApplication(operation, deviceIds);
}
}
@Override
public void updateApplicationsForDevice(DeviceIdentifier deviceIdentifier, List<Application> applications)
throws ApplicationManagementException {
int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
try {
Device device = deviceDAO.getDevice(deviceIdentifier, tenantId);
deviceDAO.addDeviceApplications(device.getId(), applications);
}catch (DeviceManagementDAOException deviceDaoEx){
String errorMsg = "Error occurred saving application list to the device";
log.error(errorMsg+":"+deviceIdentifier.toString());
throw new ApplicationManagementException(errorMsg, deviceDaoEx);
}
}
private OAuthConsumerAppDTO getAppInfo() throws ApplicationManagementException {

@ -21,6 +21,7 @@ package org.wso2.carbon.device.mgt.core.dao;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo.Status;
import org.wso2.carbon.device.mgt.common.app.mgt.Application;
import java.util.List;
@ -76,4 +77,5 @@ public interface DeviceDAO {
*/
List<Device> getDevicesByName(String deviceName, int tenantId) throws DeviceManagementDAOException;
void addDeviceApplications(int id, Object applications) throws DeviceManagementDAOException;
}

@ -23,6 +23,7 @@ import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo.Status;
import org.wso2.carbon.device.mgt.common.EnrolmentInfo.OwnerShip;
import org.wso2.carbon.device.mgt.common.app.mgt.Application;
import org.wso2.carbon.device.mgt.core.dao.DeviceDAO;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException;
import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOFactory;
@ -309,6 +310,28 @@ public class DeviceDAOImpl implements DeviceDAO {
return deviceList;
}
@Override
public void addDeviceApplications(int deviceId, Object appList) throws DeviceManagementDAOException {
Connection conn;
PreparedStatement stmt = null;
try {
conn = this.getConnection();
String sql = "INSERT INTO DM_DEVICE_APPLICATIONS(DEVICE_ID, APPLICATIONS) " +
"VALUES (?, ?)";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, deviceId);
stmt.setObject(2, appList);
stmt.executeUpdate();
} catch (SQLException e) {
throw new DeviceManagementDAOException("Error occurred while update application list for device " +
"'" + deviceId + "'", e);
} finally {
DeviceManagementDAOUtil.cleanupResources(stmt, null);
}
}
private Device loadDevice(ResultSet rs) throws SQLException {
Device device = new Device();
DeviceType deviceType = new DeviceType();

@ -23,7 +23,6 @@ import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
import java.util.Properties;
@XmlRootElement
public class Operation implements Serializable {
public enum Type {
@ -43,8 +42,8 @@ public class Operation implements Serializable {
private String createdTimeStamp;
private boolean isEnabled;
private Object payLoad;
private Object operationResponse;
@XmlElement
public String getCode() {
return code;
}
@ -53,7 +52,6 @@ public class Operation implements Serializable {
this.code = code;
}
@XmlElement
public Properties getProperties() {
return properties;
}
@ -62,7 +60,6 @@ public class Operation implements Serializable {
this.properties = properties;
}
@XmlElement
public Type getType() {
return type;
}
@ -119,5 +116,12 @@ public class Operation implements Serializable {
this.payLoad = payLoad;
}
public Object getOperationResponse() {
return operationResponse;
}
public void setOperationResponse(Object operationResponse) {
this.operationResponse = operationResponse;
}
}

@ -24,7 +24,6 @@ import org.osgi.service.component.ComponentContext;
import org.wso2.carbon.apimgt.impl.APIManagerConfigurationService;
import org.wso2.carbon.core.ServerStartupObserver;
import org.wso2.carbon.device.mgt.common.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.DeviceManager;
import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException;
import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager;
import org.wso2.carbon.device.mgt.common.license.mgt.License;
@ -39,7 +38,7 @@ import org.wso2.carbon.device.mgt.core.api.mgt.APIPublisherService;
import org.wso2.carbon.device.mgt.core.api.mgt.APIPublisherServiceImpl;
import org.wso2.carbon.device.mgt.core.api.mgt.APIRegistrationStartupObserver;
import org.wso2.carbon.device.mgt.core.app.mgt.ApplicationManagementServiceImpl;
import org.wso2.carbon.device.mgt.core.app.mgt.RemoteApplicationManager;
import org.wso2.carbon.device.mgt.core.app.mgt.ApplicationManagerProviderServiceImpl;
import org.wso2.carbon.device.mgt.core.app.mgt.config.AppManagementConfig;
import org.wso2.carbon.device.mgt.core.app.mgt.config.AppManagementConfigurationManager;
import org.wso2.carbon.device.mgt.core.config.DeviceConfigurationManager;
@ -54,15 +53,12 @@ import org.wso2.carbon.device.mgt.core.operation.mgt.dao.OperationManagementDAOF
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderServiceImpl;
import org.wso2.carbon.device.mgt.core.util.DeviceManagementSchemaInitializer;
import org.wso2.carbon.device.mgt.user.core.UserManager;
import org.wso2.carbon.ndatasource.core.DataSourceService;
import org.wso2.carbon.registry.core.service.RegistryService;
import org.wso2.carbon.user.core.service.RealmService;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @scr.component name="org.wso2.carbon.device.manager" immediate="true"
@ -183,7 +179,7 @@ public class DeviceManagementServiceComponent {
AppManagementConfig appConfig =
AppManagementConfigurationManager.getInstance().getAppManagementConfig();
DeviceManagementDataHolder.getInstance().setAppManagerConfig(appConfig);
RemoteApplicationManager appManager = new RemoteApplicationManager(appConfig, this.getPluginRepository());
ApplicationManagerProviderServiceImpl appManager = new ApplicationManagerProviderServiceImpl(appConfig, this.getPluginRepository());
DeviceManagementDataHolder.getInstance().setAppManager(appManager);
}

@ -258,38 +258,39 @@ public class OperationManagerImpl implements OperationManager {
}
@Override
public void updateOperation(DeviceIdentifier deviceId, int operationId, Operation.Status operationStatus)
throws OperationManagementException {
public void updateOperation(DeviceIdentifier deviceId, Operation operation) throws OperationManagementException {
int operationId = operation.getId();
if (log.isDebugEnabled()) {
log.debug("operation Id:" + operationId + " status:" + operationStatus);
log.debug("operation Id:" + operationId + " status:" + operation.getStatus());
}
try {
org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation dtoOperation =
operationDAO.getOperation(operationId);
if (dtoOperation == null) {
throw new OperationManagementException("Operation not found for operation id:" + operationId);
}
dtoOperation.setStatus(org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation.Status.valueOf
(operationStatus.toString()));
int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
Device device = deviceDAO.getDevice(deviceId, tenantId);
OperationManagementDAOFactory.beginTransaction();
operationDAO.updateOperation(dtoOperation);
operationDAO.updateOperationStatus(device.getId(), operationId,
org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation.Status
.valueOf(operationStatus.toString()));
OperationManagementDAOFactory.commitTransaction();
if (operation.getStatus() !=null) {
OperationManagementDAOFactory.beginTransaction();
operationDAO.updateOperationStatus(device.getId(), operationId,
org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation.Status
.valueOf(operation.getStatus().toString()));
OperationManagementDAOFactory.commitTransaction();
}
if (operation.getOperationResponse() != null){
OperationManagementDAOFactory.beginTransaction();
operationDAO.addOperationResponse(device.getId(), operationId, operation.getOperationResponse());
OperationManagementDAOFactory.commitTransaction();
}
} catch (OperationManagementDAOException ex) {
try {
OperationManagementDAOFactory.rollbackTransaction();
} catch (OperationManagementDAOException e1) {
log.warn("Error occurred while roll-backing the update operation transaction", e1);
}
log.error("Error occurred while updating the operation: " + operationId + " status:" + operationStatus, ex);
log.error("Error occurred while updating the operation: " + operationId + " status:" + operation.getStatus(), ex);
throw new OperationManagementException("Error occurred while update operation", ex);
} catch (DeviceManagementDAOException e) {
log.error("Error occurred while fetch the device for device identifier: " + deviceId.getId() + " " +

@ -42,7 +42,9 @@ public interface OperationDAO {
Operation getNextOperation(int deviceId) throws OperationManagementDAOException;
void updateOperationStatus(int deviceId, int operationId,Operation.Status status) throws
OperationManagementDAOException;
void updateOperationStatus(int deviceId, int operationId,Operation.Status status)
throws OperationManagementDAOException;
void addOperationResponse(int deviceId, int operationId, Object operationResponse)
throws OperationManagementDAOException;
}

@ -106,6 +106,27 @@ public class OperationDAOImpl implements OperationDAO {
}
@Override
public void addOperationResponse(int deviceId, int operationId, Object operationResponse)
throws OperationManagementDAOException {
PreparedStatement stmt = null;
try {
Connection connection = OperationManagementDAOFactory.getConnection();
stmt = connection.prepareStatement("INSERT INTO DM_DEVICE_OPERATION_RESPONSE(OPERATION_ID,DEVICE_ID," +
"OPERATION_RESPONSE) VALUES(?, ?, ?)");
stmt.setInt(1, operationId);
stmt.setInt(2, deviceId);
stmt.setObject(3, operationResponse);
stmt.executeUpdate();
} catch (SQLException e) {
throw new OperationManagementDAOException("Error occurred while inserting operation response", e);
} finally {
OperationManagementDAOUtil.cleanupResources(stmt);
}
}
@Override
public void deleteOperation(int id) throws OperationManagementDAOException {

@ -19,6 +19,7 @@ package org.wso2.carbon.device.mgt.core.service;
import org.wso2.carbon.device.mgt.common.*;
import org.wso2.carbon.device.mgt.common.DeviceManager;
import org.wso2.carbon.device.mgt.common.app.mgt.Application;
import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManager;
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManager;
import java.util.List;
@ -77,4 +78,13 @@ public interface DeviceManagementProviderService extends DeviceManager, LicenseM
*/
List<Device> getDevicesByName(String deviceName) throws DeviceManagementException;
/**
* The method to get application list installed for the device.
*
* @param deviceIdentifier
* @return
* @throws DeviceManagementException
*/
List<Application> getApplicationListForDevice(DeviceIdentifier deviceIdentifier) throws DeviceManagementException;
}

@ -22,6 +22,7 @@ import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.device.mgt.common.*;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.app.mgt.Application;
import org.wso2.carbon.device.mgt.common.license.mgt.License;
import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManagementException;
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
@ -44,8 +45,7 @@ import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
public class DeviceManagementProviderServiceImpl implements
DeviceManagementProviderService, PluginInitializationListener {
public class DeviceManagementProviderServiceImpl implements DeviceManagementProviderService, PluginInitializationListener {
private DeviceDAO deviceDAO;
private DeviceTypeDAO deviceTypeDAO;
@ -349,7 +349,8 @@ public class DeviceManagementProviderServiceImpl implements
EmailConstants.EnrolmentEmailConstants.ENCODED_SCHEME));
messageBuilder.append(messageHeader).append(System.getProperty("line.separator"));
messageBuilder.append(messageBody).append(System.getProperty("line.separator")).append(messageFooter1.trim());
messageBuilder.append(messageBody).append(System.getProperty("line.separator")).append(
messageFooter1.trim());
messageBuilder.append(System.getProperty("line.separator")).append(messageFooter2.trim());
messageBuilder.append(System.getProperty("line.separator")).append(messageFooter3.trim());
@ -475,10 +476,8 @@ public class DeviceManagementProviderServiceImpl implements
}
@Override
public void updateOperation(DeviceIdentifier deviceId, int operationId, Operation.Status operationStatus)
throws OperationManagementException {
DeviceManagementDataHolder.getInstance().getOperationManager().updateOperation(deviceId, operationId,
operationStatus);
public void updateOperation(DeviceIdentifier deviceId, Operation operation) throws OperationManagementException {
DeviceManagementDataHolder.getInstance().getOperationManager().updateOperation(deviceId, operation);
}
@Override
@ -630,6 +629,12 @@ public class DeviceManagementProviderServiceImpl implements
}
@Override
public List<Application> getApplicationListForDevice(DeviceIdentifier deviceIdentifier)
throws DeviceManagementException {
return null;
}
@Override
public void registerDeviceManagementService(DeviceManagementService deviceManagementService) {
try {

@ -124,4 +124,10 @@ public class TestDeviceManager implements DeviceManagementService {
throws ApplicationManagementException {
}
@Override
public void updateApplicationsForDevice(DeviceIdentifier deviceIdentifier, List<Application> applications)
throws ApplicationManagementException {
}
}

@ -76,6 +76,26 @@ CREATE TABLE IF NOT EXISTS DM_DEVICE_OPERATION_MAPPING (
DM_OPERATION (ID) ON DELETE NO ACTION ON UPDATE NO ACTION
);
CREATE TABLE IF NOT EXISTS DM_DEVICE_OPERATION_RESPONSE (
ID INTEGER AUTO_INCREMENT NOT NULL,
DEVICE_ID INTEGER NOT NULL,
OPERATION_ID INTEGER NOT NULL,
OPERATION_RESPONSE BLOB DEFAULT NULL,
PRIMARY KEY (ID),
CONSTRAINT fk_dm_device_operation_response_device FOREIGN KEY (DEVICE_ID) REFERENCES
DM_DEVICE (ID) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_dm_device_operation_response_operation FOREIGN KEY (OPERATION_ID) REFERENCES
DM_OPERATION (ID) ON DELETE NO ACTION ON UPDATE NO ACTION
);
CREATE TABLE IF NOT EXISTS DM_DEVICE_APPLICATIONS (
ID INTEGER AUTO_INCREMENT NOT NULL,
DEVICE_ID INTEGER NOT NULL,
APPLICATIONS BLOB DEFAULT NULL,
PRIMARY KEY (ID),
CONSTRAINT fk_dm_device_applications_device FOREIGN KEY (DEVICE_ID) REFERENCES
DM_DEVICE (ID) ON DELETE NO ACTION ON UPDATE NO ACTION,
);
--- POLICY RELATED TABLES ----

Loading…
Cancel
Save