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