001/*
002 * (C) Copyright 2013 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 *     Mariana Cedica
018 */
019package org.nuxeo.ecm.platform.routing.core.impl;
020
021import java.io.Serializable;
022import java.util.ArrayList;
023import java.util.List;
024import java.util.Map;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.nuxeo.ecm.automation.OperationContext;
029import org.nuxeo.ecm.core.api.CoreSession;
030import org.nuxeo.ecm.core.api.DocumentModel;
031import org.nuxeo.ecm.core.api.IdRef;
032import org.nuxeo.ecm.core.api.IterableQueryResult;
033import org.nuxeo.ecm.core.api.NuxeoException;
034import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
035import org.nuxeo.ecm.core.work.AbstractWork;
036import org.nuxeo.ecm.core.work.api.WorkManager;
037import org.nuxeo.ecm.platform.routing.core.api.DocumentRoutingEscalationService;
038import org.nuxeo.ecm.platform.routing.core.impl.GraphNode.EscalationRule;
039import org.nuxeo.runtime.api.Framework;
040
041/**
042 * @since 5.7.2
043 */
044public class DocumentRoutingEscalationServiceImpl implements DocumentRoutingEscalationService {
045
046    private static final Log log = LogFactory.getLog(DocumentRoutingEscalationServiceImpl.class);
047
048    public static final String queryForSuspendedNodesWithEscalation = "Select DISTINCT ecm:uuid from RouteNode WHERE ecm:currentLifeCycleState = 'suspended' "
049            + "AND ( rnode:escalationRules/*1/executed = 0 OR rnode:escalationRules/*1/multipleExecution = 1 )";
050
051    @Override
052    public List<String> queryForSuspendedNodesWithEscalation(CoreSession session) {
053        final List<String> nodesDocIds = new ArrayList<>();
054        new UnrestrictedSessionRunner(session) {
055            @Override
056            public void run() {
057                IterableQueryResult results = session.queryAndFetch(queryForSuspendedNodesWithEscalation, "NXQL");
058                for (Map<String, Serializable> result : results) {
059                    nodesDocIds.add(result.get("ecm:uuid").toString());
060                    log.trace("Inspecting node for escalation rules:" + result.get("ecm:uuid").toString());
061                }
062                results.close();
063            }
064        }.runUnrestricted();
065        return nodesDocIds;
066    }
067
068    @Override
069    public List<EscalationRule> computeEscalationRulesToExecute(GraphNode node) {
070        return node.evaluateEscalationRules();
071    }
072
073    @Override
074    public void scheduleExecution(EscalationRule rule, CoreSession session) {
075        WorkManager manager = Framework.getService(WorkManager.class);
076        manager.schedule(
077                new EscalationRuleWork(rule.getId(), rule.getNode().getDocument().getId(), session.getRepositoryName()),
078                WorkManager.Scheduling.IF_NOT_SCHEDULED);
079    }
080
081    public static class EscalationRuleWork extends AbstractWork {
082
083        private static final long serialVersionUID = 1L;
084
085        protected String escalationRuleId;
086
087        protected String nodeDocId;
088
089        public static final String CATEGORY = "routingEscalation";
090
091        public EscalationRuleWork(String escalationRuleId, String nodeDocId, String repositoryName) {
092            super(repositoryName + ":" + nodeDocId + ":escalationRule:" + escalationRuleId);
093            this.repositoryName = repositoryName;
094            this.escalationRuleId = escalationRuleId;
095            this.nodeDocId = nodeDocId;
096        }
097
098        @Override
099        public String getTitle() {
100            return getId();
101        }
102
103        @Override
104        public String getCategory() {
105            return CATEGORY;
106        }
107
108        @Override
109        public int getRetryCount() {
110            // retry when there is concurrency
111            return 2;
112        }
113
114        @Override
115        public boolean isIdempotent() {
116            return false;
117        }
118
119        @Override
120        public void work() {
121            openSystemSession();
122            DocumentModel nodeDoc = session.getDocument(new IdRef(nodeDocId));
123            GraphNode node = nodeDoc.getAdapter(GraphNode.class);
124            if (node == null) {
125                throw new NuxeoException("Can't execute worker '" + getId() + "' : the document '" + nodeDocId
126                        + "' can not be adapted to a GraphNode");
127            }
128            List<EscalationRule> rules = node.getEscalationRules();
129            EscalationRule rule = null;
130            for (EscalationRule escalationRule : rules) {
131                if (escalationRuleId.equals(escalationRule.getId())) {
132                    rule = escalationRule;
133                    break;
134                }
135            }
136            if (rule == null) {
137                throw new NuxeoException("Can't execute worker '" + getId() + "' : the rule '" + escalationRuleId
138                        + "' was not found on the node '" + nodeDocId + "'");
139            }
140            try (OperationContext context = new OperationContext(session)) {
141                context.putAll(node.getWorkflowContextualInfo(session, true));
142                context.setInput(context.get("documents"));
143                // check to see if the rule wasn't executed meanwhile
144                boolean alreadyExecuted = getExecutionStatus(rule, session);
145                if (alreadyExecuted && !rule.isMultipleExecution()) {
146                    log.trace("Rule " + rule.getId() + "on node " + node.getId() + " already executed");
147                    return;
148                }
149                node.executeChain(rule.getChain());
150                // mark the rule as resolved
151                markRuleAsExecuted(nodeDocId, escalationRuleId, session);
152            } catch (NuxeoException e) {
153                e.addInfo("Error when executing worker: " + getTitle());
154                throw e;
155            }
156        }
157
158        /**
159         * Used to check the executed status when the escalationRule is run by a worker in a work queue
160         */
161        public boolean getExecutionStatus(EscalationRule rule, CoreSession session) {
162            DocumentModel nodeDoc = session.getDocument(new IdRef(rule.getNode().getDocument().getId()));
163            GraphNode node = nodeDoc.getAdapter(GraphNode.class);
164            List<EscalationRule> rules = node.getEscalationRules();
165            for (EscalationRule escalationRule : rules) {
166                if (rule.compareTo(escalationRule) == 0) {
167                    return escalationRule.isExecuted();
168                }
169            }
170            return false;
171        }
172
173    }
174
175    private static void markRuleAsExecuted(String nodeDocId, String escalationRuleId, CoreSession session)
176            {
177        DocumentModel nodeDoc = session.getDocument(new IdRef(nodeDocId));
178        GraphNode node = nodeDoc.getAdapter(GraphNode.class);
179        List<EscalationRule> rules = node.getEscalationRules();
180        for (EscalationRule escalationRule : rules) {
181            if (escalationRuleId.equals(escalationRule.getId())) {
182                escalationRule.setExecuted(true);
183                break;
184            }
185        }
186        session.saveDocument(nodeDoc);
187    }
188}