Commit 185dbb8b authored by Peter Harrison's avatar Peter Harrison

Moving Rules out of Boards.

Exposing detailed rules is a security risk as parameters may include
passwords. 
A new Cache has been established, and the Rules Controller now uses
this.
parent 7a3c0fba
...@@ -42,6 +42,7 @@ import nz.net.orcon.kanban.controllers.BoardsCache; ...@@ -42,6 +42,7 @@ import nz.net.orcon.kanban.controllers.BoardsCache;
import nz.net.orcon.kanban.controllers.CardController; import nz.net.orcon.kanban.controllers.CardController;
import nz.net.orcon.kanban.controllers.NotificationController; import nz.net.orcon.kanban.controllers.NotificationController;
import nz.net.orcon.kanban.controllers.ResourceNotFoundException; import nz.net.orcon.kanban.controllers.ResourceNotFoundException;
import nz.net.orcon.kanban.controllers.RuleCache;
import nz.net.orcon.kanban.controllers.URI; import nz.net.orcon.kanban.controllers.URI;
import nz.net.orcon.kanban.model.Action; import nz.net.orcon.kanban.model.Action;
import nz.net.orcon.kanban.model.Board; import nz.net.orcon.kanban.model.Board;
...@@ -72,6 +73,9 @@ public class AutomationEngine { ...@@ -72,6 +73,9 @@ public class AutomationEngine {
@Autowired @Autowired
BoardsCache boardsCache; BoardsCache boardsCache;
@Autowired
RuleCache ruleCache;
@Autowired @Autowired
DateInterpreter dateInterpreter; DateInterpreter dateInterpreter;
...@@ -107,13 +111,8 @@ public class AutomationEngine { ...@@ -107,13 +111,8 @@ public class AutomationEngine {
return; return;
} }
Map<String, Rule> rules = board.getRules(); Map<String,Rule> rules = getRulesFromBoard(cardHolder.getBoardId());
if (rules == null) {
LOG.warn("Rules Not Found: " + cardHolder.getBoardId());
return;
}
LOG.info("Automation Examining :" + card.getPath()); LOG.info("Automation Examining :" + card.getPath());
evaluateTaskRules(rules,card); evaluateTaskRules(rules,card);
...@@ -136,6 +135,19 @@ public class AutomationEngine { ...@@ -136,6 +135,19 @@ public class AutomationEngine {
executeTaskRules(rules,card); executeTaskRules(rules,card);
} }
private Map<String,Rule> getRulesFromBoard(String boardId) throws Exception{
Map<String, String> ruleList = ruleCache.list(boardId,"");
Map<String,Rule> rules = new HashMap<String,Rule>();
for( String ruleId : ruleList.keySet()){
Rule rule = ruleCache.getItem(boardId, ruleId);
if(rule!=null){
rules.put(ruleId, rule);
}
}
return rules;
}
private void executeCompulsoryRules(Map<String, Rule> rules, Card card) throws Exception { private void executeCompulsoryRules(Map<String, Rule> rules, Card card) throws Exception {
for (Rule rule : rules.values()) { for (Rule rule : rules.values()) {
if(rule.getCompulsory() && if(rule.getCompulsory() &&
...@@ -275,28 +287,18 @@ public class AutomationEngine { ...@@ -275,28 +287,18 @@ public class AutomationEngine {
public Map<String,Map<String,Boolean>> explain(CardHolder cardHolder) throws Exception { public Map<String,Map<String,Boolean>> explain(CardHolder cardHolder) throws Exception {
Map<String,Map<String,Boolean>> result = new HashMap<String,Map<String,Boolean>>(); Map<String,Map<String,Boolean>> result = new HashMap<String,Map<String,Boolean>>();
Card card = null; Card card = null;
Board board = null;
try { try {
card = cardController.getCard(cardHolder.getBoardId(), card = cardController.getCard(cardHolder.getBoardId(),
null, cardHolder.getCardId(),"full"); null, cardHolder.getCardId(),"full");
board = boardsCache.getItem(cardHolder.getBoardId());
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
throw new Exception("Resource no longer exists: " + cardHolder.toString()); throw new Exception("Resource Not Found: " + cardHolder.toString());
}
if (board == null) {
throw new Exception("Board Not Found: " + cardHolder.toString());
}
Map<String, Rule> rules = board.getRules();
if (rules == null) {
throw new Exception("Rules Not Found: " + cardHolder.getBoardId());
} }
Map<String, Rule> rules = getRulesFromBoard(cardHolder.getBoardId());
Set<Entry<String, Rule>> entrySet = rules.entrySet(); Set<Entry<String, Rule>> entrySet = rules.entrySet();
LOG.info("Explaining :" + card.getPath()); LOG.info("Explaining :" + card.getPath());
...@@ -694,14 +696,8 @@ public class AutomationEngine { ...@@ -694,14 +696,8 @@ public class AutomationEngine {
LOG.info("Executing Actions on board:" + boardId + " Rule " + ruleId); LOG.info("Executing Actions on board:" + boardId + " Rule " + ruleId);
// Get Rule. // Get Rule.
Board board = this.boardsCache.getItem(boardId); Rule rule = ruleCache.getItem(boardId, ruleId);
if(board==null){
LOG.warn("Execute Actions: Board Not Found: "+ boardId);
return;
}
Rule rule = board.getRules().get(ruleId);
if(rule==null){ if(rule==null){
LOG.warn("Execute Actions: Rule Not Found: "+ boardId + "/" + ruleId); LOG.warn("Execute Actions: Rule Not Found: "+ boardId + "/" + ruleId);
return; return;
...@@ -732,7 +728,7 @@ public class AutomationEngine { ...@@ -732,7 +728,7 @@ public class AutomationEngine {
public void executeActions(Card card, Rule rule) throws Exception { public void executeActions(Card card, Rule rule) throws Exception {
LOG.info("Executing Actions on Card :" + card.getPath()); LOG.info("Executing ActionChain on Card :" + card.getPath() + " rule " + rule.getId());
Map<String, Object> context = card.getFields(); Map<String, Object> context = card.getFields();
context.put("boardid", card.getBoard()); context.put("boardid", card.getBoard());
......
/** /**
* GRAVITY WORKFLOW AUTOMATION * GRAVITY WORKFLOW AUTOMATION
* (C) Copyright 2015 Orcon Limited * (C) Copyright 2015 Orcon Limited
* (C) Copyright 2015 Peter Harrison
* *
* This file is part of Gravity Workflow Automation. * This file is part of Gravity Workflow Automation.
* *
...@@ -29,7 +30,7 @@ import java.util.Map; ...@@ -29,7 +30,7 @@ import java.util.Map;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import nz.net.orcon.kanban.controllers.BoardsCache; import nz.net.orcon.kanban.controllers.BoardsCache;
import nz.net.orcon.kanban.model.Board; import nz.net.orcon.kanban.controllers.RuleCache;
import nz.net.orcon.kanban.model.Condition; import nz.net.orcon.kanban.model.Condition;
import nz.net.orcon.kanban.model.ConditionType; import nz.net.orcon.kanban.model.ConditionType;
import nz.net.orcon.kanban.model.Rule; import nz.net.orcon.kanban.model.Rule;
...@@ -71,6 +72,9 @@ public class TimerManager { ...@@ -71,6 +72,9 @@ public class TimerManager {
@Autowired @Autowired
private BoardsCache boardsCache; private BoardsCache boardsCache;
@Autowired
private RuleCache ruleCache;
@Autowired @Autowired
OcmMapperFactory ocmFactory; OcmMapperFactory ocmFactory;
...@@ -96,37 +100,37 @@ public class TimerManager { ...@@ -96,37 +100,37 @@ public class TimerManager {
if(this.scheduler==null){ if(this.scheduler==null){
return; return;
} }
Board board = boardsCache.getItem(boardId); Map<String, String> rules;
if( board==null){ try {
LOG.warn("Board Not Found when Loading Timer: " + boardId); rules = ruleCache.list(boardId,"");
if( rules==null){
LOG.warn("Board Rules Not Found when Loading Timer: " + boardId);
return;
}
} catch (javax.jcr.PathNotFoundException e ){
LOG.info("No Rule for Board: "+ boardId);
return; return;
} }
// Delete All Existing Timers for this board // Delete All Existing Timers for this board
List<String> jobNames = Arrays.asList(this.scheduler.getJobNames(boardId)); List<String> jobNames = Arrays.asList(this.scheduler.getJobNames(boardId));
for( String jobName : jobNames){ for( String jobName : jobNames){
this.scheduler.deleteJob(jobName, boardId); this.scheduler.deleteJob(jobName, boardId);
} }
// Start Timers // Start Timers
Map<String,Rule> rules = board.getRules(); for( String ruleId : rules.keySet()){
if( rules==null){ Rule rule = ruleCache.getItem(boardId,ruleId);
LOG.warn("Board Rules Not Found when Loading Timer: " + boardId);
return;
}
for( Rule rule : rules.values()){
if(null != rule.getAutomationConditions()){ if(null != rule.getAutomationConditions()){
for( Condition condition : rule.getAutomationConditions().values()) { for( Condition condition : rule.getAutomationConditions().values()) {
if( ConditionType.TIMER.equals(condition.getConditionType())){ if( ConditionType.TIMER.equals(condition.getConditionType())){
activateTimer( board.getId(), rule.getId(), condition.getValue()); activateTimer( boardId, rule.getId(), condition.getValue());
LOG.info("Timer Loaded: " + boardId + "." + rule.getId());
} }
} }
} }
} }
LOG.info("Board Timers Loaded for Board: " + boardId);
} }
private void activateTimer( String boardId, String ruleId, String schedule) throws Exception{ private void activateTimer( String boardId, String ruleId, String schedule) throws Exception{
......
/** /**
* GRAVITY WORKFLOW AUTOMATION * GRAVITY WORKFLOW AUTOMATION
* (C) Copyright 2015 Orcon Limited * (C) Copyright 2015 Orcon Limited
* (C) Copyright 2015 Peter Harrison
* *
* This file is part of Gravity Workflow Automation. * This file is part of Gravity Workflow Automation.
* *
...@@ -35,19 +36,11 @@ import javax.jcr.Session; ...@@ -35,19 +36,11 @@ import javax.jcr.Session;
import javax.jcr.query.QueryResult; import javax.jcr.query.QueryResult;
import nz.net.orcon.kanban.automation.CacheInvalidationInterface; import nz.net.orcon.kanban.automation.CacheInvalidationInterface;
import nz.net.orcon.kanban.gviz.GVGraph;
import nz.net.orcon.kanban.gviz.GVNode;
import nz.net.orcon.kanban.gviz.GVShape;
import nz.net.orcon.kanban.gviz.GVStyle;
import nz.net.orcon.kanban.model.Action;
import nz.net.orcon.kanban.model.Board; import nz.net.orcon.kanban.model.Board;
import nz.net.orcon.kanban.model.Card; import nz.net.orcon.kanban.model.Card;
import nz.net.orcon.kanban.model.CardEvent; import nz.net.orcon.kanban.model.CardEvent;
import nz.net.orcon.kanban.model.CardHistoryStat; import nz.net.orcon.kanban.model.CardHistoryStat;
import nz.net.orcon.kanban.model.Condition;
import nz.net.orcon.kanban.model.ConditionType;
import nz.net.orcon.kanban.model.Operation; import nz.net.orcon.kanban.model.Operation;
import nz.net.orcon.kanban.model.Rule;
import nz.net.orcon.kanban.security.SecurityTool; import nz.net.orcon.kanban.security.SecurityTool;
import nz.net.orcon.kanban.tools.CardTools; import nz.net.orcon.kanban.tools.CardTools;
import nz.net.orcon.kanban.tools.IdentifierTools; import nz.net.orcon.kanban.tools.IdentifierTools;
...@@ -62,7 +55,6 @@ import org.slf4j.LoggerFactory; ...@@ -62,7 +55,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
...@@ -284,169 +276,7 @@ public class BoardController { ...@@ -284,169 +276,7 @@ public class BoardController {
ocm.logout(); ocm.logout();
} }
return collection; return collection;
} }
@PreAuthorize("hasPermission(#boardId, 'BOARD', 'READ,WRITE,ADMIN')")
@RequestMapping(value = "/{boardId}/processgraph", method=RequestMethod.GET)
public String processGraph(@PathVariable String boardId, Model model) throws Exception {
// Get Board
Board board = boardsCache.getItem(boardId);
// Construct a GVGraph
GVGraph graph = new GVGraph(boardId);
// Loop over rules
for( Rule rule : board.getRules().values()){
GVNode node = new GVNode(rule.getId());
node.setColor("yellow");
node.setStyle(GVStyle.filled);
node.setShape(GVShape.hexagon);
graph.addNode(node);
if(rule.getAutomationConditions()!=null){
for(Condition condition : rule.getAutomationConditions().values()){
String nodeName = condition.getFieldName() +
"-" + condition.getOperation() + "-" + condition.getValue();
if( condition.getConditionType().equals(ConditionType.TASK)){
nodeName = condition.getFieldName();
}
GVNode conditionNode = graph.getNode(nodeName);
if(conditionNode==null){
conditionNode = new GVNode(nodeName);
conditionNode.setStyle(GVStyle.filled);
if( condition.getConditionType().equals(ConditionType.PROPERTY)){
conditionNode.setShape(GVShape.oval);
conditionNode.setColor("green");
}
if( condition.getConditionType().equals(ConditionType.TASK)){
conditionNode.setShape(GVShape.octagon);
conditionNode.setColor("cyan");
}
if( condition.getConditionType().equals(ConditionType.PHASE)){
conditionNode.setShape(GVShape.box);
conditionNode.setColor("purple");
}
graph.addNode(conditionNode);
}
graph.linkNodes(conditionNode.getName(),node.getName());
}
}
if(rule.getTaskConditions()!=null){
for(Condition condition : rule.getTaskConditions().values()){
String nodeName = condition.getFieldName() +
"-" + condition.getOperation() + "-" + condition.getValue();
if( condition.getConditionType().equals(ConditionType.TASK)){
nodeName = condition.getFieldName();
}
GVNode conditionNode = graph.getNode(nodeName);
if(conditionNode==null){
conditionNode = new GVNode(nodeName);
conditionNode.setStyle(GVStyle.filled);
if( condition.getConditionType().equals(ConditionType.PROPERTY)){
conditionNode.setShape(GVShape.oval);
conditionNode.setColor("green");
}
if( condition.getConditionType().equals(ConditionType.TASK)){
conditionNode.setShape(GVShape.octagon);
conditionNode.setColor("cyan");
}
if( condition.getConditionType().equals(ConditionType.PHASE)){
conditionNode.setShape(GVShape.box);
conditionNode.setColor("purple");
}
graph.addNode(conditionNode);
}
graph.linkNodes(conditionNode.getName(),node.getName(),"blue");
}
}
if( rule.getActions()!=null){
for( Action action : rule.getActions().values()){
// Is The Action a Complete Task?
if( action.getType().equals("execute") && action.getMethod().equals("completeTask")){
String taskname = action.getProperties().get("taskname");
String nodeName = taskname + "-EQUALTO-" + taskname;
GVNode childNode = graph.getNode(nodeName);
if(childNode==null){
childNode = new GVNode(nodeName);
childNode.setStyle(GVStyle.filled);
childNode.setColor("cyan");
childNode.setShape(GVShape.octagon);
graph.addNode(childNode);
}
graph.linkNodes(node.getName(),childNode.getName());
}
// Is The Action a Move to Phase?
if( action.getType().equals("execute") && action.getMethod().equals("moveCard")){
String destination = action.getProperties().get("destination");
String nodeName = "phase-EQUALTO-" + destination;
GVNode childNode = graph.getNode(nodeName);
if(childNode==null){
childNode = new GVNode(nodeName);
childNode.setStyle(GVStyle.filled);
childNode.setColor("purple");
childNode.setShape(GVShape.box);
graph.addNode(childNode);
}
graph.linkNodes(node.getName(),childNode.getName());
}
// Is The Action a Store Propperty?
if( action.getType().equals("execute") && action.getMethod().equals("updateValue")){
String propertyName = action.getProperties().keySet().iterator().next();
String fieldName = action.getProperties().get(propertyName);
String nodeName = fieldName + "-NOTNULL-" + fieldName;
GVNode childNode = graph.getNode(nodeName);
if(childNode==null){
childNode = new GVNode(nodeName);
childNode.setStyle(GVStyle.filled);
childNode.setColor("green");
childNode.setShape(GVShape.oval);
graph.addNode(childNode);
}
graph.linkNodes(node.getName(),childNode.getName());
}
// Is The Action a Persist?
if( action.getType().equals("persist") ){
for( String fieldName : action.getParameters()){
String nodeName = fieldName + "-NOTNULL-" + fieldName;
GVNode childNode = graph.getNode(nodeName);
if(childNode==null){
childNode = new GVNode(nodeName);
childNode.setStyle(GVStyle.filled);
childNode.setColor("green");
childNode.setShape(GVShape.oval);
graph.addNode(childNode);
}
graph.linkNodes(node.getName(),childNode.getName());
}
}
}
}
}
model.addAttribute("graph", graph);
return "graph";
}
@PreAuthorize("hasPermission(#boardId, 'BOARD', 'ADMIN')") @PreAuthorize("hasPermission(#boardId, 'BOARD', 'ADMIN')")
@RequestMapping(value = "/{boardId}", method=RequestMethod.DELETE) @RequestMapping(value = "/{boardId}", method=RequestMethod.DELETE)
......
...@@ -92,6 +92,9 @@ public class CardController { ...@@ -92,6 +92,9 @@ public class CardController {
@Autowired @Autowired
BoardsCache boardCache; BoardsCache boardCache;
@Autowired
RuleCache ruleCache;
@Autowired @Autowired
CardTools cardTools; CardTools cardTools;
...@@ -677,24 +680,14 @@ public class CardController { ...@@ -677,24 +680,14 @@ public class CardController {
try{ try{
cardTasks = ocm.getChildObjects(CardTask.class, cardTasks = ocm.getChildObjects(CardTask.class,
String.format(URI.TASKS_URI, boardId, phaseId, cardId,"")); String.format(URI.TASKS_URI, boardId, phaseId, cardId,""));
Board board = boardCache.getItem(boardId);
Map<String, Rule> rules = board.getRules();
if(rules==null){
return null;
}
Set<Entry<String, Rule>> ruleEntrySet = rules.entrySet();
Map<Integer, CardTask> cardTaskMap = new TreeMap<Integer,CardTask>(); Map<Integer, CardTask> cardTaskMap = new TreeMap<Integer,CardTask>();
if(cardTasks!=null){ if(cardTasks!=null){
for (CardTask cardTask : cardTasks) { for (CardTask cardTask : cardTasks) {
for (Entry<String, Rule> entry : ruleEntrySet) { Rule rule = ruleCache.getItem(boardId,cardTask.getTaskid());
Rule rule = entry.getValue(); if(rule!=null){
if(cardTask.getTaskid().equals(rule.getId())){ cardTaskMap.put(rule.getIndex(),cardTask);
cardTaskMap.put(rule.getIndex(),cardTask);
}
} }
} }
} }
...@@ -916,13 +909,12 @@ public class CardController { ...@@ -916,13 +909,12 @@ public class CardController {
if(card==null){ if(card==null){
return null; return null;
} }
Board board = boardCache.getItem(boardId);
Map<String, Rule> boardRules = board.getRules(); Rule rule = ruleCache.getItem(boardId,taskId);
Rule rule = boardRules.get(taskId);
if(rule==null){ if(rule==null){
ocm.logout(); ocm.logout();
logger.warn("Rule Not Found in Board: " + taskId); logger.warn("Rule Not Found: " + boardId + "." + taskId);
throw new ResourceNotFoundException(); throw new ResourceNotFoundException();
} }
......
/**
* GRAVITY WORKFLOW AUTOMATION
* (C) Copyright 2015 Peter Harrison
*
* This file is part of Gravity Workflow Automation.
*
* Gravity Workflow Automation is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Gravity Workflow Automation is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Gravity Workflow Automation.
* If not, see <http://www.gnu.org/licenses/>.
*/
package nz.net.orcon.kanban.controllers;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.jackrabbit.ocm.manager.ObjectContentManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import nz.net.orcon.kanban.model.Rule;
import nz.net.orcon.kanban.tools.ListTools;
import nz.net.orcon.kanban.tools.OcmMapperFactory;
@Service
public class RuleCache extends CacheImpl<Rule> {
@Resource(name="ocmFactory")
OcmMapperFactory ocmFactory;
@Autowired
ListTools listTools;
@Override
protected Rule getFromStore(String... itemIds) throws Exception {
ObjectContentManager ocm = ocmFactory.getOcm();