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.simp.annotation.SimpAnnotationMethodMessageHandler; 18 19 import hunt.stomp.simp.annotation.AbstractMethodMessageHandler; 20 21 // import hunt.framework.beans.factory.config.ConfigurableBeanFactory; 22 // import hunt.framework.context.ApplicationContext; 23 import hunt.util.Lifecycle; 24 // import hunt.framework.context.ConfigurableApplicationContext; 25 // import hunt.framework.context.EmbeddedValueResolverAware; 26 // import hunt.framework.core.annotation.AnnotatedElementUtils; 27 // import hunt.framework.core.convert.ConversionService; 28 // import hunt.framework.format.support.DefaultFormattingConversionService; 29 30 import hunt.stomp.Message; 31 import hunt.stomp.MessageChannel; 32 import hunt.stomp.MessageHeaders; 33 34 import hunt.stomp.converter.AbstractMessageConverter; 35 import hunt.stomp.converter.ByteArrayMessageConverter; 36 import hunt.stomp.converter.CompositeMessageConverter; 37 import hunt.stomp.converter.MessageConverter; 38 import hunt.stomp.converter.StringMessageConverter; 39 import hunt.stomp.core.AbstractMessageSendingTemplate; 40 // import hunt.stomp.handler.DestinationPatternsMessageCondition; 41 // import hunt.stomp.handler.HandlerMethod; 42 // import hunt.stomp.handler.annotation.MessageMapping; 43 // import hunt.stomp.handler.annotation.support.AnnotationExceptionHandlerMethodResolver; 44 // import hunt.stomp.handler.annotation.support.DestinationVariableMethodArgumentResolver; 45 // import hunt.stomp.handler.annotation.support.HeaderMethodArgumentResolver; 46 // import hunt.stomp.handler.annotation.support.HeadersMethodArgumentResolver; 47 // import hunt.stomp.handler.annotation.support.MessageMethodArgumentResolver; 48 // import hunt.stomp.handler.annotation.support.PayloadArgumentResolver; 49 // import hunt.stomp.handler.invocation.AbstractExceptionHandlerMethodResolver; 50 // import hunt.stomp.handler.invocation.AbstractMethodMessageHandler; 51 // import hunt.stomp.handler.invocation.CompletableFutureReturnValueHandler; 52 // import hunt.stomp.handler.invocation.HandlerMethodArgumentResolver; 53 // import hunt.stomp.handler.invocation.HandlerMethodReturnValueHandler; 54 // import hunt.stomp.handler.invocation.HandlerMethodReturnValueHandlerComposite; 55 // import hunt.stomp.handler.invocation.ListenableFutureReturnValueHandler; 56 // import hunt.stomp.handler.invocation.ReactiveReturnValueHandler; 57 import hunt.stomp.simp.SimpAttributesContextHolder; 58 // 59 import hunt.stomp.simp.SimpMessageHeaderAccessor; 60 import hunt.stomp.simp.SimpMessageMappingInfo; 61 import hunt.stomp.simp.SimpMessageSendingOperations; 62 import hunt.stomp.simp.SimpMessageType; 63 import hunt.stomp.simp.SimpMessageTypeMessageCondition; 64 import hunt.stomp.simp.SimpMessagingTemplate; 65 // import hunt.stomp.simp.annotation.SubscribeMapping; 66 import hunt.stomp.support.MessageHeaderAccessor; 67 68 // import hunt.framework.stereotype.Controller; 69 // import hunt.framework.util.AntPathMatcher; 70 71 // import hunt.framework.util.CollectionUtils; 72 import hunt.text.PathMatcher; 73 // import hunt.framework.util.StringValueResolver; 74 // import hunt.framework.validation.Validator; 75 76 import hunt.collection; 77 import hunt.util.Common; 78 import hunt.Exceptions; 79 import hunt.logging; 80 81 import std.string; 82 83 /** 84 * A handler for messages delegating to {@link MessageMapping @MessageMapping} 85 * and {@link SubscribeMapping @SubscribeMapping} annotated methods. 86 * 87 * <p>Supports Ant-style path patterns with template variables. 88 * 89 * @author Rossen Stoyanchev 90 * @author Brian Clozel 91 * @author Juergen Hoeller 92 * @since 4.0 93 */ 94 class SimpAnnotationMethodMessageHandler : 95 AbstractMethodMessageHandler!(SimpMessageMappingInfo), SmartLifecycle { 96 // EmbeddedValueResolverAware 97 private SubscribableChannel clientInboundChannel; 98 99 private SimpMessageSendingOperations clientMessagingTemplate; 100 101 private SimpMessageSendingOperations brokerTemplate; 102 103 private MessageConverter messageConverter; 104 105 // private ConversionService conversionService = new DefaultFormattingConversionService(); 106 107 private PathMatcher pathMatcher; 108 109 private bool slashPathSeparator = true; 110 111 // private Validator validator; 112 113 // private StringValueResolver valueResolver; 114 115 private MessageHeaderInitializer headerInitializer; 116 117 private bool running = false; 118 119 private Object lifecycleMonitor; 120 121 122 /** 123 * Create an instance of SimpAnnotationMethodMessageHandler with the given 124 * message channels and broker messaging template. 125 * @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients) 126 * @param clientOutboundChannel the channel for messages to clients (e.g. WebSocket clients) 127 * @param brokerTemplate a messaging template to send application messages to the broker 128 */ 129 this(SubscribableChannel clientInboundChannel, 130 MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate) { 131 132 assert(clientInboundChannel, "clientInboundChannel must not be null"); 133 assert(clientOutboundChannel, "clientOutboundChannel must not be null"); 134 // assert(brokerTemplate, "brokerTemplate must not be null"); 135 136 pathMatcher = new AntPathMatcher(); 137 lifecycleMonitor = new Object(); 138 this.clientInboundChannel = clientInboundChannel; 139 this.clientMessagingTemplate = new SimpMessagingTemplate(clientOutboundChannel); 140 this.brokerTemplate = brokerTemplate; 141 142 MessageConverter[] converters = [ 143 new StringMessageConverter(), 144 new ByteArrayMessageConverter() 145 ]; 146 147 this.messageConverter = new CompositeMessageConverter(converters); 148 } 149 150 151 /** 152 * {@inheritDoc} 153 * <p>Destination prefixes are expected to be slash-separated Strings and 154 * therefore a slash is automatically appended where missing to ensure a 155 * proper prefix-based match (i.e. matching complete segments). 156 * <p>Note however that the remaining portion of a destination after the 157 * prefix may use a different separator (e.g. commonly "." in messaging) 158 * depending on the configured {@code PathMatcher}. 159 */ 160 override 161 void setDestinationPrefixes(string[] prefixes) { 162 super.setDestinationPrefixes(appendSlashes(prefixes)); 163 } 164 165 private static string[] appendSlashes(string[] prefixes) { 166 if (prefixes.length == 0) 167 return prefixes; 168 string[] result; 169 foreach (string prefix ; prefixes) { 170 if (!prefix.endsWith("/")) { 171 prefix = prefix ~ "/"; 172 } 173 result ~= prefix; 174 } 175 return result; 176 } 177 178 /** 179 * Configure a {@link MessageConverter} to use to convert the payload of a message from 180 * its serialized form with a specific MIME type to an Object matching the target method 181 * parameter. The converter is also used when sending a message to the message broker. 182 * @see CompositeMessageConverter 183 */ 184 void setMessageConverter(MessageConverter converter) { 185 this.messageConverter = converter; 186 (cast(AbstractMessageSendingTemplate!string) this.clientMessagingTemplate).setMessageConverter(converter); 187 } 188 189 /** 190 * Return the configured {@link MessageConverter}. 191 */ 192 MessageConverter getMessageConverter() { 193 return this.messageConverter; 194 } 195 196 /** 197 * Configure a {@link ConversionService} to use when resolving method arguments, 198 * for example message header values. 199 * <p>By default, {@link DefaultFormattingConversionService} is used. 200 */ 201 // void setConversionService(ConversionService conversionService) { 202 // this.conversionService = conversionService; 203 // } 204 205 /** 206 * Return the configured {@link ConversionService}. 207 */ 208 // ConversionService getConversionService() { 209 // return this.conversionService; 210 // } 211 212 /** 213 * Set the PathMatcher implementation to use for matching destinations 214 * against configured destination patterns. 215 * <p>By default, {@link AntPathMatcher} is used. 216 */ 217 void setPathMatcher(PathMatcher pathMatcher) { 218 assert(pathMatcher, "PathMatcher must not be null"); 219 this.pathMatcher = pathMatcher; 220 this.slashPathSeparator = this.pathMatcher.combine("a", "a") == ("a/a"); 221 } 222 223 /** 224 * Return the PathMatcher implementation to use for matching destinations. 225 */ 226 PathMatcher getPathMatcher() { 227 return this.pathMatcher; 228 } 229 230 /** 231 * Return the configured Validator instance. 232 */ 233 234 // Validator getValidator() { 235 // return this.validator; 236 // } 237 238 /** 239 * Set the Validator instance used for validating {@code @Payload} arguments. 240 * @see hunt.framework.validation.annotation.Validated 241 * @see PayloadArgumentResolver 242 */ 243 // void setValidator(Validator validator) { 244 // this.validator = validator; 245 // } 246 247 // override 248 // void setEmbeddedValueResolver(StringValueResolver resolver) { 249 // this.valueResolver = resolver; 250 // } 251 252 /** 253 * Configure a {@link MessageHeaderInitializer} to pass on to 254 * {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers} 255 * that send messages from controller return values. 256 * <p>By default, this property is not set. 257 */ 258 void setHeaderInitializer(MessageHeaderInitializer headerInitializer) { 259 this.headerInitializer = headerInitializer; 260 } 261 262 /** 263 * Return the configured header initializer. 264 */ 265 266 MessageHeaderInitializer getHeaderInitializer() { 267 return this.headerInitializer; 268 } 269 270 bool isAutoStartup() { 271 return true; 272 } 273 274 int getPhase() { 275 return int.max; 276 } 277 278 // override 279 final void start() { 280 synchronized (this.lifecycleMonitor) { 281 this.clientInboundChannel.subscribe(this); 282 this.running = true; 283 } 284 } 285 286 // override 287 final void stop() { 288 synchronized (this.lifecycleMonitor) { 289 this.running = false; 290 this.clientInboundChannel.unsubscribe(this); 291 } 292 } 293 294 // override 295 final void stop(Runnable callback) { 296 synchronized (this.lifecycleMonitor) { 297 stop(); 298 callback.run(); 299 } 300 } 301 302 // override 303 bool isRunning() { 304 return this.running; 305 } 306 307 // protected List!(HandlerMethodArgumentResolver) initArgumentResolvers() { 308 // ApplicationContext context = getApplicationContext(); 309 // ConfigurableBeanFactory beanFactory = (context instanceof ConfigurableApplicationContext ? 310 // ((ConfigurableApplicationContext) context).getBeanFactory() : null); 311 312 // List!(HandlerMethodArgumentResolver) resolvers = new ArrayList<>(); 313 314 // // Annotation-based argument resolution 315 // resolvers.add(new HeaderMethodArgumentResolver(this.conversionService, beanFactory)); 316 // resolvers.add(new HeadersMethodArgumentResolver()); 317 // resolvers.add(new DestinationVariableMethodArgumentResolver(this.conversionService)); 318 319 // // Type-based argument resolution 320 // resolvers.add(new PrincipalMethodArgumentResolver()); 321 // resolvers.add(new MessageMethodArgumentResolver(this.messageConverter)); 322 323 // resolvers.addAll(getCustomArgumentResolvers()); 324 // resolvers.add(new PayloadArgumentResolver(this.messageConverter, this.validator)); 325 326 // return resolvers; 327 // } 328 329 // override 330 // protected List!(HandlerMethodReturnValueHandler) initReturnValueHandlers() { 331 // List!(HandlerMethodReturnValueHandler) handlers = new ArrayList<>(); 332 333 // // Single-purpose return value types 334 335 // handlers.add(new ListenableFutureReturnValueHandler()); 336 // handlers.add(new CompletableFutureReturnValueHandler()); 337 // handlers.add(new ReactiveReturnValueHandler()); 338 339 // // Annotation-based return value types 340 341 // SendToMethodReturnValueHandler sendToHandler = 342 // new SendToMethodReturnValueHandler(this.brokerTemplate, true); 343 // sendToHandler.setHeaderInitializer(this.headerInitializer); 344 // handlers.add(sendToHandler); 345 346 // SubscriptionMethodReturnValueHandler subscriptionHandler = 347 // new SubscriptionMethodReturnValueHandler(this.clientMessagingTemplate); 348 // subscriptionHandler.setHeaderInitializer(this.headerInitializer); 349 // handlers.add(subscriptionHandler); 350 351 // // Custom return value types 352 353 // handlers.addAll(getCustomReturnValueHandlers()); 354 355 // // Catch-all 356 357 // sendToHandler = new SendToMethodReturnValueHandler(this.brokerTemplate, false); 358 // sendToHandler.setHeaderInitializer(this.headerInitializer); 359 // handlers.add(sendToHandler); 360 361 // return handlers; 362 // } 363 364 // override 365 // protected bool isHandler(Class<?> beanType) { 366 // return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class); 367 // } 368 369 // override 370 371 // protected SimpMessageMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { 372 // MessageMapping messageAnn = AnnotatedElementUtils.findMergedAnnotation(method, MessageMapping.class); 373 // if (messageAnn !is null) { 374 // MessageMapping typeAnn = AnnotatedElementUtils.findMergedAnnotation(handlerType, MessageMapping.class); 375 // // Only actually register it if there are destinations specified; 376 // // otherwise @MessageMapping is just being used as a (meta-annotation) marker. 377 // if (messageAnn.value().length > 0 || (typeAnn !is null && typeAnn.value().length > 0)) { 378 // SimpMessageMappingInfo result = createMessageMappingCondition(messageAnn.value()); 379 // if (typeAnn !is null) { 380 // result = createMessageMappingCondition(typeAnn.value()).combine(result); 381 // } 382 // return result; 383 // } 384 // } 385 386 // SubscribeMapping subscribeAnn = AnnotatedElementUtils.findMergedAnnotation(method, SubscribeMapping.class); 387 // if (subscribeAnn !is null) { 388 // MessageMapping typeAnn = AnnotatedElementUtils.findMergedAnnotation(handlerType, MessageMapping.class); 389 // // Only actually register it if there are destinations specified; 390 // // otherwise @SubscribeMapping is just being used as a (meta-annotation) marker. 391 // if (subscribeAnn.value().length > 0 || (typeAnn !is null && typeAnn.value().length > 0)) { 392 // SimpMessageMappingInfo result = createSubscribeMappingCondition(subscribeAnn.value()); 393 // if (typeAnn !is null) { 394 // result = createMessageMappingCondition(typeAnn.value()).combine(result); 395 // } 396 // return result; 397 // } 398 // } 399 400 // return null; 401 // } 402 403 // private SimpMessageMappingInfo createMessageMappingCondition(string[] destinations) { 404 // string[] resolvedDestinations = resolveEmbeddedValuesInDestinations(destinations); 405 // return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE, 406 // new DestinationPatternsMessageCondition(resolvedDestinations, this.pathMatcher)); 407 // } 408 409 // private SimpMessageMappingInfo createSubscribeMappingCondition(string[] destinations) { 410 // string[] resolvedDestinations = resolveEmbeddedValuesInDestinations(destinations); 411 // return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.SUBSCRIBE, 412 // new DestinationPatternsMessageCondition(resolvedDestinations, this.pathMatcher)); 413 // } 414 415 /** 416 * Resolve placeholder values in the given array of destinations. 417 * @return a new array with updated destinations 418 * @since 4.2 419 */ 420 // protected string[] resolveEmbeddedValuesInDestinations(string[] destinations) { 421 // if (this.valueResolver is null) { 422 // return destinations; 423 // } 424 // string[] result = new string[destinations.length]; 425 // for (int i = 0; i < destinations.length; i++) { 426 // result[i] = this.valueResolver.resolveStringValue(destinations[i]); 427 // } 428 // return result; 429 // } 430 431 // override 432 // protected Set!(string) getDirectLookupDestinations(SimpMessageMappingInfo mapping) { 433 // Set!(string) result = new LinkedHashSet<>(); 434 // for (string pattern : mapping.getDestinationConditions().getPatterns()) { 435 // if (!this.pathMatcher.isPattern(pattern)) { 436 // result.add(pattern); 437 // } 438 // } 439 // return result; 440 // } 441 442 override 443 protected string getDestination(MessageBase message) { 444 return SimpMessageHeaderAccessor.getDestination(message.getHeaders()); 445 } 446 447 override 448 protected string getLookupDestination(string destination) { 449 if (destination is null) { 450 return null; 451 } 452 string[] prefixes = getDestinationPrefixes(); 453 if (prefixes.length == 0) { 454 return destination; 455 } 456 foreach (string prefix ; prefixes) { 457 if (destination.startsWith(prefix)) { 458 size_t pos = prefix.length; 459 if (this.slashPathSeparator) 460 pos = pos - 1; 461 return destination[pos .. $]; 462 } 463 } 464 return null; 465 } 466 467 override protected void handleReturnValue(Object returnValue, TypeInfo returnType, 468 MessageBase message, string[] destinations) { 469 MessageHeaders headers = message.getHeaders(); 470 string sessionId = SimpMessageHeaderAccessor.getSessionId(headers); 471 version(HUNT_DEBUG) tracef("raw return type: %s", returnType); 472 MessageHeaders hs = createHeaders(sessionId, returnType); 473 foreach (string destination ; destinations) { 474 version(HUNT_DEBUG) trace("handling destination: ", destination); 475 this.brokerTemplate.convertAndSend(destination, returnValue, hs); 476 } 477 } 478 479 private MessageHeaders createHeaders(string sessionId, TypeInfo returnType) { 480 SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); 481 if (getHeaderInitializer() !is null) { 482 getHeaderInitializer().initHeaders(headerAccessor); 483 } 484 485 if (!sessionId.empty) { 486 headerAccessor.setSessionId(sessionId); 487 } 488 headerAccessor.setHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER, returnType); 489 headerAccessor.setLeaveMutable(true); 490 return headerAccessor.getMessageHeaders(); 491 } 492 493 // override 494 // protected SimpMessageMappingInfo getMatchingMapping(SimpMessageMappingInfo mapping, MessageBase message) { 495 // return mapping.getMatchingCondition(message); 496 497 // } 498 499 // override 500 // protected Comparator!(SimpMessageMappingInfo) getMappingComparator(final MessageBase message) { 501 // return (info1, info2) -> info1.compareTo(info2, message); 502 // } 503 504 // override 505 // protected void handleMatch(SimpMessageMappingInfo mapping, HandlerMethod handlerMethod, 506 // string lookupDestination, MessageBase message) { 507 508 // Set!(string) patterns = mapping.getDestinationConditions().getPatterns(); 509 // if (!CollectionUtils.isEmpty(patterns)) { 510 // string pattern = patterns.iterator().next(); 511 // Map!(string, string) vars = getPathMatcher().extractUriTemplateVariables(pattern, lookupDestination); 512 // if (!CollectionUtils.isEmpty(vars)) { 513 // MessageHeaderAccessor mha = MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class); 514 // assert(mha !is null && mha.isMutable(), "Mutable MessageHeaderAccessor required"); 515 // mha.setHeader(DestinationVariableMethodArgumentResolver.DESTINATION_TEMPLATE_VARIABLES_HEADER, vars); 516 // } 517 // } 518 519 // try { 520 // SimpAttributesContextHolder.setAttributesFromMessage(message); 521 // super.handleMatch(mapping, handlerMethod, lookupDestination, message); 522 // } 523 // finally { 524 // SimpAttributesContextHolder.resetAttributes(); 525 // } 526 // } 527 528 // override 529 // protected AbstractExceptionHandlerMethodResolver createExceptionHandlerMethodResolverFor(Class<?> beanType) { 530 // return new AnnotationExceptionHandlerMethodResolver(beanType); 531 // } 532 533 }