View Javadoc
1   /*
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   */
14  package com.addthis.codec.jackson;
15  
16  import java.util.ArrayList;
17  import java.util.List;
18  import java.util.Map;
19  
20  import com.addthis.codec.annotations.FieldConfig;
21  import com.addthis.codec.annotations.Pluggable;
22  import com.addthis.codec.plugins.PluginMap;
23  import com.addthis.codec.plugins.PluginRegistry;
24  
25  import com.fasterxml.jackson.databind.JavaType;
26  import com.fasterxml.jackson.databind.PropertyName;
27  import com.fasterxml.jackson.databind.cfg.MapperConfig;
28  import com.fasterxml.jackson.databind.introspect.Annotated;
29  import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
30  import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
31  import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
32  import com.fasterxml.jackson.databind.jsontype.NamedType;
33  import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
34  
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  public class CodecIntrospector extends NopAnnotationIntrospector {
39      private static final Logger log = LoggerFactory.getLogger(CodecIntrospector.class);
40  
41      private final PluginRegistry pluginRegistry;
42  
43      public CodecIntrospector(PluginRegistry pluginRegistry) {
44          this.pluginRegistry = pluginRegistry;
45      }
46  
47      @Override
48      public TypeResolverBuilder<?> findTypeResolver(MapperConfig<?> config, AnnotatedClass ac, JavaType baseType) {
49          Pluggable pluggable = ac.getAnnotation(Pluggable.class);
50          if (pluggable != null) {
51              PluginMap pluginMap = pluginRegistry.byCategory().get(pluggable.value());
52              if (pluginMap != null) {
53                  return new CodecTypeResolverBuilder(pluginMap, config.getTypeFactory(), pluginRegistry);
54              } else {
55                  log.warn("missing plugin map for {}, reached from {}", pluggable.value(), ac.getRawType());
56              }
57          } else if (pluginRegistry.byClass().containsKey(ac.getRawType())) {
58              PluginMap pluginMap = pluginRegistry.byClass().get(ac.getRawType());
59              return new CodecTypeResolverBuilder(pluginMap, config.getTypeFactory(), pluginRegistry);
60          }
61          return null;
62      }
63  
64      /** report all non-alias plugin types */
65      @Override
66      public List<NamedType> findSubtypes(Annotated a) {
67          Pluggable pluggable = a.getAnnotation(Pluggable.class);
68          PluginMap pluginMap;
69          if (pluggable != null) {
70              pluginMap = pluginRegistry.byCategory().get(pluggable.value());
71          } else if (pluginRegistry.byClass().containsKey(a.getRawType())) {
72              pluginMap = pluginRegistry.byClass().get(a.getRawType());
73          } else {
74              return null;
75          }
76          List<NamedType> result = new ArrayList<>(pluginMap.asBiMap().size());
77          for (Map.Entry<String, Class<?>> type : pluginMap.asBiMap().entrySet()) {
78              result.add(new NamedType(type.getValue(), type.getKey()));
79          }
80          return result;
81      }
82  
83      @Override
84      public boolean hasIgnoreMarker(AnnotatedMember m) {
85          FieldConfig fieldConfig = m.getAnnotation(FieldConfig.class);
86          if (fieldConfig != null) {
87              return !fieldConfig.codable();
88          }
89          return false;
90      }
91  
92      @Override
93      public Boolean hasRequiredMarker(AnnotatedMember m) {
94          FieldConfig fieldConfig = m.getAnnotation(FieldConfig.class);
95          if (fieldConfig != null) {
96              return fieldConfig.required();
97          }
98          return null;
99      }
100 
101     // read and write only settings only work in the absence of another, explicit, JsonProperty.
102     // also, note that these methods are what cause FieldConfig to trigger inclusion, so if/when
103     // we just drop readonly/writeonly, we can (should) add meta annotations to FieldConfig et al.
104 
105     @Override
106     public PropertyName findNameForDeserialization(Annotated a) {
107         FieldConfig fieldConfig = a.getAnnotation(FieldConfig.class);
108         if ((fieldConfig != null) && !fieldConfig.writeonly() && fieldConfig.codable()) {
109             return PropertyName.USE_DEFAULT;
110         }
111         return null;
112     }
113 
114     @Override
115     public PropertyName findNameForSerialization(Annotated a) {
116         FieldConfig fieldConfig = a.getAnnotation(FieldConfig.class);
117         if ((fieldConfig != null) && !fieldConfig.readonly() && fieldConfig.codable()) {
118             return PropertyName.USE_DEFAULT;
119         }
120         return null;
121     }
122 }