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 this.isEnabled = other.isEnabled; 208 209 if (other.clazz != null) { 210 this.clazz = other.clazz; 211 this.rc = other.rc; 212 } else if (other.script != null) { 213 this.script = other.script; 214 this.clazz = null; 215 this.rc = other.rc; 216 } 217 218 if (other.isAsync != null) { 219 this.isAsync = other.isAsync; 220 } 221 222 if (other.events != null) { 223 this.events = other.events; 224 } 225 226 if (other.transactionTimeOut != null) { 227 this.transactionTimeOut = other.transactionTimeOut; 228 } 229 230 if (other.priority != null) { 231 this.priority = other.priority; 232 } 233 234 if (other.retryCount != null) { 235 this.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 for (Event event : bundle) { 263 if (!acceptEvent(event.getName())) { 264 continue; 265 } 266 PostCommitEventListener pcl = asPostCommitListener(); 267 if (pcl instanceof PostCommitFilteringEventListener 268 && !((PostCommitFilteringEventListener) pcl).acceptEvent(event)) { 269 continue; 270 } 271 filtered.push(event); 272 } 273 return filtered; 274 } 275 276 /** 277 * Checks if there's at least one event of interest in the bundle. 278 * 279 * @since 5.7 280 */ 281 public boolean acceptBundle(EventBundle bundle) { 282 for (Event event : bundle) { 283 if (!acceptEvent(event.getName())) { 284 continue; 285 } 286 PostCommitEventListener pcl = asPostCommitListener(); 287 if (pcl instanceof PostCommitFilteringEventListener 288 && !((PostCommitFilteringEventListener) pcl).acceptEvent(event)) { 289 continue; 290 } 291 return true; 292 } 293 return false; 294 } 295 296}