001 /*
002 * Copyright 2008-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.codehaus.griffon.runtime.core.event;
017
018 import griffon.core.CallableWithArgs;
019 import griffon.core.ExceptionHandler;
020 import griffon.core.ExecutorServiceManager;
021 import griffon.core.RunnableWithArgs;
022 import griffon.core.event.Event;
023 import griffon.core.event.EventRouter;
024 import griffon.util.GriffonClassUtils;
025 import griffon.util.MethodDescriptor;
026 import griffon.util.MethodUtils;
027 import org.slf4j.Logger;
028 import org.slf4j.LoggerFactory;
029
030 import javax.annotation.Nonnull;
031 import javax.annotation.Nullable;
032 import javax.inject.Inject;
033 import java.lang.reflect.Method;
034 import java.util.ArrayList;
035 import java.util.Collection;
036 import java.util.HashSet;
037 import java.util.LinkedHashMap;
038 import java.util.List;
039 import java.util.Map;
040 import java.util.Set;
041 import java.util.concurrent.ConcurrentHashMap;
042 import java.util.concurrent.ExecutorService;
043 import java.util.concurrent.Executors;
044 import java.util.concurrent.ThreadFactory;
045 import java.util.concurrent.atomic.AtomicInteger;
046
047 import static griffon.util.GriffonClassUtils.convertToTypeArray;
048 import static griffon.util.GriffonNameUtils.capitalize;
049 import static griffon.util.GriffonNameUtils.requireNonBlank;
050 import static java.util.Arrays.asList;
051 import static java.util.Collections.EMPTY_LIST;
052 import static java.util.Collections.unmodifiableCollection;
053 import static java.util.Objects.requireNonNull;
054
055 /**
056 * @author Andres Almiray
057 */
058 public abstract class AbstractEventRouter implements EventRouter {
059 protected static final Object[] LOCK = new Object[0];
060 private static final String ERROR_EVENT_NAME_BLANK = "Argument 'eventName' must not be blank";
061 private static final String ERROR_EVENT_HANDLER_BLANK = "Argument 'eventHandler' must not be blank";
062 private static final String ERROR_MODE_BLANK = "Argument 'mode' must not be blank";
063 private static final String ERROR_LISTENER_NULL = "Argument 'listener' must not be null";
064 private static final String ERROR_EVENT_CLASS_NULL = "Argument 'eventClass' must not be null";
065 private static final String ERROR_EVENT_NULL = "Argument 'event' must not be null";
066 private static final String ERROR_CALLABLE_NULL = "Argument 'callable' must not be null";
067 private static final String ERROR_RUNNABLE_NULL = "Argument 'runnable' must not be null";
068 private static final String ERROR_PARAMS_NULL = "Argument 'params' must not be null";
069 private static final String ERROR_INSTANCE_NULL = "Argument 'instance' must not be null";
070 private static final String ERROR_OWNER_NULL = "Argument 'owner' must not be null";
071 private static final Logger LOG = LoggerFactory.getLogger(AbstractEventRouter.class);
072 protected final Map<String, List<Object>> instanceListeners = new ConcurrentHashMap<>();
073 protected final Map<String, List<Object>> functionalListeners = new ConcurrentHashMap<>();
074 private final MethodCache methodCache = new MethodCache();
075 private boolean enabled = true;
076
077 protected static final AtomicInteger EVENT_ROUTER_ID = new AtomicInteger(1);
078
079 protected ExecutorServiceManager executorServiceManager;
080 protected final ExecutorService executorService;
081 protected final int eventRouterId;
082
083 @Inject
084 private ExceptionHandler exceptionHandler;
085
086 public AbstractEventRouter() {
087 eventRouterId = EVENT_ROUTER_ID.getAndIncrement();
088 executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new DefaultThreadFactory(eventRouterId));
089 }
090
091 @Inject
092 public void setExecutorServiceManager(@Nonnull ExecutorServiceManager executorServiceManager) {
093 requireNonNull(executorServiceManager, "Argument 'executorServiceManager' must not be null");
094 if (this.executorServiceManager != null) {
095 this.executorServiceManager.remove(executorService);
096 }
097 this.executorServiceManager = executorServiceManager;
098 this.executorServiceManager.add(executorService);
099 }
100
101 protected void runInsideExecutorService(@Nonnull final Runnable runnable) {
102 requireNonNull(runnable, ERROR_RUNNABLE_NULL);
103 executorService.submit(new Runnable() {
104 public void run() {
105 try {
106 runnable.run();
107 } catch (Throwable throwable) {
108 exceptionHandler.uncaughtException(Thread.currentThread(), throwable);
109 }
110 }
111 });
112 }
113
114 @Override
115 public boolean isEventPublishingEnabled() {
116 synchronized (LOCK) {
117 return this.enabled;
118 }
119 }
120
121 @Override
122 public void setEventPublishingEnabled(boolean enabled) {
123 synchronized (LOCK) {
124 this.enabled = enabled;
125 }
126 }
127
128 @Override
129 public void publishEvent(@Nonnull String eventName) {
130 publishEvent(eventName, EMPTY_LIST);
131 }
132
133 @Override
134 public void publishEvent(@Nonnull String eventName, @Nullable List<?> params) {
135 if (!isEventPublishingEnabled()) return;
136 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
137 if (params == null) params = EMPTY_LIST;
138 buildPublisher(eventName, params, "synchronously").run();
139 }
140
141 @Override
142 public void publishEventOutsideUI(@Nonnull String eventName) {
143 publishEventOutsideUI(eventName, EMPTY_LIST);
144 }
145
146 @Override
147 public void publishEventOutsideUI(@Nonnull String eventName, @Nullable List<?> params) {
148 if (!isEventPublishingEnabled()) return;
149 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
150 if (params == null) params = EMPTY_LIST;
151 final Runnable publisher = buildPublisher(eventName, params, "outside UI");
152 doPublishOutsideUI(publisher);
153 }
154
155 protected abstract void doPublishOutsideUI(@Nonnull Runnable publisher);
156
157 @Override
158 public void publishEventAsync(@Nonnull String eventName) {
159 publishEventAsync(eventName, EMPTY_LIST);
160 }
161
162 @Override
163 public void publishEventAsync(@Nonnull String eventName, @Nullable List<?> params) {
164 if (!isEventPublishingEnabled()) return;
165 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
166 if (params == null) params = EMPTY_LIST;
167 final Runnable publisher = buildPublisher(eventName, params, "asynchronously");
168 doPublishAsync(publisher);
169 }
170
171 protected abstract void doPublishAsync(@Nonnull Runnable publisher);
172
173 @Override
174 public void publishEvent(@Nonnull Event event) {
175 requireNonNull(event, ERROR_EVENT_NULL);
176 publishEvent(event.getClass().getSimpleName(), asList(event));
177 }
178
179 @Override
180 public void publishEventOutsideUI(@Nonnull Event event) {
181 requireNonNull(event, ERROR_EVENT_NULL);
182 publishEventOutsideUI(event.getClass().getSimpleName(), asList(event));
183 }
184
185 @Override
186 public void publishEventAsync(@Nonnull Event event) {
187 requireNonNull(event, ERROR_EVENT_NULL);
188 publishEventAsync(event.getClass().getSimpleName(), asList(event));
189 }
190
191 @Override
192 public <E extends Event> void removeEventListener(@Nonnull Class<E> eventClass, @Nonnull CallableWithArgs<?> listener) {
193 requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
194 removeEventListener(eventClass.getSimpleName(), listener);
195 }
196
197 @Override
198 public <E extends Event> void removeEventListener(@Nonnull Class<E> eventClass, @Nonnull RunnableWithArgs listener) {
199 requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
200 removeEventListener(eventClass.getSimpleName(), listener);
201 }
202
203 protected void fireEvent(@Nonnull RunnableWithArgs runnable, @Nonnull List<?> params) {
204 requireNonNull(runnable, ERROR_RUNNABLE_NULL);
205 requireNonNull(params, ERROR_PARAMS_NULL);
206 runnable.run(asArray(params));
207 }
208
209 protected void fireEvent(@Nonnull CallableWithArgs<?> callable, @Nonnull List<?> params) {
210 requireNonNull(callable, ERROR_CALLABLE_NULL);
211 requireNonNull(params, ERROR_PARAMS_NULL);
212 callable.call(asArray(params));
213 }
214
215 protected void fireEvent(@Nonnull Object instance, @Nonnull String eventHandler, @Nonnull List<?> params) {
216 requireNonNull(instance, ERROR_INSTANCE_NULL);
217 requireNonBlank(eventHandler, ERROR_EVENT_HANDLER_BLANK);
218 requireNonNull(params, ERROR_PARAMS_NULL);
219
220 Class[] argTypes = convertToTypeArray(asArray(params));
221 MethodDescriptor target = new MethodDescriptor(eventHandler, argTypes);
222 Method method = methodCache.findMatchingMethodFor(instance.getClass(), target);
223
224 if (method != null) {
225 MethodUtils.invokeSafe(method, instance, asArray(params));
226 }
227 }
228
229 @Override
230 public <E extends Event> void addEventListener(@Nonnull Class<E> eventClass, @Nonnull CallableWithArgs<?> listener) {
231 requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
232 addEventListener(eventClass.getSimpleName(), listener);
233 }
234
235 @Override
236 public <E extends Event> void addEventListener(@Nonnull Class<E> eventClass, @Nonnull RunnableWithArgs listener) {
237 requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
238 addEventListener(eventClass.getSimpleName(), listener);
239 }
240
241 @Override
242 @SuppressWarnings("unchecked")
243 public void addEventListener(@Nonnull Object listener) {
244 requireNonNull(listener, ERROR_LISTENER_NULL);
245 if (listener instanceof RunnableWithArgs) {
246 throw new IllegalArgumentException("Cannot add an event listener of type " + RunnableWithArgs.class.getName() +
247 " because the target event name is missing. " + listener);
248 }
249 if (listener instanceof CallableWithArgs) {
250 throw new IllegalArgumentException("Cannot add an event listener of type " + CallableWithArgs.class.getName() +
251 " because the target event name is missing. " + listener);
252 }
253
254 if (listener instanceof Map) {
255 addEventListener((Map) listener);
256 return;
257 }
258
259 if (!methodCache.isEventListener(listener.getClass())) {
260 return;
261 }
262
263 boolean added = false;
264 for (String eventName : methodCache.fetchMethodMetadata(listener.getClass()).keySet()) {
265 eventName = eventName.substring(2); // cut off "on" from the name
266 List<Object> instances = instanceListeners.get(eventName);
267 if (instances == null) {
268 instances = new ArrayList<>();
269 instanceListeners.put(eventName, instances);
270 }
271 synchronized (instances) {
272 if (!instances.contains(listener)) {
273 added = true;
274 instances.add(listener);
275 }
276 }
277 }
278
279 if (added) {
280 try {
281 LOG.debug("Adding listener {}", listener);
282 } catch (UnsupportedOperationException uoe) {
283 LOG.debug("Adding listener {}", listener.getClass().getName());
284 }
285 }
286 }
287
288 @Override
289 public void addEventListener(@Nonnull Map<String, Object> listener) {
290 requireNonNull(listener, ERROR_LISTENER_NULL);
291 for (Map.Entry<String, Object> entry : listener.entrySet()) {
292 Object eventHandler = entry.getValue();
293 if (eventHandler instanceof RunnableWithArgs) {
294 addEventListener(entry.getKey(), (RunnableWithArgs) eventHandler);
295 } else if (eventHandler instanceof CallableWithArgs) {
296 addEventListener(entry.getKey(), (CallableWithArgs) eventHandler);
297 } else {
298 throw new IllegalArgumentException("Unsupported functional event listener " + eventHandler);
299 }
300 }
301 }
302
303 @Override
304 @SuppressWarnings("unchecked")
305 public void removeEventListener(@Nonnull Object listener) {
306 requireNonNull(listener, ERROR_LISTENER_NULL);
307 if (listener instanceof RunnableWithArgs) {
308 throw new IllegalArgumentException("Cannot remove an event listener of type " + RunnableWithArgs.class.getName() +
309 " because the target event name is missing. " + listener);
310 }
311 if (listener instanceof CallableWithArgs) {
312 throw new IllegalArgumentException("Cannot remove an event listener of type " + CallableWithArgs.class.getName() +
313 " because the target event name is missing. " + listener);
314 }
315
316 if (listener instanceof Map) {
317 removeEventListener((Map) listener);
318 return;
319 }
320
321 boolean removed = false;
322 for (String eventName : methodCache.fetchMethodMetadata(listener.getClass()).keySet()) {
323 eventName = eventName.substring(2); // cut off "on" from the name
324 List<Object> instances = instanceListeners.get(eventName);
325 if (instances != null && instances.contains(listener)) {
326 instances.remove(listener);
327 removed = true;
328 if (instances.isEmpty()) {
329 instanceListeners.remove(eventName);
330 }
331 }
332 }
333
334 boolean nestedRemoved = removeNestedListeners(listener);
335
336 if (removed || nestedRemoved) {
337 try {
338 LOG.debug("Removing listener {}", listener);
339 } catch (UnsupportedOperationException uoe) {
340 LOG.debug("Removing listener {}", listener.getClass().getName());
341 }
342 }
343 }
344
345 @Override
346 public void removeEventListener(@Nonnull Map<String, Object> listener) {
347 requireNonNull(listener, ERROR_LISTENER_NULL);
348 for (Map.Entry<String, Object> entry : listener.entrySet()) {
349 Object eventHandler = entry.getValue();
350 if (eventHandler instanceof RunnableWithArgs) {
351 removeEventListener(entry.getKey(), (RunnableWithArgs) eventHandler);
352 } else if (eventHandler instanceof CallableWithArgs) {
353 removeEventListener(entry.getKey(), (CallableWithArgs) eventHandler);
354 } else {
355 throw new IllegalArgumentException("Unsupported functional event listener " + eventHandler);
356 }
357 }
358 }
359
360 @Override
361 public void addEventListener(@Nonnull String eventName, @Nonnull CallableWithArgs<?> listener) {
362 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
363 requireNonNull(listener, ERROR_LISTENER_NULL);
364 synchronized (functionalListeners) {
365 List<Object> list = functionalListeners.get(capitalize(eventName));
366 if (list == null) {
367 list = new ArrayList<>();
368 functionalListeners.put(capitalize(eventName), list);
369 }
370 if (list.contains(listener)) return;
371 LOG.debug("Adding listener {} on {}", listener.getClass().getName(), capitalize(eventName));
372 list.add(listener);
373 }
374 }
375
376 @Override
377 public void addEventListener(@Nonnull String eventName, @Nonnull RunnableWithArgs listener) {
378 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
379 requireNonNull(listener, ERROR_LISTENER_NULL);
380 synchronized (functionalListeners) {
381 List<Object> list = functionalListeners.get(capitalize(eventName));
382 if (list == null) {
383 list = new ArrayList<>();
384 functionalListeners.put(capitalize(eventName), list);
385 }
386 if (list.contains(listener)) return;
387 LOG.debug("Adding listener {} on {}", listener.getClass().getName(), capitalize(eventName));
388 list.add(listener);
389 }
390 }
391
392 @Override
393 public void removeEventListener(@Nonnull String eventName, @Nonnull CallableWithArgs<?> listener) {
394 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
395 requireNonNull(listener, ERROR_LISTENER_NULL);
396 synchronized (functionalListeners) {
397 List<Object> list = functionalListeners.get(capitalize(eventName));
398 if (list != null) {
399 LOG.debug("Removing listener {} on {}", listener.getClass().getName(), capitalize(eventName));
400 list.remove(listener);
401 }
402 }
403 }
404
405 @Override
406 public void removeEventListener(@Nonnull String eventName, @Nonnull RunnableWithArgs listener) {
407 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
408 requireNonNull(listener, ERROR_LISTENER_NULL);
409 synchronized (functionalListeners) {
410 List<Object> list = functionalListeners.get(capitalize(eventName));
411 if (list != null) {
412 LOG.debug("Removing listener {} on {}", listener.getClass().getName(), capitalize(eventName));
413 list.remove(listener);
414 }
415 }
416 }
417
418 @Nonnull
419 @Override
420 public Collection<Object> getEventListeners() {
421 List<Object> listeners = new ArrayList<>();
422 synchronized (instanceListeners) {
423 Set<Object> instances = new HashSet<>();
424 for (List<Object> objects : instanceListeners.values()) {
425 instances.addAll(objects);
426 }
427 listeners.addAll(instances);
428 }
429
430 synchronized (functionalListeners) {
431 for (List<Object> objects : functionalListeners.values()) {
432 listeners.addAll(objects);
433 }
434 }
435
436 return unmodifiableCollection(listeners);
437 }
438
439 @Nonnull
440 @Override
441 public Collection<Object> getEventListeners(@Nonnull String eventName) {
442 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
443 List<Object> listeners = new ArrayList<>();
444 List<Object> instances = instanceListeners.get(eventName);
445 if (instances != null) listeners.addAll(instances);
446 instances = functionalListeners.get(eventName);
447 if (instances != null) listeners.addAll(instances);
448 return unmodifiableCollection(listeners);
449 }
450
451 protected Runnable buildPublisher(@Nonnull final String event, @Nonnull final List<?> params, @Nonnull final String mode) {
452 requireNonNull(event, ERROR_EVENT_NULL);
453 requireNonNull(params, ERROR_PARAMS_NULL);
454 requireNonBlank(mode, ERROR_MODE_BLANK);
455 return new Runnable() {
456 public void run() {
457 String eventName = capitalize(event);
458 LOG.debug("Triggering event '{}' {}", eventName, mode);
459 String eventHandler = "on" + eventName;
460 // defensive copying to avoid CME during event dispatching
461 List<Object> listenersCopy = new ArrayList<>();
462 List<Object> instances = instanceListeners.get(eventName);
463 if (instances != null) {
464 listenersCopy.addAll(instances);
465 }
466 synchronized (functionalListeners) {
467 List list = functionalListeners.get(eventName);
468 if (list != null) {
469 for (Object listener : list) {
470 listenersCopy.add(listener);
471 }
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.size() > 0;
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 }
|