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