001/* 002 * Copyright (c) 2006-2011 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 * bstefanescu 011 */ 012package org.nuxeo.ecm.automation.core.events; 013 014import java.util.ArrayList; 015import java.util.Collections; 016import java.util.HashSet; 017import java.util.List; 018import java.util.Set; 019 020import org.apache.commons.logging.Log; 021import org.apache.commons.logging.LogFactory; 022import org.mvel2.CompileException; 023import org.nuxeo.common.utils.StringUtils; 024import org.nuxeo.common.xmap.annotation.XNode; 025import org.nuxeo.common.xmap.annotation.XNodeList; 026import org.nuxeo.common.xmap.annotation.XObject; 027import org.nuxeo.ecm.automation.OperationContext; 028import org.nuxeo.ecm.automation.core.scripting.Expression; 029import org.nuxeo.ecm.automation.core.scripting.Scripting; 030import org.nuxeo.ecm.core.api.DocumentModel; 031import org.nuxeo.ecm.core.api.Filter; 032import org.nuxeo.ecm.core.api.NuxeoPrincipal; 033import org.nuxeo.ecm.core.event.EventContext; 034 035/** 036 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 037 */ 038@XObject("handler") 039public class EventHandler { 040 041 private static final Log log = LogFactory.getLog(EventHandler.class); 042 043 @XNode("@chainId") 044 protected String chainId; 045 046 @XNode("@postCommit") 047 protected boolean isPostCommit; 048 049 @XNodeList(value = "event", type = HashSet.class, componentType = String.class) 050 protected Set<String> events; 051 052 @XNodeList(value = "filters/doctype", type = HashSet.class, componentType = String.class, nullByDefault = true) 053 protected Set<String> doctypes; 054 055 @XNode("filters/facet") 056 protected String facet; 057 058 @XNode("filters/lifeCycle") 059 protected void setLifeCycleExpr(String lifeCycles) { 060 lifeCycle = StringUtils.split(lifeCycles, ',', true); 061 } 062 063 protected String[] lifeCycle; 064 065 @XNode("filters/pathStartsWith") 066 protected String pathStartsWith; 067 068 protected Filter attribute; 069 070 @XNode("filters/attribute") 071 public void setAttribute(String attribute) { 072 this.attribute = DocumentAttributeFilterFactory.getFilter(attribute); 073 } 074 075 /** 076 * the principal should be member of at least one of the groups. OR is used 077 */ 078 @XNodeList(value = "filters/group", type = ArrayList.class, componentType = String.class) 079 protected List<String> memberOf; 080 081 @XNode("filters/isAdministrator") 082 protected Boolean isAdministrator; 083 084 /** 085 * @deprecated since 5.7: expression evaluation was inverted so a new attribute condition has been defined, see 086 * NXP-8630 087 */ 088 @Deprecated 089 protected String expression; 090 091 /** 092 * @since 5.7: added to replace the 'expression' element as its evaluation is inverted 093 */ 094 protected String condition; 095 096 @XNode("filters/expression") 097 protected void _setExpression(String expr) { 098 expression = convertExpr(expr); 099 } 100 101 @XNode("filters/condition") 102 protected void _setCondition(String expr) { 103 condition = convertExpr(expr); 104 } 105 106 protected String convertExpr(String expr) { 107 String res = expr.replaceAll("<", "<"); 108 res = res.replaceAll(">", ">"); 109 res = res.replaceAll("&", "&"); 110 return res; 111 } 112 113 public EventHandler() { 114 } 115 116 public EventHandler(String eventId, String chainId) { 117 this(Collections.singleton(eventId), chainId); 118 } 119 120 public EventHandler(Set<String> eventId, String chainId) { 121 events = eventId; 122 this.chainId = chainId; 123 } 124 125 public Set<String> getEvents() { 126 return events; 127 } 128 129 public String getChainId() { 130 return chainId; 131 } 132 133 public void setPostCommit(boolean isPostCommit) { 134 this.isPostCommit = isPostCommit; 135 } 136 137 public boolean isPostCommit() { 138 return isPostCommit; 139 } 140 141 public void setAttributeFilter(Filter attribute) { 142 this.attribute = attribute; 143 } 144 145 public void setIsAdministrator(Boolean isAdministrator) { 146 this.isAdministrator = isAdministrator; 147 } 148 149 public void setMemberOf(List<String> groups) { 150 memberOf = groups; 151 } 152 153 public void setPathStartsWith(String pathStartsWith) { 154 this.pathStartsWith = pathStartsWith; 155 } 156 157 /** 158 * @deprecated since 5.7.1 use {@link #setCondition(String)} instead 159 */ 160 @Deprecated 161 public void setExpression(String expression) { 162 this.expression = expression; 163 } 164 165 public void setDoctypes(Set<String> doctypes) { 166 this.doctypes = doctypes; 167 } 168 169 public void setFacet(String facet) { 170 this.facet = facet; 171 } 172 173 public void setLifeCycle(String[] lifeCycle) { 174 this.lifeCycle = lifeCycle; 175 } 176 177 public void setChainId(String chainId) { 178 this.chainId = chainId; 179 } 180 181 /** 182 * @deprecated since 5.7: use {@link #getCondition()} instead 183 */ 184 @Deprecated 185 public String getExpression() { 186 return expression; 187 } 188 189 /** 190 * Condition to define on event handler 191 * 192 * @since 5.7 193 */ 194 public String getCondition() { 195 return condition; 196 } 197 198 /** 199 * @since 5.9.1 200 */ 201 public void setCondition(String condition) { 202 this.condition = condition; 203 } 204 205 public String getFacet() { 206 return facet; 207 } 208 209 public Filter getAttribute() { 210 return attribute; 211 } 212 213 public String[] getLifeCycle() { 214 return lifeCycle; 215 } 216 217 public List<String> getMemberOf() { 218 return memberOf; 219 } 220 221 public Boolean getIsAdministrator() { 222 return isAdministrator; 223 } 224 225 public String getPathStartsWith() { 226 return pathStartsWith; 227 } 228 229 public Set<String> getDoctypes() { 230 return doctypes; 231 } 232 233 /** 234 * @deprecated since 5.7: use 235 */ 236 @Deprecated 237 public Expression getExpr() { 238 return Scripting.newExpression(expression); 239 } 240 241 /** 242 * Checks if this handler should run for the event and operation context. 243 * 244 * @param quick If {@code true}, then this method may not check all filter parameters like {@code filter/expression} 245 * and just return {@code true} to avoid costly evaluations on {@link ShallowDocumentModel} instances 246 */ 247 public boolean isEnabled(OperationContext ctx, EventContext eventCtx, boolean quick) { 248 Object obj = ctx.getInput(); 249 DocumentModel doc = null; 250 if (obj instanceof DocumentModel) { 251 doc = (DocumentModel) obj; 252 } 253 if (doctypes != null) { 254 if (doc == null || (!doctypes.isEmpty() && !doctypes.contains(doc.getType()))) { 255 return false; 256 } 257 } 258 if (facet != null) { 259 if (doc == null || !doc.hasFacet(facet)) { 260 return false; 261 } 262 } 263 if (lifeCycle != null && lifeCycle.length > 0) { 264 if (doc == null) { 265 return false; 266 } 267 boolean match = false; 268 String currentLc = doc.getCurrentLifeCycleState(); 269 for (String lc : lifeCycle) { 270 if (lc.equals(currentLc)) { 271 match = true; 272 break; 273 } 274 } 275 if (!match) { 276 return false; 277 } 278 } 279 if (attribute != null) { 280 if (doc == null || !attribute.accept(doc)) { 281 return false; 282 } 283 } 284 if (pathStartsWith != null) { 285 if (doc == null || !doc.getPathAsString().startsWith(pathStartsWith)) { 286 return false; 287 } 288 } 289 if (memberOf != null && !memberOf.isEmpty()) { 290 NuxeoPrincipal p = (NuxeoPrincipal) eventCtx.getPrincipal(); 291 boolean granted = false; 292 for (String group : memberOf) { 293 if (p.isMemberOf(group)) { 294 granted = true; 295 break; 296 } 297 } 298 if (!granted) { 299 return false; 300 } 301 } 302 if (isAdministrator != null) { 303 if (!((NuxeoPrincipal) eventCtx.getPrincipal()).isAdministrator()) { 304 return false; 305 } 306 } 307 if (quick) { 308 return true; 309 } 310 /* 311 * The following are not evaluated in quick mode, as we need a full DocumentModelImpl to evaluate most 312 * expressions. 313 */ 314 if (!org.apache.commons.lang.StringUtils.isBlank(condition)) { 315 Expression expr = Scripting.newExpression(condition); 316 try { 317 if (!Boolean.TRUE.equals(expr.eval(ctx))) { 318 return false; 319 } 320 } catch (CompileException e) { 321 // happens for expressions evaluated over a DeletedDocumentModel for instance 322 log.debug("Failed to execute expression: " + e, e); 323 return false; 324 } 325 } else if (!org.apache.commons.lang.StringUtils.isBlank(expression)) { 326 // BBB 327 if (log.isWarnEnabled()) { 328 log.warn(String.format("The 'expression' element with value '%s' " 329 + "on event handler for chain '%s' is deprecated: please use the 'condition'" 330 + " attribute instead, as its evaluation will not be inverted.", expression, chainId)); 331 } 332 Expression expr = Scripting.newExpression(expression); 333 if (Boolean.TRUE.equals(expr.eval(ctx))) { 334 return false; 335 } 336 } 337 return true; 338 } 339}