1 /*
2  * Copyright 2002-2018 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 module hunt.stomp.converter.JsonMessageConverter;
18 
19 import hunt.stomp.converter.AbstractMessageConverter;
20 import hunt.stomp.Message;
21 import hunt.stomp.MessageHeaders;
22 import hunt.stomp.MessagingException;
23 import hunt.stomp.support.GenericMessage;
24 import hunt.stomp.support.MessageBuilder;
25 import hunt.stomp.support.MessageHeaderAccessor;
26 
27 import hunt.util.MimeType;
28 import hunt.text.Charset;
29 import hunt.Exceptions;
30 import hunt.Nullable;
31 import hunt.logging;
32 import hunt.util.TypeUtils;
33 
34 import std.array;
35 import std.conv;
36 import std.json;
37 import std.string;
38 
39 /**
40 */
41 class JsonMessageConverter : AbstractMessageConverter {
42 	
43 	private bool prettyPrint;
44 
45 	/**
46 	 * Construct a {@code JsonMessageConverter} supporting
47 	 * the {@code application/json} MIME type with {@code UTF-8} character set.
48 	 */
49 	this() {
50 		super(new MimeType("application/json", StandardCharsets.UTF_8));
51 	}
52 
53 	/**
54 	 * Construct a {@code JsonMessageConverter} supporting
55 	 * one or more custom MIME types.
56 	 * @param supportedMimeTypes the supported MIME types
57 	 * @since 4.1.5
58 	 */
59 	this(MimeType[] supportedMimeTypes...) {
60 		super(supportedMimeTypes.dup);
61 	}
62 
63 	/**
64 	 * Whether to use the {@link DefaultPrettyPrinter} when writing JSON.
65 	 * This is a shortcut for setting up an {@code ObjectMapper} as follows:
66 	 * <pre class="code">
67 	 * ObjectMapper mapper = new ObjectMapper();
68 	 * mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
69 	 * converter.setObjectMapper(mapper);
70 	 * </pre>
71 	 */
72 	void setPrettyPrint(bool prettyPrint) {
73 		this.prettyPrint = prettyPrint;
74 	}
75 
76 
77 	override
78 	protected bool canConvertFrom(MessageBase message, TypeInfo targetClass) {
79 		bool r = true;
80 
81 		if (targetClass is null || !supportsMimeType(message.getHeaders())) {
82 			r = false;
83 		}
84 
85 		version(HUNT_DEBUG) {
86 			if(targetClass is null) 
87 				tracef("checking message, converter: %s, result: %s", 
88 					TypeUtils.getSimpleName(typeid(this)), r);
89 			else
90 				tracef("checking message, target: %s, converter: %s, result: %s", targetClass,
91 					TypeUtils.getSimpleName(typeid(this)), r);
92 		} 
93 
94 		return r;
95 	}
96 
97 	override
98 	protected bool canConvertTo(Object payload, MessageHeaders headers, TypeInfo conversionHint) {
99 		bool r = supportsMimeType(headers);
100 		version(HUNT_DEBUG) tracef("checking payload, type: %s, converter: %s, result: %s", 
101 				typeid(payload), TypeUtils.getSimpleName(typeid(this)), r);
102 
103 		return r;
104 	}
105 
106 	override
107 	protected bool supports(TypeInfo typeInfo) {
108 		// should not be called, since we override canConvertFrom/canConvertTo instead
109 		throw new UnsupportedOperationException();
110 	}
111 
112 	override
113 	protected Object convertFromInternal(MessageBase message, TypeInfo targetClass, TypeInfo conversionHint) {
114 		auto m = cast(GenericMessage!(byte[]))message;
115 		if(m is null) {
116 			warningf("Wrong message type: %s", conversionHint);
117 			return null;
118 		}
119 		string payload = cast(string)m.getPayload();
120 		try {
121 			JSONValue jv = parseJSON(payload);
122 			auto r = new Nullable!(JSONValue)(jv);
123 			return r;
124 		} catch(Exception ex) {
125 			errorf("json convertion error: %s", ex.msg);
126 			infof("message payload: %s", payload);
127 			throw new MessageConversionException(message, "Could not read JSON: " ~ ex.msg, ex);
128 		}
129 	}
130 
131 	override	
132 	protected Object convertToInternal(Object payload, MessageHeaders headers,
133 			TypeInfo conversionHint) {
134 
135 		auto p = cast(Nullable!(JSONValue))payload;
136 		if(p is null) {
137 			warningf("Wrong message type: %s", conversionHint);
138 			return null;
139 		}
140 
141 		JSONValue jv = p.value();
142 		try {
143 			string r;
144 			if(this.prettyPrint)
145 				r = jv.toPrettyString();
146 			else
147 				r = jv.toString();
148 			return new Nullable!string(r);
149 		} catch(Exception ex) {
150 			warning(ex.msg);
151 			throw new MessageConversionException("Could not write JSON: " ~ ex.msg, ex);
152 		}
153 	}
154 }