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}