blob: 1f6a8cf0e434cc74282a0190357514f21eb49e26 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.stanbol.enhancer.servicesapi.helper;
import static org.apache.stanbol.enhancer.servicesapi.helper.EnhancementEngineHelper.EXECUTION_ORDER_COMPARATOR;
import static org.apache.stanbol.enhancer.servicesapi.helper.EnhancementEngineHelper.get;
import static org.apache.stanbol.enhancer.servicesapi.helper.EnhancementEngineHelper.getEngineOrder;
import static org.apache.stanbol.enhancer.servicesapi.helper.EnhancementEngineHelper.getString;
import static org.apache.stanbol.enhancer.servicesapi.rdf.ExecutionPlan.CHAIN;
import static org.apache.stanbol.enhancer.servicesapi.rdf.ExecutionPlan.DEPENDS_ON;
import static org.apache.stanbol.enhancer.servicesapi.rdf.ExecutionPlan.ENGINE;
import static org.apache.stanbol.enhancer.servicesapi.rdf.ExecutionPlan.EXECUTION_NODE;
import static org.apache.stanbol.enhancer.servicesapi.rdf.ExecutionPlan.EXECUTION_PLAN;
import static org.apache.stanbol.enhancer.servicesapi.rdf.ExecutionPlan.HAS_EXECUTION_NODE;
import static org.apache.stanbol.enhancer.servicesapi.rdf.ExecutionPlan.OPTIONAL;
import static org.apache.stanbol.enhancer.servicesapi.rdf.Properties.RDF_TYPE;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.clerezza.rdf.core.BNode;
import org.apache.clerezza.rdf.core.Graph;
import org.apache.clerezza.rdf.core.LiteralFactory;
import org.apache.clerezza.rdf.core.MGraph;
import org.apache.clerezza.rdf.core.NonLiteral;
import org.apache.clerezza.rdf.core.Resource;
import org.apache.clerezza.rdf.core.Triple;
import org.apache.clerezza.rdf.core.TripleCollection;
import org.apache.clerezza.rdf.core.impl.PlainLiteralImpl;
import org.apache.clerezza.rdf.core.impl.TripleImpl;
import org.apache.stanbol.commons.indexedgraph.IndexedMGraph;
import org.apache.stanbol.enhancer.servicesapi.ChainException;
import org.apache.stanbol.enhancer.servicesapi.EnhancementEngine;
import org.apache.stanbol.enhancer.servicesapi.EnhancementEngineManager;
import org.apache.stanbol.enhancer.servicesapi.ServiceProperties;
import org.apache.stanbol.enhancer.servicesapi.impl.EnginesTracker;
import org.apache.stanbol.enhancer.servicesapi.rdf.ExecutionPlan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ExecutionPlanHelper {
private final static Logger log = LoggerFactory.getLogger(ExecutionPlanHelper.class);
private static LiteralFactory lf = LiteralFactory.getInstance();
private ExecutionPlanHelper(){/* Do not allow instances of utility classes*/}
/**
* Writes all triples for an ep:ExecutionNode to the parsed {@link MGraph}.
* An {@link BNode} is use for representing the execution node resource.
* @param graph the graph to write the triples. MUST NOT be empty
* @param epNode the NonLiteral representing the ep:ExecutionPlan
* @param engineName the name of the engine. MUST NOT be <code>null</code> nor empty
* @param optional if the execution of this node is optional or required
* @param dependsOn other nodes that MUST BE executed before this one. Parse
* <code>null</code> or an empty set if none.
* @return the resource representing the added ep:ExecutionNode.
*/
public static NonLiteral writeExecutionNode(MGraph graph,NonLiteral epNode, String engineName, boolean optional, Set<NonLiteral> dependsOn){
if(graph == null){
throw new IllegalArgumentException("The parsed MGraph MUST NOT be NULL!");
}
if(engineName == null || engineName.isEmpty()){
throw new IllegalArgumentException("The parsed Engine name MUST NOT be NULL nor empty!");
}
if(epNode == null){
throw new IllegalArgumentException("The ep:ExecutionPlan instance MUST NOT be NULL!");
}
NonLiteral node = new BNode();
graph.add(new TripleImpl(epNode, HAS_EXECUTION_NODE, node));
graph.add(new TripleImpl(node, RDF_TYPE, EXECUTION_NODE));
graph.add(new TripleImpl(node,ENGINE,new PlainLiteralImpl(engineName)));
if(dependsOn != null){
for(NonLiteral dependend : dependsOn){
if(dependend != null){
graph.add(new TripleImpl(node, DEPENDS_ON, dependend));
}
}
}
graph.add(new TripleImpl(node, OPTIONAL, lf.createTypedLiteral(optional)));
return node;
}
/**
* Creates an ExecutionPlan for the parsed chainName in the parsed Graph
* @param graph the graph
* @param chainName the chain name
* @return the node representing the ex:ExecutionPlan
*/
public static NonLiteral createExecutionPlan(MGraph graph,String chainName){
if(graph == null){
throw new IllegalArgumentException("The parsed MGraph MUST NOT be NULL!");
}
if(chainName == null || chainName.isEmpty()){
throw new IllegalArgumentException("The parsed Chain name MUST NOT be NULL nor empty!");
}
NonLiteral node = new BNode();
graph.add(new TripleImpl(node, RDF_TYPE, EXECUTION_PLAN));
graph.add(new TripleImpl(node, CHAIN,new PlainLiteralImpl(chainName)));
return node;
}
/**
* Evaluates the parsed {@link Graph execution plan} and the set of already executed
* {@link ExecutionPlan#EXECUTION_NODE ep:ExecutionNode}s to find the next
* nodes that can be executed.
* @param executionPlan the execution plan
* @param executed the already executed {@link ExecutionPlan#EXECUTION_NODE node}s
* or an empty set to determine the nodes to start the execution.
* @return the set of nodes that can be executed next or an empty set if
* there are no more nodes to execute.
*/
public static Set<NonLiteral>getExecutable(TripleCollection executionPlan, Set<NonLiteral> executed){
Set<NonLiteral> executeable = new HashSet<NonLiteral>();
for(Iterator<Triple> nodes = executionPlan.filter(null, RDF_TYPE, EXECUTION_NODE);nodes.hasNext();){
NonLiteral node = nodes.next().getSubject();
if(!executed.contains(node)){
Iterator<Triple> dependsIt = executionPlan.filter(node, DEPENDS_ON, null);
boolean dependendExecuted = true;
while(dependsIt.hasNext() && dependendExecuted){
dependendExecuted = executed.contains(dependsIt.next().getObject());
}
if(dependendExecuted){
executeable.add(node);
}
}
}
return executeable;
}
/**
* Creates an execution plan based on the
* {@link ServiceProperties#ENHANCEMENT_ENGINE_ORDERING} of the parsed
* EnhancementEngines. NOTE that the parsed list is modified as it is sroted by
* using the {@link EnhancementEngineHelper#EXECUTION_ORDER_COMPARATOR}.<p>
* A second parameter with the set of optional engines can be used to define
* what {@link ExecutionPlan#EXECUTION_NODE} in the execution plan should be
* marked as {@link ExecutionPlan#OPTIONAL}.
* @param chainName the name of the Chain to build the execution plan for
* @param availableEngines the list of engines
* @param the names of optional engines.
* @return the execution plan
*/
public static Graph calculateExecutionPlan(String chainName, List<EnhancementEngine> availableEngines, Set<String> optional, Set<String> missing) {
if(chainName == null || chainName.isEmpty()){
throw new IllegalArgumentException("The parsed ChainName MUST NOT be empty!");
}
Collections.sort(availableEngines,EXECUTION_ORDER_COMPARATOR);
//now we have all required and possible also optional engines
// -> build the execution plan
MGraph ep = new IndexedMGraph();
NonLiteral epNode = createExecutionPlan(ep, chainName);
Integer prevOrder = null;
Set<NonLiteral> prev = null;
Set<NonLiteral> current = new HashSet<NonLiteral>();
for(String name : missing){
boolean optionalMissing = optional.contains(name);
NonLiteral node = writeExecutionNode(ep, epNode, name, optionalMissing, null);
if(!optionalMissing){
current.add(node);
} // else add missing optional engines without any dependsOn restrictions
}
for(EnhancementEngine engine : availableEngines){
String name = engine.getName();
Integer order = getEngineOrder(engine);
if(prevOrder == null || !prevOrder.equals(order)){
prev = current;
current = new HashSet<NonLiteral>();
prevOrder = order;
}
try {
current.add(writeExecutionNode(ep, epNode, name, optional.contains(name), prev));
} catch (RuntimeException e){
//add the engine and class to ease debugging in such cases
log.error("Exception while writing ExecutionNode for Enhancement Eninge: "
+ engine +"(class: "+engine.getClass()+")",e);
throw e; //rethrow it
}
}
return ep.getGraph();
}
/**
* Utility that checks if the parsed graph contains a valid execution
* plan. This method is intended to be used by components that need to
* ensure that an parsed graph contains a valid execution plan.<p>
* This especially checks: <ul>
* <li> if for all {@link ExecutionPlan#EXECUTION_NODE}s
* <li> if they define a unary and valid value for the
* {@link ExecutionPlan#ENGINE} property and
* <li> if all {@link ExecutionPlan#DEPENDS_ON} values do actually point
* to an other execution node in the parsed graph
* <ul><p>
* This method does not modify the parsed graph. Therefore it is save
* to parse a {@link Graph} object.<p>
* TODO: There is no check for cycles implemented yet.
* @param the graph to check
* @return the engine names referenced by the validated execution plan-
* @throws ChainException
*/
public static Set<String> validateExecutionPlan(TripleCollection executionPlan) throws ChainException {
Iterator<Triple> executionNodeIt = executionPlan.filter(null, RDF_TYPE, EXECUTION_NODE);
Set<String> engineNames = new HashSet<String>();
Map<NonLiteral, Collection<NonLiteral>> nodeDependencies = new HashMap<NonLiteral,Collection<NonLiteral>>();
//1. check the ExecutionNodes
while(executionNodeIt.hasNext()){
NonLiteral node = executionNodeIt.next().getSubject();
Iterator<String> engines = EnhancementEngineHelper.getStrings(executionPlan, node,ENGINE);
if(!engines.hasNext()){
throw new ChainException("Execution Node "+node+" does not define " +
"the required property "+ENGINE+"!");
}
String engine = engines.next();
if(engines.hasNext()){
throw new ChainException("Execution Node "+node+" does not define " +
"multiple values for the property "+ENGINE+"!");
}
if(engine.isEmpty()){
throw new ChainException("Execution Node "+node+" does not define " +
"an empty String as engine name (property "+ENGINE+")!");
}
engineNames.add(engine);
Collection<NonLiteral> dependsOn = new HashSet<NonLiteral>();
for(Iterator<Triple> t = executionPlan.filter(node, DEPENDS_ON, null);t.hasNext();){
Resource o = t.next().getObject();
if(o instanceof NonLiteral){
dependsOn.add((NonLiteral)o);
} else {
throw new ChainException("Execution Node "+node+" defines the literal '" +
o+"' as value for the "+DEPENDS_ON +" property. However this" +
"property requires values to be bNodes or URIs.");
}
}
nodeDependencies.put(node, dependsOn);
}
//2. now check the dependency graph
for(Entry<NonLiteral,Collection<NonLiteral>> entry : nodeDependencies.entrySet()){
if(entry.getValue() != null){
for(NonLiteral dependent : entry.getValue()){
if(!nodeDependencies.containsKey(dependent)){
throw new ChainException("Execution Node "+entry.getKey()+
" defines a dependency to an non existent ex:ExectutionNode "+
dependent+"!");
} //else the dependency is valid
}
} //no dependencies
}
//done ... the parsed graph survived all consistency checks :)
return engineNames;
}
public static Set<NonLiteral> getDependend(TripleCollection executionPlan, NonLiteral executionNode){
Set<NonLiteral> dependend = new HashSet<NonLiteral>();
addDependend(dependend, executionPlan, executionNode);
return dependend;
}
public static void addDependend(Collection<NonLiteral> collection, TripleCollection executionPlan, NonLiteral executionNode){
for(Iterator<Triple> it = executionPlan.filter(executionNode, DEPENDS_ON, null);
it.hasNext();collection.add((NonLiteral)it.next().getObject()));
}
public static boolean isOptional(TripleCollection executionPlan, NonLiteral executionNode) {
Boolean optional = get(executionPlan,executionNode,OPTIONAL,Boolean.class,lf);
return optional == null ? false : optional.booleanValue();
}
public static String getEngine(TripleCollection executionPlan, NonLiteral executionNode) {
return getString(executionPlan, executionNode, ENGINE);
}
/**
* Calculates a sorted list of active EnhancementEngines form the given
* ExecutinPlan
* @param engineManager The engine manager (OSGI service or {@link EnginesTracker})
* @param ep the execution plan
* @return
*/
public static List<EnhancementEngine> getActiveEngines(EnhancementEngineManager engineManager, TripleCollection ep) {
List<EnhancementEngine> engines = new ArrayList<EnhancementEngine>();
Set<NonLiteral> visited = new HashSet<NonLiteral>();
Set<NonLiteral> executeable;
do {
executeable = getExecutable(ep, visited);
for(NonLiteral node : executeable){
String engineName = getString(ep, node, ENGINE);
EnhancementEngine engine = engineManager.getEngine(engineName);
if(engine != null){
engines.add(engine);
}
visited.add(node);
}
} while(!executeable.isEmpty());
return engines;
}
/**
* Getter for the {@link ExecutionPlan#EXECUTION_PLAN} node of an execution
* plan for the given chainNmame. This method is handy for components that
* need to get an execution plan for a graph that might potentially contain
* more than a single execution plan.
* @param graph the graph
* @param chainName the chain name
* @return the node or <code>null</code> if not found
*/
public static NonLiteral getExecutionPlan(TripleCollection graph, String chainName){
if(graph == null){
throw new IllegalArgumentException("The parsed graph MUST NOT be NULL!");
}
if(chainName == null || chainName.isEmpty()){
throw new IllegalArgumentException("The parsed chain name MUST NOT be NULL nor empty!");
}
Iterator<Triple> it = graph.filter(null, CHAIN, new PlainLiteralImpl(chainName));
if(it.hasNext()){
return it.next().getSubject();
} else {
return null;
}
}
/**
* Getter for the set of ExecutionNodes part of an execution plan.
* @param ep the execution plan graph
* @param executionPlanNode the execution plan node
*/
public static Set<NonLiteral> getExecutionNodes(TripleCollection ep, final NonLiteral executionPlanNode) {
if(ep == null){
throw new IllegalArgumentException("The parsed graph with the Executionplan MUST NOT be NULL!");
}
if(executionPlanNode == null){
throw new IllegalArgumentException("The parsed execution plan node MUST NOT be NULL!");
}
Set<NonLiteral> executionNodes = new HashSet<NonLiteral>();
Iterator<Triple> it = ep.filter(executionPlanNode, HAS_EXECUTION_NODE, null);
while(it.hasNext()){
Triple t = it.next();
Resource node = t.getObject();
if(node instanceof NonLiteral){
executionNodes.add((NonLiteral)node);
} else {
throw new IllegalStateException("The value of the "+HAS_EXECUTION_NODE
+ " property MUST BE a NonLiteral (triple: "+t+")!");
}
}
return executionNodes;
}
}