package org.ovirt.engine.api.restapi.resource; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import org.ovirt.engine.api.common.util.LinkHelper; import org.ovirt.engine.api.model.BaseResource; import org.ovirt.engine.api.model.Link; import org.ovirt.engine.api.model.CreationStatus; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.businessentities.AsyncTaskStatus; import org.ovirt.engine.core.common.queries.GetTasksStatusesByTasksIDsParameters; 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.compat.Guid; import org.ovirt.engine.api.restapi.types.Mapper; import org.ovirt.engine.api.restapi.types.MappingLocator; public class AbstractBackendResource<R extends BaseResource, Q /* extends IVdcQueryable */> extends BackendResource { protected static final String ID_SEPARATOR = ","; protected static final long MONITOR_DELAY = 1000L; protected static final javax.ws.rs.core.Response.Status ACCEPTED_STATUS = javax.ws.rs.core.Response.Status.ACCEPTED; protected MappingLocator mappingLocator; protected Class<R> modelType; protected Class<Q> entityType; protected String[] subCollections; protected AbstractBackendResource(Class<R> modelType, Class<Q> entityType) { this.modelType = modelType; this.entityType = entityType; } protected AbstractBackendResource(Class<R> modelType, Class<Q> entityType, String... subCollections) { this(modelType, entityType); this.subCollections = subCollections; } public void setMappingLocator(MappingLocator mappingLocator) { this.mappingLocator = mappingLocator; } public MappingLocator getMappingLocator() { return mappingLocator; } //protected <S extends AbstractBackendResource<B extends BaseResource, A>> S inject(S resource) { protected <S extends AbstractBackendResource<?, ?>> S inject(S resource) { resource.setBackend(backend); resource.setMappingLocator(mappingLocator); resource.setSessionHelper(sessionHelper); resource.setMessageBundle(messageBundle); resource.setUriInfo(uriInfo); resource.setHttpHeaders(httpHeaders); return resource; } protected <F, T> Mapper<F, T> getMapper(Class<F> from, Class<T> to) { return mappingLocator.getMapper(from, to); } protected R map(Q entity) { return map(entity, null); } protected R map(Q entity, R template) { return getMapper(entityType, modelType).map(entity, template); } protected Q map(R model) { return map(model, null); } protected Q map(R model, Q template) { return getMapper(modelType, entityType).map(model, template); } protected CreationStatus awaitCompletion(VdcReturnValueBase result) { CreationStatus status = null; while (incomplete(status = getAsynchronousStatus(result))) { delay(MONITOR_DELAY); } return status; } protected CreationStatus getAsynchronousStatus(VdcReturnValueBase result) { CreationStatus asyncStatus = null; VdcQueryReturnValue monitorResult = backend.RunQuery(VdcQueryType.GetTasksStatusesByTasksIDs, sessionize(new GetTasksStatusesByTasksIDsParameters(result.getTaskIdList()))); if (monitorResult != null && monitorResult.getSucceeded() && monitorResult.getReturnValue() != null) { Mapper<AsyncTaskStatus, CreationStatus> mapper = getMapper(AsyncTaskStatus.class, CreationStatus.class); for (AsyncTaskStatus task : asCollection(AsyncTaskStatus.class, monitorResult.getReturnValue())) { asyncStatus = mapper.map(task, asyncStatus); } } return asyncStatus; } protected boolean incomplete(CreationStatus status) { return status == null || status == CreationStatus.PENDING || status == CreationStatus.IN_PROGRESS; } protected void delay(long delay) { try { Thread.sleep(delay); } catch (InterruptedException ie) { // ignore } } protected R populate(R model, Q entity) { return model; } /** * Add any parent resource references needed for constructing links. * * LinkHelper.addLinks() constructs the 'href' attribute from @model * using its 'id' attribute and the 'id' attribute of any parent * resources. * * This method provides the hook through which all sub-resource * classes should add references to parent resources so that * LinkHelper can do its job. * e.g. in order to get a URL like 'clusters/{cid}/networks/{nid}' * you would need to have: * * protected Network addParents(Network network) { * network.setCluster(new Cluster()); * network.getCluster().setId(clusterId); * return network; * } * * @param the resource representation * @return the model with any parent references added */ protected R addParents(R model) { return model; } protected R addLinks(R model, String... subCollectionMembersToExclude) { return addLinks(model, null, subCollectionMembersToExclude); } protected R addLinks(R model, boolean doNotLinkSubCollections) { return addLinks(model, null, doNotLinkSubCollections); } protected R addLinks(R model, Class<? extends BaseResource> suggestedParent, String... subCollectionMembersToExclude) { return linkSubCollections(LinkHelper.addLinks(getUriInfo(), addParents(model), suggestedParent), suggestedParent, subCollectionMembersToExclude); } protected R addLinks(R model, Class<? extends BaseResource> suggestedParent, boolean doNotLinkSubCollections) { return doNotLinkSubCollections? LinkHelper.addLinks(getUriInfo(), addParents(model), suggestedParent) : addLinks(model,suggestedParent); } protected List<Q> asCollection(Object o) { return asCollection(entityType, o); } protected String asString(List<Guid> list) { StringBuilder builder = new StringBuilder(); for (Guid id : list) { if (builder.length() > 0) { builder.append(urlEncode(ID_SEPARATOR)); } builder.append(id.toString()); } return builder.toString(); } protected String urlEncode(String url) { try { return URLEncoder.encode(url, "UTF-8"); } catch (UnsupportedEncodingException e) { // UTF-8 should always be supported e.printStackTrace(); return null; } } protected R newModel(String id) { R ret = null; try { ret = modelType.newInstance(); ret.setId(id); ret = addParents(ret); } catch (Exception e) { // trivial construction, should not fail } return ret; } protected R linkSubResource(R model, String subResource, String oid) { addOrUpdateLink(model, subResource, LinkHelper.getUriBuilder(getUriInfo(), model).path(subResource).path(oid).build().toString()); return model; } protected R linkSubCollections(R model, Class<? extends BaseResource> suggestedParent, String... subCollectionMembersToExclude) { if (subCollections != null) { for (String relation : subCollections) { if(!shouldExclude(relation, subCollectionMembersToExclude)) { addOrUpdateLink(model, relation, LinkHelper.getUriBuilder(getUriInfo(), model, suggestedParent).path(relation).build().toString()); } else{ removeIfExist(model,relation); } } } return model; } private boolean shouldExclude(String member, String[] subCollectionMembersToExclude) { if(subCollectionMembersToExclude !=null && subCollectionMembersToExclude.length > 0){ for(String excludeMember : subCollectionMembersToExclude){ if(member.equals(excludeMember))return true; } } return false; } protected R injectSearchLinks(R resource, String[] rels){ for(String rel : rels){ resource.getLinks().add(LinkHelper.createSearchLink(resource.getHref(), rel)); } return resource; } protected <B extends BaseResource >void removeIfExist(B model, String relation) { List<Link> linksCopy = new ArrayList<Link>(model.getLinks()); for (Link link : model.getLinks()) { if (link.getRel().equals(relation)) { linksCopy.remove(link); break; } } model.getLinks().retainAll(linksCopy); } protected <B extends BaseResource >void addOrUpdateLink(B model, String relation, String href) { for (Link link : model.getLinks()) { if (link.getRel().equals(relation)) { link.setHref(href); return; } } Link link = new Link(); link.setRel(relation); link.setHref(href); model.getLinks().add(link); } protected VdcQueryParametersBase getQueryParams(Class<? extends VdcQueryParametersBase> queryParamsClass, Guid id) { VdcQueryParametersBase params = null; try { params = queryParamsClass.getConstructor(Guid.class).newInstance(id); } catch (Exception e) { // trivial class construction } return params; } /** * Convert a string to a Guid, or return a 404 response. * * If an invalid UUID is supplied to a sub-resource locator, this * method will cause us to return a 404 response via the sub-resource * constructor. * * @param id the incoming UUID * @return a Guid * @throws WebApplicationException a 404 response, if the UUID is invalid */ protected Guid asGuidOr404(String id) { try { return asGuid(id); } catch (IllegalArgumentException iae) { throw new WebApplicationException(Response.Status.NOT_FOUND); } } protected R notFound() { return notFound(modelType); } protected Q entityNotFound() { return notFound(entityType); } protected <T> T notFound(Class<T> clz) { throw new WebApplicationException(Response.Status.NOT_FOUND); } protected abstract class EntityIdResolver { public abstract Q lookupEntity(Guid id) throws BackendFailureException; public Q resolve(Guid id) throws BackendFailureException { Q entity = lookupEntity(id); if (entity == null) { throw new EntityNotFoundException(id.toString()); } return entity; } } protected class QueryIdResolver extends EntityIdResolver { private VdcQueryType query; private Class<? extends VdcQueryParametersBase> queryParamsClass; public QueryIdResolver(VdcQueryType query, Class<? extends VdcQueryParametersBase> queryParamsClass) { this.query = query; this.queryParamsClass = queryParamsClass; } public Q lookupEntity(Guid id) throws BackendFailureException { return doGetEntity(entityType, query, getQueryParams(queryParamsClass, id), id.toString()); } } }