/*
* (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* bstefanescu
*/
package org.nuxeo.ecm.automation.core.events;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mvel2.CompileException;
import org.nuxeo.common.utils.StringUtils;
import org.nuxeo.common.xmap.annotation.XNode;
import org.nuxeo.common.xmap.annotation.XNodeList;
import org.nuxeo.common.xmap.annotation.XObject;
import org.nuxeo.ecm.automation.OperationContext;
import org.nuxeo.ecm.automation.core.scripting.Expression;
import org.nuxeo.ecm.automation.core.scripting.Scripting;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.Filter;
import org.nuxeo.ecm.core.api.NuxeoPrincipal;
import org.nuxeo.ecm.core.event.EventContext;
import org.nuxeo.ecm.core.event.impl.ShallowDocumentModel;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
@XObject("handler")
public class EventHandler {
private static final Log log = LogFactory.getLog(EventHandler.class);
@XNode("@chainId")
protected String chainId;
@XNode("@postCommit")
protected boolean isPostCommit;
@XNodeList(value = "event", type = HashSet.class, componentType = String.class)
protected Set<String> events;
@XNodeList(value = "filters/doctype", type = HashSet.class, componentType = String.class, nullByDefault = true)
protected Set<String> doctypes;
@XNode("filters/facet")
protected String facet;
@XNode("filters/lifeCycle")
protected void setLifeCycleExpr(String lifeCycles) {
lifeCycle = StringUtils.split(lifeCycles, ',', true);
}
protected String[] lifeCycle;
@XNode("filters/pathStartsWith")
protected String pathStartsWith;
protected Filter attribute;
@XNode("filters/attribute")
public void setAttribute(String attribute) {
this.attribute = DocumentAttributeFilterFactory.getFilter(attribute);
}
/**
* the principal should be member of at least one of the groups. OR is used
*/
@XNodeList(value = "filters/group", type = ArrayList.class, componentType = String.class)
protected List<String> memberOf;
@XNode("filters/isAdministrator")
protected Boolean isAdministrator;
/**
* @since 5.7: added to replace the 'expression' element as its evaluation is inverted
*/
protected String condition;
@XNode("filters/condition")
protected void _setCondition(String expr) {
condition = convertExpr(expr);
}
protected String convertExpr(String expr) {
String res = expr.replaceAll("<", "<");
res = res.replaceAll(">", ">");
res = res.replaceAll("&", "&");
return res;
}
public EventHandler() {
}
public EventHandler(String eventId, String chainId) {
this(Collections.singleton(eventId), chainId);
}
public EventHandler(Set<String> eventId, String chainId) {
events = eventId;
this.chainId = chainId;
}
public Set<String> getEvents() {
return events;
}
public String getChainId() {
return chainId;
}
public void setPostCommit(boolean isPostCommit) {
this.isPostCommit = isPostCommit;
}
public boolean isPostCommit() {
return isPostCommit;
}
public void setAttributeFilter(Filter attribute) {
this.attribute = attribute;
}
public void setIsAdministrator(Boolean isAdministrator) {
this.isAdministrator = isAdministrator;
}
public void setMemberOf(List<String> groups) {
memberOf = groups;
}
public void setPathStartsWith(String pathStartsWith) {
this.pathStartsWith = pathStartsWith;
}
public void setDoctypes(Set<String> doctypes) {
this.doctypes = doctypes;
}
public void setFacet(String facet) {
this.facet = facet;
}
public void setLifeCycle(String[] lifeCycle) {
this.lifeCycle = lifeCycle;
}
public void setChainId(String chainId) {
this.chainId = chainId;
}
/**
* Condition to define on event handler
*
* @since 5.7
*/
public String getCondition() {
return condition;
}
/**
* @since 5.9.1
*/
public void setCondition(String condition) {
this.condition = condition;
}
public String getFacet() {
return facet;
}
public Filter getAttribute() {
return attribute;
}
public String[] getLifeCycle() {
return lifeCycle;
}
public List<String> getMemberOf() {
return memberOf;
}
public Boolean getIsAdministrator() {
return isAdministrator;
}
public String getPathStartsWith() {
return pathStartsWith;
}
public Set<String> getDoctypes() {
return doctypes;
}
/**
* Checks if this handler should run for the event and operation context.
*
* @param quick If {@code true}, then this method may not check all filter parameters like {@code filter/expression}
* and just return {@code true} to avoid costly evaluations on {@link ShallowDocumentModel} instances
*/
public boolean isEnabled(OperationContext ctx, EventContext eventCtx, boolean quick) {
Object obj = ctx.getInput();
DocumentModel doc = null;
if (obj instanceof DocumentModel) {
doc = (DocumentModel) obj;
}
if (doctypes != null) {
if (doc == null || (!doctypes.isEmpty() && !doctypes.contains(doc.getType()))) {
return false;
}
}
if (facet != null) {
if (doc == null || !doc.hasFacet(facet)) {
return false;
}
}
if (lifeCycle != null && lifeCycle.length > 0) {
if (doc == null) {
return false;
}
boolean match = false;
String currentLc = doc.getCurrentLifeCycleState();
for (String lc : lifeCycle) {
if (lc.equals(currentLc)) {
match = true;
break;
}
}
if (!match) {
return false;
}
}
if (attribute != null) {
if (doc == null || !attribute.accept(doc)) {
return false;
}
}
if (pathStartsWith != null) {
if (doc == null || !doc.getPathAsString().startsWith(pathStartsWith)) {
return false;
}
}
if (memberOf != null && !memberOf.isEmpty()) {
NuxeoPrincipal p = (NuxeoPrincipal) eventCtx.getPrincipal();
boolean granted = false;
for (String group : memberOf) {
if (p.isMemberOf(group)) {
granted = true;
break;
}
}
if (!granted) {
return false;
}
}
if (isAdministrator != null) {
if (!((NuxeoPrincipal) eventCtx.getPrincipal()).isAdministrator()) {
return false;
}
}
if (quick) {
return true;
}
/*
* The following are not evaluated in quick mode, as we need a full DocumentModelImpl to evaluate most
* expressions.
*/
if (!org.apache.commons.lang.StringUtils.isBlank(condition)) {
Expression expr = Scripting.newExpression(condition);
try {
if (!Boolean.TRUE.equals(expr.eval(ctx))) {
return false;
}
} catch (CompileException e) {
// happens for expressions evaluated over a DeletedDocumentModel for instance
log.debug("Failed to execute expression: " + e, e);
return false;
}
}
return true;
}
}