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