| /* |
| * 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; |
| } |
| } |