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