Java tutorial
/** * The contents of this file are subject to the OpenMRS Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://license.openmrs.org * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * Copyright (C) OpenMRS, LLC. All Rights Reserved. */ package org.openmrs.module.webservices.rest.web.resource.impl; 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.Hyperlink; 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.PropertySetter; 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.annotation.SubResource; 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.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; 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; /** * 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> 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(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); 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 (versionMatches(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; } private boolean versionMatches(String supportedVersion) { try { ModuleUtil.checkRequiredVersion(OpenmrsConstants.OPENMRS_VERSION_SHORT, supportedVersion); } catch (Exception e) { return false; } return true; } /** * 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 */ 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 */ 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 */ 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.substring(1, def.length() - 1); //remove '(' and ')' 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); 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; } protected SimpleObject convertDelegateToRepresentation(T delegate, DelegatingResourceDescription rep) throws ConversionException { if (delegate == null) throw new NullPointerException(); SimpleObject ret = new SimpleObject(); for (Entry<String, 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; } /** * @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) { 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.name().equals(rep.getRepresentation())) return method; if (ann.value().isAssignableFrom(rep.getClass())) return method; } } return null; } /** * @see org.openmrs.module.webservices.rest.util.ReflectionUtil#findMethod(Class, String) */ protected Method findMethod(String name) { // TODO replace this with something that looks specifically for a method that takes a single T argument Method ret = ReflectionUtil.findMethod(getClass(), name); return ret; } /** * @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((T) instance); // try to find a @PropertyGetter-annotated method Method annotatedGetter = findGetterMethod(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, 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 = findSetterMethod(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(); value = ConversionUtil.convert(value, setter.getGenericParameterTypes()[0]); 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); } } catch (Exception ex) { throw new ConversionException(propertyName + " on " + instance.getClass(), ex); } } private Method findSetterMethod(DelegatingResourceHandler<? extends T> handler, String propName) { for (Method candidate : handler.getClass().getMethods()) { PropertySetter ann = candidate.getAnnotation(PropertySetter.class); if (ann != null && ann.value().equals(propName)) { return candidate; } } return null; } private Method findGetterMethod(DelegatingResourceHandler<? extends T> handler, String propName) { for (Method candidate : handler.getClass().getMethods()) { PropertyGetter ann = candidate.getAnnotation(PropertyGetter.class); if (ann != null && ann.value().equals(propName)) { return candidate; } } return null; } /** * 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"); } }