001 /*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2008-2017 the original author or authors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package org.codehaus.griffon.runtime.core.event;
019
020 import griffon.core.CallableWithArgs;
021 import griffon.core.ExceptionHandler;
022 import griffon.core.ExecutorServiceManager;
023 import griffon.core.RunnableWithArgs;
024 import griffon.core.event.Event;
025 import griffon.core.event.EventRouter;
026 import griffon.util.GriffonClassUtils;
027 import griffon.util.MethodDescriptor;
028 import griffon.util.MethodUtils;
029 import org.slf4j.Logger;
030 import org.slf4j.LoggerFactory;
031
032 import javax.annotation.Nonnull;
033 import javax.annotation.Nullable;
034 import javax.inject.Inject;
035 import java.lang.reflect.Method;
036 import java.util.ArrayList;
037 import java.util.Collection;
038 import java.util.HashSet;
039 import java.util.LinkedHashMap;
040 import java.util.List;
041 import java.util.Map;
042 import java.util.Set;
043 import java.util.concurrent.ConcurrentHashMap;
044 import java.util.concurrent.ExecutorService;
045 import java.util.concurrent.Executors;
046 import java.util.concurrent.ThreadFactory;
047 import java.util.concurrent.atomic.AtomicInteger;
048
049 import static griffon.util.GriffonClassUtils.convertToTypeArray;
050 import static griffon.util.GriffonNameUtils.capitalize;
051 import static griffon.util.GriffonNameUtils.requireNonBlank;
052 import static java.util.Arrays.asList;
053 import static java.util.Collections.EMPTY_LIST;
054 import static java.util.Collections.unmodifiableCollection;
055 import static java.util.Objects.requireNonNull;
056
057 /**
058 * @author Andres Almiray
059 */
060 public abstract class AbstractEventRouter implements EventRouter {
061 protected static final Object[] LOCK = new Object[0];
062 private static final String ERROR_EVENT_NAME_BLANK = "Argument 'eventName' must not be blank";
063 private static final String ERROR_EVENT_HANDLER_BLANK = "Argument 'eventHandler' must not be blank";
064 private static final String ERROR_MODE_BLANK = "Argument 'mode' must not be blank";
065 private static final String ERROR_LISTENER_NULL = "Argument 'listener' must not be null";
066 private static final String ERROR_EVENT_CLASS_NULL = "Argument 'eventClass' must not be null";
067 private static final String ERROR_EVENT_NULL = "Argument 'event' must not be null";
068 private static final String ERROR_CALLABLE_NULL = "Argument 'callable' must not be null";
069 private static final String ERROR_RUNNABLE_NULL = "Argument 'runnable' must not be null";
070 private static final String ERROR_PARAMS_NULL = "Argument 'params' must not be null";
071 private static final String ERROR_INSTANCE_NULL = "Argument 'instance' must not be null";
072 private static final String ERROR_OWNER_NULL = "Argument 'owner' must not be null";
073 private static final Logger LOG = LoggerFactory.getLogger(AbstractEventRouter.class);
074 protected final Map<String, List<Object>> instanceListeners = new ConcurrentHashMap<>();
075 protected final Map<String, List<Object>> functionalListeners = new ConcurrentHashMap<>();
076 private final MethodCache methodCache = new MethodCache();
077 private boolean enabled = true;
078
079 protected static final AtomicInteger EVENT_ROUTER_ID = new AtomicInteger(1);
080
081 protected ExecutorServiceManager executorServiceManager;
082 protected final ExecutorService executorService;
083 protected final int eventRouterId;
084
085 @Inject
086 private ExceptionHandler exceptionHandler;
087
088 public AbstractEventRouter() {
089 eventRouterId = EVENT_ROUTER_ID.getAndIncrement();
090 executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new DefaultThreadFactory(eventRouterId));
091 }
092
093 @Inject
094 public void setExecutorServiceManager(@Nonnull ExecutorServiceManager executorServiceManager) {
095 requireNonNull(executorServiceManager, "Argument 'executorServiceManager' must not be null");
096 if (this.executorServiceManager != null) {
097 this.executorServiceManager.remove(executorService);
098 }
099 this.executorServiceManager = executorServiceManager;
100 this.executorServiceManager.add(executorService);
101 }
102
103 protected void runInsideExecutorService(@Nonnull final Runnable runnable) {
104 requireNonNull(runnable, ERROR_RUNNABLE_NULL);
105 executorService.submit(new Runnable() {
106 public void run() {
107 try {
108 runnable.run();
109 } catch (Throwable throwable) {
110 exceptionHandler.uncaughtException(Thread.currentThread(), throwable);
111 }
112 }
113 });
114 }
115
116 @Override
117 public boolean isEventPublishingEnabled() {
118 synchronized (LOCK) {
119 return this.enabled;
120 }
121 }
122
123 @Override
124 public void setEventPublishingEnabled(boolean enabled) {
125 synchronized (LOCK) {
126 this.enabled = enabled;
127 }
128 }
129
130 @Override
131 public void publishEvent(@Nonnull String eventName) {
132 publishEvent(eventName, EMPTY_LIST);
133 }
134
135 @Override
136 public void publishEvent(@Nonnull String eventName, @Nullable List<?> params) {
137 if (!isEventPublishingEnabled()) { return; }
138 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
139 if (params == null) { params = EMPTY_LIST; }
140 buildPublisher(eventName, params, "synchronously").run();
141 }
142
143 @Override
144 public void publishEventOutsideUI(@Nonnull String eventName) {
145 publishEventOutsideUI(eventName, EMPTY_LIST);
146 }
147
148 @Override
149 public void publishEventOutsideUI(@Nonnull String eventName, @Nullable List<?> params) {
150 if (!isEventPublishingEnabled()) { return; }
151 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
152 if (params == null) { params = EMPTY_LIST; }
153 final Runnable publisher = buildPublisher(eventName, params, "outside UI");
154 doPublishOutsideUI(publisher);
155 }
156
157 protected abstract void doPublishOutsideUI(@Nonnull Runnable publisher);
158
159 @Override
160 public void publishEventAsync(@Nonnull String eventName) {
161 publishEventAsync(eventName, EMPTY_LIST);
162 }
163
164 @Override
165 public void publishEventAsync(@Nonnull String eventName, @Nullable List<?> params) {
166 if (!isEventPublishingEnabled()) { return; }
167 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
168 if (params == null) { params = EMPTY_LIST; }
169 final Runnable publisher = buildPublisher(eventName, params, "asynchronously");
170 doPublishAsync(publisher);
171 }
172
173 protected abstract void doPublishAsync(@Nonnull Runnable publisher);
174
175 @Override
176 public void publishEvent(@Nonnull Event event) {
177 requireNonNull(event, ERROR_EVENT_NULL);
178 publishEvent(event.getClass().getSimpleName(), asList(event));
179 }
180
181 @Override
182 public void publishEventOutsideUI(@Nonnull Event event) {
183 requireNonNull(event, ERROR_EVENT_NULL);
184 publishEventOutsideUI(event.getClass().getSimpleName(), asList(event));
185 }
186
187 @Override
188 public void publishEventAsync(@Nonnull Event event) {
189 requireNonNull(event, ERROR_EVENT_NULL);
190 publishEventAsync(event.getClass().getSimpleName(), asList(event));
191 }
192
193 @Override
194 public <E extends Event> void removeEventListener(@Nonnull Class<E> eventClass, @Nonnull CallableWithArgs<?> listener) {
195 requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
196 removeEventListener(eventClass.getSimpleName(), listener);
197 }
198
199 @Override
200 public <E extends Event> void removeEventListener(@Nonnull Class<E> eventClass, @Nonnull RunnableWithArgs listener) {
201 requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
202 removeEventListener(eventClass.getSimpleName(), listener);
203 }
204
205 protected void fireEvent(@Nonnull RunnableWithArgs runnable, @Nonnull List<?> params) {
206 requireNonNull(runnable, ERROR_RUNNABLE_NULL);
207 requireNonNull(params, ERROR_PARAMS_NULL);
208 runnable.run(asArray(params));
209 }
210
211 protected void fireEvent(@Nonnull CallableWithArgs<?> callable, @Nonnull List<?> params) {
212 requireNonNull(callable, ERROR_CALLABLE_NULL);
213 requireNonNull(params, ERROR_PARAMS_NULL);
214 callable.call(asArray(params));
215 }
216
217 protected void fireEvent(@Nonnull Object instance, @Nonnull String eventHandler, @Nonnull List<?> params) {
218 requireNonNull(instance, ERROR_INSTANCE_NULL);
219 requireNonBlank(eventHandler, ERROR_EVENT_HANDLER_BLANK);
220 requireNonNull(params, ERROR_PARAMS_NULL);
221
222 Class[] argTypes = convertToTypeArray(asArray(params));
223 MethodDescriptor target = new MethodDescriptor(eventHandler, argTypes);
224 Method method = methodCache.findMatchingMethodFor(instance.getClass(), target);
225
226 if (method != null) {
227 MethodUtils.invokeUnwrapping(method, instance, asArray(params));
228 }
229 }
230
231 @Override
232 public <E extends Event> void addEventListener(@Nonnull Class<E> eventClass, @Nonnull CallableWithArgs<?> listener) {
233 requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
234 addEventListener(eventClass.getSimpleName(), listener);
235 }
236
237 @Override
238 public <E extends Event> void addEventListener(@Nonnull Class<E> eventClass, @Nonnull RunnableWithArgs listener) {
239 requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
240 addEventListener(eventClass.getSimpleName(), listener);
241 }
242
243 @Override
244 @SuppressWarnings("unchecked")
245 public void addEventListener(@Nonnull Object listener) {
246 requireNonNull(listener, ERROR_LISTENER_NULL);
247 if (listener instanceof RunnableWithArgs) {
248 throw new IllegalArgumentException("Cannot add an event listener of type " + RunnableWithArgs.class.getName() +
249 " because the target event name is missing. " + listener);
250 }
251 if (listener instanceof CallableWithArgs) {
252 throw new IllegalArgumentException("Cannot add an event listener of type " + CallableWithArgs.class.getName() +
253 " because the target event name is missing. " + listener);
254 }
255
256 if (listener instanceof Map) {
257 addEventListener((Map) listener);
258 return;
259 }
260
261 if (!methodCache.isEventListener(listener.getClass())) {
262 return;
263 }
264
265 boolean added = false;
266 for (String eventName : methodCache.fetchMethodMetadata(listener.getClass()).keySet()) {
267 eventName = eventName.substring(2); // cut off "on" from the name
268 List<Object> instances = instanceListeners.get(eventName);
269 if (instances == null) {
270 instances = new ArrayList<>();
271 instanceListeners.put(eventName, instances);
272 }
273 synchronized (instances) {
274 if (!instances.contains(listener)) {
275 added = true;
276 instances.add(listener);
277 }
278 }
279 }
280
281 if (added) {
282 try {
283 LOG.debug("Adding listener {}", listener);
284 } catch (UnsupportedOperationException uoe) {
285 LOG.debug("Adding listener {}", listener.getClass().getName());
286 }
287 }
288 }
289
290 @Override
291 public void addEventListener(@Nonnull Map<String, Object> listener) {
292 requireNonNull(listener, ERROR_LISTENER_NULL);
293 for (Map.Entry<String, Object> entry : listener.entrySet()) {
294 Object eventHandler = entry.getValue();
295 if (eventHandler instanceof RunnableWithArgs) {
296 addEventListener(entry.getKey(), (RunnableWithArgs) eventHandler);
297 } else if (eventHandler instanceof CallableWithArgs) {
298 addEventListener(entry.getKey(), (CallableWithArgs) eventHandler);
299 } else {
300 throw new IllegalArgumentException("Unsupported functional event listener " + eventHandler);
301 }
302 }
303 }
304
305 @Override
306 @SuppressWarnings("unchecked")
307 public void removeEventListener(@Nonnull Object listener) {
308 requireNonNull(listener, ERROR_LISTENER_NULL);
309 if (listener instanceof RunnableWithArgs) {
310 throw new IllegalArgumentException("Cannot remove an event listener of type " + RunnableWithArgs.class.getName() +
311 " because the target event name is missing. " + listener);
312 }
313 if (listener instanceof CallableWithArgs) {
314 throw new IllegalArgumentException("Cannot remove an event listener of type " + CallableWithArgs.class.getName() +
315 " because the target event name is missing. " + listener);
316 }
317
318 if (listener instanceof Map) {
319 removeEventListener((Map) listener);
320 return;
321 }
322
323 boolean removed = false;
324 for (String eventName : methodCache.fetchMethodMetadata(listener.getClass()).keySet()) {
325 eventName = eventName.substring(2); // cut off "on" from the name
326 List<Object> instances = instanceListeners.get(eventName);
327 if (instances != null && instances.contains(listener)) {
328 instances.remove(listener);
329 removed = true;
330 if (instances.isEmpty()) {
331 instanceListeners.remove(eventName);
332 }
333 }
334 }
335
336 boolean nestedRemoved = removeNestedListeners(listener);
337
338 if (removed || nestedRemoved) {
339 try {
340 LOG.debug("Removing listener {}", listener);
341 } catch (UnsupportedOperationException uoe) {
342 LOG.debug("Removing listener {}", listener.getClass().getName());
343 }
344 }
345 }
346
347 @Override
348 public void removeEventListener(@Nonnull Map<String, Object> listener) {
349 requireNonNull(listener, ERROR_LISTENER_NULL);
350 for (Map.Entry<String, Object> entry : listener.entrySet()) {
351 Object eventHandler = entry.getValue();
352 if (eventHandler instanceof RunnableWithArgs) {
353 removeEventListener(entry.getKey(), (RunnableWithArgs) eventHandler);
354 } else if (eventHandler instanceof CallableWithArgs) {
355 removeEventListener(entry.getKey(), (CallableWithArgs) eventHandler);
356 } else {
357 throw new IllegalArgumentException("Unsupported functional event listener " + eventHandler);
358 }
359 }
360 }
361
362 @Override
363 public void addEventListener(@Nonnull String eventName, @Nonnull CallableWithArgs<?> listener) {
364 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
365 requireNonNull(listener, ERROR_LISTENER_NULL);
366 synchronized (functionalListeners) {
367 List<Object> list = functionalListeners.get(capitalize(eventName));
368 if (list == null) {
369 list = new ArrayList<>();
370 functionalListeners.put(capitalize(eventName), list);
371 }
372 if (list.contains(listener)) { return; }
373 LOG.debug("Adding listener {} on {}", listener.getClass().getName(), capitalize(eventName));
374 list.add(listener);
375 }
376 }
377
378 @Override
379 public void addEventListener(@Nonnull String eventName, @Nonnull RunnableWithArgs listener) {
380 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
381 requireNonNull(listener, ERROR_LISTENER_NULL);
382 synchronized (functionalListeners) {
383 List<Object> list = functionalListeners.get(capitalize(eventName));
384 if (list == null) {
385 list = new ArrayList<>();
386 functionalListeners.put(capitalize(eventName), list);
387 }
388 if (list.contains(listener)) { return; }
389 LOG.debug("Adding listener {} on {}", listener.getClass().getName(), capitalize(eventName));
390 list.add(listener);
391 }
392 }
393
394 @Override
395 public void removeEventListener(@Nonnull String eventName, @Nonnull CallableWithArgs<?> listener) {
396 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
397 requireNonNull(listener, ERROR_LISTENER_NULL);
398 synchronized (functionalListeners) {
399 List<Object> list = functionalListeners.get(capitalize(eventName));
400 if (list != null) {
401 LOG.debug("Removing listener {} on {}", listener.getClass().getName(), capitalize(eventName));
402 list.remove(listener);
403 }
404 }
405 }
406
407 @Override
408 public void removeEventListener(@Nonnull String eventName, @Nonnull RunnableWithArgs listener) {
409 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
410 requireNonNull(listener, ERROR_LISTENER_NULL);
411 synchronized (functionalListeners) {
412 List<Object> list = functionalListeners.get(capitalize(eventName));
413 if (list != null) {
414 LOG.debug("Removing listener {} on {}", listener.getClass().getName(), capitalize(eventName));
415 list.remove(listener);
416 }
417 }
418 }
419
420 @Nonnull
421 @Override
422 public Collection<Object> getEventListeners() {
423 List<Object> listeners = new ArrayList<>();
424 synchronized (instanceListeners) {
425 Set<Object> instances = new HashSet<>();
426 for (List<Object> objects : instanceListeners.values()) {
427 instances.addAll(objects);
428 }
429 listeners.addAll(instances);
430 }
431
432 synchronized (functionalListeners) {
433 for (List<Object> objects : functionalListeners.values()) {
434 listeners.addAll(objects);
435 }
436 }
437
438 return unmodifiableCollection(listeners);
439 }
440
441 @Nonnull
442 @Override
443 public Collection<Object> getEventListeners(@Nonnull String eventName) {
444 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
445 List<Object> listeners = new ArrayList<>();
446 List<Object> instances = instanceListeners.get(eventName);
447 if (instances != null) { listeners.addAll(instances); }
448 instances = functionalListeners.get(eventName);
449 if (instances != null) { listeners.addAll(instances); }
450 return unmodifiableCollection(listeners);
451 }
452
453 protected Runnable buildPublisher(@Nonnull final String event, @Nonnull final List<?> params, @Nonnull final String mode) {
454 requireNonNull(event, ERROR_EVENT_NULL);
455 requireNonNull(params, ERROR_PARAMS_NULL);
456 requireNonBlank(mode, ERROR_MODE_BLANK);
457 return new Runnable() {
458 public void run() {
459 String eventName = capitalize(event);
460 LOG.debug("Triggering event '{}' {}", eventName, mode);
461 String eventHandler = "on" + eventName;
462 // defensive copying to avoid CME during event dispatching
463 List<Object> listenersCopy = new ArrayList<>();
464 List<Object> instances = instanceListeners.get(eventName);
465 if (instances != null) {
466 listenersCopy.addAll(instances);
467 }
468 synchronized (functionalListeners) {
469 List<?> list = functionalListeners.get(eventName);
470 if (list != null) {
471 listenersCopy.addAll(list);
472 }
473 }
474
475 for (Object listener : listenersCopy) {
476 if (listener instanceof RunnableWithArgs) {
477 fireEvent((RunnableWithArgs) listener, params);
478 } else if (listener instanceof CallableWithArgs) {
479 fireEvent((CallableWithArgs<?>) listener, params);
480 } else {
481 fireEvent(listener, eventHandler, params);
482 }
483 }
484 }
485 };
486 }
487
488 protected boolean removeNestedListeners(@Nonnull Object owner) {
489 requireNonNull(owner, ERROR_OWNER_NULL);
490
491 boolean removed = false;
492 synchronized (functionalListeners) {
493 for (Map.Entry<String, List<Object>> event : functionalListeners.entrySet()) {
494 String eventName = event.getKey();
495 List<Object> listenerList = event.getValue();
496 List<Object> toRemove = new ArrayList<>();
497 for (Object listener : listenerList) {
498 if (isNestedListener(listener, owner)) {
499 toRemove.add(listener);
500 }
501 }
502 removed = !toRemove.isEmpty();
503 for (Object listener : toRemove) {
504 LOG.debug("Removing listener {} on {}", listener.getClass().getName(), capitalize(eventName));
505 listenerList.remove(listener);
506 }
507 }
508 }
509
510 return removed;
511 }
512
513 protected boolean isNestedListener(@Nonnull Object listener, @Nonnull Object owner) {
514 requireNonNull(listener, ERROR_LISTENER_NULL);
515 requireNonNull(owner, ERROR_OWNER_NULL);
516 Class<?> listenerClass = listener.getClass();
517 return (listenerClass.isMemberClass() || listenerClass.isAnonymousClass() || listenerClass.isLocalClass()) &&
518 owner.getClass().equals(listenerClass.getEnclosingClass()) &&
519 owner.equals(GriffonClassUtils.getFieldValue(listener, "this$0"));
520 }
521
522 protected Object[] asArray(@Nonnull List<?> list) {
523 return list.toArray(new Object[list.size()]);
524 }
525
526 protected static class MethodCache {
527 private final Map<Class<?>, Map<String, List<MethodInfo>>> methodMap = new ConcurrentHashMap<>();
528
529 public boolean isEventListener(@Nonnull Class<?> klass) {
530 Map<String, List<MethodInfo>> methodMetadata = methodMap.get(klass);
531 if (methodMetadata == null) {
532 methodMetadata = fetchMethodMetadata(klass);
533 if (!methodMetadata.isEmpty()) {
534 methodMap.put(klass, methodMetadata);
535 } else {
536 methodMetadata = null;
537 }
538 }
539 return methodMetadata != null;
540 }
541
542 @Nullable
543 public Method findMatchingMethodFor(@Nonnull Class<?> klass, @Nonnull MethodDescriptor target) {
544 Map<String, List<MethodInfo>> methodMetadata = methodMap.get(klass);
545
546 List<MethodInfo> descriptors = methodMetadata.get(target.getName());
547 if (descriptors != null) {
548 for (MethodInfo info : descriptors) {
549 if (info.descriptor.matches(target)) {
550 return info.method;
551 }
552 }
553 }
554
555 return null;
556 }
557
558 private Map<String, List<MethodInfo>> fetchMethodMetadata(Class<?> klass) {
559 Map<String, List<MethodInfo>> methodMetadata = new LinkedHashMap<>();
560
561 for (Method method : klass.getMethods()) {
562 MethodDescriptor descriptor = MethodDescriptor.forMethod(method);
563 if (GriffonClassUtils.isEventHandler(descriptor)) {
564 String methodName = method.getName();
565 List<MethodInfo> descriptors = methodMetadata.get(methodName);
566 if (descriptors == null) {
567 descriptors = new ArrayList<>();
568 methodMetadata.put(methodName, descriptors);
569 }
570 descriptors.add(new MethodInfo(descriptor, method));
571 }
572 }
573
574 return methodMetadata;
575 }
576 }
577
578 protected static class MethodInfo {
579 private final MethodDescriptor descriptor;
580 private final Method method;
581
582 public MethodInfo(MethodDescriptor descriptor, Method method) {
583 this.descriptor = descriptor;
584 this.method = method;
585 }
586
587 public MethodDescriptor getDescriptor() {
588 return descriptor;
589 }
590
591 public Method getMethod() {
592 return method;
593 }
594 }
595
596 private static class DefaultThreadFactory implements ThreadFactory {
597 private final ThreadGroup group;
598 private final AtomicInteger threadNumber = new AtomicInteger(1);
599 private final String namePrefix;
600
601 private DefaultThreadFactory(int eventRouterId) {
602 SecurityManager s = System.getSecurityManager();
603 group = (s != null) ? s.getThreadGroup() :
604 Thread.currentThread().getThreadGroup();
605 namePrefix = "event-router-" + eventRouterId + "-thread-";
606 }
607
608 public Thread newThread(Runnable r) {
609 Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
610 if (t.isDaemon()) { t.setDaemon(false); }
611 if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); }
612 return t;
613 }
614 }
615 }
|