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.getLocalService(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 void work() {
111            openSystemSession();
112            DocumentModel nodeDoc = session.getDocument(new IdRef(nodeDocId));
113            GraphNode node = nodeDoc.getAdapter(GraphNode.class);
114            if (node == null) {
115                throw new NuxeoException("Can't execute worker '" + getId() + "' : the document '" + nodeDocId
116                        + "' can not be adapted to a GraphNode");
117            }
118            List<EscalationRule> rules = node.getEscalationRules();
119            EscalationRule rule = null;
120            for (EscalationRule escalationRule : rules) {
121                if (escalationRuleId.equals(escalationRule.getId())) {
122                    rule = escalationRule;
123                    break;
124                }
125            }
126            if (rule == null) {
127                throw new NuxeoException("Can't execute worker '" + getId() + "' : the rule '" + escalationRuleId
128                        + "' was not found on the node '" + nodeDocId + "'");
129            }
130            try (OperationContext context = new OperationContext(session)) {
131                context.putAll(node.getWorkflowContextualInfo(session, true));
132                context.setInput(context.get("documents"));
133                // check to see if the rule wasn't executed meanwhile
134                boolean alreadyExecuted = getExecutionStatus(rule, session);
135                if (alreadyExecuted && !rule.isMultipleExecution()) {
136                    log.trace("Rule " + rule.getId() + "on node " + node.getId() + " already executed");
137                    return;
138                }
139                node.executeChain(rule.getChain());
140                // mark the rule as resolved
141                markRuleAsExecuted(nodeDocId, escalationRuleId, session);
142            } catch (NuxeoException e) {
143                e.addInfo("Error when executing worker: " + getTitle());
144                throw e;
145            } catch (OperationException e) {
146                throw new NuxeoException("Error when executing worker: " + getTitle(), e);
147            }
148        }
149
150        /**
151         * Used to check the executed status when the escalationRule is run by a worker in a work queue
152         *
153         * @param session
154         */
155        public boolean getExecutionStatus(EscalationRule rule, CoreSession session) {
156            DocumentModel nodeDoc = session.getDocument(new IdRef(rule.getNode().getDocument().getId()));
157            GraphNode node = nodeDoc.getAdapter(GraphNode.class);
158            List<EscalationRule> rules = node.getEscalationRules();
159            for (EscalationRule escalationRule : rules) {
160                if (rule.compareTo(escalationRule) == 0) {
161                    return escalationRule.isExecuted();
162                }
163            }
164            return false;
165        }
166
167    }
168
169    private static void markRuleAsExecuted(String nodeDocId, String escalationRuleId, CoreSession session)
170            {
171        DocumentModel nodeDoc = session.getDocument(new IdRef(nodeDocId));
172        GraphNode node = nodeDoc.getAdapter(GraphNode.class);
173        List<EscalationRule> rules = node.getEscalationRules();
174        EscalationRule rule = null;
175        for (EscalationRule escalationRule : rules) {
176            if (escalationRuleId.equals(escalationRule.getId())) {
177                rule = escalationRule;
178                break;
179            }
180        }
181        rule.setExecuted(true);
182        session.saveDocument(nodeDoc);
183    }
184}