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.AbstractMethodMessageHandler;
18 
19 import hunt.stomp.simp.SimpMessageHeaderAccessor;
20 import hunt.stomp.simp.SimpMessageType;
21 
22 // import hunt.framework.beans.factory.InitializingBean;
23 // import hunt.framework.context.ApplicationContext;
24 // import hunt.framework.websocket.WebSocketController;
25 // import hunt.framework.context.ApplicationContextAware;
26 // import hunt.framework.core.MethodIntrospector;
27 // import hunt.framework.core.MethodParameter;
28 
29 import hunt.stomp.Message;
30 import hunt.stomp.MessagingException;
31 import hunt.stomp.handler.DestinationPatternsMessageCondition;
32 // import hunt.stomp.handler.HandlerMethod;
33 // import hunt.stomp.handler.MessagingAdviceBean;
34 import hunt.stomp.support.GenericMessage;
35 import hunt.stomp.support.MessageBuilder;
36 import hunt.stomp.support.MessageHeaderAccessor;
37 
38 // import hunt.framework.util.ClassUtils;
39 // import hunt.framework.util.concurrent.ListenableFuture;
40 // import hunt.framework.util.concurrent.ListenableFutureCallback;
41 
42 import hunt.collection;
43 import hunt.Exceptions;
44 import hunt.Nullable;
45 import hunt.logging;
46 import hunt.util.TypeUtils;
47 
48 import std.array;
49 import std.conv;
50 import std.string;
51 
52 /**
53  * Abstract base class for HandlerMethod-based message handling. Provides most of
54  * the logic required to discover handler methods at startup, find a matching handler
55  * method at runtime for a given message and invoke it.
56  *
57  * <p>Also supports discovering and invoking exception handling methods to process
58  * exceptions raised during message handling.
59  *
60  * @author Rossen Stoyanchev
61  * @author Juergen Hoeller
62  * @since 4.0
63  * @param (T) the type of the Object that contains information mapping a
64  * {@link hunt.stomp.handler.HandlerMethod} to incoming messages
65  */
66 abstract class AbstractMethodMessageHandler(T)
67 		: MessageHandler { // , ApplicationContextAware, InitializingBean 
68 
69 	/**
70 	 * Bean name prefix for target beans behind scoped proxies. Used to exclude those
71 	 * targets from handler method detection, in favor of the corresponding proxies.
72 	 * <p>We're not checking the autowire-candidate status here, which is how the
73 	 * proxy target filtering problem is being handled at the autowiring level,
74 	 * since autowire-candidate may have been turned to {@code false} for other
75 	 * reasons, while still expecting the bean to be eligible for handler methods.
76 	 * <p>Originally defined in {@link hunt.framework.aop.scope.ScopedProxyUtils}
77 	 * but duplicated here to avoid a hard dependency on the spring-aop module.
78 	 */
79 	private enum string SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
80 
81 	private string[] destinationPrefixes;
82 
83 	// private List!(HandlerMethodArgumentResolver) customArgumentResolvers = new ArrayList<>(4);
84 
85 	// private List!(HandlerMethodReturnValueHandler) customReturnValueHandlers = new ArrayList<>(4);
86 
87 	// private HandlerMethodArgumentResolverComposite argumentResolvers =
88 	// 		new HandlerMethodArgumentResolverComposite();
89 
90 	// private HandlerMethodReturnValueHandlerComposite returnValueHandlers =
91 	// 		new HandlerMethodReturnValueHandlerComposite();
92 
93 	
94 	// private ApplicationContext applicationContext;
95 
96 	// private Map!(T, HandlerMethod) handlerMethods = new LinkedHashMap<>(64);
97 
98 	// private MultiValueMap!(string, T) destinationLookup = new LinkedMultiValueMap<>(64);
99 
100 	// private Map<Class<?>, AbstractExceptionHandlerMethodResolver> exceptionHandlerCache =
101 	// 		new ConcurrentHashMap<>(64);
102 
103 	// private Map!(MessagingAdviceBean, AbstractExceptionHandlerMethodResolver) exceptionHandlerAdviceCache =
104 	// 		new LinkedHashMap<>(64);
105 
106 	this() {
107 	}
108 
109 	/**
110 	 * When this property is configured only messages to destinations matching
111 	 * one of the configured prefixes are eligible for handling. When there is a
112 	 * match the prefix is removed and only the remaining part of the destination
113 	 * is used for method-mapping purposes.
114 	 * <p>By default, no prefixes are configured in which case all messages are
115 	 * eligible for handling.
116 	 */
117 	void setDestinationPrefixes(string[] prefixes) {
118 		this.destinationPrefixes = [];
119 		foreach (string prefix ; prefixes) {
120 			prefix = prefix.strip();
121 			this.destinationPrefixes ~= prefix;
122 		}
123 	}
124 
125 	/**
126 	 * Return the configured destination prefixes, if any.
127 	 */
128 	string[] getDestinationPrefixes() {
129 		return this.destinationPrefixes;
130 	}
131 
132 	/**
133 	 * Sets the list of custom {@code HandlerMethodArgumentResolver}s that will be used
134 	 * after resolvers for supported argument type.
135 	 */
136 	// void setCustomArgumentResolvers(List!(HandlerMethodArgumentResolver) customArgumentResolvers) {
137 	// 	this.customArgumentResolvers.clear();
138 	// 	if (customArgumentResolvers !is null) {
139 	// 		this.customArgumentResolvers.addAll(customArgumentResolvers);
140 	// 	}
141 	// }
142 
143 	/**
144 	 * Return the configured custom argument resolvers, if any.
145 	 */
146 	// List!(HandlerMethodArgumentResolver) getCustomArgumentResolvers() {
147 	// 	return this.customArgumentResolvers;
148 	// }
149 
150 	/**
151 	 * Set the list of custom {@code HandlerMethodReturnValueHandler}s that will be used
152 	 * after return value handlers for known types.
153 	 */
154 	// void setCustomReturnValueHandlers(List!(HandlerMethodReturnValueHandler) customReturnValueHandlers) {
155 	// 	this.customReturnValueHandlers.clear();
156 	// 	if (customReturnValueHandlers !is null) {
157 	// 		this.customReturnValueHandlers.addAll(customReturnValueHandlers);
158 	// 	}
159 	// }
160 
161 	/**
162 	 * Return the configured custom return value handlers, if any.
163 	 */
164 	// List!(HandlerMethodReturnValueHandler) getCustomReturnValueHandlers() {
165 	// 	return this.customReturnValueHandlers;
166 	// }
167 
168 	/**
169 	 * Configure the complete list of supported argument types, effectively overriding
170 	 * the ones configured by default. This is an advanced option; for most use cases
171 	 * it should be sufficient to use {@link #setCustomArgumentResolvers}.
172 	 */
173 	// void setArgumentResolvers(List!(HandlerMethodArgumentResolver) argumentResolvers) {
174 	// 	if (argumentResolvers is null) {
175 	// 		this.argumentResolvers.clear();
176 	// 		return;
177 	// 	}
178 	// 	this.argumentResolvers.addResolvers(argumentResolvers);
179 	// }
180 
181 	/**
182 	 * Return the complete list of argument resolvers.
183 	 */
184 	// List!(HandlerMethodArgumentResolver) getArgumentResolvers() {
185 	// 	return this.argumentResolvers.getResolvers();
186 	// }
187 
188 	/**
189 	 * Configure the complete list of supported return value types, effectively overriding
190 	 * the ones configured by default. This is an advanced option; for most use cases
191 	 * it should be sufficient to use {@link #setCustomReturnValueHandlers}.
192 	 */
193 	// void setReturnValueHandlers(List!(HandlerMethodReturnValueHandler) returnValueHandlers) {
194 	// 	if (returnValueHandlers is null) {
195 	// 		this.returnValueHandlers.clear();
196 	// 		return;
197 	// 	}
198 	// 	this.returnValueHandlers.addHandlers(returnValueHandlers);
199 	// }
200 
201 	/**
202 	 * Return the complete list of return value handlers.
203 	 */
204 	// List!(HandlerMethodReturnValueHandler) getReturnValueHandlers() {
205 	// 	return this.returnValueHandlers.getReturnValueHandlers();
206 	// }
207 
208 	// override
209 	// void setApplicationContext(ApplicationContext applicationContext) {
210 	// 	this.applicationContext = applicationContext;
211 	// }
212 
213 	
214 	// ApplicationContext getApplicationContext() {
215 	// 	return this.applicationContext;
216 	// }
217 
218 
219 	// override
220 	void afterPropertiesSet() {
221 		implementationMissing(false);
222 		// if (this.argumentResolvers.getResolvers().isEmpty()) {
223 		// 	this.argumentResolvers.addResolvers(initArgumentResolvers());
224 		// }
225 
226 		// if (this.returnValueHandlers.getReturnValueHandlers().isEmpty()) {
227 		// 	this.returnValueHandlers.addHandlers(initReturnValueHandlers());
228 		// }
229 		// Log returnValueLogger = getReturnValueHandlerLogger();
230 		// if (returnValueLogger !is null) {
231 		// 	this.returnValueHandlers.setLogger(returnValueLogger);
232 		// }
233 
234 		// this.handlerMethodLogger = getHandlerMethodLogger();
235 
236 		// ApplicationContext context = getApplicationContext();
237 		// if (context is null) {
238 		// 	return;
239 		// }
240 		// for (string beanName : context.getBeanNamesForType(Object.class)) {
241 		// 	if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
242 		// 		Class<?> beanType = null;
243 		// 		try {
244 		// 			beanType = context.getType(beanName);
245 		// 		}
246 		// 		catch (Throwable ex) {
247 		// 			// An unresolvable bean type, probably from a lazy bean - let's ignore it.
248 		// 			version(HUNT_DEBUG) {
249 		// 				trace("Could not resolve target class for bean with name '" ~ beanName ~ "'", ex);
250 		// 			}
251 		// 		}
252 		// 		if (beanType !is null && isHandler(beanType)) {
253 		// 			detectHandlerMethods(beanName);
254 		// 		}
255 		// 	}
256 		// }
257 	}
258 
259 	/**
260 	 * Return the list of argument resolvers to use. Invoked only if the resolvers
261 	 * have not already been set via {@link #setArgumentResolvers}.
262 	 * <p>Subclasses should also take into account custom argument types configured via
263 	 * {@link #setCustomArgumentResolvers}.
264 	 */
265 	// protected abstract List!(HandlerMethodArgumentResolver) initArgumentResolvers();
266 
267 	/**
268 	 * Return the list of return value handlers to use. Invoked only if the return
269 	 * value handlers have not already been set via {@link #setReturnValueHandlers}.
270 	 * <p>Subclasses should also take into account custom return value types configured
271 	 * via {@link #setCustomReturnValueHandlers}.
272 	 */
273 	// protected abstract List!(HandlerMethodReturnValueHandler) initReturnValueHandlers();
274 
275 
276 	/**
277 	 * Whether the given bean type should be introspected for messaging handling methods.
278 	 */
279 	// protected abstract  isHandler(Class<?> beanType);
280 
281 	/**
282 	 * Detect if the given handler has any methods that can handle messages and if
283 	 * so register it with the extracted mapping information.
284 	 * @param handler the handler to check, either an instance of a Spring bean name
285 	 */
286 	// protected final void detectHandlerMethods(Object handler) {
287 	// 	Class<?> handlerType;
288 	// 	if (handler instanceof string) {
289 	// 		ApplicationContext context = getApplicationContext();
290 	// 		assert(context !is null, "ApplicationContext is required for resolving handler bean names");
291 	// 		handlerType = context.getType((string) handler);
292 	// 	}
293 	// 	else {
294 	// 		handlerType = handler.getClass();
295 	// 	}
296 
297 	// 	if (handlerType !is null) {
298 	// 		Class<?> userType = ClassUtils.getUserClass(handlerType);
299 	// 		Map!(Method, T) methods = MethodIntrospector.selectMethods(userType,
300 	// 				(MethodIntrospector.MetadataLookup!(T)) method -> getMappingForMethod(method, userType));
301 	// 		version(HUNT_DEBUG) {
302 	// 			trace(methods.size() ~ " message handler methods found on " ~ userType ~ ": " ~ methods);
303 	// 		}
304 	// 		methods.forEach((key, value) -> registerHandlerMethod(handler, key, value));
305 	// 	}
306 	// }
307 
308 	/**
309 	 * Provide the mapping for a handler method.
310 	 * @param method the method to provide a mapping for
311 	 * @param handlerType the handler type, possibly a sub-type of the method's declaring class
312 	 * @return the mapping, or {@code null} if the method is not mapped
313 	 */
314 	
315 	// protected abstract T getMappingForMethod(Method method, Class<?> handlerType);
316 
317 	/**
318 	 * Register a handler method and its unique mapping.
319 	 * @param handler the bean name of the handler or the handler instance
320 	 * @param method the method to register
321 	 * @param mapping the mapping conditions associated with the handler method
322 	 * @throws IllegalStateException if another method was already registered
323 	 * under the same mapping
324 	 */
325 	// protected void registerHandlerMethod(Object handler, Method method, T mapping) {
326 	// 	assert(mapping, "Mapping must not be null");
327 	// 	HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
328 	// 	HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
329 
330 	// 	if (oldHandlerMethod !is null && !oldHandlerMethod.equals(newHandlerMethod)) {
331 	// 		throw new IllegalStateException("Ambiguous mapping found. Cannot map '" ~ newHandlerMethod.getBean() +
332 	// 				"' bean method \n" ~ newHandlerMethod ~ "\nto " ~ mapping ~ ": There is already '" ~
333 	// 				oldHandlerMethod.getBean() ~ "' bean method\n" ~ oldHandlerMethod ~ " mapped.");
334 	// 	}
335 
336 	// 	this.handlerMethods.put(mapping, newHandlerMethod);
337 	// 	version(HUNT_DEBUG) {
338 	// 		trace("Mapped \"" ~ mapping ~ "\" onto " ~ newHandlerMethod);
339 	// 	}
340 
341 	// 	for (string pattern : getDirectLookupDestinations(mapping)) {
342 	// 		this.destinationLookup.add(pattern, mapping);
343 	// 	}
344 	// }
345 
346 	/**
347 	 * Create a HandlerMethod instance from an Object handler that is either a handler
348 	 * instance or a string-based bean name.
349 	 */
350 	// protected HandlerMethod createHandlerMethod(Object handler, Method method) {
351 	// 	HandlerMethod handlerMethod;
352 	// 	if (handler instanceof string) {
353 	// 		ApplicationContext context = getApplicationContext();
354 	// 		assert(context !is null, "ApplicationContext is required for resolving handler bean names");
355 	// 		string beanName = (string) handler;
356 	// 		handlerMethod = new HandlerMethod(beanName, context.getAutowireCapableBeanFactory(), method);
357 	// 	}
358 	// 	else {
359 	// 		handlerMethod = new HandlerMethod(handler, method);
360 	// 	}
361 	// 	return handlerMethod;
362 	// }
363 
364 	/**
365 	 * Return destinations contained in the mapping that are not patterns and are
366 	 * therefore suitable for direct lookups.
367 	 */
368 	// protected abstract Set!(string) getDirectLookupDestinations(T mapping);
369 
370 	/**
371 	 * Return a logger to set on {@link HandlerMethodReturnValueHandlerComposite}.
372 	 * @since 5.1
373 	 */
374 	
375 	// protected Log getReturnValueHandlerLogger() {
376 	// 	return null;
377 	// }
378 
379 	/**
380 	 * Return a logger to set on {@link InvocableHandlerMethod}.
381 	 * @since 5.1
382 	 */
383 	
384 	// protected Log getHandlerMethodLogger() {
385 	// 	return null;
386 	// }
387 
388 	/**
389 	 * Subclasses can invoke this method to populate the MessagingAdviceBean cache
390 	 * (e.g. to support "global" {@code @MessageExceptionHandler}).
391 	 * @since 4.2
392 	 */
393 	// protected void registerExceptionHandlerAdvice(
394 	// 		MessagingAdviceBean bean, AbstractExceptionHandlerMethodResolver resolver) {
395 
396 	// 	this.exceptionHandlerAdviceCache.put(bean, resolver);
397 	// }
398 
399 	/**
400 	 * Return a map with all handler methods and their mappings.
401 	 */
402 	// Map!(T, HandlerMethod) getHandlerMethods() {
403 	// 	return Collections.unmodifiableMap(this.handlerMethods);
404 	// }
405 
406 
407 	override
408 	void handleMessage(MessageBase message) {
409 		version(HUNT_DEBUG) {
410 			trace("Processing " ~  typeid(cast(Object)message).name);
411 		}
412 
413 		string destination = getDestination(message);
414 		if (destination is null) {
415 			return;
416 		}
417 		string lookupDestination = getLookupDestination(destination);
418 		if (lookupDestination is null) {
419 			return;
420 		}
421 		MessageHeaderAccessor headerAccessor = MessageHeaderAccessor.getMutableAccessor(message);
422 		headerAccessor.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, lookupDestination);
423 		headerAccessor.setLeaveMutable(true);
424 
425 		if(message.payloadType == typeid(byte[])) {
426 			GenericMessage!(byte[]) gm = cast(GenericMessage!(byte[]))message;
427 			message = MessageHelper.createMessage(gm.getPayload(), headerAccessor.getMessageHeaders());
428 
429 			version(HUNT_DEBUG) {
430 				trace("Searching methods to handle " ~
431 						headerAccessor.getShortLogMessage(new Nullable!(byte[])(gm.getPayload())) ~
432 						", lookupDestination='" ~ lookupDestination ~ "'");
433 			}
434 		} else {
435 			warning("Can't handle message: ", message.payloadType.toString());
436 		}
437 
438 		handleMessageInternal(message, lookupDestination);
439 		headerAccessor.setImmutable();
440 	}
441 
442 	
443 	protected abstract string getDestination(MessageBase message);
444 
445 	/**
446 	 * Check whether the given destination (of an incoming message) matches to
447 	 * one of the configured destination prefixes and if so return the remaining
448 	 * portion of the destination after the matched prefix.
449 	 * <p>If there are no matching prefixes, return {@code null}.
450 	 * <p>If there are no destination prefixes, return the destination as is.
451 	 */
452 	protected string getLookupDestination(string destination) {
453 		if (destination.empty()) {
454 			return null;
455 		}
456 		if (this.destinationPrefixes.length >0) {
457 			return destination;
458 		}
459 
460 		foreach(string prefix; destinationPrefixes) {
461 			if (destination.startsWith(prefix)) {
462 				return destination[prefix.length .. $];
463 			}
464 		}
465 		return null;
466 	}
467 
468 	protected void handleReturnValue(Object returnValue, TypeInfo returnType, 
469 			MessageBase message, string[] destinations) ;
470 
471 	protected void handleMessageInternal(MessageBase message, string lookupDestination) {
472 		implementationMissing(false);
473 	}
474 	// protected void handleMessageInternal(MessageBase message, string lookupDestination) {
475 	// 	// FIXME: Needing refactor or cleanup -@zxp at 11/13/2018, 3:07:59 PM
476 	// 	// more tests
477 	// 	try {
478 	// 		WebSocketControllerHelper.invoke(lookupDestination, message, 
479 	// 			(Object returnValue, TypeInfo returnType, string[] destinations) {
480 	// 			handleReturnValue(returnValue, returnType, message, destinations);
481 	// 		});
482 	// 	}
483 	// 	catch (Exception ex) {
484 	// 		warning(ex.msg);
485 	// 		// processHandlerMethodException(handlerMethod, ex, message);
486 	// 	}
487 	// 	catch (Throwable ex) {
488 	// 		warning(ex.msg);
489 	// 		Exception handlingException = new MessageHandlingException(message, 
490 	// 			"Unexpected handler method invocation error", ex);
491 	// 	}
492 	// }
493 
494 	// private void addMatchesToCollection(Collection!(T) mappingsToCheck, MessageBase message, List!(Match) matches) {
495 	// 	foreach (T mapping ; mappingsToCheck) {
496 	// 		T match = getMatchingMapping(mapping, message);
497 	// 		if (match !is null) {
498 	// 			matches.add(new Match(match, this.handlerMethods.get(mapping)));
499 	// 		}
500 	// 	}
501 	// }
502 
503 	/**
504 	 * Check if a mapping matches the current message and return a possibly
505 	 * new mapping with conditions relevant to the current request.
506 	 * @param mapping the mapping to get a match for
507 	 * @param message the message being handled
508 	 * @return the match or {@code null} if there is no match
509 	 */
510 	
511 	// protected abstract T getMatchingMapping(T mapping, MessageBase message);
512 
513 	// protected void handleNoMatch(Set!(T) ts, string lookupDestination, MessageBase message) {
514 	// 	trace("No matching message handler methods.");
515 	// }
516 
517 	/**
518 	 * Return a comparator for sorting matching mappings.
519 	 * The returned comparator should sort 'better' matches higher.
520 	 * @param message the current Message
521 	 * @return the comparator, never {@code null}
522 	 */
523 	// protected abstract Comparator!(T) getMappingComparator(MessageBase message);
524 
525 	// protected void handleMatch(T mapping, HandlerMethod handlerMethod, string lookupDestination, MessageBase message) {
526 	// 	version(HUNT_DEBUG) {
527 	// 		trace("Invoking " ~ handlerMethod.getShortLogMessage());
528 	// 	}
529 	// 	handlerMethod = handlerMethod.createWithResolvedBean();
530 	// 	InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
531 	// 	if (this.handlerMethodLogger !is null) {
532 	// 		invocable.setLogger(this.handlerMethodLogger);
533 	// 	}
534 	// 	invocable.setMessageMethodArgumentResolvers(this.argumentResolvers);
535 	// 	try {
536 	// 		Object returnValue = invocable.invoke(message);
537 	// 		MethodParameter returnType = handlerMethod.getReturnType();
538 	// 		if (void.class == returnType.getParameterType()) {
539 	// 			return;
540 	// 		}
541 	// 		if (returnValue !is null && this.returnValueHandlers.isAsyncReturnValue(returnValue, returnType)) {
542 	// 			ListenableFuture<?> future = this.returnValueHandlers.toListenableFuture(returnValue, returnType);
543 	// 			if (future !is null) {
544 	// 				future.addCallback(new ReturnValueListenableFutureCallback(invocable, message));
545 	// 			}
546 	// 		}
547 	// 		else {
548 	// 			this.returnValueHandlers.handleReturnValue(returnValue, returnType, message);
549 	// 		}
550 	// 	}
551 	// 	catch (Exception ex) {
552 	// 		processHandlerMethodException(handlerMethod, ex, message);
553 	// 	}
554 	// 	catch (Throwable ex) {
555 	// 		Exception handlingException =
556 	// 				new MessageHandlingException(message, "Unexpected handler method invocation error", ex);
557 	// 		processHandlerMethodException(handlerMethod, handlingException, message);
558 	// 	}
559 	// }
560 
561 	// protected void processHandlerMethodException(HandlerMethod handlerMethod, Exception exception, MessageBase message) {
562 	// 	InvocableHandlerMethod invocable = getExceptionHandlerMethod(handlerMethod, exception);
563 	// 	if (invocable is null) {
564 	// 		error("Unhandled exception from message handler method", exception);
565 	// 		return;
566 	// 	}
567 	// 	invocable.setMessageMethodArgumentResolvers(this.argumentResolvers);
568 	// 	version(HUNT_DEBUG) {
569 	// 		trace("Invoking " ~ invocable.getShortLogMessage());
570 	// 	}
571 	// 	try {
572 	// 		Throwable cause = exception.getCause();
573 	// 		Object returnValue = (cause !is null ?
574 	// 				invocable.invoke(message, exception, cause, handlerMethod) :
575 	// 				invocable.invoke(message, exception, handlerMethod));
576 	// 		MethodParameter returnType = invocable.getReturnType();
577 	// 		if (void.class == returnType.getParameterType()) {
578 	// 			return;
579 	// 		}
580 	// 		this.returnValueHandlers.handleReturnValue(returnValue, returnType, message);
581 	// 	}
582 	// 	catch (Throwable ex2) {
583 	// 		error("Error while processing handler method exception", ex2);
584 	// 	}
585 	// }
586 
587 	/**
588 	 * Find an {@code @MessageExceptionHandler} method for the given exception.
589 	 * The default implementation searches methods in the class hierarchy of the
590 	 * HandlerMethod first and if not found, it continues searching for additional
591 	 * {@code @MessageExceptionHandler} methods among the configured
592 	 * {@linkplain hunt.stomp.handler.MessagingAdviceBean
593 	 * MessagingAdviceBean}, if any.
594 	 * @param handlerMethod the method where the exception was raised
595 	 * @param exception the raised exception
596 	 * @return a method to handle the exception, or {@code null}
597 	 * @since 4.2
598 	 */
599 	
600 	// protected InvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
601 	// 	version(HUNT_DEBUG) {
602 	// 		trace("Searching methods to handle " ~ exception.TypeUtils.getSimpleName(typeid(this)));
603 	// 	}
604 	// 	Class<?> beanType = handlerMethod.getBeanType();
605 	// 	AbstractExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(beanType);
606 	// 	if (resolver is null) {
607 	// 		resolver = createExceptionHandlerMethodResolverFor(beanType);
608 	// 		this.exceptionHandlerCache.put(beanType, resolver);
609 	// 	}
610 	// 	Method method = resolver.resolveMethod(exception);
611 	// 	if (method !is null) {
612 	// 		return new InvocableHandlerMethod(handlerMethod.getBean(), method);
613 	// 	}
614 	// 	for (MessagingAdviceBean advice : this.exceptionHandlerAdviceCache.keySet()) {
615 	// 		if (advice.isApplicableToBeanType(beanType)) {
616 	// 			resolver = this.exceptionHandlerAdviceCache.get(advice);
617 	// 			method = resolver.resolveMethod(exception);
618 	// 			if (method !is null) {
619 	// 				return new InvocableHandlerMethod(advice.resolveBean(), method);
620 	// 			}
621 	// 		}
622 	// 	}
623 	// 	return null;
624 	// }
625 
626 	// protected abstract AbstractExceptionHandlerMethodResolver createExceptionHandlerMethodResolverFor(
627 	// 		Class<?> beanType);
628 
629 	override int opCmp(MessageHandler o) {
630 		implementationMissing(false);
631 		return 0;
632 	}
633 
634 	override
635 	string toString() {
636 		return TypeUtils.getSimpleName(typeid(this)) ~ "[prefixes=" ~ getDestinationPrefixes().to!string() ~ "]";
637 	}
638 
639 
640 	/**
641 	 * A thin wrapper around a matched HandlerMethod and its matched mapping for
642 	 * the purpose of comparing the best match with a comparator in the context
643 	 * of a message.
644 	 */
645 	// private class Match {
646 
647 	// 	private T mapping;
648 
649 	// 	private HandlerMethod handlerMethod;
650 
651 	// 	this(T mapping, HandlerMethod handlerMethod) {
652 	// 		this.mapping = mapping;
653 	// 		this.handlerMethod = handlerMethod;
654 	// 	}
655 
656 	// 	override
657 	// 	string toString() {
658 	// 		return this.mapping.toString();
659 	// 	}
660 	// }
661 
662 
663 	// private class MatchComparator : Comparator!(Match) {
664 
665 	// 	private Comparator!(T) comparator;
666 
667 	// 	this(Comparator!(T) comparator) {
668 	// 		this.comparator = comparator;
669 	// 	}
670 
671 	// 	override
672 	// 	int compare(Match match1, Match match2) {
673 	// 		return this.comparator.compare(match1.mapping, match2.mapping);
674 	// 	}
675 	// }
676 
677 
678 	// private class ReturnValueListenableFutureCallback : ListenableFutureCallback!(Object) {
679 
680 	// 	private InvocableHandlerMethod handlerMethod;
681 
682 	// 	private MessageBase message;
683 
684 	// 	this(InvocableHandlerMethod handlerMethod, MessageBase message) {
685 	// 		this.handlerMethod = handlerMethod;
686 	// 		this.message = message;
687 	// 	}
688 
689 	// 	override
690 	// 	void onSuccess(Object result) {
691 	// 		try {
692 	// 			MethodParameter returnType = this.handlerMethod.getAsyncReturnValueType(result);
693 	// 			returnValueHandlers.handleReturnValue(result, returnType, this.message);
694 	// 		}
695 	// 		catch (Throwable ex) {
696 	// 			handleFailure(ex);
697 	// 		}
698 	// 	}
699 
700 	// 	override
701 	// 	void onFailure(Throwable ex) {
702 	// 		handleFailure(ex);
703 	// 	}
704 
705 	// 	private void handleFailure(Throwable ex) {
706 	// 		Exception cause = (ex instanceof Exception ? (Exception) ex : new IllegalStateException(ex));
707 	// 		processHandlerMethodException(this.handlerMethod, cause, this.message);
708 	// 	}
709 	// }
710 
711 }