/** * 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.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.proxy.HibernateProxy; import org.openmrs.OpenmrsObject; import org.openmrs.api.context.Context; import org.openmrs.module.ModuleUtil; 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.RequestContext; import org.openmrs.module.webservices.rest.web.RestConstants; import org.openmrs.module.webservices.rest.web.annotation.PropertyGetter; import org.openmrs.module.webservices.rest.web.annotation.RepHandler; import org.openmrs.module.webservices.rest.web.annotation.SubClassHandler; import org.openmrs.module.webservices.rest.web.api.RestService; import org.openmrs.module.webservices.rest.web.representation.CustomRepresentation; import org.openmrs.module.webservices.rest.web.representation.NamedRepresentation; import org.openmrs.module.webservices.rest.web.representation.RefRepresentation; 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.resource.api.Resource; import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceDescription.Property; import org.openmrs.module.webservices.rest.web.response.ConversionException; import org.openmrs.module.webservices.rest.web.response.ResourceDoesNotSupportOperationException; import org.openmrs.module.webservices.rest.web.response.ResponseException; import org.openmrs.util.OpenmrsConstants; import org.openmrs.util.OpenmrsUtil; /** * A base implementation of a resource or sub-resource that delegates operations to a wrapped * object. Implementations generally should extend either {@link DelegatingCrudResource} or * {@link DelegatingSubResource} rather than this class directly. * * @param <T> the class we're delegating to */ public abstract class BaseDelegatingResource<T> extends BaseDelegatingConverter<T> implements Converter<T>, Resource, DelegatingResourceHandler<T> { private final Log log = LogFactory.getLog(getClass()); protected Set<String> propertiesIgnoredWhenUpdating = new HashSet<String>(); /** * Properties that should silently be ignored if you try to get them. Implementations should * generally configure this property with a list of properties that were added to their * underlying domain object after the minimum OpenMRS version required by this module. For * example PatientIdentifierTypeResource will allow "locationBehavior" to be missing, since it * wasn't added to PatientIdentifierType until OpenMRS 1.9. delegate class */ protected Set<String> allowedMissingProperties = new HashSet<String>(); /** * If this resource represents a class hierarchy (rather than a single class), this will hold * handlers for each subclass */ protected volatile List<DelegatingSubclassHandler<T, ? extends T>> subclassHandlers; /** * Default constructor will set propertiesIgnoredWhenUpdating to include "display", "links", and * "resourceVersion" */ protected BaseDelegatingResource() { propertiesIgnoredWhenUpdating.add("display"); propertiesIgnoredWhenUpdating.add("links"); propertiesIgnoredWhenUpdating.add("auditInfo"); propertiesIgnoredWhenUpdating.add(RestConstants.PROPERTY_FOR_RESOURCE_VERSION); } /** * All our resources support letting modules register subclass handlers. If any are registered, * then the resource represents a class hierarchy, e.g. requiring a "type" parameter when * creating a new instance. * * @return whether there are any subclass handlers registered with this resource */ public boolean hasTypesDefined() { return subclassHandlers != null && subclassHandlers.size() > 0; } /** * This will be automatically called with the first call to {@link #getSubclassHandler(Class)} * or {@link #getSubclassHandler(String)}. It finds all subclass handlers intended for this * resource, and registers them. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void init() { List<DelegatingSubclassHandler<T, ? extends T>> tmpSubclassHandlers = new ArrayList<DelegatingSubclassHandler<T, ? extends T>>(); List<DelegatingSubclassHandler> handlers = Context.getRegisteredComponents(DelegatingSubclassHandler.class); for (DelegatingSubclassHandler handler : handlers) { Class<? extends DelegatingSubclassHandler> handlerClass = handler.getClass(); Class forDelegateClass = ReflectionUtil.getParameterizedTypeFromInterface(handlerClass, DelegatingSubclassHandler.class, 0); if (forDelegateClass == null) { throw new IllegalStateException( "Could not determine type information. Make sure that " + handlerClass.getName() + " explicitly says implements DelegatingSubclassHandler<...>. It is not sufficient to just extend a base class that implements this."); } Resource resourceForHandler = Context.getService(RestService.class) .getResourceBySupportedClass(forDelegateClass); if (getClass().equals(resourceForHandler.getClass())) { SubClassHandler annotation = handlerClass.getAnnotation(SubClassHandler.class); if (annotation != null) { String[] supportedOpenmrsVersions = annotation.supportedOpenmrsVersions(); for (String version : supportedOpenmrsVersions) { if (ModuleUtil.matchRequiredVersions(OpenmrsConstants.OPENMRS_VERSION_SHORT, version)) { tmpSubclassHandlers.add(handler); break; } } } else { log.warn("SubclassHandler " + handlerClass.getName() + " does not have a @SubClassHandler annotation. This can cause conflicts in resolving handlers for your subclass."); } } } subclassHandlers = tmpSubclassHandlers; } /** * Registers the given subclass handler. * * @param handler */ public void registerSubclassHandler(DelegatingSubclassHandler<T, ? extends T> handler) { if (subclassHandlers == null) { init(); } for (DelegatingSubclassHandler<T, ? extends T> current : subclassHandlers) { if (current.getClass().equals(handler.getClass())) { log.info("Tried to register a subclass handler, but the class is already registered: " + handler.getClass()); return; } } subclassHandlers.add(handler); } /** * @see org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceHandler#getResourceVersion() */ @Override public String getResourceVersion() { return RestConstants.PROPERTY_FOR_RESOURCE_VERSION_DEFAULT_VALUE; } /** * @return the value of the {@link org.openmrs.module.webservices.rest.web.annotation.Resource} * annotation on the concrete subclass */ protected String getResourceName() { org.openmrs.module.webservices.rest.web.annotation.Resource ann = getClass().getAnnotation( org.openmrs.module.webservices.rest.web.annotation.Resource.class); if (ann == null) throw new RuntimeException("There is no " + Resource.class + " annotation on " + getClass()); if (StringUtils.isEmpty(ann.name())) throw new RuntimeException(Resource.class.getSimpleName() + " annotation on " + getClass() + " must specify a name"); return ann.name(); } /** * @see org.openmrs.module.webservices.rest.web.resource.api.Converter#newInstance(java.lang.String) */ @Override public T newInstance(String type) { if (hasTypesDefined()) { if (type == null) throw new IllegalArgumentException(getClass().getSimpleName() + " requires a '" + RestConstants.PROPERTY_FOR_TYPE + "' property to create a new object"); DelegatingResourceHandler<? extends T> handler = getResourceHandler(type); return handler.newDelegate(); } else { return newDelegate(); } } /** * Gets the delegate object with the given unique id. Implementations may decide whether * "unique id" means a uuid, or if they also want to retrieve delegates based on a unique * human-readable property. * * @param uniqueId * @return the delegate for the given uniqueId */ @Override public abstract T getByUniqueId(String uniqueId); /** * Void or retire delegate, whichever action is appropriate for the resource type. Subclasses * need to override this method, which is called internally by * {@link #delete(String, String, RequestContext)}. * * @param delegate * @param reason * @param context * @throws ResponseException */ protected abstract void delete(T delegate, String reason, RequestContext context) throws ResponseException; /** * Purge delegate from persistent storage. Subclasses need to override this method, which is * called internally by {@link #purge(String, RequestContext)}. * * @param delegate * @param context * @throws ResponseException */ @Override public abstract void purge(T delegate, RequestContext context) throws ResponseException; /** * Gets a description of resource's properties which can be set on creation. * * @return the description * @throws ResponseException */ @Override public DelegatingResourceDescription getCreatableProperties() throws ResourceDoesNotSupportOperationException { throw new ResourceDoesNotSupportOperationException(); } /** * Gets a description of resource's properties which can be edited. * <p/> * By default delegates to {@link #getCreatableProperties()} and removes sub-resources returned * by {@link #getPropertiesToExposeAsSubResources()}. * * @return the description * @throws ResponseException */ @Override public DelegatingResourceDescription getUpdatableProperties() throws ResourceDoesNotSupportOperationException { DelegatingResourceDescription description = getCreatableProperties(); for (String property : getPropertiesToExposeAsSubResources()) { description.getProperties().remove(property); } return description; } /** * Implementations should override this method if they support sub-resources * * @return a list of properties available as sub-resources or an empty list */ public List<String> getPropertiesToExposeAsSubResources() { return Collections.emptyList(); } /** * Implementations should override this method if T is not uniquely identified by a "uuid" * property. * * @param delegate * @return the uuid property of delegate */ protected String getUniqueId(T delegate) { try { return (String) getProperty(delegate, "uuid"); } catch (Exception ex) { throw new RuntimeException("Cannot find String uuid property on " + delegate.getClass(), null); } } /** * Creates an object of the given representation, pulling values from fields and methods as * specified by a subclass * * @param representation * @return * @should return valid RefRepresentation * @should return valid DefaultRepresentation * @should return valid FullRepresentation */ @Override public SimpleObject asRepresentation(T delegate, Representation representation) throws ConversionException { if (delegate == null) throw new NullPointerException(); DelegatingResourceHandler<? extends T> handler = getResourceHandler(delegate); // first call getRepresentationDescription() DelegatingResourceDescription repDescription = handler.getRepresentationDescription(representation); if (repDescription != null) { SimpleObject simple = convertDelegateToRepresentation(delegate, repDescription); maybeDecorateWithType(simple, delegate); decorateWithResourceVersion(simple, representation); return simple; } // otherwise look for a method annotated to handle this representation Method meth = findAnnotatedMethodForRepresentation(handler.getClass(), representation); if (meth != null) { try { // TODO verify that the method takes 1 or 2 parameters SimpleObject simple; if (meth.getParameterTypes().length == 1) simple = (SimpleObject) meth.invoke(handler, delegate); else simple = (SimpleObject) meth.invoke(handler, delegate, representation); maybeDecorateWithType(simple, delegate); decorateWithResourceVersion(simple, representation); return simple; } catch (Exception ex) { throw new ConversionException(null, ex); } } // finally if it is a custom representation and not supported by any other handler if (representation instanceof CustomRepresentation) { repDescription = getCustomRepresentationDescription((CustomRepresentation) representation); if (repDescription != null) { SimpleObject simple = convertDelegateToRepresentation(delegate, repDescription); return simple; } } throw new ConversionException("Don't know how to get " + getClass().getSimpleName() + "(" + delegate.getClass() + ") as " + representation.getRepresentation(), null); } /** * @should return delegating resource description */ private DelegatingResourceDescription getCustomRepresentationDescription(CustomRepresentation representation) { DelegatingResourceDescription desc = new DelegatingResourceDescription(); String def = representation.getRepresentation(); def = def.startsWith("(") ? def.substring(1) : def; def = def.endsWith(")") ? def.substring(0, def.length() - 1) : def; String[] fragments = def.split(","); for (int i = 0; i < fragments.length; i++) { String[] field = fragments[i].split(":"); //split into field and representation if (field.length == 1) { if (!field[0].equals("links")) desc.addProperty(field[0]); if (field[0].equals("links")) { desc.addSelfLink(); desc.addLink("default", ".?v=" + RestConstants.REPRESENTATION_DEFAULT); } } else { String property = field[0]; String rep = field[1]; // if custom representation if (rep.startsWith("(")) { StringBuilder customRep = new StringBuilder(); customRep.append(rep); if (!rep.endsWith(")")) { for (int j = 2; j < field.length; j++) { customRep.append(":").append(field[j]); } int open = 1; for (i = i + 1; i < fragments.length; i++) { for (char fragment : fragments[i].toCharArray()) { if (fragment == '(') { open++; } else if (fragment == ')') { open--; } } customRep.append(","); customRep.append(fragments[i]); if (open == 0) { break; } } } desc.addProperty(property, new CustomRepresentation(customRep.toString())); } else { rep = rep.toUpperCase(); //normalize if (rep.equals("REF")) { desc.addProperty(property, Representation.REF); } else if (rep.equals("FULL")) { desc.addProperty(property, Representation.FULL); } else if (rep.equals("DEFAULT")) { desc.addProperty(property, Representation.DEFAULT); } } } } return desc; } /** * Sets resourceVersion to {@link #getResourceVersion()} for representations other than REF. * * @param simple the simplified representation which will be decorated with the resource version * @param representation the type of representation */ private void decorateWithResourceVersion(SimpleObject simple, Representation representation) { if (!(representation instanceof RefRepresentation)) { simple.put(RestConstants.PROPERTY_FOR_RESOURCE_VERSION, getResourceVersion()); } } /** * If this resource supports subclasses, then we add a type property to the input, and return it * * @param simple simplified representation which will be decorated with the user-friendly type * name * @param delegate the object that simple represents */ private void maybeDecorateWithType(SimpleObject simple, T delegate) { if (hasTypesDefined()) simple.add(RestConstants.PROPERTY_FOR_TYPE, getTypeName(delegate)); } /** * If this resources supports subclasses, this method gets the user-friendly type name for the * given subclass * * @param subclass * @return */ protected String getTypeName(Class<? extends T> subclass) { if (hasTypesDefined()) { DelegatingSubclassHandler<T, ? extends T> handler = getSubclassHandler(subclass); if (handler != null) return handler.getTypeName(); if (newDelegate().getClass().equals(subclass)) { String resourceName = getResourceName(); int lastSlash = resourceName.lastIndexOf("/"); resourceName = resourceName.substring(lastSlash + 1); return resourceName; } } return null; } /** * @see #getTypeName(Class) */ protected String getTypeName(T delegate) { return getTypeName((Class<? extends T>) delegate.getClass()); } /** * @param type user-friendly type name * @return the actual java class for this type */ protected Class<? extends T> getActualSubclass(String type) { DelegatingSubclassHandler<T, ? extends T> handler = getSubclassHandler(type); if (handler != null) return handler.getSubclassHandled(); // otherwise we need to return our own declared class return ReflectionUtil.getParameterizedTypeFromInterface(getClass(), DelegatingResourceHandler.class, 0); } /** * @param type user-friendly type name * @return a subclass handler if any is suitable for type, or this resource itself if it is * suitable */ protected DelegatingResourceHandler<? extends T> getResourceHandler(String type) { if (type == null || !hasTypesDefined()) return this; DelegatingSubclassHandler<T, ? extends T> handler = getSubclassHandler(type); if (handler != null) return handler; if (getResourceName().endsWith(type)) return this; throw new IllegalArgumentException("type=" + type + " is not handled by this resource (" + getClass() + ") or any subclass"); } /** * Delegates to @see {@link #getResourceHandler(Class)} */ @SuppressWarnings("unchecked") protected DelegatingResourceHandler<? extends T> getResourceHandler(T delegate) { if (!hasTypesDefined()) return this; if (delegate == null) return null; return getResourceHandler((Class<? extends T>) delegate.getClass()); } /** * @param clazz * @return a subclass handler if any is suitable for the given class, or this resource itself if * no subclass handler works */ protected DelegatingResourceHandler<? extends T> getResourceHandler(Class<? extends T> clazz) { if (!hasTypesDefined()) return this; DelegatingResourceHandler<? extends T> handler = getSubclassHandler(clazz); if (handler != null) return handler; return this; } /** * @param subclass * @return the handler most appropriate for the given subclass, or null if none is suitable */ protected DelegatingSubclassHandler<T, ? extends T> getSubclassHandler(Class<? extends T> subclass) { if (subclassHandlers == null) { init(); } if (!hasTypesDefined()) return null; // look for an exact match for (DelegatingSubclassHandler<T, ? extends T> handler : subclassHandlers) { Class<? extends T> subclassHandled = handler.getSubclassHandled(); if (subclass.equals(subclassHandled)) return handler; } // TODO should we recurse to subclass's superclass, e.g. so DrugOrderHandler can handle HivDrugOrder if no handler is defined? // didn't find anything suitable return null; } /** * @param type the user-friendly name of a registered subclass handler * @return the handler for the given user-friendly type name */ protected DelegatingSubclassHandler<T, ? extends T> getSubclassHandler(String type) { if (hasTypesDefined()) { if (subclassHandlers == null) { init(); } for (DelegatingSubclassHandler<T, ? extends T> handler : subclassHandlers) { if (type.equals(handler.getTypeName())) return handler; } } return null; } /** * @param delegate * @param propertyMap * @param description * @param mustIncludeRequiredProperties * @throws ResponseException * @should allow setting a null value */ public void setConvertedProperties(T delegate, Map<String, Object> propertyMap, DelegatingResourceDescription description, boolean mustIncludeRequiredProperties) throws ConversionException { Map<String, Property> allowedProperties = new LinkedHashMap<String, Property>(description.getProperties()); Map<String, Object> propertiesToSet = new HashMap<String, Object>(propertyMap); propertiesToSet.keySet().removeAll(propertiesIgnoredWhenUpdating); // Apply properties in the order specified in the resource description (necessary e.g. so the obs resource // can apply "concept" before "value"); we have already excluded unchanged and ignored properties. // Because some resources (e.g. any AttributeResource) require some properties to be set before others can // be fetched, we apply each property in its iteration, rather than testing everything first and applying later. for (String property : allowedProperties.keySet()) { if (!propertiesToSet.containsKey(property)) { continue; } if (propertiesToSet.containsKey(property)) { // Ignore any properties that were not actually changed, also covering the case where you post back an // incomplete rep of a complex property Object oldValue = getProperty(delegate, property); Object newValue = propertiesToSet.get(property); if (unchangedValue(oldValue, newValue)) { propertiesToSet.remove(property); continue; } setProperty(delegate, property, propertiesToSet.get(property)); } } // If any non-settable properties remain after the above logic, fail Collection<String> notAllowedProperties = CollectionUtils.subtract(propertiesToSet.keySet(), allowedProperties.keySet()); // Do allow posting back an unchanged value to an unchangeable property for (Iterator<String> iterator = notAllowedProperties.iterator(); iterator.hasNext();) { String property = iterator.next(); Object oldValue = getProperty(delegate, property); Object newValue = propertiesToSet.get(property); if (unchangedValue(oldValue, newValue)) { iterator.remove(); } } if (!notAllowedProperties.isEmpty()) { throw new ConversionException("Some properties are not allowed to be set: " + StringUtils.join(notAllowedProperties, ", ")); } if (mustIncludeRequiredProperties) { Set<String> missingProperties = new HashSet<String>(); for (Entry<String, Property> prop : allowedProperties.entrySet()) { if (prop.getValue().isRequired() && !propertyMap.containsKey(prop.getKey())) { missingProperties.add(prop.getKey()); } } if (!missingProperties.isEmpty()) { throw new ConversionException("Some required properties are missing: " + StringUtils.join(missingProperties, ", ")); } } } private boolean unchangedValue(Object oldValue, Object newValue) { if (newValue instanceof Map && oldValue != null && !(oldValue instanceof Map)) { newValue = ConversionUtil.convert(newValue, oldValue.getClass()); if (oldValue instanceof OpenmrsObject) { return ((OpenmrsObject) oldValue).getUuid().equals(((OpenmrsObject) newValue).getUuid()); } } return OpenmrsUtil.nullSafeEquals(oldValue, newValue); } /** * Finds a method on clazz or a superclass that is annotated with {@link RepHandler} and is * suitable for rep * * @param clazz * @param rep * @return */ private Method findAnnotatedMethodForRepresentation(Class<?> clazz, Representation rep) { for (Method method : clazz.getMethods()) { RepHandler ann = method.getAnnotation(RepHandler.class); if (ann != null) { if (ann.value().isAssignableFrom(rep.getClass())) { if (!(rep instanceof NamedRepresentation)) { return method; } else if (ann.name().equals(rep.getRepresentation())) { return method; } } } } return null; } /** * @see org.openmrs.module.webservices.rest.web.resource.api.Converter#getProperty(java.lang.Object, * java.lang.String) */ @Override public Object getProperty(T instance, String propertyName) throws ConversionException { try { DelegatingResourceHandler<? extends T> handler = getResourceHandler(instance); // try to find a @PropertyGetter-annotated method Method annotatedGetter = ReflectionUtil.findPropertyGetterMethod(handler, propertyName); if (annotatedGetter != null) { return annotatedGetter.invoke(handler, instance); } return PropertyUtils.getProperty(instance, propertyName); } catch (Exception ex) { // some properties are allowed to be missing, since they may have been added in later OpenMRS versions if (allowedMissingProperties.contains(propertyName)) return null; throw new ConversionException(propertyName + " on " + instance.getClass(), ex); } } /** * @see org.openmrs.module.webservices.rest.web.resource.api.Converter#setProperty(java.lang.Object, * java.lang.String, java.lang.Object) */ @Override public void setProperty(Object instance, String propertyName, Object value) throws ConversionException { if (propertiesIgnoredWhenUpdating.contains(propertyName)) { return; } try { DelegatingResourceHandler<? extends T> handler; try { handler = getResourceHandler((T) instance); } catch (Exception e) { // this try/catch isn't really needed because of java erasure behaviour at run time. // but I'm putting in here just in case handler = this; } // try to find a @PropertySetter-annotated method Method annotatedSetter = ReflectionUtil.findPropertySetterMethod(handler, propertyName); if (annotatedSetter != null) { Type expectedType = annotatedSetter.getGenericParameterTypes()[1]; value = ConversionUtil.convert(value, expectedType); annotatedSetter.invoke(handler, 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); } } /** * Removes any elements from the passed-in collection that aren't of the given type. This is a * convenience method for subclass-aware resources that want to limit query results to a given * type. * * @param collection * @param type a user-friendly type name */ protected void filterByType(Collection<T> collection, String type) { for (Iterator<T> i = collection.iterator(); i.hasNext();) { T instance = i.next(); if (!getTypeName(instance).equals(type)) i.remove(); } } /** * Convenience method that looks for a specific method on the subclass handler for the given * type * * @param type user-friendly type name * @param methodName * @param argumentTypes * @return the indicated method if it exists, null otherwise */ protected Method findSubclassHandlerMethod(String type, String methodName, Class<?>... argumentTypes) { DelegatingSubclassHandler<T, ? extends T> handler = getSubclassHandler(type); if (handler == null) return null; try { Method method = handler.getClass().getMethod(methodName, argumentTypes); return method; } catch (Exception e) { return null; } } /** * Convenience method that finds a specific method on the subclass handler for the given type, * and invokes it * * @param type user-friendly type name * @param methodName * @param arguments * @return the result of invoking the indicated method, or null if the method wasn't found */ protected Object findAndInvokeSubclassHandlerMethod(String type, String methodName, Object... arguments) { Class<?>[] argumentTypes = new Class<?>[arguments.length]; for (int i = 0; i < arguments.length; ++i) { Class<?> t = arguments[i].getClass(); if (arguments[i] instanceof HibernateProxy) { t = ((HibernateProxy) arguments[i]).getHibernateLazyInitializer().getPersistentClass(); } argumentTypes[i] = t; } Method method = findSubclassHandlerMethod(type, methodName, argumentTypes); if (method == null) return null; try { DelegatingSubclassHandler<T, ? extends T> handler = getSubclassHandler(type); return method.invoke(handler, arguments); } catch (RuntimeException ex) { throw ex; } catch (Exception ex) { throw new RuntimeException(ex); } } /** * @param delegate * @return the URI for the given delegate object */ @SuppressWarnings("unchecked") @Override public String getUri(Object delegate) { if (delegate == null) return ""; org.openmrs.module.webservices.rest.web.annotation.Resource res = getClass().getAnnotation( org.openmrs.module.webservices.rest.web.annotation.Resource.class); if (res != null) { return RestConstants.URI_PREFIX + res.name() + "/" + getUniqueId((T) delegate); } throw new RuntimeException(getClass() + " needs a @Resource or @SubResource annotation"); } /** * @see org.openmrs.module.webservices.rest.util.ReflectionUtil#findMethod(Class,String) * @deprecated It is always best to annotate the method with @PropertyGetter instead of finding * it this way, because properties defined this way cannot be included in custom * representations */ @Deprecated protected Method findMethod(String name) { Method ret = ReflectionUtil.findMethod(getClass(), name); return ret; } /** * Gets the audit information of a resource. * * @param resource the resource. * @return a {@link SimpleObject} with the audit information. */ @PropertyGetter("auditInfo") public SimpleObject getAuditInfo(Object resource) { return ConversionUtil.getAuditInfo(resource); } @Override public T newDelegate(SimpleObject object) { return newDelegate(); } }