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 }