View Javadoc

1   package com.atlassian.plugins.rest.common.expand;
2   
3   import com.atlassian.plugins.rest.common.expand.parameter.ExpandParameter;
4   import com.atlassian.plugins.rest.common.expand.resolver.EntityExpanderResolver;
5   import com.atlassian.plugins.rest.common.util.ReflectionUtils;
6   import static com.atlassian.plugins.rest.common.util.ReflectionUtils.*;
7   import com.google.common.base.Predicate;
8   import com.google.common.collect.Collections2;
9   import org.apache.commons.lang.StringUtils;
10  
11  import javax.xml.bind.annotation.XmlAttribute;
12  import javax.xml.bind.annotation.XmlElement;
13  import java.lang.annotation.Annotation;
14  import java.lang.reflect.Field;
15  import java.util.Collection;
16  
17  /**
18   * This allows for crawling the fields of any arbitrary object, looking for fields that should be expanded.
19   */
20  public class EntityCrawler
21  {
22      /**
23       * Crawls an entity for fields that should be expanded and expands them.
24       * @param entity the object to crawl, can be {@code null}.
25       * @param expandParameter the parameters to match for expansion
26       * @param expanderResolver the resolver to lookup {@link EntityExpander} for fields to be expanded.
27       */
28      public void crawl(Object entity, ExpandParameter expandParameter, EntityExpanderResolver expanderResolver)
29      {
30          if (entity == null)
31          {
32              return;
33          }
34  
35          final Collection<Field> expandableFields = getExpandableFields(entity);
36          setExpandParameter(expandableFields, entity);
37          expandFields(expandableFields, entity, expandParameter, expanderResolver);
38      }
39  
40      private void setExpandParameter(Collection<Field> expandableFields, Object entity)
41      {
42          final Field expand = getExpandField(entity);
43          if (expand != null && !expandableFields.isEmpty())
44          {
45              final StringBuilder expandValue = new StringBuilder();
46              for (Field field : expandableFields)
47              {
48                  expandValue.append(getExpandable(field).value()).append(",");
49              }
50              expandValue.deleteCharAt(expandValue.length() - 1); // remove the last ","
51  
52              setFieldValue(expand, entity, expandValue.toString());
53          }
54      }
55  
56      private Field getExpandField(Object entity)
57      {
58          for (Field field : getDeclaredFields(entity.getClass()))
59          {
60              if (field.getType().equals(String.class))
61              {
62                  final XmlAttribute annotation = field.getAnnotation(XmlAttribute.class);
63                  if (annotation != null && (field.getName().equals("expand") || "expand".equals(annotation.name())))
64                  {
65                      return field;
66                  }
67              }
68          }
69          return null;
70      }
71  
72      private Collection<Field> getExpandableFields(final Object entity)
73      {
74          return Collections2.filter(getDeclaredFields(entity.getClass()), new Predicate<Field>()
75          {
76              public boolean apply(Field field)
77              {
78                  return getExpandable(field) != null && ReflectionUtils.getFieldValue(field, entity) != null;
79              }
80          });
81      }
82  
83      private void expandFields(Collection<Field> expandableFields, Object entity, ExpandParameter expandParameter, EntityExpanderResolver expanderResolver)
84      {
85          for (Field field : expandableFields)
86          {
87              final Expandable expandable = getExpandable(field);
88              if (expandParameter.shouldExpand(expandable) && expanderResolver.hasExpander(field.getType()))
89              {
90                  // we know the expander is not null, as per ExpanderResolver contract
91                  final EntityExpander<Object> entityExpander = expanderResolver.getExpander(field.getType());
92  
93                  final ExpandContext<Object> context = new DefaultExpandContext<Object>(getFieldValue(field, entity), expandable, expandParameter);
94                  setFieldValue(field, entity, entityExpander.expand(context, expanderResolver, this));
95              }
96          }
97      }
98  
99      /**
100      * Returns the expandable annotation with the properly set value. The value is defined as the first valid point in the following list:
101      * <ol>
102      * <li>the value of the {@link Expandable} annotation if it is set</li>
103      * <li>the name of an {@link XmlElement} if the annotation is present on the field and its name is not {@code ##default}</li>
104      * <li>the name of the field</li>
105      * <ol>
106      * @param field the field to look up the Expandable for
107      * @return {@code null} if the field is null, {@code null} if the field doesn't have an expandable annotation,
108      *         an expandable annotation with a properly set value.
109      */
110     Expandable getExpandable(final Field field)
111     {
112         if (field == null)
113         {
114             return null;
115         }
116 
117         final Expandable expandable = field.getAnnotation(Expandable.class);
118         if (expandable == null)
119         {
120             return null;
121         }
122 
123         if (StringUtils.isNotEmpty(expandable.value()))
124         {
125             return expandable;
126         }
127 
128         final XmlElement xmlElement = field.getAnnotation(XmlElement.class);
129         if (xmlElement != null && StringUtils.isNotEmpty(xmlElement.name()) && !StringUtils.equals("##default", xmlElement.name()))
130         {
131             return new ExpandableWithValue(xmlElement.name());
132         }
133 
134         return new ExpandableWithValue(field.getName());
135     }
136 
137     private static class ExpandableWithValue implements Expandable
138     {
139         private final String value;
140 
141         public ExpandableWithValue(String value)
142         {
143             this.value = value;
144         }
145 
146         public String value()
147         {
148             return value;
149         }
150 
151         public Class<? extends Annotation> annotationType()
152         {
153             return Expandable.class;
154         }
155     }
156 }