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