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("&lt;", "<");
108        res = res.replaceAll("&gt;", ">");
109        res = res.replaceAll("&amp;", "&");
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}