001/*
002 * (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and others.
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 * Contributors:
017 *     Bogdan Stefanescu
018 *     Florent Guillaume
019 */
020package org.nuxeo.ecm.core.event.impl;
021
022import java.io.IOException;
023import java.net.URL;
024import java.util.HashSet;
025import java.util.Set;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029import org.nuxeo.common.xmap.annotation.XNode;
030import org.nuxeo.common.xmap.annotation.XNodeList;
031import org.nuxeo.common.xmap.annotation.XObject;
032import org.nuxeo.ecm.core.event.Event;
033import org.nuxeo.ecm.core.event.EventBundle;
034import org.nuxeo.ecm.core.event.EventListener;
035import org.nuxeo.ecm.core.event.PostCommitEventListener;
036import org.nuxeo.ecm.core.event.PostCommitFilteringEventListener;
037import org.nuxeo.ecm.core.event.script.Script;
038import org.nuxeo.ecm.core.event.script.ScriptingEventListener;
039import org.nuxeo.ecm.core.event.script.ScriptingPostCommitEventListener;
040import org.nuxeo.runtime.model.RuntimeContext;
041
042/**
043 * XObject descriptor to declare event listeners
044 */
045@XObject("listener")
046public class EventListenerDescriptor {
047
048    public static final Log log = LogFactory.getLog(EventListenerDescriptor.class);
049
050    @XNode("@name")
051    protected String name;
052
053    /**
054     * The event listener class.
055     */
056    @XNode("@class")
057    protected String className;
058
059    /**
060     * A script reference: URL, file path, or bundle entry. Runtime variable are expanded. To specify a bundle entry use
061     * the URL schema "bundle:"
062     */
063    @XNode("@script")
064    protected String script;
065
066    /**
067     * Applies only for scripts.
068     */
069    @XNode("@postCommit")
070    protected boolean isPostCommit;
071
072    /**
073     * Applies only for post commit listener
074     */
075    @XNode("@async")
076    protected Boolean isAsync;
077
078    @XNode("@transactionTimeOut")
079    protected Integer transactionTimeOut;
080
081    /**
082     * The priority to be used to order listeners.
083     */
084    @XNode("@priority")
085    protected Integer priority;
086
087    @XNode("@enabled")
088    protected boolean isEnabled = true;
089
090    @XNode("@retryCount")
091    protected Integer retryCount;
092
093    @XNode("@singlethread")
094    protected boolean singleThreaded = false;
095
096    protected Set<String> events;
097
098    protected RuntimeContext rc;
099
100    protected EventListener inLineListener;
101
102    protected PostCommitEventListener postCommitEventListener;
103
104    public int getPriority() {
105        return priority == null ? 0 : priority.intValue();
106    }
107
108    public void setRuntimeContext(RuntimeContext rc) {
109        this.rc = rc;
110    }
111
112    public RuntimeContext getRuntimeContext() {
113        return rc;
114    }
115
116    public boolean isEnabled() {
117        return isEnabled;
118    }
119
120    public Integer getRetryCount() {
121        return retryCount;
122    }
123
124    public Set<String> getEvents() {
125        return events;
126    }
127
128    @XNodeList(value = "event", componentType = String.class, type = HashSet.class, nullByDefault = true)
129    public void setEvents(Set<String> events) {
130        this.events = events.isEmpty() ? null : events;
131    }
132
133    public void setEnabled(boolean isEnabled) {
134        this.isEnabled = isEnabled;
135    }
136
137    public void setRetryCount(Integer retryCount) {
138        this.retryCount = retryCount;
139    }
140
141    public void initListener() {
142        try {
143            if (className != null) {
144                Class<?> klass;
145                try {
146                    klass = getRuntimeContext().loadClass(className);
147                } catch (ClassNotFoundException e) {
148                    throw new RuntimeException(e);
149                }
150                if (EventListener.class.isAssignableFrom(klass)) {
151                    inLineListener = (EventListener) klass.newInstance();
152                    isPostCommit = false;
153                } else if (PostCommitEventListener.class.isAssignableFrom(klass)) {
154                    postCommitEventListener = (PostCommitEventListener) klass.newInstance();
155                    isPostCommit = true;
156                } else {
157                    throw new IllegalArgumentException(
158                            "Listener extension must define a class extending EventListener or PostCommitEventListener: '"
159                                    + className + "'.");
160                }
161            } else if (script != null) {
162                if (isPostCommit) {
163                    postCommitEventListener = new ScriptingPostCommitEventListener(getScript());
164                } else {
165                    inLineListener = new ScriptingEventListener(getScript());
166                }
167            } else {
168                throw new IllegalArgumentException("Listener extension must define either a class or a script");
169            }
170        } catch (ReflectiveOperationException | NoClassDefFoundError | IOException e) {
171            throw new RuntimeException(e);
172        }
173    }
174
175    public EventListener asEventListener() {
176        return inLineListener;
177    }
178
179    public PostCommitEventListener asPostCommitListener() {
180        return postCommitEventListener;
181    }
182
183    public Script getScript() throws IOException {
184        if (rc != null) {
185            URL url = rc.getBundle().getEntry(script);
186            if (url == null) {
187                // if not found using bundle entries try using classloader
188                // in a test environment bundle entries may not work
189                url = rc.getResource(script);
190                if (url == null) {
191                    throw new IOException("Script Not found: " + script);
192                }
193            }
194            return Script.newScript(url);
195        } else {
196            return Script.newScript(script);
197        }
198    }
199
200    public String getName() {
201        if (name == null) {
202            if (className != null) {
203                name = className;
204            } else {
205                name = script;
206            }
207        }
208        return name;
209    }
210
211    public Integer getTransactionTimeout() {
212        return transactionTimeOut;
213    }
214
215    public void merge(EventListenerDescriptor other) {
216
217        isEnabled = other.isEnabled;
218
219        if (other.className != null) {
220            className = other.className;
221            rc = other.rc;
222        } else if (other.script != null) {
223            script = other.script;
224            className = null;
225            rc = other.rc;
226        }
227
228        if (other.isAsync != null) {
229            isAsync = other.isAsync;
230        }
231
232        if (other.events != null) {
233            events = other.events;
234        }
235
236        if (other.transactionTimeOut != null) {
237            transactionTimeOut = other.transactionTimeOut;
238        }
239
240        if (other.priority != null) {
241            priority = other.priority;
242        }
243
244        if (other.retryCount != null) {
245            retryCount = other.retryCount;
246        }
247    }
248
249    public final boolean acceptEvent(String eventName) {
250        return events == null || events.contains(eventName);
251    }
252
253    public void setIsAsync(Boolean isAsync) {
254        this.isAsync = isAsync;
255    }
256
257    public boolean getIsAsync() {
258        return isAsync == null ? false : isAsync.booleanValue();
259    }
260
261    public boolean isSingleThreaded() {
262        return singleThreaded;
263    }
264
265    /**
266     * Filters the event bundle to only keep events of interest to this listener.
267     *
268     * @since 5.7
269     */
270    public EventBundle filterBundle(EventBundle bundle) {
271        EventBundle filtered = new EventBundleImpl();
272
273        for (Event event : bundle) {
274            if (!acceptEvent(event.getName())) {
275                continue;
276            }
277            PostCommitEventListener pcl = asPostCommitListener();
278            if (pcl instanceof PostCommitFilteringEventListener
279                    && !((PostCommitFilteringEventListener) pcl).acceptEvent(event)) {
280                continue;
281            }
282            filtered.push(event);
283        }
284        return filtered;
285    }
286
287    /**
288     * Checks if there's at least one event of interest in the bundle.
289     *
290     * @since 5.7
291     */
292    public boolean acceptBundle(EventBundle bundle) {
293        for (Event event : bundle) {
294            if (!acceptEvent(event.getName())) {
295                continue;
296            }
297            PostCommitEventListener pcl = asPostCommitListener();
298            if (pcl instanceof PostCommitFilteringEventListener
299                    && !((PostCommitFilteringEventListener) pcl).acceptEvent(event)) {
300                continue;
301            }
302            return true;
303        }
304        return false;
305    }
306
307}