mirror of
https://repository.entgra.net/community/device-mgt-core.git
synced 2025-10-06 02:01:45 +00:00
Refactor operations
This commit is contained in:
parent
3fc11654e7
commit
932cb003da
@ -119,5 +119,17 @@ public class Operation implements Serializable {
|
||||
this.payLoad = payLoad;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Operation{" +
|
||||
"code='" + code + '\'' +
|
||||
", type=" + type +
|
||||
", id=" + id +
|
||||
", status=" + status +
|
||||
", receivedTimeStamp='" + receivedTimeStamp + '\'' +
|
||||
", createdTimeStamp='" + createdTimeStamp + '\'' +
|
||||
", isEnabled=" + isEnabled +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@ -18,6 +18,8 @@
|
||||
package org.wso2.carbon.device.mgt.common.operation.mgt;
|
||||
|
||||
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
|
||||
import org.wso2.carbon.device.mgt.common.DeviceManagementException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -34,8 +36,8 @@ public interface OperationManager {
|
||||
* @throws OperationManagementException If some unusual behaviour is observed while adding the
|
||||
* operation
|
||||
*/
|
||||
public boolean addOperation(Operation operation,
|
||||
List<DeviceIdentifier> devices) throws OperationManagementException;
|
||||
public boolean addOperation(Operation operation, List<DeviceIdentifier> devices) throws
|
||||
OperationManagementException;
|
||||
|
||||
/**
|
||||
* Method to retrieve the list of all operations to a device.
|
||||
@ -60,4 +62,15 @@ public interface OperationManager {
|
||||
|
||||
public void updateOperation(int operationId, Operation.Status operationStatus) throws OperationManagementException;
|
||||
|
||||
public void deleteOperation(int operationId) throws OperationManagementException;
|
||||
|
||||
public Operation getOperationByDeviceAndOperationId(DeviceIdentifier deviceId, int operationId)
|
||||
throws OperationManagementException;
|
||||
|
||||
public List<? extends Operation> getOperationsByDeviceAndStatus(DeviceIdentifier identifier,
|
||||
Operation.Status status) throws OperationManagementException, DeviceManagementException;
|
||||
|
||||
public Operation getOperation(int operationId) throws OperationManagementException;
|
||||
|
||||
public List<? extends Operation> getOperationsForStatus(Operation.Status status) throws OperationManagementException;
|
||||
}
|
||||
@ -363,6 +363,34 @@ public class DeviceManagementServiceProviderImpl implements DeviceManagementServ
|
||||
@Override
|
||||
public void updateOperation(int operationId, Operation.Status operationStatus)
|
||||
throws OperationManagementException {
|
||||
operationManager.updateOperation(operationId,operationStatus);
|
||||
operationManager.updateOperation(operationId, operationStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteOperation(int operationId) throws OperationManagementException {
|
||||
operationManager.deleteOperation(operationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Operation getOperationByDeviceAndOperationId(DeviceIdentifier deviceId, int operationId)
|
||||
throws OperationManagementException {
|
||||
return operationManager.getOperationByDeviceAndOperationId(deviceId, operationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Operation> getOperationsByDeviceAndStatus(DeviceIdentifier identifier,
|
||||
Operation.Status status) throws OperationManagementException, DeviceManagementException {
|
||||
return operationManager.getOperationsByDeviceAndStatus(identifier, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Operation getOperation(int operationId) throws OperationManagementException {
|
||||
return operationManager.getOperation(operationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Operation> getOperationsForStatus(Operation.Status status)
|
||||
throws OperationManagementException {
|
||||
return operationManager.getOperationsForStatus(status);
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ package org.wso2.carbon.device.mgt.core.operation.mgt;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
|
||||
import org.wso2.carbon.device.mgt.common.DeviceManagementException;
|
||||
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
|
||||
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
|
||||
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManager;
|
||||
@ -65,6 +66,13 @@ public class OperationManagerImpl implements OperationManager {
|
||||
@Override
|
||||
public boolean addOperation(Operation operation,
|
||||
List<DeviceIdentifier> devices) throws OperationManagementException {
|
||||
|
||||
if (log.isDebugEnabled()){
|
||||
log.debug("operation:["+operation.toString()+"]");
|
||||
for(DeviceIdentifier deviceIdentifier:devices){
|
||||
log.debug("device identifier id:["+deviceIdentifier.getId()+"] type:["+deviceIdentifier.getType()+"]");
|
||||
}
|
||||
}
|
||||
try {
|
||||
OperationManagementDAOFactory.beginTransaction();
|
||||
|
||||
@ -79,6 +87,7 @@ public class OperationManagerImpl implements OperationManager {
|
||||
OperationManagementDAOFactory.commitTransaction();
|
||||
return true;
|
||||
} catch (OperationManagementDAOException e) {
|
||||
log.error("Error occurred while adding operation: ",e);
|
||||
try {
|
||||
OperationManagementDAOFactory.rollbackTransaction();
|
||||
} catch (OperationManagementDAOException e1) {
|
||||
@ -86,6 +95,7 @@ public class OperationManagerImpl implements OperationManager {
|
||||
}
|
||||
throw new OperationManagementException("Error occurred while adding operation", e);
|
||||
} catch (DeviceManagementDAOException e) {
|
||||
log.error("Error occurred while adding operation device mapping: ",e);
|
||||
try {
|
||||
OperationManagementDAOFactory.rollbackTransaction();
|
||||
} catch (OperationManagementDAOException e1) {
|
||||
@ -123,19 +133,31 @@ public class OperationManagerImpl implements OperationManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Operation> getPendingOperations(
|
||||
DeviceIdentifier deviceId) throws OperationManagementException {
|
||||
public List<? extends Operation> getPendingOperations(DeviceIdentifier deviceId)
|
||||
throws OperationManagementException {
|
||||
|
||||
if (log.isDebugEnabled()){
|
||||
log.debug("device identifier id:["+deviceId.getId()+"] type:["+deviceId.getType()+"]");
|
||||
}
|
||||
|
||||
try {
|
||||
Device device;
|
||||
device = deviceDAO.getDevice(deviceId);
|
||||
List<Operation> operations = new ArrayList<Operation>();
|
||||
List<? extends org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation> dtoOperationList = operationDAO
|
||||
.getOperationsForStatus(org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation.Status.PENDING);
|
||||
List<? extends org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation> dtoOperationList =
|
||||
operationDAO.getOperationsByDeviceAndStatus(device.getId(), org.wso2.carbon.device.mgt.core.dto
|
||||
.operation.mgt.Operation.Status.PENDING);
|
||||
Operation operation;
|
||||
for (org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation dtoOperation : dtoOperationList) {
|
||||
operation = OperationDAOUtil.convertOperation(dtoOperation);
|
||||
operations.add(operation);
|
||||
}
|
||||
|
||||
return operations;
|
||||
} catch (DeviceManagementDAOException deviceDAOException) {
|
||||
String errorMsg = "Error occurred while retrieving the device " +
|
||||
"for device Identifier type -'" + deviceId.getType() + "' and device Id '" + deviceId.getId();
|
||||
log.error(errorMsg, deviceDAOException);
|
||||
throw new OperationManagementException(errorMsg, deviceDAOException);
|
||||
} catch (OperationManagementDAOException e) {
|
||||
throw new OperationManagementException("Error occurred while retrieving the list of " +
|
||||
"pending operations assigned for '" + deviceId.getType() + "' device '" +
|
||||
@ -145,10 +167,17 @@ public class OperationManagerImpl implements OperationManager {
|
||||
|
||||
@Override
|
||||
public Operation getNextPendingOperation(DeviceIdentifier deviceId) throws OperationManagementException {
|
||||
|
||||
if (log.isDebugEnabled()){
|
||||
log.debug("device identifier id:["+deviceId.getId()+"] type:["+deviceId.getType()+"]");
|
||||
}
|
||||
try {
|
||||
org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation dtoOperation = operationDAO
|
||||
.getNextOperation(deviceId);
|
||||
Operation operation = OperationDAOUtil.convertOperation(dtoOperation);
|
||||
Operation operation = null;
|
||||
if (dtoOperation != null) {
|
||||
operation = OperationDAOUtil.convertOperation(dtoOperation);
|
||||
}
|
||||
return operation;
|
||||
} catch (OperationManagementDAOException e) {
|
||||
throw new OperationManagementException("Error occurred while retrieving next pending operation", e);
|
||||
@ -159,11 +188,19 @@ public class OperationManagerImpl implements OperationManager {
|
||||
public void updateOperation(int operationId, Operation.Status operationStatus)
|
||||
throws OperationManagementException {
|
||||
|
||||
if (log.isDebugEnabled()){
|
||||
log.debug("operation Id:"+operationId+" status:"+operationStatus);
|
||||
}
|
||||
|
||||
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()));
|
||||
(operationStatus.toString()));
|
||||
OperationManagementDAOFactory.beginTransaction();
|
||||
operationDAO.updateOperation(dtoOperation);
|
||||
OperationManagementDAOFactory.commitTransaction();
|
||||
@ -173,10 +210,153 @@ public class OperationManagerImpl implements OperationManager {
|
||||
} catch (OperationManagementDAOException e1) {
|
||||
log.warn("Error occurred while roll-backing the update operation transaction", e1);
|
||||
}
|
||||
log.error("Error occurred while updating the operation: " + operationId);
|
||||
log.error("Error occurred while updating the operation: " + operationId + " status:"+operationStatus,ex);
|
||||
throw new OperationManagementException("Error occurred while update operation", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteOperation(int operationId) throws OperationManagementException {
|
||||
|
||||
try {
|
||||
OperationManagementDAOFactory.beginTransaction();
|
||||
org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation operation = operationDAO.getOperation
|
||||
(operationId);
|
||||
|
||||
if (operation == null){
|
||||
throw new OperationManagementException("Operation not found for operation id:"+ operationId);
|
||||
}
|
||||
|
||||
if (operation.getType().equals(org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation.Type.COMMAND)) {
|
||||
commandOperationDAO.deleteOperation(operationId);
|
||||
} else if (operation.getType().equals(
|
||||
org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation.Type.CONFIG)) {
|
||||
configOperationDAO.deleteOperation(operationId);
|
||||
} else if (operation.getType().equals(org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation.Type
|
||||
.PROFILE)) {
|
||||
profileOperationDAO.deleteOperation(operationId);
|
||||
}
|
||||
OperationManagementDAOFactory.commitTransaction();
|
||||
} catch (OperationManagementDAOException ex) {
|
||||
try {
|
||||
OperationManagementDAOFactory.rollbackTransaction();
|
||||
} catch (OperationManagementDAOException e1) {
|
||||
log.warn("Error occurred while roll-backing the delete operation transaction", e1);
|
||||
}
|
||||
log.error("Error occurred while deleting the operation: " + operationId, ex);
|
||||
throw new OperationManagementException("Error occurred while delete operation", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Operation getOperationByDeviceAndOperationId(DeviceIdentifier deviceId, int operationId)
|
||||
throws OperationManagementException {
|
||||
|
||||
Device device;
|
||||
Operation operation;
|
||||
|
||||
if (log.isDebugEnabled()){
|
||||
log.debug("Device Type:"+deviceId.getType()+" Id:"+deviceId.getId());
|
||||
}
|
||||
|
||||
try {
|
||||
device = deviceDAO.getDevice(deviceId);
|
||||
if (device == null){
|
||||
throw new OperationManagementException("Device not found for given device identifier:"+deviceId.getId
|
||||
()+" type:"+device.getDeviceType());
|
||||
}
|
||||
org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation dtoOperation = operationDAO
|
||||
.getOperationByDeviceAndId(device.getId(), operationId);
|
||||
|
||||
if (dtoOperation == null){
|
||||
throw new OperationManagementException("Operation not found for operation Id:"+ operationId +" device" +
|
||||
" Id:"+device.getId());
|
||||
}
|
||||
operation = OperationDAOUtil.convertOperation(dtoOperation);
|
||||
} catch (DeviceManagementDAOException deviceDAOException) {
|
||||
String errorMsg = "Error occurred while retrieving the device " +
|
||||
"for device Identifier type -'" + deviceId.getType() + "' and device Id '" + deviceId.getId();
|
||||
log.error(errorMsg, deviceDAOException);
|
||||
throw new OperationManagementException(errorMsg, deviceDAOException);
|
||||
} catch (OperationManagementDAOException e) {
|
||||
throw new OperationManagementException("Error occurred while retrieving the list of " +
|
||||
"operations assigned for '" + deviceId.getType() + "' device '" + deviceId.getId() + "'", e);
|
||||
}
|
||||
return operation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Operation> getOperationsByDeviceAndStatus(DeviceIdentifier identifier,
|
||||
Operation.Status status) throws OperationManagementException, DeviceManagementException {
|
||||
|
||||
try {
|
||||
List<Operation> operations = new ArrayList<Operation>();
|
||||
Device device = deviceDAO.getDevice(identifier);
|
||||
|
||||
if (device == null){
|
||||
throw new DeviceManagementException("Device not found for device id:"+identifier.getId()+" " +
|
||||
"type:"+identifier.getType());
|
||||
}
|
||||
List<? extends org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation> dtoOperationList = operationDAO
|
||||
.getOperationsByDeviceAndStatus(device.getId(),
|
||||
org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation.Status
|
||||
.valueOf(status.toString()));
|
||||
Operation operation;
|
||||
for (org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation dtoOperation : dtoOperationList) {
|
||||
operation = OperationDAOUtil.convertOperation(dtoOperation);
|
||||
operations.add(operation);
|
||||
}
|
||||
return operations;
|
||||
} catch (DeviceManagementDAOException deviceDAOException) {
|
||||
String errorMsg = "Error occurred while retrieving the device " +
|
||||
"for device Identifier type -'" + identifier.getType() + "' and device Id '" + identifier.getId();
|
||||
log.error(errorMsg, deviceDAOException);
|
||||
throw new OperationManagementException(errorMsg, deviceDAOException);
|
||||
} catch (OperationManagementDAOException e) {
|
||||
throw new OperationManagementException("Error occurred while retrieving the list of " +
|
||||
"operations assigned for '" + identifier.getType() + "' device '" +
|
||||
identifier.getId() + "' and status:" + status.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Operation getOperation(int operationId) throws OperationManagementException {
|
||||
|
||||
Operation operation;
|
||||
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 given Id:"+operationId);
|
||||
}
|
||||
operation = OperationDAOUtil.convertOperation(dtoOperation);
|
||||
} catch (OperationManagementDAOException e) {
|
||||
String errorMsg = "Error occurred while retrieving the operation with operation Id '" + operationId;
|
||||
log.error(errorMsg, e);
|
||||
throw new OperationManagementException(errorMsg, e);
|
||||
}
|
||||
return operation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Operation> getOperationsForStatus(Operation.Status status)
|
||||
throws OperationManagementException {
|
||||
|
||||
try {
|
||||
List<Operation> operations = new ArrayList<Operation>();
|
||||
List<? extends org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation> dtoOperationList =
|
||||
operationDAO.getOperationsForStatus(org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation.Status
|
||||
.valueOf(status.toString()));
|
||||
Operation operation;
|
||||
for (org.wso2.carbon.device.mgt.core.dto.operation.mgt.Operation dtoOperation : dtoOperationList) {
|
||||
operation = OperationDAOUtil.convertOperation(dtoOperation);
|
||||
operations.add(operation);
|
||||
}
|
||||
return operations;
|
||||
} catch (OperationManagementDAOException e) {
|
||||
throw new OperationManagementException("Error occurred while retrieving the list of " +
|
||||
"operations for status:'" + status.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private OperationDAO lookupOperationDAO(Operation operation) {
|
||||
|
||||
@ -33,11 +33,10 @@ public interface OperationDAO {
|
||||
|
||||
Operation getOperation(int operationId) throws OperationManagementDAOException;
|
||||
|
||||
Operation getOperationByDeviceAndId(int deviceId,
|
||||
int operationId) throws OperationManagementDAOException;
|
||||
Operation getOperationByDeviceAndId(int deviceId, int operationId) throws OperationManagementDAOException;
|
||||
|
||||
List<? extends Operation> getOperationsByDeviceAndStatus(int deviceId, Operation.Status status) throws
|
||||
OperationManagementDAOException;
|
||||
List<? extends Operation> getOperationsByDeviceAndStatus(int deviceId, Operation.Status status)
|
||||
throws OperationManagementDAOException;
|
||||
|
||||
List<? extends Operation> getOperationsForDevice(int deviceId) throws OperationManagementDAOException;
|
||||
|
||||
|
||||
@ -160,8 +160,8 @@ public class OperationDAOImpl implements OperationDAO {
|
||||
log.error(errorMsg, e);
|
||||
throw new OperationManagementDAOException(errorMsg, e);
|
||||
} catch (SQLException e) {
|
||||
String errorMsg = "SQL Error occurred while retrieving the operation object " +
|
||||
"available for the id '" + id;
|
||||
String errorMsg = "SQL Error occurred while retrieving the operation object " + "available for the id '"
|
||||
+ id;
|
||||
log.error(errorMsg, e);
|
||||
throw new OperationManagementDAOException(errorMsg, e);
|
||||
} finally {
|
||||
@ -248,7 +248,7 @@ public class OperationDAOImpl implements OperationDAO {
|
||||
|
||||
PreparedStatement stmt = null;
|
||||
ResultSet rs = null;
|
||||
Operation operation = null;
|
||||
Operation operation;
|
||||
|
||||
ByteArrayInputStream bais;
|
||||
ObjectInputStream ois;
|
||||
@ -393,7 +393,7 @@ public class OperationDAOImpl implements OperationDAO {
|
||||
|
||||
PreparedStatement stmt = null;
|
||||
ResultSet rs = null;
|
||||
Operation operation = null;
|
||||
Operation operation;
|
||||
|
||||
ByteArrayInputStream bais;
|
||||
ObjectInputStream ois;
|
||||
@ -459,6 +459,7 @@ public class OperationDAOImpl implements OperationDAO {
|
||||
|
||||
@Override
|
||||
public Operation getNextOperation(DeviceIdentifier deviceId) throws OperationManagementDAOException {
|
||||
|
||||
PreparedStatement stmt = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@ -478,8 +479,8 @@ public class OperationDAOImpl implements OperationDAO {
|
||||
rs = stmt.executeQuery();
|
||||
|
||||
Operation operation = null;
|
||||
if (rs.next()) {
|
||||
|
||||
if (rs.next()) {
|
||||
operation = new Operation();
|
||||
operation.setType(this.getType(rs.getString("TYPE")));
|
||||
operation.setStatus(this.getStatus(rs.getString("STATUS")));
|
||||
|
||||
@ -23,10 +23,10 @@ import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManagementException;
|
||||
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
|
||||
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
|
||||
import org.wso2.carbon.device.mgt.core.internal.DeviceManagementDataHolder;
|
||||
|
||||
import org.wso2.carbon.device.mgt.core.operation.mgt.OperationManagerImpl;
|
||||
import java.util.List;
|
||||
|
||||
public class DeviceManagementServiceImpl implements DeviceManagementService {
|
||||
public class DeviceManagementServiceImpl implements DeviceManagementService{
|
||||
|
||||
@Override
|
||||
public String getProviderType() {
|
||||
@ -142,6 +142,36 @@ public class DeviceManagementServiceImpl implements DeviceManagementService {
|
||||
operationStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteOperation(int operationId) throws OperationManagementException {
|
||||
DeviceManagementDataHolder.getInstance().getDeviceManagementProvider().deleteOperation(operationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Operation getOperationByDeviceAndOperationId(DeviceIdentifier deviceId,
|
||||
int operationId) throws OperationManagementException {
|
||||
return DeviceManagementDataHolder.getInstance().getDeviceManagementProvider()
|
||||
.getOperationByDeviceAndOperationId(deviceId, operationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Operation> getOperationsByDeviceAndStatus(DeviceIdentifier identifier,
|
||||
Operation.Status status) throws OperationManagementException, DeviceManagementException {
|
||||
return DeviceManagementDataHolder.getInstance().getDeviceManagementProvider().getOperationsByDeviceAndStatus
|
||||
(identifier, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Operation getOperation(int operationId) throws OperationManagementException {
|
||||
return DeviceManagementDataHolder.getInstance().getDeviceManagementProvider().getOperation(operationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Operation> getOperationsForStatus(Operation.Status status)
|
||||
throws OperationManagementException {
|
||||
return DeviceManagementDataHolder.getInstance().getDeviceManagementProvider().getOperationsForStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendEnrolmentInvitation(
|
||||
EmailMessageProperties emailMessageProperties) throws DeviceManagementException {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user