1 /*
2  * Copyright 2002-2017 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.AbstractMessageConverter;
18 
19 import hunt.stomp.converter.ContentTypeResolver;
20 import hunt.stomp.converter.DefaultContentTypeResolver;
21 import hunt.stomp.converter.SmartMessageConverter;
22 import hunt.stomp.Message;
23 import hunt.stomp.MessageHeaders;
24 import hunt.stomp.support.GenericMessage;
25 import hunt.stomp.support.MessageBuilder;
26 import hunt.stomp.support.MessageHeaderAccessor;
27 
28 
29 import hunt.collection;
30 import hunt.util.MimeType;
31 import hunt.Exceptions;
32 import hunt.Nullable;
33 import hunt.logging;
34 import hunt.util.TypeUtils;
35 
36 import std.array;
37 
38 
39 /**
40  * Abstract base class for {@link SmartMessageConverter} implementations including
41  * support for common properties and a partial implementation of the conversion methods,
42  * mainly to check if the converter supports the conversion based on the payload class
43  * and MIME type.
44  *
45  * @author Rossen Stoyanchev
46  * @author Sebastien Deleuze
47  * @author Juergen Hoeller
48  * @since 4.0
49  */
50 abstract class AbstractMessageConverter : SmartMessageConverter {
51 
52 	private MimeType[] supportedMimeTypes;
53 
54 	private ContentTypeResolver contentTypeResolver;
55 
56 	private bool strictContentTypeMatch = false;
57 
58 	// private Class<?> serializedPayloadClass = byte[].class;
59 
60 
61 	/**
62 	 * Construct an {@code AbstractMessageConverter} supporting a single MIME type.
63 	 * @param supportedMimeType the supported MIME type
64 	 */
65 	protected this(MimeType supportedMimeType) {
66 		assert(supportedMimeType, "supportedMimeType is required");
67 		this.supportedMimeTypes = [supportedMimeType];
68 		contentTypeResolver = new DefaultContentTypeResolver();
69 	}
70 
71 	/**
72 	 * Construct an {@code AbstractMessageConverter} supporting multiple MIME types.
73 	 * @param supportedMimeTypes the supported MIME types
74 	 */
75 	protected this(MimeType[] supportedMimeTypes) {
76 		assert(supportedMimeTypes.length>0, "supportedMimeTypes must not be null");
77 		this.supportedMimeTypes = supportedMimeTypes;
78 		contentTypeResolver = new DefaultContentTypeResolver();
79 	}
80 
81 
82 	/**
83 	 * Return the supported MIME types.
84 	 */
85 	MimeType[] getSupportedMimeTypes() {
86 		return this.supportedMimeTypes;
87 	}
88 
89 	/**
90 	 * Configure the {@link ContentTypeResolver} to use to resolve the content
91 	 * type of an input message.
92 	 * <p>Note that if no resolver is configured, then
93 	 * {@link #setStrictContentTypeMatch() strictContentTypeMatch} should
94 	 * be left as {@code false} (the default) or otherwise this converter will
95 	 * ignore all messages.
96 	 * <p>By default, a {@code DefaultContentTypeResolver} instance is used.
97 	 */
98 	void setContentTypeResolver(ContentTypeResolver resolver) {
99 		this.contentTypeResolver = resolver;
100 	}
101 
102 	/**
103 	 * Return the configured {@link ContentTypeResolver}.
104 	 */
105 	
106 	ContentTypeResolver getContentTypeResolver() {
107 		return this.contentTypeResolver;
108 	}
109 
110 	/**
111 	 * Whether this converter should convert messages for which no content type
112 	 * could be resolved through the configured
113 	 * {@link hunt.stomp.converter.ContentTypeResolver}.
114 	 * <p>A converter can configured to be strict only when a
115 	 * {@link #setContentTypeResolver contentTypeResolver} is configured and the
116 	 * list of {@link #getSupportedMimeTypes() supportedMimeTypes} is not be empty.
117 	 * <p>When this flag is set to {@code true}, {@link #supportsMimeType(MessageHeaders)}
118 	 * will return {@code false} if the {@link #setContentTypeResolver contentTypeResolver}
119 	 * is not defined or if no content-type header is present.
120 	 */
121 	void setStrictContentTypeMatch(bool strictContentTypeMatch) {
122 		if (strictContentTypeMatch) {
123 			// assert(getSupportedMimeTypes(), "Strict match requires non-empty list of supported mime types");
124 			// assert(getContentTypeResolver(), "Strict match requires ContentTypeResolver");
125 		}
126 		this.strictContentTypeMatch = strictContentTypeMatch;
127 	}
128 
129 	/**
130 	 * Whether content type resolution must produce a value that matches one of
131 	 * the supported MIME types.
132 	 */
133 	bool isStrictContentTypeMatch() {
134 		return this.strictContentTypeMatch;
135 	}
136 
137 
138 	/**
139 	 * Returns the default content type for the payload. Called when
140 	 * {@link #toMessage(Object, MessageHeaders)} is invoked without message headers or
141 	 * without a content type header.
142 	 * <p>By default, this returns the first element of the {@link #getSupportedMimeTypes()
143 	 * supportedMimeTypes}, if any. Can be overridden in sub-classes.
144 	 * @param payload the payload being converted to message
145 	 * @return the content type, or {@code null} if not known
146 	 */	
147 	protected MimeType getDefaultContentType(Object payload) {
148 		MimeType[] mimeTypes = getSupportedMimeTypes();
149 		return (mimeTypes.length >0 ? mimeTypes[0] : null);
150 	}
151 
152 	final Object fromMessage(MessageBase message, TypeInfo targetClass) {
153 		return fromMessage(message, targetClass, null);
154 	}
155 
156 	final Object fromMessage(MessageBase message, TypeInfo targetClass, TypeInfo conversionHint) {
157 		if (!canConvertFrom(message, targetClass)) {
158 			return null;
159 		}
160 		return convertFromInternal(message, targetClass, conversionHint);
161 	}
162 
163 	protected bool canConvertFrom(MessageBase message, TypeInfo targetClass) {
164 		bool r = (supports(targetClass) && supportsMimeType(message.getHeaders()));
165 		version(HUNT_DEBUG) tracef("checking message, target: %s, converter: %s, result: %s", 
166 			targetClass, TypeUtils.getSimpleName(typeid(this)), r);
167 		return r;
168 	}
169 
170 	override	
171 	final MessageBase toMessage(Object payload, MessageHeaders headers) {
172 		return toMessage(payload, headers, null);
173 	}
174 
175 	override
176 	final MessageBase toMessage(Object payload, MessageHeaders headers, TypeInfo conversionHint) {
177 		version(HUNT_DEBUG) trace("converting message...");
178 		if (!canConvertTo(payload, headers, conversionHint)) {
179 			version(HUNT_DEBUG) warning("A message can't be converted.");
180 			return null;
181 		}
182 		
183 		version(HUNT_DEBUG) {
184 			if(conversionHint !is null)
185 				tracef("conversionHint: %s", conversionHint);
186 		}
187 
188 		Object payloadToUse = convertToInternal(payload, headers, conversionHint);
189 		if (payloadToUse is null) {
190 			warningf("Can't convert payload: %s", typeid((cast(Object)payload)));
191 			return null;
192 		}
193 
194 		MimeType mimeType = getDefaultContentType(payloadToUse);
195 		if (headers !is null) {
196 			MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor!(MessageHeaderAccessor)(headers);
197 			if (accessor !is null && accessor.isMutable()) {
198 				if (mimeType !is null) {
199 					accessor.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, mimeType);
200 				}
201 
202 				if(conversionHint == typeid(TypeInfo_Class) || conversionHint == typeid(TypeInfo_Interface))
203 					return MessageHelper.createMessage!(Object)(payloadToUse, accessor.getMessageHeaders());
204 				else {
205 					INullable t = cast(INullable)payloadToUse;
206 					if(t is null)  {
207 						warningf("Can't handle: %s", typeid((cast(Object)payloadToUse)));
208 						return null;
209 					} else {
210 						// TODO: Tasks pending completion -@zxp at 11/12/2018, 3:02:11 PM
211 						// handle payload of byte[]
212 						return new GenericMessage!(string)(payloadToUse.toString(), 
213 							accessor.getMessageHeaders());
214 					}
215 				}
216 			}
217 		}
218 
219 		if(conversionHint == typeid(TypeInfo_Class) || conversionHint == typeid(TypeInfo_Interface)) {
220 			MessageBuilder!Object builder = MessageHelper.withPayload(payloadToUse);
221 			if (headers !is null) {
222 				builder.copyHeaders(headers);
223 			}
224 			if (mimeType !is null) {
225 				builder.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, mimeType);
226 			}
227 			return builder.build();
228 		} else {
229 			INullable t = cast(INullable)payloadToUse;
230 			if(t is null)  {
231 				warningf("Can't handle: %s", typeid((cast(Object)payloadToUse)));
232 				return null;
233 			} else {
234 				// TODO: Tasks pending completion -@zxp at 11/12/2018, 3:02:11 PM
235 				// handle payload of byte[]
236 				// MessageBuilder!(byte[]) builder = 
237 				// 	MessageHelper.withPayload!(byte[])(cast(byte[])payloadToUse.toString());
238 				MessageBuilder!(string) builder = 
239 					MessageHelper.withPayload!(string)(payloadToUse.toString());
240 				if (headers !is null) {
241 					builder.copyHeaders(headers);
242 				}
243 				if (mimeType !is null) {
244 					builder.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, mimeType);
245 				}
246 				return builder.build();
247 			}
248 		}
249 
250 	}
251 
252 	protected bool canConvertTo(Object payload, MessageHeaders headers, TypeInfo conversionHint) {
253 		bool r = false;
254 		if(conversionHint !is null) {
255 			r = (supports(conversionHint) && supportsMimeType(headers));
256 			version(HUNT_DEBUG) tracef("checking payload, type: %s, converter: %s, result: %s", 
257 				conversionHint, TypeUtils.getSimpleName(typeid(this)), r);
258 		} else {
259 			r = (supports(typeid(payload)) && supportsMimeType(headers));
260 			version(HUNT_DEBUG) tracef("checking payload, type: %s, converter: %s, result: %s", 
261 				typeid(payload), TypeUtils.getSimpleName(typeid(this)), r);
262 		}
263 		return r;
264 	}
265 
266 	protected bool supportsMimeType(MessageHeaders headers) {
267 		if (getSupportedMimeTypes().empty()) {
268 			return true;
269 		}
270 		MimeType mimeType = getMimeType(headers);
271 		if (mimeType is null) {
272 			return !isStrictContentTypeMatch();
273 		}
274 		MimeType mimeTypeBaseType = mimeType.getBaseType();
275 		string mimeTypeName = mimeTypeBaseType.asString();
276 
277 		foreach (MimeType current ; getSupportedMimeTypes()) {
278 			MimeType currentBaseType = current.getBaseType();
279 			if(currentBaseType.isSame(mimeTypeName))
280 				return true;
281 		}
282 		return false;
283 	}
284 
285 	
286 	protected MimeType getMimeType(MessageHeaders headers) {
287 		return (headers !is null && this.contentTypeResolver !is null ? 
288 			this.contentTypeResolver.resolve(headers) : null);
289 	}
290 
291 
292 	/**
293 	 * Whether the given class is supported by this converter.
294 	 * @param clazz the class to test for support
295 	 * @return {@code true} if supported; {@code false} otherwise
296 	 */
297 	protected abstract bool supports(TypeInfo typeInfo);
298 
299 	/**
300 	 * Convert the message payload from serialized form to an Object.
301 	 * @param message the input message
302 	 * @param targetClass the target class for the conversion
303 	 * @param conversionHint an extra object passed to the {@link MessageConverter},
304 	 * e.g. the associated {@code MethodParameter} (may be {@code null}}
305 	 * @return the result of the conversion, or {@code null} if the converter cannot
306 	 * perform the conversion
307 	 * @since 4.2
308 	 */
309 	
310 	protected Object convertFromInternal(
311 			MessageBase message, TypeInfo targetClass, TypeInfo conversionHint) {
312 		
313 		auto m = cast(GenericMessage!(byte[]))message;
314 		if(targetClass == typeid(string)) {
315 			return new Nullable!string(cast(string) m.getPayload());
316 		} else {
317             warningf("Can't handle message for type: %s", targetClass);
318         }
319 
320 		return null;
321 	}
322 
323 	/**
324 	 * Convert the payload object to serialized form.
325 	 * @param payload the Object to convert
326 	 * @param headers optional headers for the message (may be {@code null})
327 	 * @param conversionHint an extra object passed to the {@link MessageConverter},
328 	 * e.g. the associated {@code MethodParameter} (may be {@code null}}
329 	 * @return the resulting payload for the message, or {@code null} if the converter
330 	 * cannot perform the conversion
331 	 * @since 4.2
332 	 */
333 	
334 	protected Object convertToInternal(
335 			Object payload, MessageHeaders headers, TypeInfo conversionHint) {
336 				
337 		return payload;
338 	}
339 
340 }