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