001/*
002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Mariana Cedica
016 */
017package org.nuxeo.ecm.platform.routing.core.impl;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026import org.nuxeo.ecm.automation.OperationContext;
027import org.nuxeo.ecm.core.api.CoreSession;
028import org.nuxeo.ecm.core.api.DocumentModel;
029import org.nuxeo.ecm.core.api.IdRef;
030import org.nuxeo.ecm.core.api.IterableQueryResult;
031import org.nuxeo.ecm.core.api.NuxeoException;
032import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
033import org.nuxeo.ecm.core.work.AbstractWork;
034import org.nuxeo.ecm.core.work.api.WorkManager;
035import org.nuxeo.ecm.platform.routing.core.api.DocumentRoutingEscalationService;
036import org.nuxeo.ecm.platform.routing.core.impl.GraphNode.EscalationRule;
037import org.nuxeo.runtime.api.Framework;
038
039/**
040 * @since 5.7.2
041 */
042public class DocumentRoutingEscalationServiceImpl implements DocumentRoutingEscalationService {
043
044    private static Log log = LogFactory.getLog(DocumentRoutingEscalationServiceImpl.class);
045
046    public static final String queryForSuspendedNodesWithEscalation = "Select DISTINCT ecm:uuid from RouteNode WHERE ecm:currentLifeCycleState = 'suspended' "
047            + "AND ( rnode:escalationRules/*1/executed = 0 OR rnode:escalationRules/*1/multipleExecution = 1 )";
048
049    @Override
050    public List<String> queryForSuspendedNodesWithEscalation(CoreSession session) {
051        final List<String> nodesDocIds = new ArrayList<String>();
052        new UnrestrictedSessionRunner(session) {
053            @Override
054            public void run() {
055                IterableQueryResult results = session.queryAndFetch(queryForSuspendedNodesWithEscalation, "NXQL");
056                for (Map<String, Serializable> result : results) {
057                    nodesDocIds.add(result.get("ecm:uuid").toString());
058                    log.trace("Inspecting node for escalation rules:" + result.get("ecm:uuid").toString());
059                }
060                results.close();
061            }
062        }.runUnrestricted();
063        return nodesDocIds;
064    }
065
066    @Override
067    public List<EscalationRule> computeEscalationRulesToExecute(GraphNode node) {
068        return node.evaluateEscalationRules();
069    }
070
071    @Override
072    public void scheduleExecution(EscalationRule rule, CoreSession session) {
073        WorkManager manager = Framework.getLocalService(WorkManager.class);
074        manager.schedule(
075                new EscalationRuleWork(rule.getId(), rule.getNode().getDocument().getId(), session.getRepositoryName()),
076                WorkManager.Scheduling.IF_NOT_SCHEDULED);
077    }
078
079    public static class EscalationRuleWork extends AbstractWork {
080
081        private static final long serialVersionUID = 1L;
082
083        protected String escalationRuleId;
084
085        protected String nodeDocId;
086
087        public static final String CATEGORY = "routingEscalation";
088
089        public EscalationRuleWork(String escalationRuleId, String nodeDocId, String repositoryName) {
090            super(repositoryName + ":" + nodeDocId + ":escalationRule:" + escalationRuleId);
091            this.repositoryName = repositoryName;
092            this.escalationRuleId = escalationRuleId;
093            this.nodeDocId = nodeDocId;
094        }
095
096        @Override
097        public String getTitle() {
098            return getId();
099        }
100
101        @Override
102        public String getCategory() {
103            return CATEGORY;
104        }
105
106        @Override
107        public void work() {
108            initSession();
109            DocumentModel nodeDoc = session.getDocument(new IdRef(nodeDocId));
110            GraphNode node = nodeDoc.getAdapter(GraphNode.class);
111            if (node == null) {
112                throw new NuxeoException("Can't execute worker '" + getId() + "' : the document '" + nodeDocId
113                        + "' can not be adapted to a GraphNode");
114            }
115            List<EscalationRule> rules = node.getEscalationRules();
116            EscalationRule rule = null;
117            for (EscalationRule escalationRule : rules) {
118                if (escalationRuleId.equals(escalationRule.getId())) {
119                    rule = escalationRule;
120                    break;
121                }
122            }
123            if (rule == null) {
124                throw new NuxeoException("Can't execute worker '" + getId() + "' : the rule '" + escalationRuleId
125                        + "' was not found on the node '" + nodeDocId + "'");
126            }
127            OperationContext context = new OperationContext(session);
128            context.putAll(node.getWorkflowContextualInfo(session, true));
129            context.setInput(context.get("documents"));
130            try {
131                // check to see if the rule wasn't executed meanwhile
132                boolean alreadyExecuted = getExecutionStatus(rule, session);
133                if (alreadyExecuted && !rule.isMultipleExecution()) {
134                    log.trace("Rule " + rule.getId() + "on node " + node.getId() + " already executed");
135                    return;
136                }
137                node.executeChain(rule.getChain());
138                // mark the rule as resolved
139                markRuleAsExecuted(nodeDocId, escalationRuleId, session);
140            } catch (NuxeoException e) {
141                e.addInfo("Error when executing worker: " + getTitle());
142                throw e;
143            }
144        }
145
146        /**
147         * Used to check the executed status when the escalationRule is run by a worker in a work queue
148         *
149         * @param session
150         */
151        public boolean getExecutionStatus(EscalationRule rule, CoreSession session) {
152            DocumentModel nodeDoc = session.getDocument(new IdRef(rule.getNode().getDocument().getId()));
153            GraphNode node = nodeDoc.getAdapter(GraphNode.class);
154            List<EscalationRule> rules = node.getEscalationRules();
155            for (EscalationRule escalationRule : rules) {
156                if (rule.compareTo(escalationRule) == 0) {
157                    return escalationRule.isExecuted();
158                }
159            }
160            return false;
161        }
162
163    }
164
165    private static void markRuleAsExecuted(String nodeDocId, String escalationRuleId, CoreSession session)
166            {
167        DocumentModel nodeDoc = session.getDocument(new IdRef(nodeDocId));
168        GraphNode node = nodeDoc.getAdapter(GraphNode.class);
169        List<EscalationRule> rules = node.getEscalationRules();
170        EscalationRule rule = null;
171        for (EscalationRule escalationRule : rules) {
172            if (escalationRuleId.equals(escalationRule.getId())) {
173                rule = escalationRule;
174                break;
175            }
176        }
177        rule.setExecuted(true);
178        session.saveDocument(nodeDoc);
179    }
180}