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 Class<?> clazz;
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 (clazz != null) {
144                if (EventListener.class.isAssignableFrom(clazz)) {
145                    inLineListener = (EventListener) clazz.newInstance();
146                    isPostCommit = false;
147                } else if (PostCommitEventListener.class.isAssignableFrom(clazz)) {
148                    postCommitEventListener = (PostCommitEventListener) clazz.newInstance();
149                    isPostCommit = true;
150                }
151            } else if (script != null) {
152                if (isPostCommit) {
153                    postCommitEventListener = new ScriptingPostCommitEventListener(getScript());
154                } else {
155                    inLineListener = new ScriptingEventListener(getScript());
156                }
157            } else {
158                throw new IllegalArgumentException("Listener extension must define either a class or a script");
159            }
160        } catch (ReflectiveOperationException | IOException e) {
161            throw new RuntimeException(e);
162        }
163    }
164
165    public EventListener asEventListener() {
166        return inLineListener;
167    }
168
169    public PostCommitEventListener asPostCommitListener() {
170        return postCommitEventListener;
171    }
172
173    public Script getScript() throws IOException {
174        if (rc != null) {
175            URL url = rc.getBundle().getEntry(script);
176            if (url == null) {
177                // if not found using bundle entries try using classloader
178                // in a test environment bundle entries may not work
179                url = rc.getResource(script);
180                if (url == null) {
181                    throw new IOException("Script Not found: " + script);
182                }
183            }
184            return Script.newScript(url);
185        } else {
186            return Script.newScript(script);
187        }
188    }
189
190    public String getName() {
191        if (name == null) {
192            if (clazz != null) {
193                name = clazz.getSimpleName();
194            } else {
195                name = script;
196            }
197        }
198        return name;
199    }
200
201    public Integer getTransactionTimeout() {
202        return transactionTimeOut;
203    }
204
205    public void merge(EventListenerDescriptor other) {
206
207        isEnabled = other.isEnabled;
208
209        if (other.clazz != null) {
210            clazz = other.clazz;
211            rc = other.rc;
212        } else if (other.script != null) {
213            script = other.script;
214            clazz = null;
215            rc = other.rc;
216        }
217
218        if (other.isAsync != null) {
219            isAsync = other.isAsync;
220        }
221
222        if (other.events != null) {
223            events = other.events;
224        }
225
226        if (other.transactionTimeOut != null) {
227            transactionTimeOut = other.transactionTimeOut;
228        }
229
230        if (other.priority != null) {
231            priority = other.priority;
232        }
233
234        if (other.retryCount != null) {
235            retryCount = other.retryCount;
236        }
237    }
238
239    public final boolean acceptEvent(String eventName) {
240        return events == null || events.contains(eventName);
241    }
242
243    public void setIsAsync(Boolean isAsync) {
244        this.isAsync = isAsync;
245    }
246
247    public boolean getIsAsync() {
248        return isAsync == null ? false : isAsync.booleanValue();
249    }
250
251    public boolean isSingleThreaded() {
252        return singleThreaded;
253    }
254
255    /**
256     * Filters the event bundle to only keep events of interest to this listener.
257     *
258     * @since 5.7
259     */
260    public EventBundle filterBundle(EventBundle bundle) {
261        EventBundle filtered = new EventBundleImpl();
262
263        for (Event event : bundle) {
264            if (!acceptEvent(event.getName())) {
265                continue;
266            }
267            PostCommitEventListener pcl = asPostCommitListener();
268            if (pcl instanceof PostCommitFilteringEventListener
269                    && !((PostCommitFilteringEventListener) pcl).acceptEvent(event)) {
270                continue;
271            }
272            filtered.push(event);
273        }
274        return filtered;
275    }
276
277    /**
278     * Checks if there's at least one event of interest in the bundle.
279     *
280     * @since 5.7
281     */
282    public boolean acceptBundle(EventBundle bundle) {
283        for (Event event : bundle) {
284            if (!acceptEvent(event.getName())) {
285                continue;
286            }
287            PostCommitEventListener pcl = asPostCommitListener();
288            if (pcl instanceof PostCommitFilteringEventListener
289                    && !((PostCommitFilteringEventListener) pcl).acceptEvent(event)) {
290                continue;
291            }
292            return true;
293        }
294        return false;
295    }
296
297}