1 package com.atlassian.plugins.rest.doclet.generators.schema;
2
3 import com.atlassian.rest.annotation.RestProperty;
4 import com.atlassian.rest.annotation.RestProperty.Scope;
5 import com.fasterxml.jackson.annotation.JsonPropertyDescription;
6 import com.google.common.collect.ImmutableSet;
7 import org.codehaus.jackson.annotate.JsonAutoDetect;
8 import org.codehaus.jackson.annotate.JsonIgnore;
9 import org.codehaus.jackson.annotate.JsonProperty;
10
11 import javax.xml.bind.annotation.XmlAttribute;
12 import javax.xml.bind.annotation.XmlElement;
13 import javax.xml.bind.annotation.XmlRootElement;
14 import javax.xml.bind.annotation.XmlTransient;
15 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
16 import java.beans.IntrospectionException;
17 import java.beans.Introspector;
18 import java.beans.PropertyDescriptor;
19 import java.lang.annotation.Annotation;
20 import java.lang.reflect.AnnotatedElement;
21 import java.lang.reflect.Field;
22 import java.lang.reflect.Method;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.Optional;
26 import java.util.Set;
27
28 import static com.atlassian.plugins.rest.doclet.generators.schema.Types.isJDKClass;
29 import static com.google.common.base.Strings.emptyToNull;
30
31 public final class Annotations {
32 private Annotations() {
33 }
34
35 private static final Set<Class<? extends Annotation>> propertyAnnotations = ImmutableSet.of(
36 JsonProperty.class,
37 com.fasterxml.jackson.annotation.JsonProperty.class,
38 XmlAttribute.class,
39 XmlElement.class,
40 XmlJavaTypeAdapter.class
41 );
42
43 private static final Set<Class<? extends Annotation>> autodetectAnnotations = ImmutableSet.of(
44 JsonAutoDetect.class,
45 com.fasterxml.jackson.annotation.JsonAutoDetect.class,
46 XmlRootElement.class
47 );
48
49 private static final Set<Class<? extends Annotation>> ignoreAnnotations = ImmutableSet.of(
50 JsonIgnore.class,
51 com.fasterxml.jackson.annotation.JsonIgnore.class,
52 XmlTransient.class
53 );
54
55 public static boolean shouldFieldBeIncludedInSchema(final AnnotatedElement element, String name, final Class<?> modelClass, final Scope scope) {
56 boolean isJsonField = isCustomInterface(modelClass) || isAnyAnnotationPresent(element, propertyAnnotations) ||
57 element instanceof Field && isAnyAnnotationPresent(modelClass, autodetectAnnotations);
58
59 Scope fieldScope = element.isAnnotationPresent(RestProperty.class) ? element.getAnnotation(RestProperty.class).scope() : Scope.AUTO;
60
61 return isJsonField && !isIgnored(name, modelClass) && scope.includes(fieldScope, name);
62 }
63
64 private static boolean isIgnored(final String name, final Class<?> modelClass) {
65 try {
66 for (PropertyDescriptor propertyDescriptor : getPropertyDescriptors(modelClass)) {
67 Method getter = propertyDescriptor.getReadMethod();
68 if (getter != null && propertyDescriptor.getName().equals(name) && isAnyAnnotationPresent(getter, ignoreAnnotations)) {
69 return true;
70 }
71 }
72
73 Field field = modelClass.getDeclaredField(name);
74 return field != null && isAnyAnnotationPresent(field, ignoreAnnotations);
75 } catch (NoSuchFieldException ex) {
76 return false;
77 }
78 }
79
80 private static boolean isCustomInterface(final Class<?> modelClass) {
81 return !isJDKClass(modelClass) && modelClass.isInterface();
82 }
83
84 public static String resolveFieldName(final AnnotatedElement element, String defaultName) {
85 if (element.isAnnotationPresent(JsonProperty.class)) {
86 return Optional.ofNullable(emptyToNull(element.getAnnotation(JsonProperty.class).value())).orElse(defaultName);
87 } else if (element.isAnnotationPresent(XmlElement.class)) {
88 String name = element.getAnnotation(XmlElement.class).name();
89 return !name.equals("##default") ? name : defaultName;
90 } else if (element.isAnnotationPresent(XmlAttribute.class)) {
91 String name = element.getAnnotation(XmlAttribute.class).name();
92 return !name.equals("##default") ? name : defaultName;
93 } else {
94 return defaultName;
95 }
96 }
97
98 public static boolean isRequired(final AnnotatedElement element) {
99 return element.isAnnotationPresent(com.fasterxml.jackson.annotation.JsonProperty.class) &&
100 element.getAnnotation(com.fasterxml.jackson.annotation.JsonProperty.class).required() ||
101 element.isAnnotationPresent(RestProperty.class) && element.getAnnotation(RestProperty.class).required();
102 }
103
104 public static String getDescription(final AnnotatedElement field) {
105 if (field != null) {
106 if (field.isAnnotationPresent(JsonPropertyDescription.class)) {
107 return emptyToNull(field.getAnnotation(JsonPropertyDescription.class).value());
108 } else if (field.isAnnotationPresent(RestProperty.class)) {
109 return emptyToNull(field.getAnnotation(RestProperty.class).description());
110 }
111 }
112 return null;
113 }
114
115 private static boolean isAnyAnnotationPresent(AnnotatedElement element, Iterable<Class<? extends Annotation>> annotations) {
116 for (Class<? extends Annotation> annotation : annotations) {
117 if (element.isAnnotationPresent(annotation)) {
118 return true;
119 }
120 }
121 return false;
122 }
123
124 private static Iterable<PropertyDescriptor> getPropertyDescriptors(Class<?> type) {
125 try {
126 return Arrays.asList(Introspector.getBeanInfo(type).getPropertyDescriptors());
127 } catch (IntrospectionException e) {
128 return Collections.emptyList();
129 }
130 }
131 }