package org.ovirt.engine.api.restapi.resource;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import java.net.URI;
import java.util.List;
import java.util.Set;
import javax.ws.rs.core.Response;
import org.ovirt.engine.api.model.ActionableResource;
import org.ovirt.engine.api.model.BaseResource;
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.api.restapi.util.QueryHelper;
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.businessentities.IVdcQueryable;
import org.ovirt.engine.core.common.interfaces.SearchType;
import org.ovirt.engine.core.common.queries.SearchParameters;
import org.ovirt.engine.core.common.queries.VdcQueryParametersBase;
import org.ovirt.engine.core.common.queries.VdcQueryType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractBackendCollectionResource<R extends BaseResource, Q /* extends IVdcQueryable */>
extends AbstractBackendResource<R, Q> {
private static final String BLOCKING_EXPECTATION = "201-created";
private static final String CREATION_STATUS_REL = "creation_status";
public static final String FROM_CONSTRAINT_PARAMETER = "from";
public static final String CASE_SENSITIVE_CONSTRAINT_PARAMETER = "case_sensitive";
private static final Logger log = LoggerFactory.getLogger(AbstractBackendCollectionResource.class);
protected AbstractBackendCollectionResource(Class<R> modelType, Class<Q> entityType) {
super(modelType, entityType);
}
protected List<Q> getBackendCollection(SearchType searchType) {
return getBackendCollection(searchType, QueryHelper.getConstraint(httpHeaders, uriInfo, "", modelType));
}
protected List<Q> getBackendCollection(SearchType searchType, String constraint) {
return getBackendCollection(entityType,
VdcQueryType.Search,
getSearchParameters(searchType, constraint));
}
private SearchParameters getSearchParameters(SearchType searchType, String constraint) {
SearchParameters searchParams = new SearchParameters(constraint, searchType);
boolean caseSensitive = ParametersHelper.getBooleanParameter(httpHeaders, uriInfo, CASE_SENSITIVE_CONSTRAINT_PARAMETER, true, false);
int from = ParametersHelper.getIntegerParameter(httpHeaders, uriInfo, FROM_CONSTRAINT_PARAMETER, -1, -1);
int max = ParametersHelper.getIntegerParameter(httpHeaders, uriInfo, MAX, Integer.MAX_VALUE, Integer.MAX_VALUE);
searchParams.setCaseSensitive(caseSensitive);
if (from != -1) {
searchParams.setSearchFrom(from);
}
searchParams.setMaxCount(max);
return searchParams;
}
protected List<Q> getBackendCollection(VdcQueryType query, VdcQueryParametersBase queryParams) {
return getBackendCollection(entityType, query, queryParams);
}
/**
* get the entities according to the filter and intersect them with those resulted from running the search query
*/
protected List<Q> getBackendCollection(VdcQueryType query, VdcQueryParametersBase queryParams, SearchType searchType) {
List<Q> filteredList = getBackendCollection(entityType, query, queryParams);
// check if we got search expression in the URI
String search = ParametersHelper.getParameter(httpHeaders, uriInfo, QueryHelper.CONSTRAINT_PARAMETER);
if (search != null) {
List<Q> searchList = getBackendCollection(searchType);
// Note that it is key to pass here first the search list and then the filtered list, as we want to make
// sure that the order of the search list is preserved, as the user my have specified 'sort by ...' as one
// of the search criteria.
return safeIntersection(searchList, filteredList);
}
else {
return filteredList;
}
}
/**
* Calculates the intersection of two lists of objects, comparing them by id, and preserving the objects and the
* order that was used in the {@code sorted} parameter.
*
* @param sorted the list of objects whose order should be preserved
* @param other the other list of objects
* @return the intersection of both lists of objects
*/
private List<Q> safeIntersection(List<Q> sorted, List<Q> other) {
// Calculate the sets of ids of all the objects:
Set<Object> sortedIds = sorted.stream().map(this::getId).collect(toSet());
Set<Object> otherIds = other.stream().map(this::getId).collect(toSet());
// Remove from the set of sorted ids the ids that aren't part also of the other ids, thus effectively
// calculating the intersection of both sets of ids:
sortedIds.retainAll(otherIds);
// Remove from the sorted list all the objects that aren't part of the intersection, and return the result (this
// way the result will be always from the sorted list, and the order will be preserved):
return sorted.stream()
.filter(object -> sortedIds.contains(getId(object)))
.collect(toList());
}
/**
* Obtains the identifier of a backend object. This id will be used to compare the objects instead of the
* {@link Object#equals(Object)} method. Should be overridden by resources that manage objects that don't implement
* the {@link IVdcQueryable} interface.
*
* @param entity the entity
* @return the id of the entity, or {@code null} if the entity doesn't have an id
*/
protected Object getId(Q entity) {
Object id = null;
if (entity != null && entity instanceof IVdcQueryable) {
id = ((IVdcQueryable) entity).getQueryableId();
}
return id;
}
protected final <T> Response performCreate(VdcActionType task,
VdcActionParametersBase taskParams,
IResolver<T, Q> entityResolver,
boolean block) {
return performCreate(task, taskParams, entityResolver, block, null);
}
protected final <T> Response performCreate(VdcActionType task,
VdcActionParametersBase taskParams,
IResolver<T, Q> entityResolver,
boolean block,
Class<? extends BaseResource> suggestedParentType) {
// create (overridable)
VdcReturnValueBase createResult = doCreateEntity(task, taskParams);
// fetch + map
return fetchCreatedEntity(entityResolver, block, suggestedParentType, createResult);
}
protected final <T> Response performCreate(VdcActionType task,
VdcActionParametersBase taskParams,
IResolver<T, Q> entityResolver) {
return performCreate(task, taskParams, entityResolver, expectBlocking());
}
protected final <T> Response performCreate(VdcActionType task,
VdcActionParametersBase taskParams,
IResolver<T, Q> entityResolver,
Class<? extends BaseResource> suggestedParentType) {
return performCreate(task, taskParams, entityResolver, expectBlocking(), suggestedParentType);
}
protected boolean expectBlocking() {
Set<String> expectations = ExpectationHelper.getExpectations(httpHeaders);
return expectations.contains(BLOCKING_EXPECTATION);
}
protected void handleAsynchrony(VdcReturnValueBase result, R model) {
model.setCreationStatus(getAsynchronousStatus(result).value());
linkSubResource(model, CREATION_STATUS_REL, asString(result.getVdsmTaskIdList()));
}
@SuppressWarnings("unchecked")
protected <T> Q resolveCreated(VdcReturnValueBase result, IResolver<T, Q> entityResolver) {
try {
return entityResolver.resolve(result.getActionReturnValue());
} catch (Exception e) {
// Handling exception as we can't tolerate the failure
return handleError(e, false);
}
}
/**
*
* @param model the resource to add actions to
* @return collection with action links
*/
protected <C extends ActionableResource> C addActions(C model) {
LinkHelper.addActions(model, this);
return model;
}
private <T> Response fetchCreatedEntity(IResolver<T, Q> entityResolver,
boolean block,
Class<? extends BaseResource> suggestedParentType,
VdcReturnValueBase createResult) {
Q created = resolveCreated(createResult, entityResolver);
R model = mapEntity(suggestedParentType, created);
Response response = null;
if (createResult.getHasAsyncTasks()) {
if (block) {
awaitCompletion(createResult);
// refresh model state
created = resolveCreated(createResult, entityResolver);
model = mapEntity(suggestedParentType, created);
response = Response.created(URI.create(model.getHref())).entity(model).build();
} else {
if (model == null) {
response = Response.status(ACCEPTED_STATUS).build();
} else {
handleAsynchrony(createResult, model);
response = Response.status(ACCEPTED_STATUS).entity(model).build();
}
}
} else {
if (model == null) {
response = Response.status(ACCEPTED_STATUS).build();
} else if (model.isSetHref()) {
response = Response.created(URI.create(model.getHref())).entity(model).build();
} else {
response = Response.ok(Response.Status.CREATED).entity(model).build();
}
}
return response;
}
protected VdcReturnValueBase doCreateEntity(VdcActionType task, VdcActionParametersBase taskParams) {
VdcReturnValueBase createResult;
try {
createResult = doAction(task, taskParams);
} catch (Exception e) {
return handleError(e, false);
}
return createResult;
}
private R mapEntity(Class<? extends BaseResource> suggestedParentType, Q created) {
R model = map(created);
model = deprecatedPopulate(model, created);
model = doPopulate(model, created);
return addLinks(model, suggestedParentType);
}
}