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 }