org.ovirt.engine.core.bll.CommandBase.java Source code

Java tutorial

Introduction

Here is the source code for org.ovirt.engine.core.bll.CommandBase.java

Source

package org.ovirt.engine.core.bll;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.ejb.TransactionRolledbackLocalException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.groups.Default;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.ovirt.engine.core.bll.context.CompensationContext;
import org.ovirt.engine.core.bll.context.DefaultCompensationContext;
import org.ovirt.engine.core.bll.context.NoOpCompensationContext;
import org.ovirt.engine.core.bll.session.SessionDataContainer;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.VdcActionParametersBase;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.asynctasks.AsyncTaskCreationInfo;
import org.ovirt.engine.core.common.businessentities.AsyncTaskStatus;
import org.ovirt.engine.core.common.businessentities.BusinessEntity;
import org.ovirt.engine.core.common.businessentities.BusinessEntitySnapshot;
import org.ovirt.engine.core.common.businessentities.BusinessEntitySnapshot.EntityStatusSnapshot;
import org.ovirt.engine.core.common.businessentities.BusinessEntitySnapshot.SnapshotType;
import org.ovirt.engine.core.common.businessentities.action_version_map;
import org.ovirt.engine.core.common.businessentities.tags;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.errors.VdcBLLException;
import org.ovirt.engine.core.common.errors.VdcBllErrors;
import org.ovirt.engine.core.common.errors.VdcFault;
import org.ovirt.engine.core.common.interfaces.IBackendCallBackServer;
import org.ovirt.engine.core.common.interfaces.IVdcUser;
import org.ovirt.engine.core.common.vdscommands.SPMTaskGuidBaseVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSParametersBase;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.LogCompat;
import org.ovirt.engine.core.compat.LogFactoryCompat;
import org.ovirt.engine.core.compat.NGuid;
import org.ovirt.engine.core.compat.NotImplementedException;
import org.ovirt.engine.core.compat.StringHelper;
import org.ovirt.engine.core.compat.TimeSpan;
import org.ovirt.engine.core.compat.TransactionScopeOption;
import org.ovirt.engine.core.compat.Version;
import org.ovirt.engine.core.compat.backendcompat.PropertyInfo;
import org.ovirt.engine.core.compat.backendcompat.TypeCompat;
import org.ovirt.engine.core.dal.VdcBllMessages;
import org.ovirt.engine.core.dal.dbbroker.DbFacade;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableBase;
import org.ovirt.engine.core.dal.dbbroker.generic.RepositoryException;
import org.ovirt.engine.core.dao.BusinessEntitySnapshotDAO;
import org.ovirt.engine.core.dao.GenericDao;
import org.ovirt.engine.core.dao.StatusAwareDao;
import org.ovirt.engine.core.utils.Deserializer;
import org.ovirt.engine.core.utils.ReflectionUtils;
import org.ovirt.engine.core.utils.SerializationFactory;
import org.ovirt.engine.core.utils.ThreadLocalParamsContainer;
import org.ovirt.engine.core.utils.lock.EngineLock;
import org.ovirt.engine.core.utils.lock.LockManagerFactory;
import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil;
import org.ovirt.engine.core.utils.transaction.RollbackHandler;
import org.ovirt.engine.core.utils.transaction.TransactionMethod;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
import org.springframework.dao.DataAccessException;

