package org.ovirt.engine.core.bll;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.ovirt.engine.core.bll.aaa.SessionDataContainer;
import org.ovirt.engine.core.bll.context.EngineContext;
import org.ovirt.engine.core.bll.interfaces.BackendInternal;
import org.ovirt.engine.core.common.businessentities.aaa.DbUser;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.interfaces.VDSBrokerFrontend;
import org.ovirt.engine.core.common.queries.VdcQueryParametersBase;
import org.ovirt.engine.core.common.queries.VdcQueryReturnValue;
import org.ovirt.engine.core.common.queries.VdcQueryType;
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.dal.VdcCommandBase;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector;
import org.ovirt.engine.core.utils.CorrelationIdTracker;
import org.ovirt.engine.core.utils.log.Logged;
import org.ovirt.engine.core.utils.log.Logged.LogLevel;
@Logged(executionLevel = LogLevel.TRACE, errorLevel = LogLevel.WARN)
public abstract class QueriesCommandBase<P extends VdcQueryParametersBase> extends VdcCommandBase {
private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
private static final String QuerySuffix = "Query";
// get correct return value type
private final VdcQueryReturnValue returnValue;
private final VdcQueryType queryType;
private DbUser user;
private final P parameters;
private boolean isInternalExecution = false;
private final EngineContext engineContext;
@Inject
protected AuditLogDirector auditLogDirector;
@Inject
private SessionDataContainer sessionDataContainer;
@Inject
private VDSBrokerFrontend vdsBroker;
@Inject
protected BackendInternal backend;
public QueriesCommandBase(P parameters) {
this(parameters, null);
}
public QueriesCommandBase(P parameters, EngineContext engineContext) {
if (parameters.getCorrelationId() == null) {
parameters.setCorrelationId(CorrelationIdTracker.getCorrelationId());
} else {
CorrelationIdTracker.setCorrelationId(parameters.getCorrelationId());
}
this.parameters = parameters;
returnValue = new VdcQueryReturnValue();
returnValue.setCorrelationId(parameters.getCorrelationId());
queryType = initQueryType();
this.engineContext = engineContext == null ? new EngineContext().withSessionId(parameters.getSessionId()) : engineContext;
}
/**
* @see PostConstruct
*/
@PostConstruct
protected final void postConstruct() {
user = initUser();
}
private VdcQueryType initQueryType() {
try {
String name = getClass().getSimpleName();
name = name.substring(0, name.length() - QuerySuffix.length());
return VdcQueryType.valueOf(name);
} catch (Exception e) {
return VdcQueryType.Unknown;
}
}
protected DbUser initUser() {
return getSessionDataContainer().getUser(engineContext.getSessionId(),
parameters.getRefresh());
}
protected SessionDataContainer getSessionDataContainer() {
return sessionDataContainer;
}
@Override
protected void executeCommand() {
if (getParameters().getRefresh() || getSessionDataContainer().isSsoOvirtAppApiScope(getParameters().getSessionId())) {
getSessionDataContainer().updateSessionLastActiveTime(getParameters().getSessionId());
}
if (validatePermissions()) {
if (validateInputs()) {
long start = System.currentTimeMillis();
try {
returnValue.setSucceeded(true);
executeQueryCommand();
} catch (RuntimeException ex) {
returnValue.setSucceeded(false);
Throwable th = ex instanceof EngineException ? ex : ex.getCause();
if (th instanceof EngineException) {
EngineException vdcExc = (EngineException) th;
if (vdcExc.getErrorCode() != null) {
returnValue.setExceptionString(vdcExc.getErrorCode().toString());
} else {
returnValue.setExceptionString(vdcExc.getMessage());
}
log.error("Query '{}' failed: {}",
getClass().getSimpleName(),
vdcExc.getMessage());
log.error("Exception", vdcExc);
} else {
returnValue.setExceptionString(ex.getMessage());
log.error("Query '{}' failed: {}",
getClass().getSimpleName(),
ex.getMessage());
log.error("Exception", ex);
}
} finally {
log.debug("Query {} took {} ms", getCommandName(), System.currentTimeMillis() - start);
}
} else {
log.error("Query execution failed due to invalid inputs: {}", returnValue.getExceptionString());
}
} else {
String errMessage = "Query execution failed due to insufficient permissions.";
log.error(errMessage);
returnValue.setExceptionString(errMessage);
}
}
/**
* Validates if this query is permitted to run.
*
* @return <code>true</code> if the query is OK (i.e., the issuing user has enough permissions to execute it), or
* <code>false</code> otherwise.
*/
private boolean validatePermissions() {
// If the user requests filtered execution, his permissions are inconsequential.
// If the query supports filtering it should be allowed, and if not - not.
if (parameters.isFiltered()) {
return !queryType.isAdmin();
}
// If the query was executed internally, it should be allowed in any event.
if (isInternalExecution) {
return true;
}
// In any other event, we have admin execution, which should only be allowed according to the user's
// permissions.
// Note that is cached per session
return getUser().isAdmin();
}
/**
* @return true if all parameters class and its inner members passed
* validation
*/
protected boolean validateInputs() {
Set<ConstraintViolation<P>> violations = validator.validate(getParameters());
if (!violations.isEmpty()) {
returnValue.setExceptionString(violations.toString());
return false;
}
return true;
}
public VdcQueryReturnValue getQueryReturnValue() {
return returnValue;
}
public EngineContext getEngineContext() {
return engineContext;
}
public P getParameters() {
return parameters;
}
@Override
public String toString() {
return String.format("%s(%s)", super.toString(),
getParameters() != null ? getParameters().toString() : "null");
}
protected abstract void executeQueryCommand();
@Override
public void setReturnValue(Object value) {
returnValue.setReturnValue(value);
}
public boolean isInternalExecution() {
return isInternalExecution;
}
public void setInternalExecution(boolean isInternalExecution) {
this.isInternalExecution = isInternalExecution;
}
protected DbUser getUser() {
return user;
}
protected Guid getUserID() {
if (user == null) {
return null;
}
return user.getId();
}
protected long getEngineSessionSeqId() {
if (engineContext.getSessionId() == null) {
throw new RuntimeException("No sessionId found for query " + getClass().getName());
}
return getSessionDataContainer().getEngineSessionSeqId(engineContext.getSessionId());
}
protected VdcQueryReturnValue runInternalQuery(VdcQueryType actionType, VdcQueryParametersBase parameters) {
//All internal queries should have refresh set to false, since the decision to refresh the session should
//be up to the client. All internal queries will not refresh the session.
parameters.setRefresh(false);
return backend.runInternalQuery(actionType, parameters, getEngineContext());
}
protected VDSReturnValue runVdsCommand(VDSCommandType commandType, VDSParametersBase parameters)
throws EngineException {
return vdsBroker.runVdsCommand(commandType, parameters);
}
}