1 package com.fasterxml.jackson.databind.ser;
2 
3 import java.util.*;
4 import java.util.concurrent.atomic.AtomicReference;
5 
6 import com.fasterxml.jackson.databind.*;
7 import com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap;
8 import com.fasterxml.jackson.databind.util.TypeKey;
9 
10 /**
11  * Simple cache object that allows for doing 2-level lookups: first level is
12  * by "local" read-only lookup Map (used without locking)
13  * and second backup level is by a shared modifiable HashMap.
14  * The idea is that after a while, most serializers are found from the
15  * local Map (to optimize performance, reduce lock contention),
16  * but that during buildup we can use a shared map to reduce both
17  * number of distinct read-only maps constructed, and number of
18  * serializers constructed.
19  *<p>
20  * Cache contains three kinds of entries,
21  * based on combination of class pair key. First class in key is for the
22  * type to serialize, and second one is type used for determining how
23  * to resolve value type. One (but not both) of entries can be null.
24  */
25 public final class SerializerCache
26 {
27     /**
28      * Shared, modifiable map; all access needs to be through synchronized blocks.
29      *<p>
30      * NOTE: keys are of various types (see below for key types), in addition to
31      * basic {@link JavaType} used for "untyped" serializers.
32      */
33     private final HashMap<TypeKey, JsonSerializer<Object>> _sharedMap
34         = new HashMap<TypeKey, JsonSerializer<Object>>(64);
35 
36     /**
37      * Most recent read-only instance, created from _sharedMap, if any.
38      */
39     private final AtomicReference<ReadOnlyClassToSerializerMap> _readOnlyMap
40         = new AtomicReference<ReadOnlyClassToSerializerMap>();
41 
SerializerCache()42     public SerializerCache() { }
43 
44     /**
45      * Method that can be called to get a read-only instance populated from the
46      * most recent version of the shared lookup Map.
47      */
getReadOnlyLookupMap()48     public ReadOnlyClassToSerializerMap getReadOnlyLookupMap()
49     {
50         ReadOnlyClassToSerializerMap m = _readOnlyMap.get();
51         if (m != null) {
52             return m;
53         }
54         return _makeReadOnlyLookupMap();
55     }
56 
_makeReadOnlyLookupMap()57     private final synchronized ReadOnlyClassToSerializerMap _makeReadOnlyLookupMap() {
58         // double-locking; safe, but is it really needed? Not doing that is only a perf problem,
59         // not correctness
60         ReadOnlyClassToSerializerMap m = _readOnlyMap.get();
61         if (m == null) {
62             m = ReadOnlyClassToSerializerMap.from(_sharedMap);
63             _readOnlyMap.set(m);
64         }
65         return m;
66     }
67 
68     /*
69     /**********************************************************
70     /* Lookup methods for accessing shared (slow) cache
71     /**********************************************************
72      */
73 
size()74     public synchronized int size() {
75         return _sharedMap.size();
76     }
77 
78     /**
79      * Method that checks if the shared (and hence, synchronized) lookup Map might have
80      * untyped serializer for given type.
81      */
untypedValueSerializer(Class<?> type)82     public JsonSerializer<Object> untypedValueSerializer(Class<?> type)
83     {
84         synchronized (this) {
85             return _sharedMap.get(new TypeKey(type, false));
86         }
87     }
88 
untypedValueSerializer(JavaType type)89     public JsonSerializer<Object> untypedValueSerializer(JavaType type)
90     {
91         synchronized (this) {
92             return _sharedMap.get(new TypeKey(type, false));
93         }
94     }
95 
typedValueSerializer(JavaType type)96     public JsonSerializer<Object> typedValueSerializer(JavaType type)
97     {
98         synchronized (this) {
99             return _sharedMap.get(new TypeKey(type, true));
100         }
101     }
102 
typedValueSerializer(Class<?> cls)103     public JsonSerializer<Object> typedValueSerializer(Class<?> cls)
104     {
105         synchronized (this) {
106             return _sharedMap.get(new TypeKey(cls, true));
107         }
108     }
109 
110     /*
111     /**********************************************************
112     /* Methods for adding shared serializer instances
113     /**********************************************************
114      */
115 
116     /**
117      * Method called if none of lookups succeeded, and caller had to construct
118      * a serializer. If so, we will update the shared lookup map so that it
119      * can be resolved via it next time.
120      */
addTypedSerializer(JavaType type, JsonSerializer<Object> ser)121     public void addTypedSerializer(JavaType type, JsonSerializer<Object> ser)
122     {
123         synchronized (this) {
124             if (_sharedMap.put(new TypeKey(type, true), ser) == null) {
125                 // let's invalidate the read-only copy, too, to get it updated
126                 _readOnlyMap.set(null);
127             }
128         }
129     }
130 
addTypedSerializer(Class<?> cls, JsonSerializer<Object> ser)131     public void addTypedSerializer(Class<?> cls, JsonSerializer<Object> ser)
132     {
133         synchronized (this) {
134             if (_sharedMap.put(new TypeKey(cls, true), ser) == null) {
135                 // let's invalidate the read-only copy, too, to get it updated
136                 _readOnlyMap.set(null);
137             }
138         }
139     }
140 
addAndResolveNonTypedSerializer(Class<?> type, JsonSerializer<Object> ser, SerializerProvider provider)141     public void addAndResolveNonTypedSerializer(Class<?> type, JsonSerializer<Object> ser,
142             SerializerProvider provider)
143         throws JsonMappingException
144     {
145         synchronized (this) {
146             if (_sharedMap.put(new TypeKey(type, false), ser) == null) {
147                 _readOnlyMap.set(null);
148             }
149             // Need resolution to handle cyclic POJO type dependencies
150             /* 14-May-2011, tatu: Resolving needs to be done in synchronized manner;
151              *   this because while we do need to register instance first, we also must
152              *   keep lock until resolution is complete.
153              */
154             if (ser instanceof ResolvableSerializer) {
155                 ((ResolvableSerializer) ser).resolve(provider);
156             }
157         }
158     }
159 
addAndResolveNonTypedSerializer(JavaType type, JsonSerializer<Object> ser, SerializerProvider provider)160     public void addAndResolveNonTypedSerializer(JavaType type, JsonSerializer<Object> ser,
161             SerializerProvider provider)
162         throws JsonMappingException
163     {
164         synchronized (this) {
165             if (_sharedMap.put(new TypeKey(type, false), ser) == null) {
166                 _readOnlyMap.set(null);
167             }
168             // Need resolution to handle cyclic POJO type dependencies
169             /* 14-May-2011, tatu: Resolving needs to be done in synchronized manner;
170              *   this because while we do need to register instance first, we also must
171              *   keep lock until resolution is complete.
172              */
173             if (ser instanceof ResolvableSerializer) {
174                 ((ResolvableSerializer) ser).resolve(provider);
175             }
176         }
177     }
178 
179     /**
180      * Another alternative that will cover both access via raw type and matching
181      * fully resolved type, in one fell swoop.
182      *
183      * @since 2.7
184      */
addAndResolveNonTypedSerializer(Class<?> rawType, JavaType fullType, JsonSerializer<Object> ser, SerializerProvider provider)185     public void addAndResolveNonTypedSerializer(Class<?> rawType, JavaType fullType,
186             JsonSerializer<Object> ser,
187             SerializerProvider provider)
188         throws JsonMappingException
189     {
190         synchronized (this) {
191             Object ob1 = _sharedMap.put(new TypeKey(rawType, false), ser);
192             Object ob2 = _sharedMap.put(new TypeKey(fullType, false), ser);
193             if ((ob1 == null) || (ob2 == null)) {
194                 _readOnlyMap.set(null);
195             }
196             if (ser instanceof ResolvableSerializer) {
197                 ((ResolvableSerializer) ser).resolve(provider);
198             }
199         }
200     }
201 
202     /**
203      * Method called by StdSerializerProvider#flushCachedSerializers() to
204      * clear all cached serializers
205      */
flush()206     public synchronized void flush() {
207         _sharedMap.clear();
208     }
209 }
210