001/* Copyright 2011-2012 the original author or authors:
002 *
003 *    Marc Palmer (marc@grailsrocks.com)
004 *    Stéphane Maldini (smaldini@vmware.com)
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 */
018package org.grails.plugin.platform.events.registry;
019
020import groovy.lang.Closure;
021import org.apache.log4j.Logger;
022import org.grails.plugin.platform.events.EventMessage;
023import org.grails.plugin.platform.events.ListenerId;
024import org.springframework.aop.framework.Advised;
025import org.springframework.util.ReflectionUtils;
026
027import java.lang.reflect.InvocationTargetException;
028import java.lang.reflect.Method;
029import java.util.ArrayList;
030import java.util.HashSet;
031import java.util.List;
032import java.util.Set;
033
034/**
035 * @author Stephane Maldini <smaldini@vmware.com>
036 * @version 1.0
037 * @file
038 * @date 02/01/12
039 * @section DESCRIPTION
040 * <p/>
041 * [Does stuff]
042 */
043public class DefaultEventsRegistry implements EventsRegistry {
044
045    static final private Logger log = Logger.getLogger(DefaultEventsRegistry.class);
046
047    private Set<ListenerHandler> listeners = new HashSet<ListenerHandler>();
048
049    /*
050        API
051     */
052
053    public String on(String namespace, String topic, Closure callback) {
054        return registerHandler(callback, namespace, topic);
055    }
056
057    public String on(String namespace, String topic, Object bean, String callbackName) {
058        return registerHandler(bean, ReflectionUtils.findMethod(bean.getClass(), callbackName), namespace, topic);
059    }
060
061    public String on(String namespace, String topic, Object bean, Method callback) {
062        return registerHandler(bean, callback, namespace, topic);
063    }
064
065    public int removeListeners(String callbackId) {
066        ListenerId listener = ListenerId.parse(callbackId);
067        if (listener == null)
068            return 0;
069        synchronized (listeners) {
070            Set<ListenerHandler> listeners = findAll(listener);
071            for (ListenerHandler _listener : listeners) {
072                this.listeners.remove(_listener);
073            }
074        }
075
076        return listeners.size();
077    }
078
079    public int countListeners(String callbackId) {
080        ListenerId listener = ListenerId.parse(callbackId);
081        if (listener == null)
082            return 0;
083
084        return findAll(listener).size();
085    }
086
087    /*
088       INTERNAL
089    */
090
091    private String registerHandler(Closure callback, String namespace, String topic) {
092        if (log.isDebugEnabled()) {
093            log.debug("Registering event handler [" + callback.getClass() + "] for topic [" + topic + "]");
094        }
095
096        ListenerId listener = ListenerId.build(namespace, topic, callback);
097        ListenerHandler handler = new ListenerHandler(callback, ReflectionUtils.findMethod(
098                callback.getClass(),
099                "call",
100                Object.class
101        ), listener);
102
103        synchronized (listeners) {
104            listeners.add(handler);
105        }
106
107        return listener.toString();
108    }
109
110    private String registerHandler(Object bean, Method callback, String namespace, String topic) {
111        if (log.isDebugEnabled()) {
112            log.debug("Registering event handler on bean [" + bean + "] method [" + callback + "] for topic [" + topic + "]");
113        }
114
115        ListenerId listener = ListenerId.build(namespace, topic, bean, callback);
116
117        ListenerHandler handler = new ListenerHandler(bean, callback, listener);
118
119        synchronized (listeners) {
120            listeners.add(handler);
121        }
122
123        return listener.toString();
124    }
125
126    private Set<ListenerHandler> findAll(ListenerId listener) {
127        if (log.isDebugEnabled()) {
128            log.debug("Finding listeners matching listener id [" + listener.toString() + "]");
129        }
130        Set<ListenerHandler> listeners =
131                new HashSet<ListenerHandler>();
132
133        for (ListenerHandler _listener : this.listeners) {
134            if (listener.matches(_listener.getListenerId())) {
135                listeners.add(_listener);
136            }
137        }
138
139        return listeners;
140    }
141
142    public InvokeResult invokeListeners(EventMessage evt) {
143        if (log.isDebugEnabled()) {
144            log.debug("Invoking listeners for event [" + evt.getEvent() + "] namespaced on [" + evt.getNamespace() + "] with data [" + evt.getData() + "]");
145        }
146        ListenerId listener = new ListenerId(evt.getNamespace(), evt.getEvent());
147        Set<ListenerHandler> listeners = findAll(listener);
148
149        if (log.isDebugEnabled()) {
150            log.debug("Found " + listeners.size() + " listeners for event [" + evt.getEvent() + "] with data [" + evt.getData() + "]");
151        }
152        List<Object> results = new ArrayList<Object>();
153        Object result;
154        for (ListenerHandler _listener : listeners) {
155            if (log.isDebugEnabled()) {
156                log.debug("Invoking listener [" + _listener.bean.getClass() + '.' + _listener.method.getName() + "(arg)] for event [" + evt.getEvent() + "] with data [" + evt.getData() + "]");
157            }
158            try {
159                result = _listener.invoke(evt);
160            } catch (Throwable throwable) {
161                result = throwable;
162            }
163            if (result != null) results.add(result);
164        }
165
166        Object resultValues = null;
167        // Make sure no-result does not cause an error
168        if (results.size() >= 1) {
169            if (results.size() != 1) {
170                resultValues = results;
171            } else {
172                resultValues = results.get(0);
173            }
174        }
175        return new InvokeResult(results.size(), resultValues);
176    }
177
178    public class InvokeResult {
179        private int invoked;
180        private Object result;
181
182        public int getInvoked() {
183            return invoked;
184        }
185
186        public Object getResult() {
187            return result;
188        }
189
190        public InvokeResult(int invoked, Object result) {
191            this.invoked = invoked;
192            this.result = result;
193        }
194    }
195
196    private static class ListenerHandler implements EventHandler {
197        private Object bean;
198        private Method method;
199        private ListenerId listenerId;
200        private boolean useEventMessage = false;
201        private boolean noArgs = false;
202
203        public ListenerHandler(Object bean, Method m, ListenerId listenerId) {
204            this.listenerId = listenerId;
205            this.method = m;
206
207            if (m.getParameterTypes().length > 0) {
208                Class type = m.getParameterTypes()[0];
209                useEventMessage = EventMessage.class.isAssignableFrom(type);
210                if (useEventMessage && log.isDebugEnabled()) {
211                    log.debug("Listener " + bean.getClass() + "." + method.getName() + " will receive EventMessage enveloppe");
212                }
213            } else {
214                noArgs = true;
215            }
216            this.bean = bean;
217            //this.mapping = mapping;
218        }
219
220        public Object invoke(EventMessage _arg) throws Throwable {
221            Object res = null;
222
223            Object arg = this.isUseEventMessage() ? _arg : _arg.getData();
224
225            if (log.isDebugEnabled()) {
226                StringBuilder argTypes = new StringBuilder();
227                for (Object e : method.getParameterTypes()) {
228                    argTypes.append(e.toString());
229                    argTypes.append(',');
230                }
231                log.debug("About to invoke listener method " + bean.getClass() + "." + method.getName() + " with arg type " + argTypes +
232                        " with arg " + arg.toString());
233            }
234            try {
235                if (noArgs) {
236                    res = method.invoke(bean);
237                } else {
238                    res = method.invoke(bean, arg);
239                }
240            } catch (IllegalArgumentException e) {
241                //ignoring
242                if (log.isDebugEnabled()) {
243                    log.debug("Ignoring call to " + bean.getClass() + "." + method.getName() + " with args " + arg.toString() + " - illegal arg exception: " + e.toString());
244                }
245            } catch (Throwable e) {
246                if (log.isDebugEnabled()) {
247                    log.debug("Failing call to " + bean.getClass() + "." + method.getName() + " with args " + arg.toString() + " : " + e.toString());
248                }
249                throw e;
250            }
251
252            return res;
253        }
254
255        public ListenerId getListenerId() {
256            return listenerId;
257        }
258
259        public boolean isUseEventMessage() {
260            return useEventMessage;
261        }
262    }
263
264
265}