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