001/*
002 * (C) Copyright 2017-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 *     Funsho David
018 *     Kevin Leturc
019 *
020 */
021
022package org.nuxeo.ecm.core.versioning;
023
024import static org.nuxeo.ecm.platform.el.ELConstants.CURRENT_DOCUMENT;
025import static org.nuxeo.ecm.platform.el.ELConstants.CURRENT_USER;
026import static org.nuxeo.ecm.platform.el.ELConstants.DOCUMENT;
027import static org.nuxeo.ecm.platform.el.ELConstants.PREVIOUS_DOCUMENT;
028import static org.nuxeo.ecm.platform.el.ELConstants.PRINCIPAL;
029
030import java.util.Collection;
031
032import javax.el.ELContext;
033import javax.el.ExpressionFactory;
034import javax.el.ValueExpression;
035import javax.el.VariableMapper;
036
037import org.apache.commons.lang3.StringUtils;
038import org.jboss.el.ExpressionFactoryImpl;
039import org.nuxeo.ecm.core.api.DocumentModel;
040import org.nuxeo.ecm.core.api.NuxeoPrincipal;
041import org.nuxeo.ecm.core.schema.DocumentType;
042import org.nuxeo.ecm.platform.el.ELService;
043import org.nuxeo.runtime.api.Framework;
044
045/**
046 * @since 9.1
047 */
048public class StandardVersioningPolicyFilter implements VersioningPolicyFilter {
049
050    protected Collection<String> types;
051
052    protected Collection<String> facets;
053
054    protected Collection<String> schemas;
055
056    protected String condition;
057
058    public StandardVersioningPolicyFilter(Collection<String> types, Collection<String> facets,
059            Collection<String> schemas, String condition) {
060        this.types = types;
061        this.facets = facets;
062        this.schemas = schemas;
063        this.condition = condition;
064    }
065
066    @Override
067    public boolean test(DocumentModel previousDocument, DocumentModel currentDocument) {
068        if (!types.isEmpty() && !types.contains(currentDocument.getType())) {
069            return false;
070        }
071        DocumentType docType = currentDocument.getDocumentType();
072        if (!schemas.isEmpty() && schemas.stream().noneMatch(docType::hasSchema)) {
073            return false;
074        }
075        if (!facets.isEmpty() && facets.stream().noneMatch(docType::hasFacet)) {
076            return false;
077        }
078        if (!StringUtils.isBlank(condition)) {
079
080            String cond = evaluateCondition(condition);
081
082            ELContext context = Framework.getService(ELService.class).createELContext();
083            ExpressionFactory expressionFactory = new ExpressionFactoryImpl();
084
085            VariableMapper vm = context.getVariableMapper();
086
087            // init default variables
088            ValueExpression previousDocExpr = expressionFactory.createValueExpression(previousDocument,
089                    DocumentModel.class);
090            ValueExpression currentDocExpr = expressionFactory.createValueExpression(currentDocument,
091                    DocumentModel.class);
092            ValueExpression userExpr = expressionFactory.createValueExpression(NuxeoPrincipal.getCurrent(),
093                    NuxeoPrincipal.class);
094            vm.setVariable(PREVIOUS_DOCUMENT, previousDocExpr);
095            vm.setVariable(CURRENT_DOCUMENT, currentDocExpr);
096            vm.setVariable(DOCUMENT, currentDocExpr);
097            vm.setVariable(PRINCIPAL, userExpr);
098            vm.setVariable(CURRENT_USER, userExpr);
099
100            // evaluate expression
101            ValueExpression ve = expressionFactory.createValueExpression(context, cond, Boolean.class);
102            return Boolean.TRUE.equals(ve.getValue(context));
103        }
104        return true;
105    }
106
107    /**
108     * Evaluate and build a valid condition
109     *
110     * @param condition the initial condition
111     */
112    public static String evaluateCondition(String condition) {
113
114        String cond = condition.trim();
115        // compatibility code, as JEXL could resolve that kind of expression:
116        // detect if expression is in brackets #{}, otherwise add it
117        if (!cond.startsWith("#{") && !cond.startsWith("${") && !cond.endsWith("}")) {
118            cond = "#{" + cond + "}";
119        }
120
121        // Check if there is a null/not-null evaluation on previousDocument, if not
122        // Add a not-null evaluation on it to prevent NPE
123        String p1 = ".*" + PREVIOUS_DOCUMENT + "\\..+";
124        String p2 = ".*" + PREVIOUS_DOCUMENT + "\\s*[!=]=\\s*null.*";
125        if (cond.matches(p1) && !cond.matches(p2)) {
126            cond = "#{" + PREVIOUS_DOCUMENT + " != null && (" + cond.substring(2, cond.length() - 1) + ")}";
127        }
128        return cond;
129    }
130}