1
18 package net.bull.javamelody.internal.model;
19
20 import java.awt.datatransfer.DataFlavor;
21 import java.io.BufferedInputStream;
22 import java.io.BufferedOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.io.ObjectInputStream;
27 import java.io.ObjectOutputStream;
28 import java.io.ObjectStreamClass;
29 import java.io.OutputStream;
30 import java.io.OutputStreamWriter;
31 import java.io.Serializable;
32 import java.lang.reflect.Type;
33 import java.util.Locale;
34 import java.util.Map;
35
36 import com.google.gson.Gson;
37 import com.google.gson.GsonBuilder;
38 import com.google.gson.JsonElement;
39 import com.google.gson.JsonPrimitive;
40 import com.google.gson.JsonSerializationContext;
41 import com.google.gson.JsonSerializer;
42 import com.thoughtworks.xstream.XStream;
43 import com.thoughtworks.xstream.converters.collections.MapConverter;
44 import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver;
45 import com.thoughtworks.xstream.io.xml.CompactWriter;
46 import com.thoughtworks.xstream.security.NoTypePermission;
47 import com.thoughtworks.xstream.security.NullPermission;
48 import com.thoughtworks.xstream.security.PrimitiveTypePermission;
49
50
55 public enum TransportFormat {
56
59 SERIALIZED(DataFlavor.javaSerializedObjectMimeType),
60
61
64 XML("text/xml; charset=utf-8"),
65
66
70 JSON("application/json"),
71
72
76 GSON("application/json");
77
78 private static final String NULL_VALUE = "null";
79
80
81
82 private static final class XmlIO {
83 private static final String PACKAGE_NAME = TransportFormat.class.getName().substring(0,
84 TransportFormat.class.getName().length()
85 - TransportFormat.class.getSimpleName().length() - 1);
86 private static final String XML_CHARSET_NAME = "utf-8";
87
88 private XmlIO() {
89 super();
90 }
91
92 static void writeToXml(Serializable serializable, BufferedOutputStream bufferedOutput)
93 throws IOException {
94 final XStream xstream = createXStream(false);
95
96
97 final CompactWriter writer = new CompactWriter(
98 new OutputStreamWriter(bufferedOutput, XML_CHARSET_NAME));
99 try {
100 xstream.marshal(serializable, writer);
101 } finally {
102 writer.close();
103 }
104 }
105
106 static Object readFromXml(InputStream bufferedInput) throws IOException {
107 final XStream xstream = createXStream(false);
108
109
110 xstream.addPermission(NoTypePermission.NONE);
111
112 xstream.addPermission(NullPermission.NULL);
113 xstream.addPermission(PrimitiveTypePermission.PRIMITIVES);
114 xstream.allowTypesByWildcard(
115 new String[] { "java.lang.*", "java.util.*", "java.util.concurrent.*" });
116
117 xstream.allowTypesByWildcard(new String[] { PACKAGE_NAME + ".*" });
118 try (InputStreamReader reader = new InputStreamReader(bufferedInput,
119 XML_CHARSET_NAME)) {
120 return xstream.fromXML(reader);
121 }
122 }
123
124 static void writeToJson(Serializable serializable, BufferedOutputStream bufferedOutput)
125 throws IOException {
126 final XStream xstream = createXStream(true);
127 try {
128 xstream.toXML(serializable, bufferedOutput);
129 } finally {
130 bufferedOutput.close();
131 }
132 }
133
134 private static XStream createXStream(boolean json) {
135 final XStream xstream;
136 if (json) {
137
138 xstream = new XStream(new JsonHierarchicalStreamDriver());
139
140 } else {
141
142 xstream = new XStream();
143 }
144 for (final Map.Entry<String, Class<?>> entry : XStreamAlias.getMap().entrySet()) {
145 xstream.alias(entry.getKey(), entry.getValue());
146 }
147 final MapConverter mapConverter = new MapConverter(xstream.getMapper()) {
148
149 @SuppressWarnings("rawtypes")
150 @Override
151 public boolean canConvert(Class type) {
152 return true;
153 }
154 };
155 xstream.registerLocalConverter(Counter.class, "requests", mapConverter);
156 xstream.registerLocalConverter(Counter.class, "rootCurrentContextsByThreadId",
157 mapConverter);
158 return xstream;
159 }
160 }
161
162
163
164
165 private static final class GsonIO {
166 private static final String GSON_CHARSET_NAME = "UTF-8";
167
168 private GsonIO() {
169 super();
170 }
171
172 static void writeToGson(Serializable serializable, BufferedOutputStream bufferedOutput)
173 throws IOException {
174 final JsonSerializer<StackTraceElement> stackTraceElementJsonSerializer = new JsonSerializer<StackTraceElement>() {
175 @Override
176 public JsonElement serialize(StackTraceElement src, Type typeOfSrc,
177 JsonSerializationContext context) {
178 return new JsonPrimitive(src.toString());
179 }
180 };
181 final Gson gson = new GsonBuilder()
182
183 .registerTypeAdapter(StackTraceElement.class, stackTraceElementJsonSerializer)
184 .create();
185 try (OutputStreamWriter writer = new OutputStreamWriter(bufferedOutput,
186 GSON_CHARSET_NAME)) {
187 gson.toJson(serializable, writer);
188 }
189 }
190 }
191
192 private static class MyObjectInputStream extends ObjectInputStream {
193 private static final String PACKAGE_NAME = TransportFormat.class.getName().substring(0,
194 TransportFormat.class.getName().length()
195 - TransportFormat.class.getSimpleName().length() - 1);
196
197 MyObjectInputStream(InputStream input) throws IOException {
198 super(input);
199 }
200
201
202
203 @Override
204 protected Class<?> resolveClass(ObjectStreamClass desc)
205 throws IOException, ClassNotFoundException {
206 final String name = desc.getName();
207 int i = 0;
208 if (name.indexOf("[[") == 0) {
209
210 i++;
211 }
212 if (name.indexOf("[L", i) == i) {
213
214 i += 2;
215 }
216 if (name.indexOf("java.lang.", i) == i || name.indexOf("java.util.", i) == i
217 || name.indexOf("java.io.", i) == i || name.indexOf(PACKAGE_NAME, i) == i
218 || name.length() <= 2) {
219
220 return super.resolveClass(desc);
221 } else if (name.indexOf("net.bull.javamelody", i) == i) {
222
223 return Class.forName(
224 name.replace("net.bull.javamelody", "net.bull.javamelody.internal.model"));
225 }
226 throw new ClassNotFoundException(name);
227 }
228 }
229
230 private final String code;
231 private final String mimeType;
232
233 TransportFormat(String mimeType) {
234 this.mimeType = mimeType;
235 this.code = this.toString().toLowerCase(Locale.ENGLISH);
236 }
237
238 public static TransportFormat valueOfIgnoreCase(String transportFormat) {
239 return valueOf(transportFormat.toUpperCase(Locale.ENGLISH).trim());
240 }
241
242 public static boolean isATransportFormat(String format) {
243 if (format == null) {
244 return false;
245 }
246 final String upperCase = format.toUpperCase(Locale.ENGLISH).trim();
247 for (final TransportFormat transportFormat : values()) {
248 if (transportFormat.toString().equals(upperCase)) {
249 return true;
250 }
251 }
252 return false;
253 }
254
255 public String getCode() {
256 return code;
257 }
258
259 public String getMimeType() {
260 return mimeType;
261 }
262
263 public void checkDependencies() throws IOException {
264 if (this == XML || this == JSON) {
265 try {
266 Class.forName("com.thoughtworks.xstream.XStream");
267 } catch (final ClassNotFoundException e) {
268 throw new IOException(
269 "Classes of the XStream library not found. Add the XStream dependency in your webapp for the XML or JSON formats.",
270 e);
271 }
272 }
273 if (this == XML) {
274 try {
275 Class.forName("org.xmlpull.v1.XmlPullParser");
276 } catch (final ClassNotFoundException e) {
277 throw new IOException(
278 "Classes of the XPP3 library not found. Add the XPP3 dependency in your webapp for the XML format.",
279 e);
280 }
281 }
282 if (this == GSON) {
283 try {
284 Class.forName("com.google.gson.Gson");
285 } catch (final ClassNotFoundException e) {
286 throw new IOException(
287 "Classes of the Gson library not found. Add the Gson dependency in your webapp for the GSON format.",
288 e);
289 }
290
291 }
292 }
293
294 public void writeSerializableTo(Serializable serializable, OutputStream output)
295 throws IOException {
296 final Serializable nonNullSerializable;
297 if (serializable == null) {
298 nonNullSerializable = NULL_VALUE;
299 } else {
300 nonNullSerializable = serializable;
301 }
302 final BufferedOutputStream bufferedOutput = new BufferedOutputStream(output);
303 switch (this) {
304 case SERIALIZED:
305 try (ObjectOutputStream out = new ObjectOutputStream(bufferedOutput)) {
306 out.writeObject(nonNullSerializable);
307 }
308 break;
309 case XML:
310
311
312 XmlIO.writeToXml(nonNullSerializable, bufferedOutput);
313 break;
314 case JSON:
315 XmlIO.writeToJson(nonNullSerializable, bufferedOutput);
316 break;
317 case GSON:
318 GsonIO.writeToGson(nonNullSerializable, bufferedOutput);
319 break;
320 default:
321 throw new IllegalStateException(toString());
322 }
323 }
324
325 Serializable readSerializableFrom(InputStream input)
326 throws IOException, ClassNotFoundException {
327 final InputStream bufferedInput = new BufferedInputStream(input);
328 final Object result;
329 switch (this) {
330 case SERIALIZED:
331 try (ObjectInputStream in = createObjectInputStream(bufferedInput)) {
332 result = in.readObject();
333 }
334 break;
335 case XML:
336 result = XmlIO.readFromXml(bufferedInput);
337 break;
338 case JSON:
339
340
341 throw new UnsupportedOperationException();
342 case GSON:
343 throw new UnsupportedOperationException();
344 default:
345 throw new IllegalStateException(toString());
346 }
347 if (NULL_VALUE.equals(result)) {
348 return null;
349 }
350
351 return (Serializable) result;
352 }
353
354 static ObjectInputStream createObjectInputStream(InputStream input) throws IOException {
355 return new MyObjectInputStream(input);
356 }
357 }
358