/**
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.module.webservices.rest.web.resource.impl;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.module.webservices.rest.SimpleObject;
import org.openmrs.module.webservices.rest.web.ConversionUtil;
import org.openmrs.module.webservices.rest.web.RequestContext;
import org.openmrs.module.webservices.rest.web.RestConstants;
import org.openmrs.module.webservices.rest.web.annotation.Resource;
import org.openmrs.module.webservices.rest.web.representation.Representation;
import org.openmrs.module.webservices.rest.web.resource.api.CrudResource;
import org.openmrs.module.webservices.rest.web.resource.api.Listable;
import org.openmrs.module.webservices.rest.web.resource.api.PageableResult;
import org.openmrs.module.webservices.rest.web.resource.api.Searchable;
import org.openmrs.module.webservices.rest.web.response.IllegalPropertyException;
import org.openmrs.module.webservices.rest.web.response.ObjectNotFoundException;
import org.openmrs.module.webservices.rest.web.response.ResourceDoesNotSupportOperationException;
import org.openmrs.module.webservices.rest.web.response.ResponseException;
import org.openmrs.module.webservices.rest.web.v1_0.controller.MainResourceController;
import org.openmrs.module.webservices.rest.web.v1_0.controller.MainSubResourceController;
import org.openmrs.module.webservices.validation.ValidateUtil;
/**
* A base implementation of a {@link CrudResource} that delegates CRUD operations to a wrapped
* object
*
* @param <T> the class we're delegating to
*/
public abstract class DelegatingCrudResource<T> extends BaseDelegatingResource<T> implements CrudResource, Searchable, Listable {
protected final Log log = LogFactory.getLog(getClass());
/**
* @see org.openmrs.module.webservices.rest.web.resource.api.Retrievable#retrieve(java.lang.String,
* org.openmrs.module.webservices.rest.web.representation.Representation)
*/
@Override
public Object retrieve(String uuid, RequestContext context) throws ResponseException {
T delegate = getByUniqueId(uuid);
if (delegate == null)
throw new ObjectNotFoundException();
SimpleObject ret = asRepresentation(delegate, context.getRepresentation());
if (hasTypesDefined())
ret.add(RestConstants.PROPERTY_FOR_TYPE, getTypeName(delegate));
return ret;
}
/**
* Default implementation that returns REF, DEFAULT, and FULL
*
* @see org.openmrs.module.webservices.rest.web.resource.api.Retrievable#getAvailableRepresentations()
*/
@Override
public List<Representation> getAvailableRepresentations() {
return Arrays.asList(Representation.DEFAULT, Representation.FULL, Representation.REF);
}
/**
* @see org.openmrs.module.webservices.rest.web.resource.api.Creatable#create(org.springframework.web.context.request.WebRequest)
*/
@Override
public Object create(SimpleObject propertiesToCreate, RequestContext context) throws ResponseException {
T delegate = convert(propertiesToCreate);
ValidateUtil.validate(delegate);
delegate = save(delegate);
SimpleObject ret = (SimpleObject) ConversionUtil.convertToRepresentation(delegate, Representation.DEFAULT);
// add the 'type' discriminator if we support subclasses
if (hasTypesDefined()) {
ret.add(RestConstants.PROPERTY_FOR_TYPE, getTypeName(delegate));
}
return ret;
}
public T convert(SimpleObject propertiesToCreate) {
DelegatingResourceHandler<? extends T> handler;
if (hasTypesDefined()) {
String type = (String) propertiesToCreate.remove(RestConstants.PROPERTY_FOR_TYPE);
if (type == null)
throw new IllegalArgumentException(
"When creating a resource that supports subclasses, you must indicate the particular subclass with a "
+ RestConstants.PROPERTY_FOR_TYPE + " property");
handler = getResourceHandler(type);
} else {
handler = this;
}
T delegate = handler.newDelegate(propertiesToCreate);
setConvertedProperties(delegate, propertiesToCreate, handler.getCreatableProperties(), true);
return delegate;
}
/**
* @see org.openmrs.module.webservices.rest.web.resource.api.Updatable#update(java.lang.String,
* org.openmrs.module.webservices.rest.SimpleObject)
*/
@Override
public Object update(String uuid, SimpleObject propertiesToUpdate, RequestContext context) throws ResponseException {
T delegate = getByUniqueId(uuid);
if (delegate == null)
throw new ObjectNotFoundException();
if (hasTypesDefined()) {
// if they specify a type discriminator it must match the expected one--type can't be modified
if (propertiesToUpdate.containsKey(RestConstants.PROPERTY_FOR_TYPE)) {
String type = (String) propertiesToUpdate.remove(RestConstants.PROPERTY_FOR_TYPE);
if (!delegate.getClass().equals(getActualSubclass(type))) {
String nameToShow = getTypeName(delegate);
if (nameToShow == null)
nameToShow = delegate.getClass().getName();
throw new IllegalArgumentException("You passed " + RestConstants.PROPERTY_FOR_TYPE + "=" + type
+ " but this instance is a " + nameToShow);
}
}
}
DelegatingResourceHandler<? extends T> handler = getResourceHandler(delegate);
DelegatingResourceDescription description = handler.getUpdatableProperties();
if (isRetirable()) {
description.addProperty("retired");
} else if (isVoidable()) {
description.addProperty("voided");
}
setConvertedProperties(delegate, propertiesToUpdate, description, false);
ValidateUtil.validate(delegate);
delegate = save(delegate);
SimpleObject ret = (SimpleObject) ConversionUtil.convertToRepresentation(delegate, Representation.DEFAULT);
// add the 'type' discriminator if we support subclasses
if (hasTypesDefined()) {
ret.add(RestConstants.PROPERTY_FOR_TYPE, getTypeName(delegate));
}
return ret;
}
/**
* @see org.openmrs.module.webservices.rest.web.resource.api.Deletable#delete(java.lang.String)
*/
@Override
public void delete(String uuid, String reason, RequestContext context) throws ResponseException {
T delegate = getByUniqueId(uuid);
if (delegate == null)
throw new ObjectNotFoundException();
delete(delegate, reason, context);
}
/**
* @see org.openmrs.module.webservices.rest.web.resource.api.Purgeable#purge(java.lang.String)
*/
@Override
public void purge(String uuid, RequestContext context) throws ResponseException {
T delegate = getByUniqueId(uuid);
if (delegate == null) {
// HTTP DELETE is idempotent, so if we can't find the object, we assume it's already deleted and return success
return;
}
purge(delegate, context);
}
/**
* @see org.openmrs.module.webservices.rest.web.resource.api.Searchable#search(org.openmrs.module.webservices.rest.web.RequestContext)
*/
@Override
public SimpleObject search(RequestContext context) throws ResponseException {
PageableResult result = doSearch(context);
return result.toSimpleObject(this);
}
/**
* Implementations should override this method if they are actually searchable.
*/
protected PageableResult doSearch(RequestContext context) {
return new EmptySearchResult();
}
/**
* @see org.openmrs.module.webservices.rest.web.resource.api.Listable#getAll(org.openmrs.module.webservices.rest.web.RequestContext)
*/
@Override
public SimpleObject getAll(RequestContext context) throws ResponseException {
if (context.getType() != null) {
if (!hasTypesDefined())
throw new IllegalArgumentException(getClass() + " does not support "
+ RestConstants.REQUEST_PROPERTY_FOR_TYPE);
if (context.getType().equals(getResourceName()))
throw new IllegalArgumentException("You may not specify " + RestConstants.REQUEST_PROPERTY_FOR_TYPE + "="
+ context.getType() + " because it is the default behavior for this resource");
DelegatingSubclassHandler<T, ? extends T> handler = getSubclassHandler(context.getType());
if (handler == null)
throw new IllegalArgumentException("No handler is specified for " + RestConstants.REQUEST_PROPERTY_FOR_TYPE
+ "=" + context.getType());
PageableResult result = handler.getAllByType(context);
return result.toSimpleObject(this);
} else {
PageableResult result = doGetAll(context);
return result.toSimpleObject(this);
}
}
/**
* Implementations should override this method to return a list of all instances represented by
* the specified rest resource in the database. (If the resource supports subclasses, this
* method should return all of its documents regardless of their type/subclass.)
*
* @throws ResponseException
*/
protected PageableResult doGetAll(RequestContext context) throws ResponseException {
throw new ResourceDoesNotSupportOperationException();
}
/**
* TODO
*
* @param delegateUuid
* @param subResourceName
* @param rep
* @return
* @throws ResponseException
*/
public Object listSubResource(String delegateUuid, String subResourceName, Representation rep) throws ResponseException {
// TODO SUBCLASSHANDLER
List<String> legal = getPropertiesToExposeAsSubResources();
if (legal == null || !legal.contains(subResourceName))
throw new IllegalPropertyException();
T delegate = getByUniqueId(delegateUuid);
if (delegate == null)
throw new ObjectNotFoundException();
return ConversionUtil.getPropertyWithRepresentation(delegate, subResourceName, rep);
}
/**
* Resources provided by this module itself are published without any particular namespace (e.g.
* /ws/rest/v1/concept) but when modules publish resources, they should be namespaced (e.g.
* /ws/rest/v1/moduleId/moduleresource).
*
* @deprecated Since 2.x the namespace must be declared in {@link Resource}'s name,
* {@link MainResourceController} and {@link MainSubResourceController}.
*/
@Deprecated
protected final String getNamespacePrefix() {
throw new UnsupportedOperationException();
}
/**
* Determines if the resource can be retired.
*
* @return true if can be retired, else false.
*/
public boolean isRetirable() {
return false;
}
/**
* Determines if the resource can be voided.
*
* @return true if can be voided, else false.
*/
public boolean isVoidable() {
return false;
}
}