public abstract class CommandBase<T extends VdcActionParametersBase> extends AuditLogableBase
        implements RollbackHandler, TransactionMethod<Object> {
    /**
     * Multiplier used to convert GB to bytes or vice versa.
     */
    protected static final long BYTES_IN_GB = 1024 * 1024 * 1024;
    private T _parameters;
    private VdcReturnValueBase _returnValue;
    private final IBackendCallBackServer _backendCallBack = CallbackServer.Instance;
    private CommandActionState _actionState = CommandActionState.forValue(0);
    private boolean isInternalExecution = false;
    private VdcActionType actionType;
    /**
     * According to hibernate validator documentation it is safe to assume it is
     * thread-safe.So holding one is encouraged.
     */
    private final static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
    private final List<Class<?>> validationGroups = new ArrayList<Class<?>>(
            Arrays.asList(new Class<?>[] { Default.class }));
    private CompensationContext compensationContext;
    private Guid commandId = Guid.NewGuid();

    protected LogCompat log = LogFactoryCompat.getLog(getClass());

    protected CommandActionState getActionState() {
        return _actionState;
    }

    protected CommandBase() {
    }

    protected CommandBase(T parameters) {
        _parameters = parameters;
        setCurrentUser(addUserToThredContext(parameters.getSessionId()));
    }

    /**
     * Constructor for command creation when compensation is applied on startup
     *
     * @param commandId
     */
    protected CommandBase(Guid commandId) {
        this.commandId = commandId;
    }

    /**
     * Create an appropriate compensation context. The default is one that does nothing for command that don't run in a
     * transaction, and a real one for commands that run in a transaction.
     *
     * @param transactionScopeOption
     *            The transaction scope.
     * @param forceCompensation
     * @return The compensation context to use.
     */
    private CompensationContext createCompensationContext(TransactionScopeOption transactionScopeOption,
            boolean forceCompensation) {
        if (transactionScopeOption == TransactionScopeOption.Suppress && !forceCompensation) {
            return new NoOpCompensationContext();
        }

        DefaultCompensationContext context = new DefaultCompensationContext();
        context.setCommandId(commandId);
        context.setCommandType(getClass().getName());
        context.setBusinessEntitySnapshotDAO(getBusinessEntitySnapshotDAO());
        context.setSnapshotSerializer(SerializationFactory.getFactory().createSerializer());
        return context;
    }

    protected BusinessEntitySnapshotDAO getBusinessEntitySnapshotDAO() {
        return DbFacade.getInstance().getBusinessEntitySnapshotDAO();
    }

    /**
     * @return the compensationContext
     */
    protected CompensationContext getCompensationContext() {
        return compensationContext;
    }

    /**
     * @param compensationContext the compensationContext to set
     */
    protected void setCompensationContext(CompensationContext compensationContext) {
        this.compensationContext = compensationContext;
    }

    /**
     * This method will add a user to thread local, at case that user is not
     * already added to context. If session is null or empty will try to get
     * session from thread local
     *
     * @param sessionId
     *            -id of session
     */
    private IVdcUser addUserToThredContext(String sessionId) {
        IVdcUser vdcUser = ThreadLocalParamsContainer.getVdcUser();
        if (vdcUser == null) {
            if (!StringHelper.isNullOrEmpty(sessionId)) {
                vdcUser = (IVdcUser) SessionDataContainer.getInstance().GetData(sessionId, "VdcUser");
                ThreadLocalParamsContainer.setHttpSessionId(sessionId);
            } else {
                vdcUser = (IVdcUser) SessionDataContainer.getInstance().GetData("VdcUser");
            }
            ThreadLocalParamsContainer.setVdcUser(vdcUser);
        }
        return vdcUser;
    }

    private String _description = "";
    private TransactionScopeOption scope;
    private TransactionScopeOption endActionScope;

    public VdcReturnValueBase CanDoActionOnly() {
        setActionMessageParameters();
        getReturnValue().setCanDoAction(InternalCanDoAction());
        String tempVar = getDescription();
        getReturnValue().setDescription((tempVar != null) ? tempVar : getReturnValue().getDescription());
        return _returnValue;
    }

    public VdcReturnValueBase ExecuteAction() {
        _actionState = CommandActionState.EXECUTE;

        String tempVar = getDescription();
        getReturnValue().setDescription((tempVar != null) ? tempVar : getReturnValue().getDescription());
        setActionMessageParameters();
        if (acquireLock() && (getReturnValue().getCanDoAction() || InternalCanDoAction())) {
            getReturnValue().setCanDoAction(true);
            getReturnValue().setIsSyncronious(true);
            getParameters().setTaskStartTime(System.currentTimeMillis());
            Execute();
        } else {
            getReturnValue().setCanDoAction(false);
        }
        return getReturnValue();
    }

    /**
     * Run the default compensation logic (inside a new transaction):<br>
     * <ol>
     * <li>Get all the entity snapshots that this command has created.</li>
     * <li>For each snapshot:</li>
     * <ol>
     * <li>Deserialize the entity.</li>
     * <li>Using the entity DAO:</li>
     * <ul>
     * <li>If the entity was added by the command, remove it.</li>
     * <li>Otherwise, If the entity is not in DB anymore, restore it.</li>
     * <li>Otherwise, update it.</li>
     * </ul>
     * </ol>
     * <li>Remove all the snapshots for this command, since we handled them.</li> </ol>
     */
    @SuppressWarnings("unchecked")
    protected void compensate() {
        TransactionSupport.executeInNewTransaction(new TransactionMethod<Object>() {
            @Override
            public Object runInTransaction() {
                Deserializer deserializer = SerializationFactory.getFactory().createDeserializer();
                List<BusinessEntitySnapshot> entitySnapshots = getBusinessEntitySnapshotDAO()
                        .getAllForCommandId(commandId);
                log.debugFormat("Command [id={0}]: {1} compensation data.", commandId,
                        entitySnapshots.isEmpty() ? "No" : "Going over");
                for (BusinessEntitySnapshot snapshot : entitySnapshots) {
                    Class<Serializable> snapshotClass = (Class<Serializable>) ReflectionUtils
                            .getClassFor(snapshot.getSnapshotClass());
                    Serializable snapshotData = deserializer.deserialize(snapshot.getEntitySnapshot(),
                            snapshotClass);
                    log.infoFormat("Command [id={0}]: Compensating {1} of {2}; snapshot: {3}.", commandId,
                            snapshot.getSnapshotType(), snapshot.getEntityType(),
                            (snapshot.getSnapshotType() == SnapshotType.CHANGED_ENTITY
                                    ? "id=" + snapshot.getEntityId()
                                    : snapshotData.toString()));
                    Class<BusinessEntity<Serializable>> entityClass = (Class<BusinessEntity<Serializable>>) ReflectionUtils
                            .getClassFor(snapshot.getEntityType());
                    GenericDao<BusinessEntity<Serializable>, Serializable> daoForEntity = DbFacade.getInstance()
                            .getDaoForEntity(entityClass);

                    switch (snapshot.getSnapshotType()) {
                    case CHANGED_STATUS_ONLY:
                        EntityStatusSnapshot entityStatusSnapshot = (EntityStatusSnapshot) snapshotData;
                        ((StatusAwareDao<Serializable, Enum<?>>) daoForEntity)
                                .updateStatus(entityStatusSnapshot.getId(), entityStatusSnapshot.getStatus());
                        break;
                    case CHANGED_ENTITY:
                        BusinessEntity<Serializable> entitySnapshot = (BusinessEntity<Serializable>) snapshotData;
                        if (daoForEntity.get(entitySnapshot.getId()) == null) {
                            daoForEntity.save(entitySnapshot);
                        } else {
                            daoForEntity.update(entitySnapshot);
                        }
                        break;
                    case NEW_ENTITY_ID:
                        daoForEntity.remove(snapshotData);
                        break;
                    }
                }

                cleanUpCompensationData();
                return null;
            }

        });
    }

    /**
    * Delete the compensation data, so that we don't accidentaly try to compensate it at a later time.
    */
    private void cleanUpCompensationData() {
        getBusinessEntitySnapshotDAO().removeAllForCommandId(commandId);
    }

    public VdcReturnValueBase EndAction() {
        try {
            SetActionState();
            handleTransactivity();
            TransactionSupport.executeInScope(endActionScope, this);
        } catch (TransactionRolledbackLocalException e) {
            log.infoFormat("EndAction: Transaction was aborted in {0}", this.getClass().getName());
        } finally {
            if (getCommandShouldBeLogged()) {
                LogCommand();
            }
        }

        return getReturnValue();
    }

    private void handleTransactivity() {
        scope = (getParameters() != null) ? getParameters().getTransactionScopeOption()
                : TransactionScopeOption.Required;
        endActionScope = scope;
        boolean forceCompensation = getForceCompensation();
        // @NonTransactiveAttribute annotation overrides the scope passed by the
        // command parameters
        if (!getTransactive()) {
            scope = TransactionScopeOption.Suppress;

            // Set the end action scope to suppress only for non-compensating commands, or the end action for commands
            // will run without transaction but compensation is not supported for end action.
            endActionScope = forceCompensation ? endActionScope : scope;
        }

        if (compensationContext == null) {
            compensationContext = createCompensationContext(scope, forceCompensation);
        }
    }

    private void SetActionState() {
        if (getParameters().getTaskGroupSuccess()) {
            _actionState = CommandActionState.END_SUCCESS;
        } else {
            _actionState = CommandActionState.END_FAILURE;
        }
    }

    public void endActionInTransactionScope() {
        try {
            if (getParameters().getTaskGroupSuccess()) {
                InternalEndSuccessfully();
            } else {
                InternalEndWithFailure();
            }
        } finally {
            if (TransactionSupport.current() == null) {
                cleanUpCompensationData();
            } else {
                try {
                    if (TransactionSupport.current().getStatus() == Status.STATUS_ACTIVE) {
                        cleanUpCompensationData();
                    } else {
                        compensate();
                    }
                } catch (SystemException e) {
                    log.errorFormat("Exception while wrapping-up compensation in endAction: {0}.",
                            ExceptionUtils.getMessage(e), e);
                    compensate();
                }
            }
        }
    }

    private void InternalEndSuccessfully() {
        log.infoFormat("Ending command successfully: {0}", getClass().getName());
        EndSuccessfully();
    }

    protected void EndSuccessfully() {
        setSucceeded(true);
    }

    private void InternalEndWithFailure() {
        log.errorFormat("Ending command with failure: {0}", getClass().getName());
        EndWithFailure();
    }

    protected void EndWithFailure() {
        setSucceeded(true);
    }

    private boolean InternalCanDoAction() {
        try {
            boolean returnValue;
            Transaction transaction = TransactionSupport.suspend();
            try {
                returnValue = IsUserAutorizedToRunAction() && IsBackwardsCompatible() && validateInputs()
                        && canDoAction();
                if (!returnValue && getReturnValue().getCanDoActionMessages().size() > 0) {
                    log.warnFormat("CanDoAction of action {0} failed. Reasons:{1}", getActionType(),
                            StringHelper.aggregate(getReturnValue().getCanDoActionMessages(), ','));
                }
            } finally {
                TransactionSupport.resume(transaction);
            }
            return returnValue;
        } catch (DataAccessException dataAccessEx) {
            log.error("Data access error during CanDoActionFailure.", dataAccessEx);
            addCanDoActionMessage(VdcBllMessages.CAN_DO_ACTION_DATABASE_CONNECTION_FAILURE);
            return false;
        } catch (RuntimeException ex) {
            log.error("Error during CanDoActionFailure.", ex);
            addCanDoActionMessage(VdcBllMessages.CAN_DO_ACTION_GENERAL_FAILURE);
            return false;
        }

    }

    /**
     * @return true if all parameters class and its inner members passed
     *         validation
     */
    protected boolean validateInputs() {
        List<Class<?>> validationGroupList = getValidationGroups();
        Set<ConstraintViolation<T>> violations = validator.validate(getParameters(),
                ((Class<?>[]) validationGroupList.toArray(new Class<?>[validationGroupList.size()])));
        if (!violations.isEmpty()) {
            ArrayList<String> msgs = getReturnValue().getCanDoActionMessages();
            for (ConstraintViolation<T> constraintViolation : violations) {
                msgs.add(constraintViolation.getMessage());
            }
            return false;
        }
        return true;
    }

    /**
     * Set the parameters for bll messages (such as type and action).
     * The parameters should be initialized through the command that is called,
     * instead set them at the canDoAction()
     */
    protected void setActionMessageParameters() {
    }

    protected List<Class<?>> getValidationGroups() {
        return validationGroups;
    };

    protected List<Class<?>> addValidationGroup(Class<?>... validationGroup) {
        validationGroups.addAll(Arrays.asList(validationGroup));
        return validationGroups;
    }

    protected boolean IsBackwardsCompatible() {
        boolean result = true;
        action_version_map actionVersionMap = DbFacade.getInstance().getActionGroupDAO()
                .getActionVersionMapByActionType(getActionType());
        // if actionVersionMap not null check cluster level
        // cluster level ok check storage_pool level
        if (actionVersionMap != null && ((getVdsGroup() != null && getVdsGroup().getcompatibility_version()
                .compareTo(new Version(actionVersionMap.getcluster_minimal_version())) < 0)
                || (!StringHelper.EqOp(actionVersionMap.getstorage_pool_minimal_version(), "*")
                        && getStoragePool() != null && getStoragePool().getcompatibility_version()
                                .compareTo(new Version(actionVersionMap.getstorage_pool_minimal_version())) < 0))) {
            result = false;
            addCanDoActionMessage(VdcBllMessages.ACTION_NOT_SUPPORTED_FOR_CLUSTER_POOL_LEVEL);
        }
        return result;
    }

    /**
     * Check if current user is authorized to run current action. skip check if
     * MLA is off or command is internal
     *
     * @return
     */
    protected boolean IsUserAutorizedToRunAction() {
        boolean returnValue = true;
        // skip internal actions and MLA is off
        if (isInternalExecution || !Config.<Boolean>GetValue(ConfigValues.IsMultilevelAdministrationOn)) {
            if (log.isDebugEnabled()) {
                log.debugFormat(
                        "IsUserAutorizedToRunAction: Internal action or MLA is off - permission check skipped for action {0}",
                        getActionType());
            }
        } // check ActionGroup defined for this action
        else if (getActionType().getActionGroup() == null) {
            if (log.isDebugEnabled()) {
                returnValue = false;
                log.debugFormat(
                        "IsUserAutorizedToRunAction: No ActionGroup defiend for action {0} - check cannot proceed",
                        getActionType().toString());
            }
        } else if (getCurrentUser() != null) {
            // get subjects to check permissions on
            Map<Guid, VdcObjectType> permSubjects = getPermissionCheckSubjects();
            if (permSubjects == null || permSubjects.isEmpty()) {
                returnValue = false;
                if (log.isDebugEnabled()) {
                    log.debugFormat(
                            "IsUserAutorizedToRunAction: PermissionCheckSubjects is null or empty for action {0}",
                            getActionType());
                }
            } else {
                for (Map.Entry<Guid, VdcObjectType> entry : permSubjects.entrySet()) {
                    // if objectId is null we can't check permission
                    if (entry.getKey() == null) {
                        returnValue = false;
                        if (log.isDebugEnabled()) {
                            log.debugFormat(
                                    "IsUserAutorizedToRunAction: Object from PermissionCheckSubjects is null for action {0} permission check failed.",
                                    getActionType());
                        }
                        break;
                    }
                    NGuid permId = DbFacade.getInstance().getEntityPermissions(getCurrentUser().getUserId(),
                            getActionType().getActionGroup(), entry.getKey(), entry.getValue());
                    if (permId != null) {
                        if (log.isDebugEnabled()) {
                            log.debugFormat(
                                    "IsUserAutorizedToRunAction: found permission {0} for user when running {1}, on {2} with id {3}",
                                    permId, getActionType(), entry.getValue().getVdcObjectTranslation(),
                                    entry.getKey());
                        }
                    } else {
                        returnValue = false;
                        if (log.isDebugEnabled()) {
                            log.debugFormat(
                                    "IsUserAutorizedToRunAction: no permission found for user when running {0}, on {1} with id {2}",
                                    getActionType(), entry.getValue().getVdcObjectTranslation(), entry.getKey());
                        }
                        break;
                    }
                }
            }
        } else {
            addCanDoActionMessage(VdcBllMessages.USER_IS_NOT_LOGGED_IN);
            return false;
        }
        if (returnValue == false) {
            addCanDoActionMessage(VdcBllMessages.USER_NOT_AUTHORIZED_TO_PERFORM_ACTION);
        }
        return returnValue;
    }

    protected List<tags> GetTagsAttachedToObject() {
        // tags_permissions_map
        return new java.util.ArrayList<tags>();
    }

    protected boolean canDoAction() {
        return true;
    }

    /**
     * Factory to determine the type of the ReturnValue field
     *
     * @return
     */
    protected VdcReturnValueBase CreateReturnValue() {
        return new VdcReturnValueBase();
    }

    protected boolean getSucceeded() {

        return getReturnValue().getSucceeded();

    }

    protected void setSucceeded(boolean value) {

        getReturnValue().setSucceeded(value);

    }

    public boolean getCommandShouldBeLogged() {
        return getParameters().getShouldBeLogged();
    }

    public void setCommandShouldBeLogged(boolean value) {
        getParameters().setShouldBeLogged(value);
    }

    protected void setActionReturnValue(Object value) {
        getReturnValue().setActionReturnValue(value);
    }

    protected Object getActionReturnValue() {
        return getReturnValue().getActionReturnValue();
    }

    public TimeSpan getTransactionTimeout() {
        return new TimeSpan(1, 1, 0);
    }

    /**
     * Calculates the proper parameters for the task
     * @param parentCommandType parent command type for which the task is created
     * @param parameters parameter of the creating command
     * @return
     */
    protected VdcActionParametersBase getParametersForTask(VdcActionType parentCommandType,
            VdcActionParametersBase parameters) {
        //If there is no parent command, the command that its type
        //will be stored in the DB for thr task is the one creating the command
        if (parentCommandType == VdcActionType.Unknown) {
            return parameters;
        }
        VdcActionParametersBase parentParameters = parameters.getParentParameters();
        if (parentParameters == null) {
            String msg = "No parameters exist for " + parentCommandType;
            log.error(msg);
            throw new VdcBLLException(VdcBllErrors.NO_PARAMETERS_FOR_TASK, msg);
        }
        return parentParameters;
    }

    private boolean ExecuteWithoutTransaction() {
        boolean functionReturnValue = false;
        boolean exceptionOccurred = true;
        try {
            logRunningCommand();
            executeCommand();
            functionReturnValue = getSucceeded();
            exceptionOccurred = false;
        } catch (RepositoryException e) {
            log.error(String.format("Command %1$s throw Database exception", getClass().getName()), e);
            ProcessExceptionToClient(new VdcFault(e, VdcBllErrors.DB));
        }
        // catch (LicenseException e)
        // {
        // log.error(
        // string.Format("Command {0} throw License exception", GetType().Name),
        // e);
        // ProcessExceptionToClient(new VdcFault(e, VdcBllErrors.LICENSE));

        // }
        catch (VdcBLLException e) {
            log.error(String.format("Command %1$s throw Vdc Bll exception. With error message %2$s",
                    getClass().getName(), e.getMessage()));
            if (log.isDebugEnabled()) {
                log.debug(String.format("Command %1$s throw Vdc Bll exception", getClass().getName()), e);
            }
            ProcessExceptionToClient(new VdcFault(e, e.getVdsError().getCode()));
        } catch (RuntimeException e) {
            ProcessExceptionToClient(new VdcFault(e, VdcBllErrors.ENGINE));
            log.error(String.format("Command %1$s throw exception", getClass().getName()), e);
        } finally {
            // If we failed to execute due to exception or some other reason, we compensate for the failure.
            if (exceptionOccurred || !getSucceeded()) {
                compensate();
            } else {
                cleanUpCompensationData();
            }
        }
        return functionReturnValue;
    }

    protected TransactionScopeOption getTransactionScopeOption() {
        return getParameters().getTransactionScopeOption();
    }

    /**
     * Log the running command , and log the affected entity id and type (if
     * there are any).
     */
    private void logRunningCommand() {
        // Set start of log for running command.
        StringBuilder logInfo = new StringBuilder("Running command: ").append(getClass().getSimpleName())
                .append(" internal: ").append(isInternalExecution).append(".");

        // Get permissions of object ,to get object id.
        Map<Guid, VdcObjectType> permSubjects = getPermissionCheckSubjects();

        // Log if there is entry in the permission map.
        if (permSubjects != null && !permSubjects.isEmpty()) {
            // Build entities string for entities affected by this operation.
            StringBuilder logEntityIdsInfo = new StringBuilder();

            // Iterate all over the entities , which should be affected.
            for (Map.Entry<Guid, VdcObjectType> entry : permSubjects.entrySet()) {
                if (entry.getKey() != null) {
                    // Add comma when there are more then one entity
                    // affected.
                    if (logEntityIdsInfo.length() != 0) {
                        logEntityIdsInfo.append(", ");
                    }
                    logEntityIdsInfo.append(" ID: ").append(entry.getKey()).append(" Type: ")
                            .append(entry.getValue());
                }
            }

            // If found any entities, add the log to the logInfo.
            if (logEntityIdsInfo.length() != 0) {
                // Print all the entities affected.
                logInfo.append(" Entities affected : ").append(logEntityIdsInfo);
            }
        }

        // Log the final appended message to the log.
        log.info(logInfo);
    }

    private void executeActionInTransactionScope() {
        if (TransactionSupport.current() != null) {
            TransactionSupport.registerRollbackHandler(CommandBase.this);
        }

        // If we didn't managed to acquire lock for command or the object wasn't managed to execute properly, then
        // rollback the transaction.
        if (!ExecuteWithoutTransaction()) {
            if (TransactionSupport.current() == null) {
                cancelTasks();
            }

            // we don't want to commit transaction here
            TransactionSupport.setRollbackOnly();
        }
    }

    private void Execute() {
        try {
            handleTransactivity();
            TransactionSupport.executeInScope(scope, this);
        } catch (TransactionRolledbackLocalException e) {
            log.infoFormat("Transaction was aborted in {0}", this.getClass().getName());
            //Transaction was aborted - we must sure we compensation for all previous applicative stages of the command
            compensate();
        } finally {
            freeLock();
            if (getCommandShouldBeLogged()) {
                LogCommand();
            }
            if (getSucceeded()) {
                // only after creating all tasks, we can start polling them (we
                // don't want
                // to start polling before all tasks were created, otherwise we
                // might change
                // the VM/VmTemplate status to 'Down'/'OK' too soon.
                UpdateTasksWithActionParameters();

                StartPollingAsyncTasks();
            }
        }
    }

    private boolean getForceCompensation() {
        NonTransactiveCommandAttribute annotation = getClass().getAnnotation(NonTransactiveCommandAttribute.class);
        return annotation != null && annotation.forceCompensation();
    }

    protected abstract void executeCommand();

    private void LogCommand() {
        Class<?> type = getClass();
        // Object[] attributes = new Object[] {}; // FIXED
        // type.GetCustomAttributes(InternalCommandAttribute.class, false);
        InternalCommandAttribute annotation = type.getAnnotation(InternalCommandAttribute.class);
        if (annotation == null) {
            log();
        }
    }

    private boolean getTransactive() {
        // Object[] attributes = new Object[] {}; // FIXED
        // getClass().GetCustomAttributes(NonTransactiveCommandAttribute.class,
        // true);
        NonTransactiveCommandAttribute annotation = getClass().getAnnotation(NonTransactiveCommandAttribute.class);
        return annotation == null;
    }

    protected static java.util.Date getNow() {
        long nowMiliSeconds = System.currentTimeMillis();
        return new java.util.Date(nowMiliSeconds);
    }

    protected T getParameters() {
        return _parameters;
    }

    public VdcReturnValueBase getReturnValue() {
        if (_returnValue == null) {
            _returnValue = CreateReturnValue();
        }
        return _returnValue;
    }

    protected VdcActionType getActionType() {
        try {
            if (actionType == null) {
                String name = getClass().getName();
                name = name.substring(0, name.length() - 7);
                name = name.substring(name.lastIndexOf('.') + 1);
                actionType = VdcActionType.valueOf(name);
            }
            return actionType;
        } catch (java.lang.Exception e) {
            return VdcActionType.Unknown;
        }
    }

    protected String getDescription() {
        return _description;
    }

    protected void setDescription(String value) {
        _description = value;
    }

    private void ProcessExceptionToClient(VdcFault fault) {
        fault.setSessionID(getParameters().getSessionId());

        if (getParameters().getMultipleAction()) {
            if (_backendCallBack != null) {
                try {
                    Guid FaultQueryId = _backendCallBack.BackendException(getActionType(), fault);
                    BackendCallBacksDirector.getInstance().RegisterFaultQuery(FaultQueryId, fault.getSessionID());
                } catch (RuntimeException ex) {
                    log.errorFormat("{0}", ex);
                }
            }
        } else {
            _returnValue.setFault(fault);
        }
    }

    /**
     * Use this method in order to create task in the AsyncTaskManager in a safe
     * way. If you use this method within a certain command, make sure that the
     * command implemented the ConcreteCreateTask method.
     *
     * @param asyncTaskCreationInfo
     *            info to send to AsyncTaskManager when creating the task.
     * @param parentCommand
     *            VdcActionType of the command that its EndAction we want to
     *            invoke when tasks are finished.
     * @return Guid of the created task.
     */
    protected Guid CreateTask(AsyncTaskCreationInfo asyncTaskCreationInfo, VdcActionType parentCommand) {
        Guid retValue = Guid.Empty;

        Transaction transaction = TransactionSupport.suspend();
        try {
            try {
                retValue = ConcreteCreateTask(asyncTaskCreationInfo, parentCommand);
            } catch (RuntimeException ex) {
                log.errorFormat("Error during CreateTask for command: {0}. Exception {1}", getClass().getName(),
                        ex);
            }
        } finally {
            TransactionSupport.resume(transaction);
        }

        return retValue;
    }

    protected Guid ConcreteCreateTask(AsyncTaskCreationInfo asyncTaskCreationInfo, VdcActionType parentCommand) {
        throw new NotImplementedException();
    }

    protected void UpdateTasksWithActionParameters() {
        for (Guid taskID : getReturnValue().getTaskIdList()) {
            AsyncTaskManager.getInstance().UpdateTaskWithActionParameters(taskID, getParameters());
        }
    }

    private void StartPollingAsyncTasks() {

        for (Guid taskID : getReturnValue().getTaskIdList()) {
            AsyncTaskManager.getInstance().StartPollingTask(taskID);
        }
    }

    protected java.util.ArrayList<Guid> getTaskIdList() {
        return getParameters().getParentCommand() != VdcActionType.Unknown
                ? getReturnValue().getInternalTaskIdList()
                : getReturnValue().getTaskIdList();
    }

    @Override
    public void Rollback() {
        log.errorFormat("Transaction rolled-back for command: {0}.", CommandBase.this.getClass().getName());
        cancelTasks();
    }

    private void cancelTasks() {
        if (!getReturnValue().getTaskIdList().isEmpty()) {
            ThreadPoolUtil.execute(new Runnable() {
                @Override
                public void run() {
                    String threadName = Thread.currentThread().getName();
                    Thread.currentThread().setName("Rollback-" + threadName);
                    TransactionSupport.executeInNewTransaction(new TransactionMethod<Object>() {
                        @Override
                        public Object runInTransaction() {
                            try {
                                AsyncTaskManager.getInstance().CancelTasks(getReturnValue().getTaskIdList());
                            } catch (java.lang.Exception e) {
                                log.errorFormat("Failed to cancel tasks for command: {0}.",
                                        CommandBase.this.getClass().getName());
                            }
                            return null;
                        }
                    });
                }
            });
        }
    }

    protected void RevertTasks() {
        if (getParameters().getTaskIds() != null) {
            // list to send to the PollTasks mathod
            java.util.ArrayList<Guid> taskIdAsList = new java.util.ArrayList<Guid>();
            for (Guid taskId : getParameters().getTaskIds()) {
                taskIdAsList.add(taskId);
                java.util.ArrayList<AsyncTaskStatus> tasksStatuses = AsyncTaskManager.getInstance()
                        .PollTasks(taskIdAsList);
                // call revert task only if ended succeesfully
                if (tasksStatuses.get(0).getTaskEndedSuccessfully()) {
                    SPMTaskGuidBaseVDSCommandParameters tempVar = new SPMTaskGuidBaseVDSCommandParameters(
                            getStoragePool().getId(), taskId);
                    tempVar.setCompatibilityVersion(getStoragePool().getcompatibility_version().toString());
                    Backend.getInstance().getResourceManager().RunVdsCommand(VDSCommandType.SPMRevertTask, tempVar);
                }
                taskIdAsList.clear();
            }
        }
    }

    protected Guid getObjectLockingId() {
        Guid returnValue = Guid.Empty;
        Class<?> type = getClass();
        LockIdNameAttribute annotation = type.getAnnotation(LockIdNameAttribute.class);
        if (annotation != null) {
            PropertyInfo propertyInfo = TypeCompat.GetProperty(type, annotation.fieldName());
            if (propertyInfo != null) {
                returnValue = (Guid) propertyInfo.GetValue(this, Guid.Empty);

            }

        }
        return returnValue;
    }

    /**
     * Object which is representing a lock that some commands will acquire
     */
    private EngineLock commandLock = null;

    protected boolean acquireLock() {
        Guid objectLockingId = getObjectLockingId();
        boolean returnValue = true;
        if (!Guid.Empty.equals(objectLockingId)) {
            EngineLock lock = new EngineLock();
            String currType = getClass().getName();
            Map<String, Guid> exclusiveLock = Collections.singletonMap(currType, objectLockingId);
            lock.setExclusiveLocks(exclusiveLock);
            if (LockManagerFactory.getLockManager().acquireLock(lock)) {
                log.infoFormat("Lock Acquired to object {0}", lock);
                commandLock = lock;
            } else {
                log.infoFormat("Failed to Acquire Lock to object {0}", lock);
                addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_FAILED_OBJECT_LOCKED);
                returnValue = false;
            }
        }
        return returnValue;

    }

    protected void freeLock() {
        if (commandLock != null) {
            LockManagerFactory.getLockManager().releaseLock(commandLock);
            log.infoFormat("Lock freed to object {0}", commandLock);
            commandLock = null;
        }
    }

    @Override
    public Object runInTransaction() {
        if (_actionState == CommandActionState.EXECUTE) {
            executeActionInTransactionScope();
            return null;
        } else {
            endActionInTransactionScope();
            return null;
        }
    }

    protected boolean isInternalExecution() {
        return isInternalExecution;
    }

    public void setInternalExecution(boolean isInternalExecution) {
        this.isInternalExecution = isInternalExecution;
    }

    /**
     * Add a message to the {@link CommandBase#canDoAction()}'s return value.
     * This return value will be sent to the client for the detailed information
     * of why the action can't be performed.
     *
     * @param message
     *            The message to add.
     */
    protected void addCanDoActionMessage(VdcBllMessages message) {
        getReturnValue().getCanDoActionMessages().add(message.name());
    }

    /**
     * Add a message to the {@link CommandBase#canDoAction()}'s return value.
     * This return value will be sent to the client for the detailed information of why the action can't be performed.
     *
     * @param message The message to add.
     */
    protected void addCanDoActionMessage(String message) {
        getReturnValue().getCanDoActionMessages().add(message);
    }

    /**
     * Run the given command in the VDS and return the VDS's response.
     *
     * @param commandType
     *            The command to run.
     * @param parameters
     *            The corresponding parameters for the command.
     * @return The return from the VDS, containing success/failure, async task ids (in case of success), or error data
     *         (in case of failure).
     * @throws VdcBLLException
     *             In case of an unhandled exception (Usually more severe than failure of the command, because we don't
     *             know why).
     */
    protected VDSReturnValue runVdsCommand(VDSCommandType commandType, VDSParametersBase parameters)
            throws VdcBLLException {
        return Backend.getInstance().getResourceManager().RunVdsCommand(commandType, parameters);
    }

    /**
     * Permissions are attached to object so every command must declare its
     * object target type and its GUID
     *
     * @return Map of Guids to Object types
     */
    public abstract Map<Guid, VdcObjectType> getPermissionCheckSubjects();

}