package org.ovirt.engine.api.restapi.resource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.ws.rs.GET;
import javax.ws.rs.core.Response;
import org.ovirt.engine.api.model.Action;
import org.ovirt.engine.api.model.CreationStatus;
import org.ovirt.engine.api.model.Job;
import org.ovirt.engine.api.restapi.util.ErrorMessageHelper;
import org.ovirt.engine.api.restapi.util.ExpectationHelper;
import org.ovirt.engine.api.restapi.util.LinkHelper;
import org.ovirt.engine.api.restapi.util.ParametersHelper;
import org.ovirt.engine.core.common.HasCorrelationId;
import org.ovirt.engine.core.common.action.RunAsyncActionParameters;
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.config.ConfigCommon;
import org.ovirt.engine.core.common.interfaces.BackendLocal;
import org.ovirt.engine.core.common.interfaces.SearchType;
import org.ovirt.engine.core.common.queries.ConfigurationValues;
import org.ovirt.engine.core.common.queries.GetConfigurationValueParameters;
import org.ovirt.engine.core.common.queries.SearchParameters;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BackendResource extends BaseBackendResource {
protected static final int NO_LIMIT = -1;
private static final String CORRELATION_ID = "correlation_id";
private static final String ASYNC_CONSTRAINT = "async";
public static final String FORCE_CONSTRAINT = "force";
protected static final String MAX = "max";
private static final String NON_BLOCKING_EXPECTATION = "202-accepted";
private static final Logger log = LoggerFactory.getLogger(BackendResource.class);
public static final String POPULATE = "All-Content";
public static final String JOB_ID_CONSTRAINT = "JobId";
public static final String STEP_ID_CONSTRAINT = "StepId";
private <T> T castQueryResultToEntity(Class<T> clz, VdcQueryReturnValue result,
String constraint) throws BackendFailureException {
T entity;
if (List.class.isAssignableFrom(clz) && result.getReturnValue() instanceof List) {
entity = clz.cast(result.getReturnValue());
} else {
List<T> list = asCollection(clz, result.getReturnValue());
if (list == null || list.isEmpty()) {
throw new EntityNotFoundException(constraint);
}
entity = clz.cast(list.get(0));
}
return entity;
}
@Deprecated
protected <T> T getEntity(Class<T> clz, SearchType searchType, String constraint) {
try {
VdcQueryReturnValue result = runQuery(VdcQueryType.Search,
createSearchParameters(searchType, constraint));
if (!result.getSucceeded()) {
backendFailure(result.getExceptionString());
}
return castQueryResultToEntity(clz, result, constraint);
} catch (Exception e) {
return handleError(clz, e, false);
}
}
public VdcQueryReturnValue runQuery(VdcQueryType queryType, VdcQueryParametersBase queryParams) {
BackendLocal backend = getBackend();
setCorrelationId(queryParams);
queryParams.setFiltered(isFiltered());
return backend.runQuery(queryType, sessionize(queryParams));
}
protected SearchParameters createSearchParameters(SearchType searchType, String constraint) {
return new SearchParameters(constraint, searchType);
}
protected <T> T getEntity(Class<T> clz, VdcQueryType query, VdcQueryParametersBase queryParams, String identifier) {
return getEntity(clz, query, queryParams, identifier, false);
}
public <T> T getEntity(Class<T> clz,
VdcQueryType query,
VdcQueryParametersBase queryParams,
String identifier,
boolean notFoundAs404) {
return getEntity(clz, query, queryParams, identifier, notFoundAs404, true);
}
public <T> T getOptionalEntity(Class<T> clz,
VdcQueryType query,
VdcQueryParametersBase queryParams,
String identifier,
boolean notFoundAs404) {
return getEntity(clz, query, queryParams, identifier, notFoundAs404, false);
}
public <T> T getEntity(Class<T> clz,
VdcQueryType query,
VdcQueryParametersBase queryParams,
String identifier,
boolean notFoundAs404,
boolean isMandatory) {
try {
return doGetEntity(clz, query, queryParams, identifier, isMandatory);
} catch (Exception e) {
return handleError(clz, e, notFoundAs404);
}
}
protected <T> T doGetEntity(Class<T> clz,
VdcQueryType query,
VdcQueryParametersBase queryParams,
String identifier) throws BackendFailureException {
return doGetEntity(clz, query, queryParams, identifier, true);
}
protected <T> T doGetEntity(Class<T> clz,
VdcQueryType query,
VdcQueryParametersBase queryParams,
String identifier,
boolean isMandatory) throws BackendFailureException {
VdcQueryReturnValue result = runQuery(query, queryParams);
if (!result.getSucceeded() || (isMandatory && result.getReturnValue() == null)) {
if (result.getExceptionString() != null) {
backendFailure(result.getExceptionString());
} else {
throw new EntityNotFoundException(identifier);
}
} else {
if (result.getReturnValue() == null) {
return null;
}
}
return castQueryResultToEntity(clz, result, identifier);
}
protected <T> List<T> getBackendCollection(Class<T> clz, VdcQueryType query, VdcQueryParametersBase queryParams) {
try {
List<T> results = asCollection(clz, new ArrayList<T>());
VdcQueryReturnValue result = runQuery(query, queryParams);
if (result!=null ) {
if (!result.getSucceeded()) {
backendFailure(result.getExceptionString());
}
results = asCollection(clz, result.getReturnValue());
int max = ParametersHelper.getIntegerParameter(httpHeaders, uriInfo, MAX, NO_LIMIT, NO_LIMIT);
if (max != NO_LIMIT && max < results.size()) {
results = results.subList(0, max);
}
}
return results;
} catch (Exception e) {
return handleError(e, false);
}
}
public Response performAction(VdcActionType task, VdcActionParametersBase params, Action action) {
return performAction(task, params, action, false);
}
public Response performAction(VdcActionType task, VdcActionParametersBase params) {
return performAction(task, params, null, false);
}
protected Response performAction(VdcActionType task, VdcActionParametersBase params, Action action, boolean getEntityWhenDone) {
try {
if (isAsync() || expectNonBlocking()) {
return performNonBlockingAction(task, params, action);
} else {
VdcReturnValueBase actionResult = doAction(task, params);
if (action == null) {
action = new Action();
}
if (actionResult.getJobId() != null) {
setJobLink(action, actionResult);
}
action.setStatus(CreationStatus.COMPLETE.value());
if (getEntityWhenDone) {
setActionItem(action, getEntity());
}
return Response.ok().entity(action).build();
}
} catch (Exception e) {
return handleError(Response.class, e, false);
}
}
protected void setJobLink(final Action action, VdcReturnValueBase actionResult) {
Job job = new Job();
job.setId(actionResult.getJobId().toString());
LinkHelper.addLinks(job, null, false);
action.setJob(job);
}
protected boolean isAsync() {
return ParametersHelper.getBooleanParameter(httpHeaders, uriInfo, ASYNC_CONSTRAINT, true, false);
}
protected boolean isForce() {
return ParametersHelper.getBooleanParameter(httpHeaders, uriInfo, FORCE_CONSTRAINT, true, false);
}
protected void badRequest(String message) {
throw new WebFaultException(null, message, Response.Status.BAD_REQUEST);
}
protected boolean expectNonBlocking() {
Set<String> expectations = ExpectationHelper.getExpectations(httpHeaders);
return expectations.contains(NON_BLOCKING_EXPECTATION);
}
protected Response performNonBlockingAction(VdcActionType task, VdcActionParametersBase params, Action action) {
try {
doNonBlockingAction(task, params);
if (action!=null) {
action.setStatus(CreationStatus.IN_PROGRESS.value());
return Response.status(Response.Status.ACCEPTED).entity(action).build();
} else {
return Response.status(Response.Status.ACCEPTED).build();
}
} catch (Exception e) {
return handleError(Response.class, e, false);
}
}
protected <T> T performAction(VdcActionType task, VdcActionParametersBase params, Class<T> resultType) {
try {
return resultType.cast(doAction(task, params).getActionReturnValue());
} catch (Exception e) {
return handleError(resultType, e, false);
}
}
protected VdcReturnValueBase doAction(VdcActionType task,
VdcActionParametersBase params) throws BackendFailureException {
BackendLocal backend = getBackend();
setJobOrStepId(params);
setCorrelationId(params);
VdcReturnValueBase result = backend.runAction(task, sessionize(params));
if (result != null && !result.isValid()) {
backendFailure(result.getValidationMessages());
} else if (result != null && !result.getSucceeded()) {
backendFailure(result.getExecuteFailedMessages());
}
assert result != null;
return result;
}
protected void doNonBlockingAction(final VdcActionType task, final VdcActionParametersBase params) {
BackendLocal backend = getBackend();
setCorrelationId(params);
setJobOrStepId(params);
backend.runAction(VdcActionType.RunAsyncAction, sessionize(new RunAsyncActionParameters(task, sessionize(params))));
}
private void setJobOrStepId(VdcActionParametersBase params) {
String jobId = ParametersHelper.getParameter(httpHeaders, uriInfo, JOB_ID_CONSTRAINT);
if (jobId != null) {
params.setJobId(asGuid(jobId));
}
String stepId = ParametersHelper.getParameter(httpHeaders, uriInfo, STEP_ID_CONSTRAINT);
if (stepId != null) {
params.setJobId(asGuid(stepId));
}
}
private void setCorrelationId(HasCorrelationId params) {
String correlationId = getCurrent().getParameters().get(CORRELATION_ID);
if (correlationId != null) {
params.setCorrelationId(correlationId);
}
}
@SuppressWarnings("unchecked")
protected <T> T getConfigurationValueDefault(ConfigurationValues config) {
VdcQueryReturnValue result = runQuery(
VdcQueryType.GetConfigurationValue,
new GetConfigurationValueParameters(config, ConfigCommon.defaultConfigurationVersion)
);
if (result.getSucceeded()) {
return (T) result.getReturnValue();
}
return null;
}
/**
* @return true if request header contains [All-Content='true']
*/
protected boolean isPopulate() {
List<String> populates = httpHeaders.getRequestHeader(POPULATE);
if (populates != null && populates.size() > 0) {
return Boolean.valueOf(populates.get(0)).booleanValue();
} else {
return false;
}
}
/**
* Runs implementation of the @GET annotated method of this resource (get() for single entity resources, and list()
* for collection resources).
*
* @return The result of the @GET annotated method, an entity or list of entities.
*/
protected Object getEntity() {
try {
Method m = resolveGet();
if (m == null) {
return null;
}
Object entity = m.invoke(this);
return getEntityWithIdAndHref(entity);
} catch (Exception e) {
log.error("Getting resource after action failed.", e);
return null;
}
}
private Method resolveGet() throws NoSuchMethodException, SecurityException {
Method methodSignature = findGetSignature(this.getClass());
if (methodSignature == null) {
return null;
}
Method methodImplementation =
this.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
return methodImplementation;
}
private static Method findGetSignature(Class<?> clazz) {
Class<?> currentAncestor = clazz;
while (currentAncestor != null) {
Class<?>[] interfaces = currentAncestor.getInterfaces();
for (Class<?> ifc : interfaces) {
Method m = find(ifc);
if (m != null) {
return m;
}
}
currentAncestor = currentAncestor.getSuperclass();
}
return null;
}
private static Method find(Class<?> ifc) {
Class<?> currentAncestor = ifc;
while (currentAncestor != null) {
for (Method m : currentAncestor.getMethods()) {
if (m.isAnnotationPresent(GET.class)) {
return m;
}
}
currentAncestor = currentAncestor.getSuperclass();
}
return null;
}
protected Object getEntityWithIdAndHref(Object entity) throws InstantiationException, IllegalAccessException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
Object newEntity = entity.getClass().newInstance();
setEntityValue(newEntity, "setId", getEntityValue(entity, "getId"));
setEntityValue(newEntity, "setHref", getEntityValue(entity, "getHref"));
return entity;
}
private void setEntityValue(Object entity, String methodName, Object value) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method method = entity.getClass().getMethod(methodName);
method.invoke(entity, value);
}
private Object getEntityValue(Object entity, String methodName) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Object nullObj = null;
Method method = entity.getClass().getMethod(methodName);
return method.invoke(entity, nullObj);
}
protected Response actionStatus(CreationStatus status, Action action, Object result) {
setActionItem(action, result);
action.setStatus(status.value());
return Response.ok().entity(action).build();
}
protected void setActionItem(Action action, Object result) {
if (result == null) {
return;
}
String name = result.getClass().getSimpleName().toLowerCase();
for (Method m : action.getClass().getMethods()) {
if (m.getName().startsWith("set") && m.getName().replace("set", "").toLowerCase().equals(name)) {
try {
m.invoke(action, result);
break;
} catch (Exception e) {
// should not happen
log.error("Resource to action assignment failure.", e);
break;
}
}
}
}
protected void backendFailure(String msg) throws BackendFailureException {
throw new BackendFailureException(localize(msg), ErrorMessageHelper.getErrorStatus(msg));
}
protected void backendFailure(List<String> messages) throws BackendFailureException {
throw new BackendFailureException(localize(messages), ErrorMessageHelper.getErrorStatus(messages));
}
}