1 | /** |
2 | * Licensed to jclouds, Inc. (jclouds) under one or more |
3 | * contributor license agreements. See the NOTICE file |
4 | * distributed with this work for additional information |
5 | * regarding copyright ownership. jclouds licenses this file |
6 | * to you under the Apache License, Version 2.0 (the |
7 | * "License"); you may not use this file except in compliance |
8 | * with the License. You may obtain a copy of the License at |
9 | * |
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | * |
12 | * Unless required by applicable law or agreed to in writing, |
13 | * software distributed under the License is distributed on an |
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
15 | * KIND, either express or implied. See the License for the |
16 | * specific language governing permissions and limitations |
17 | * under the License. |
18 | */ |
19 | package org.jclouds.json.config; |
20 | |
21 | import java.lang.reflect.Type; |
22 | import java.util.Date; |
23 | import java.util.Enumeration; |
24 | import java.util.List; |
25 | import java.util.Map; |
26 | import java.util.Properties; |
27 | import java.util.logging.Level; |
28 | import java.util.logging.Logger; |
29 | |
30 | import javax.inject.Inject; |
31 | import javax.inject.Singleton; |
32 | |
33 | import org.jclouds.crypto.CryptoStreams; |
34 | import org.jclouds.date.DateService; |
35 | import org.jclouds.domain.JsonBall; |
36 | import org.jclouds.json.Json; |
37 | import org.jclouds.json.internal.EnumTypeAdapterThatReturnsFromValue; |
38 | import org.jclouds.json.internal.GsonWrapper; |
39 | |
40 | import com.google.common.collect.ImmutableMap; |
41 | import com.google.common.collect.ImmutableMap.Builder; |
42 | import com.google.common.collect.Maps; |
43 | import com.google.common.primitives.Bytes; |
44 | import com.google.gson.Gson; |
45 | import com.google.gson.GsonBuilder; |
46 | import com.google.gson.JsonDeserializationContext; |
47 | import com.google.gson.JsonDeserializer; |
48 | import com.google.gson.JsonElement; |
49 | import com.google.gson.JsonLiteral; |
50 | import com.google.gson.JsonParseException; |
51 | import com.google.gson.JsonPrimitive; |
52 | import com.google.gson.JsonSerializationContext; |
53 | import com.google.gson.JsonSerializer; |
54 | import com.google.gson.ObjectMapTypeAdapter; |
55 | import com.google.gson.reflect.TypeToken; |
56 | import com.google.inject.AbstractModule; |
57 | import com.google.inject.ImplementedBy; |
58 | import com.google.inject.Provides; |
59 | import com.google.inject.TypeLiteral; |
60 | |
61 | /** |
62 | * Contains logic for parsing objects from Strings. |
63 | * |
64 | * @author Adrian Cole |
65 | */ |
66 | public class GsonModule extends AbstractModule { |
67 | |
68 | @SuppressWarnings("rawtypes") |
69 | @Provides |
70 | @Singleton |
71 | Gson provideGson(JsonBallAdapter jsonAdapter, DateAdapter adapter, ByteListAdapter byteListAdapter, |
72 | ByteArrayAdapter byteArrayAdapter, SerializePropertiesDefaults propertiesAdapter, |
73 | JsonAdapterBindings bindings) throws ClassNotFoundException, Exception { |
74 | GsonBuilder builder = new GsonBuilder(); |
75 | Logger.getLogger("com.google.gson.ParameterizedTypeHandlerMap").setLevel(Level.OFF); |
76 | builder.registerTypeHierarchyAdapter(Enum.class, new EnumTypeAdapterThatReturnsFromValue()); |
77 | builder.registerTypeHierarchyAdapter(Map.class, new ObjectMapTypeAdapter()); |
78 | builder.registerTypeAdapter(JsonBall.class, jsonAdapter); |
79 | builder.registerTypeAdapter(Date.class, adapter); |
80 | builder.registerTypeAdapter(Properties.class, propertiesAdapter); |
81 | builder.registerTypeAdapter(new TypeToken<List<Byte>>() { |
82 | }.getType(), byteListAdapter); |
83 | builder.registerTypeAdapter(byte[].class, byteArrayAdapter); |
84 | for (Map.Entry<Type, Object> binding : bindings.getBindings().entrySet()) { |
85 | builder.registerTypeAdapter(binding.getKey(), binding.getValue()); |
86 | } |
87 | return builder.create(); |
88 | } |
89 | |
90 | // http://code.google.com/p/google-gson/issues/detail?id=326 |
91 | @ImplementedBy(JsonBallAdapterImpl.class) |
92 | public static interface JsonBallAdapter extends JsonSerializer<JsonBall>, JsonDeserializer<JsonBall> { |
93 | |
94 | } |
95 | |
96 | @Singleton |
97 | public static class JsonBallAdapterImpl implements JsonBallAdapter { |
98 | |
99 | public JsonElement serialize(JsonBall src, Type typeOfSrc, JsonSerializationContext context) { |
100 | return new JsonLiteral(src); |
101 | } |
102 | |
103 | public JsonBall deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) |
104 | throws JsonParseException { |
105 | return new JsonBall(json.toString()); |
106 | } |
107 | |
108 | } |
109 | |
110 | @ImplementedBy(CDateAdapter.class) |
111 | public static interface DateAdapter extends JsonSerializer<Date>, JsonDeserializer<Date> { |
112 | |
113 | } |
114 | |
115 | @ImplementedBy(HexByteListAdapter.class) |
116 | public static interface ByteListAdapter extends JsonSerializer<List<Byte>>, JsonDeserializer<List<Byte>> { |
117 | |
118 | } |
119 | |
120 | @ImplementedBy(HexByteArrayAdapter.class) |
121 | public static interface ByteArrayAdapter extends JsonSerializer<byte[]>, JsonDeserializer<byte[]> { |
122 | |
123 | } |
124 | |
125 | @Singleton |
126 | public static class HexByteListAdapter implements ByteListAdapter { |
127 | |
128 | @Override |
129 | public List<Byte> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) |
130 | throws JsonParseException { |
131 | return Bytes.asList(CryptoStreams.hex(json.getAsString())); |
132 | } |
133 | |
134 | @Override |
135 | public JsonElement serialize(List<Byte> src, Type typeOfSrc, JsonSerializationContext context) { |
136 | return new JsonPrimitive(CryptoStreams.hex(Bytes.toArray(src))); |
137 | } |
138 | |
139 | } |
140 | |
141 | @Singleton |
142 | public static class HexByteArrayAdapter implements ByteArrayAdapter { |
143 | |
144 | @Override |
145 | public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) |
146 | throws JsonParseException { |
147 | return CryptoStreams.hex(json.getAsString()); |
148 | } |
149 | |
150 | @Override |
151 | public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) { |
152 | return new JsonPrimitive(CryptoStreams.hex(src)); |
153 | } |
154 | } |
155 | |
156 | @Singleton |
157 | public static class Iso8601DateAdapter implements DateAdapter { |
158 | private final DateService dateService; |
159 | |
160 | @Inject |
161 | private Iso8601DateAdapter(DateService dateService) { |
162 | this.dateService = dateService; |
163 | } |
164 | |
165 | public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { |
166 | return new JsonPrimitive(dateService.iso8601DateFormat(src)); |
167 | } |
168 | |
169 | public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) |
170 | throws JsonParseException { |
171 | String toParse = json.getAsJsonPrimitive().getAsString(); |
172 | try { |
173 | return dateService.iso8601DateParse(toParse); |
174 | } catch (RuntimeException e) { |
175 | return dateService.iso8601SecondsDateParse(toParse); |
176 | } |
177 | } |
178 | |
179 | } |
180 | |
181 | @Singleton |
182 | public static class SerializePropertiesDefaults implements JsonSerializer<Properties> { |
183 | private final Json json; |
184 | private final Type mapType = new TypeLiteral<Map<String, String>>() { |
185 | }.getRawType(); |
186 | |
187 | @Inject |
188 | public SerializePropertiesDefaults(Json json) { |
189 | this.json = json; |
190 | } |
191 | |
192 | public JsonElement serialize(Properties src, Type typeOfSrc, JsonSerializationContext context) { |
193 | Builder<String, String> srcMap = ImmutableMap.<String, String> builder(); |
194 | for (Enumeration<?> propNames = src.propertyNames(); propNames.hasMoreElements();) { |
195 | String propName = (String) propNames.nextElement(); |
196 | srcMap.put(propName, src.getProperty(propName)); |
197 | } |
198 | return new JsonLiteral(json.toJson(srcMap.build(), mapType)); |
199 | } |
200 | |
201 | } |
202 | |
203 | @Singleton |
204 | public static class CDateAdapter implements DateAdapter { |
205 | private final DateService dateService; |
206 | |
207 | @Inject |
208 | private CDateAdapter(DateService dateService) { |
209 | this.dateService = dateService; |
210 | } |
211 | |
212 | public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { |
213 | return new JsonPrimitive(dateService.cDateFormat(src)); |
214 | } |
215 | |
216 | public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) |
217 | throws JsonParseException { |
218 | String toParse = json.getAsJsonPrimitive().getAsString(); |
219 | Date toReturn = dateService.cDateParse(toParse); |
220 | return toReturn; |
221 | } |
222 | |
223 | } |
224 | |
225 | @Singleton |
226 | public static class LongDateAdapter implements DateAdapter { |
227 | |
228 | public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { |
229 | return new JsonPrimitive(src.getTime()); |
230 | } |
231 | |
232 | public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) |
233 | throws JsonParseException { |
234 | long toParse = json.getAsJsonPrimitive().getAsLong(); |
235 | if (toParse == -1) |
236 | return null; |
237 | Date toReturn = new Date(toParse); |
238 | return toReturn; |
239 | } |
240 | } |
241 | |
242 | @Singleton |
243 | public static class JsonAdapterBindings { |
244 | private final Map<Type, Object> bindings = Maps.newHashMap(); |
245 | |
246 | @com.google.inject.Inject(optional = true) |
247 | public void setBindings(Map<Type, Object> bindings) { |
248 | this.bindings.putAll(bindings); |
249 | } |
250 | |
251 | public Map<Type, Object> getBindings() { |
252 | return bindings; |
253 | } |
254 | } |
255 | |
256 | @Override |
257 | protected void configure() { |
258 | bind(Json.class).to(GsonWrapper.class); |
259 | } |
260 | } |