/**
* 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 org.apache.commons.beanutils.PropertyUtils;
import org.openmrs.module.webservices.rest.SimpleObject;
import org.openmrs.module.webservices.rest.util.ReflectionUtil;
import org.openmrs.module.webservices.rest.web.ConversionUtil;
import org.openmrs.module.webservices.rest.web.Hyperlink;
import org.openmrs.module.webservices.rest.web.annotation.SubResource;
import org.openmrs.module.webservices.rest.web.representation.Representation;
import org.openmrs.module.webservices.rest.web.resource.api.Converter;
import org.openmrs.module.webservices.rest.web.response.ConversionException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* A base implementation of a converter that can transform something that is _not_ a full resource
* into a representation for sending over the wire. Typically you'd use this as a convenience to
* convert some domain object that is contained in a proper resource (but is not a sub-resource)
* Direct subclasses should typically be annotated with @Handler(supports = T.class). (If you
* subclass one of the Resource subclasses of this class, you don't need to do that, but would use a @Resource
* annotation.)
*
* @param <T> the class we're delegating to
*/
public abstract class BaseDelegatingConverter<T> implements Converter<T>, DelegatingPropertyAccessor<T> {
/**
* Gets the {@link DelegatingResourceDescription} for the given representation for this
* resource, if it exists
*
* @param rep
* @return
*/
public abstract DelegatingResourceDescription getRepresentationDescription(Representation rep);
/**
* (Note that this method doesn't support @RepHandler; the implementation in
* BaseDelegatingResource does.)
*
* @param delegate
* @param rep
* @return
* @throws ConversionException
*/
@Override
public SimpleObject asRepresentation(T delegate, Representation rep) throws ConversionException {
if (delegate == null)
throw new NullPointerException();
DelegatingResourceDescription description = getRepresentationDescription(rep);
SimpleObject simple = convertDelegateToRepresentation(delegate, description);
return simple;
}
@Override
public Object getProperty(T instance, String propertyName) throws ConversionException {
try {
Method annotatedGetter = ReflectionUtil.findPropertyGetterMethod(this, propertyName);
if (annotatedGetter != null) {
return annotatedGetter.invoke(this, instance);
}
return PropertyUtils.getProperty(instance, propertyName);
}
catch (Exception ex) {
throw new ConversionException("Unable to get property " + propertyName, ex);
}
}
@Override
public void setProperty(Object instance, String propertyName, Object value) throws ConversionException {
try {
// try to find a @PropertySetter-annotated method
Method annotatedSetter = ReflectionUtil.findPropertySetterMethod(this, propertyName);
if (annotatedSetter != null) {
Type expectedType = annotatedSetter.getGenericParameterTypes()[1];
value = ConversionUtil.convert(value, expectedType);
annotatedSetter.invoke(this, instance, value);
return;
}
// we need the generic type of this property, not just the class
Method setter = PropertyUtils.getPropertyDescriptor(instance, propertyName).getWriteMethod();
// Convert the value to the specified type
value = ConversionUtil.convert(value, setter.getGenericParameterTypes()[0], instance);
setPropertyWhichMayBeAHibernateCollection(instance, propertyName, value);
}
catch (Exception ex) {
throw new ConversionException(propertyName + " on " + instance.getClass(), ex);
}
}
protected void setPropertyWhichMayBeAHibernateCollection(Object instance, String propertyName, Object value)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
if (value instanceof Collection) {
//We need to handle collections in a way that Hibernate can track.
Collection<?> newCollection = (Collection<?>) value;
Object oldValue = PropertyUtils.getProperty(instance, propertyName);
if (oldValue instanceof Collection) {
Collection collection = (Collection) oldValue;
collection.clear();
collection.addAll(newCollection);
} else {
PropertyUtils.setProperty(instance, propertyName, value);
}
} else {
PropertyUtils.setProperty(instance, propertyName, value);
}
}
/**
* If rep contains any links, and you are not extending a subclass (e.g. BaseDelegatingResource)
* that implements getUri, this will throw an exception
*
* @param delegate
* @param rep
* @return
* @throws ConversionException
*/
protected SimpleObject convertDelegateToRepresentation(T delegate, DelegatingResourceDescription rep)
throws ConversionException {
if (delegate == null)
throw new NullPointerException();
SimpleObject ret = new SimpleObject();
for (Map.Entry<String, DelegatingResourceDescription.Property> e : rep.getProperties().entrySet()) {
ret.put(e.getKey(), e.getValue().evaluate(this, delegate));
}
List<Hyperlink> links = new ArrayList<Hyperlink>();
for (Hyperlink link : rep.getLinks()) {
if (link.getUri().startsWith(".")) {
link = new Hyperlink(link.getRel(), getUri(delegate) + link.getUri().substring(1));
}
org.openmrs.module.webservices.rest.web.annotation.Resource res = getClass().getAnnotation(
org.openmrs.module.webservices.rest.web.annotation.Resource.class);
if (res != null) {
String name = res.name();
if (name.contains("/")) {
name = name.substring(name.lastIndexOf("/") + 1);
}
link.setResourceAlias(name);
} else {
SubResource sub = getClass().getAnnotation(SubResource.class);
if (sub != null) {
link.setResourceAlias(sub.path());
}
}
links.add(link);
}
if (links.size() > 0)
ret.put("links", links);
return ret;
}
/**
* Subclasses that represent resources with a URI need to override this. (This implementation
* throws an exception.)
*
* @param delegate
* @return
*/
public String getUri(Object delegate) {
throw new IllegalStateException(
"representation description includes a link, but this converter doesn't define a URI: "
+ getClass().getName());
}
}