1 package com.atlassian.plugins.rest.common.expand;
2
3 import java.lang.annotation.Annotation;
4 import java.lang.reflect.Field;
5 import java.util.Collection;
6 import java.util.List;
7
8 import javax.annotation.Nonnull;
9 import javax.xml.bind.annotation.XmlAttribute;
10 import javax.xml.bind.annotation.XmlElement;
11
12 import com.atlassian.annotations.tenancy.Tenantless;
13 import com.atlassian.plugins.rest.common.expand.parameter.ExpandParameter;
14 import com.atlassian.plugins.rest.common.expand.resolver.EntityExpanderResolver;
15 import com.atlassian.plugins.rest.common.util.ReflectionUtils;
16
17 import com.google.common.base.Optional;
18 import com.google.common.base.Predicate;
19 import com.google.common.cache.CacheBuilder;
20 import com.google.common.cache.CacheLoader;
21 import com.google.common.cache.LoadingCache;
22 import com.google.common.collect.Iterables;
23
24 import org.apache.commons.lang.StringUtils;
25
26 import static com.atlassian.plugins.rest.common.util.ReflectionUtils.getFieldValue;
27 import static com.atlassian.plugins.rest.common.util.ReflectionUtils.setFieldValue;
28 import static com.google.common.collect.ImmutableList.copyOf;
29
30
31
32
33 public class EntityCrawler {
34
35 @Tenantless
36 private LoadingCache<Class, List<Field>> declaredFields = CacheBuilder.newBuilder().build(new CacheLoader<Class, List<Field>>() {
37 @Override
38 public List<Field> load(@Nonnull final Class cls) throws Exception {
39 return copyOf(ReflectionUtils.getDeclaredFields(cls));
40 }
41 });
42
43 @Tenantless
44 private LoadingCache<Class, Optional<Field>> expandFields = CacheBuilder.newBuilder().build(new CacheLoader<Class, Optional<Field>>() {
45 @Override
46 public Optional<Field> load(@Nonnull final Class cls) throws Exception {
47 for (Field field : declaredFields.getUnchecked(cls)) {
48 if (field.getType().equals(String.class)) {
49 final XmlAttribute annotation = field.getAnnotation(XmlAttribute.class);
50 if (annotation != null && (field.getName().equals("expand") || "expand".equals(annotation.name()))) {
51 return Optional.of(field);
52 }
53 }
54 }
55 return Optional.absent();
56 }
57 });
58
59
60
61
62
63
64
65
66 public void crawl(Object entity, ExpandParameter expandParameter, EntityExpanderResolver expanderResolver) {
67 if (entity == null) {
68 return;
69 }
70
71 final Collection<Field> expandableFields = getExpandableFields(entity);
72 setExpandParameter(expandableFields, entity);
73 expandFields(expandableFields, entity, expandParameter, expanderResolver);
74 }
75
76 private void setExpandParameter(Collection<Field> expandableFields, Object entity) {
77 final Optional<Field> expand = expandFields.getUnchecked(entity.getClass());
78 if (expand != null && expand.isPresent() && !expandableFields.isEmpty()) {
79 final StringBuilder expandValue = new StringBuilder();
80 for (Field field : expandableFields) {
81 expandValue.append(getExpandable(field).value()).append(",");
82 }
83 expandValue.deleteCharAt(expandValue.length() - 1);
84
85 setFieldValue(expand.get(), entity, expandValue.toString());
86 }
87 }
88
89 private Collection<Field> getExpandableFields(final Object entity) {
90 return copyOf(Iterables.filter(declaredFields.getUnchecked(entity.getClass()), new Predicate<Field>() {
91 public boolean apply(Field field) {
92 return getExpandable(field) != null && ReflectionUtils.getFieldValue(field, entity) != null;
93 }
94 }));
95 }
96
97 private void expandFields(Collection<Field> expandableFields, Object entity, ExpandParameter expandParameter, EntityExpanderResolver expanderResolver) {
98 for (Field field : expandableFields) {
99 final Expandable expandable = getExpandable(field);
100 if (expandParameter.shouldExpand(expandable) && expanderResolver.hasExpander(field.getType())) {
101
102 final EntityExpander<Object> entityExpander = expanderResolver.getExpander(field.getType());
103
104 final ExpandContext<Object> context = new DefaultExpandContext<Object>(getFieldValue(field, entity), expandable, expandParameter);
105 setFieldValue(field, entity, entityExpander.expand(context, expanderResolver, this));
106 }
107 }
108 }
109
110
111
112
113
114
115
116
117
118
119
120
121
122 Expandable getExpandable(final Field field) {
123 if (field == null) {
124 return null;
125 }
126
127 final Expandable expandable = field.getAnnotation(Expandable.class);
128 if (expandable == null) {
129 return null;
130 }
131
132 if (StringUtils.isNotEmpty(expandable.value())) {
133 return expandable;
134 }
135
136 final XmlElement xmlElement = field.getAnnotation(XmlElement.class);
137 if (xmlElement != null && StringUtils.isNotEmpty(xmlElement.name()) && !StringUtils.equals("##default", xmlElement.name())) {
138 return new ExpandableWithValue(xmlElement.name());
139 }
140
141 return new ExpandableWithValue(field.getName());
142 }
143
144 private static class ExpandableWithValue implements Expandable {
145 private final String value;
146
147 public ExpandableWithValue(String value) {
148 this.value = value;
149 }
150
151 public String value() {
152 return value;
153 }
154
155 public Class<? extends Annotation> annotationType() {
156 return Expandable.class;
157 }
158 }
159 }