/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package annis.sqlgen;
import annis.dao.DocumentNameMapRow;
import annis.model.AnnisNode;
import annis.model.Annotation;
import annis.model.AnnotationGraph;
import annis.model.Edge;
import annis.model.Edge.EdgeType;
import static annis.sqlgen.TableAccessStrategy.COMPONENT_TABLE;
import static annis.sqlgen.TableAccessStrategy.NODE_TABLE;
import static annis.sqlgen.TableAccessStrategy.RANK_TABLE;
import java.sql.Array;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ResultSetExtractor;
/**
*
* @author thomas
*/
public class AomAnnotateExtractor implements ResultSetExtractor<List<AnnotationGraph>>
{
private static final Logger log =
LoggerFactory.getLogger(AomAnnotateExtractor.class);
private TableAccessStrategy outerQueryTableAccessStrategy;
public AnnisNode mapNode(ResultSet resultSet, TableAccessStrategy tableAccessStrategy,
Map<Long, ComponentEntry> spans) throws SQLException
{
AnnisNode annisNode = new AnnisNode(longValue(resultSet, NODE_TABLE, "id", tableAccessStrategy));
annisNode.setCorpus(longValue(resultSet, NODE_TABLE, "corpus_ref", tableAccessStrategy));
annisNode.setTextId(longValue(resultSet, NODE_TABLE, "text_ref", tableAccessStrategy));
annisNode.setLeft(longValue(resultSet, NODE_TABLE, "left", tableAccessStrategy));
annisNode.setRight(longValue(resultSet, NODE_TABLE, "right", tableAccessStrategy));
annisNode.setNamespace(stringValue(resultSet, NODE_TABLE, "namespace", tableAccessStrategy));
annisNode.setName(stringValue(resultSet, NODE_TABLE, "name", tableAccessStrategy));
annisNode.setTokenIndex(longValue(resultSet, NODE_TABLE, "token_index", tableAccessStrategy));
if (resultSet.wasNull())
annisNode.setTokenIndex(null);
annisNode.setSpannedText(stringValue(resultSet, NODE_TABLE, "span", tableAccessStrategy));
annisNode.setLeftToken(longValue(resultSet, NODE_TABLE, "left_token", tableAccessStrategy));
annisNode.setRightToken(longValue(resultSet, NODE_TABLE, "right_token", tableAccessStrategy));
String typeAsString = stringValue(resultSet, COMPONENT_TABLE, "type",
tableAccessStrategy);
if(spans != null && "c".equals(typeAsString))
{
ComponentEntry entry = new ComponentEntry(
longValue(resultSet, COMPONENT_TABLE, "id", tableAccessStrategy),
typeAsString.charAt(0),
stringValue(resultSet, COMPONENT_TABLE, "namespace", tableAccessStrategy),
stringValue(resultSet, COMPONENT_TABLE, "name", tableAccessStrategy));
spans.put(annisNode.getId(), entry);
}
return annisNode;
}
public Edge mapEdge(ResultSet resultSet, TableAccessStrategy tableAccessStrategy)
throws SQLException {
Edge edge = new Edge();
edge.setPre(longValue(resultSet, RANK_TABLE, "pre", tableAccessStrategy));
edge.setComponentID(longValue(resultSet, RANK_TABLE, "component_id",
tableAccessStrategy));
edge.setEdgeType(EdgeType.parseEdgeType(stringValue(resultSet, RANK_TABLE, "edge_type", tableAccessStrategy)));
edge.setNamespace(stringValue(resultSet, COMPONENT_TABLE, "namespace", tableAccessStrategy));
edge.setName(stringValue(resultSet, COMPONENT_TABLE, "name", tableAccessStrategy));
edge.setDestination(new AnnisNode(longValue(resultSet, RANK_TABLE, "node_ref", tableAccessStrategy)));
edge.setComponentID(longValue(resultSet, COMPONENT_TABLE, "id",
tableAccessStrategy));
edge.setId(longValue(resultSet, RANK_TABLE, "id", tableAccessStrategy));
// create nodes for src with rank value (parent) as id.
// this must later be fixed by AnnotationGraphDaoHelper.fixSourceNodeIds().
// this is simpler than chaining the edgeByPre map in AnnisResultSetBuilder
// and making the EdgeRowMapper thread-safe.
// FIXME: use custum mapRow(resultSet, edgeByPre) function, throw Exception here
// also, no need to implement Spring RowMapper
long parent = longValue(resultSet, RANK_TABLE, "parent", tableAccessStrategy);
if ( ! resultSet.wasNull() )
edge.setSource(new AnnisNode(parent));
return edge;
}
public Annotation mapAnnotation(ResultSet resultSet, TableAccessStrategy tableAccessStrategy, String table) throws SQLException
{
// NOT NULL constraint on NAME => NULL indicates no annotation (of this type)
String name = stringValue(resultSet, table, "name", tableAccessStrategy);
if (resultSet.wasNull())
return null;
String namespace = stringValue(resultSet, table, "namespace", tableAccessStrategy);
String value = stringValue(resultSet, table, "value", tableAccessStrategy);
return new Annotation(namespace, name, value);
}
private long longValue(ResultSet resultSet, String table, String column, TableAccessStrategy tableAccessStrategy) throws SQLException {
return resultSet.getLong(tableAccessStrategy.columnName(table, column));
}
private String stringValue(ResultSet resultSet, String table, String column, TableAccessStrategy tableAccessStrategy) throws SQLException {
return resultSet.getString(tableAccessStrategy.columnName(table, column));
}
private boolean booleanValue(ResultSet resultSet, String table, String column, TableAccessStrategy tableAccessStrategy) throws SQLException {
return resultSet.getBoolean(tableAccessStrategy.columnName(table, column));
}
@Override
public List<AnnotationGraph> extractData(ResultSet resultSet)
throws SQLException, DataAccessException
{
TableAccessStrategy tableAccessStrategy = outerQueryTableAccessStrategy;
// function result
List<AnnotationGraph> graphs =
new LinkedList<>();
// fn: match group -> annotation graph
Map<List<Long>, AnnotationGraph> graphByMatchGroup =
new HashMap<>();
// fn: node id -> node
Map<Long, AnnisNode> nodeById = new HashMap<>();
// fn: edge pre order value -> edge
Map<Long, Edge> edgeByRankID = new HashMap<>();
// maps span that are continous to their coverage component
Map<List<Long>, Map<Long, ComponentEntry>> keyToSpanToComponent
= new HashMap<>();
int rowNum = 0;
while (resultSet.next())
{
// process result by match group
// match group is identified by the ids of the matched
// nodes
Array sqlKey = resultSet.getArray("key");
Validate.isTrue(!resultSet.wasNull(),
"Match group identifier must not be null");
Validate.isTrue(sqlKey.getBaseType() == Types.BIGINT,
"Key in database must be from the type \"bigint\" but was \""
+ sqlKey.getBaseTypeName() + "\"");
Long[] keyArray = (Long[]) sqlKey.getArray();
List<Long> key = Arrays.asList(keyArray);
if (!graphByMatchGroup.containsKey(key))
{
log.debug("starting annotation graph for match: " + key);
Map<Long, ComponentEntry> spans = new HashMap<>();
AnnotationGraph graph = new AnnotationGraph();
graphs.add(graph);
graphByMatchGroup.put(key, graph);
keyToSpanToComponent.put(key, spans);
// clear mapping functions for this graph
// assumes that the result set is sorted by key, pre
nodeById.clear();
edgeByRankID.clear();
// set the matched keys
for (Long l : key)
{
if (l != null)
{
graph.addMatchedNodeId(l);
}
}
}
AnnotationGraph graph = graphByMatchGroup.get(key);
Map<Long, ComponentEntry> spanToComponent = keyToSpanToComponent.
get(key);
graph.setDocumentName(new DocumentNameMapRow().mapRow(
resultSet, rowNum));
Array path = resultSet.getArray("path");
graph.setPath((String[]) path.getArray());
// get node data
AnnisNode node = mapNode(resultSet, tableAccessStrategy, spanToComponent);
// add node to graph if it is new, else get known copy
long id = node.getId();
if (!nodeById.containsKey(id))
{
log.debug("new node: " + id);
nodeById.put(id, node);
graph.addNode(node);
}
else
{
node = nodeById.get(id);
}
// we now have the id of the node and the general key,
// so we can
// add the matched node index to the graph (if matched)
long matchIndex = 1;
// node.setMatchedNodeInQuery(null);
for (Long l : key)
{
if (l != null)
{
if (id == l)
{
node.setMatchedNodeInQuery(matchIndex);
break;
}
matchIndex++;
}
}
// get edge data
Edge edge = mapEdge(resultSet, tableAccessStrategy);
// add edge to graph if it is new, else get known copy
long rank_id = edge.getId();
if (!edgeByRankID.containsKey(rank_id))
{
// fix source references in edge
edge.setDestination(node);
fixNodes(edge, edgeByRankID, nodeById);
// add edge to src and dst nodes
node.addIncomingEdge(edge);
AnnisNode source = edge.getSource();
if (source != null)
{
source.addOutgoingEdge(edge);
}
log.debug("new edge: " + edge);
edgeByRankID.put(edge.getId(), edge);
graph.addEdge(edge);
}
else
{
edge = edgeByRankID.get(rank_id);
}
// add annotation data
Annotation nodeAnnotation =
mapAnnotation(resultSet, tableAccessStrategy, TableAccessStrategy.NODE_ANNOTATION_TABLE);
if (nodeAnnotation != null)
{
node.addNodeAnnotation(nodeAnnotation);
}
Annotation edgeAnnotation =
mapAnnotation(resultSet, tableAccessStrategy, TableAccessStrategy.EDGE_ANNOTATION_TABLE);
if (edgeAnnotation != null)
{
edge.addAnnotation(edgeAnnotation);
}
rowNum++;
} // end for each row
// remove edges from the graph with a source node inside the match
for(Entry<List<Long>,AnnotationGraph> entry : graphByMatchGroup.entrySet())
{
AnnotationGraph graph = entry.getValue();
ListIterator<Edge> itEdge = graph.getEdges().listIterator();
while(itEdge.hasNext())
{
Edge edge = itEdge.next();
if(edge.getSource() == null)
{
edge.getDestination().getIncomingEdges().remove(edge);
itEdge.remove();
}
}
Map<Long, ComponentEntry> spans = keyToSpanToComponent.get(entry.getKey());
// filter out the continuous spans by finding all discontinuous spans
// discontinuos spans will have a an entry for token
createMissingSpanningRelations(graph, spans, nodeById);
}
return graphs;
}
private void createMissingSpanningRelations(AnnotationGraph graph,
Map<Long, ComponentEntry> allSpans, Map<Long, AnnisNode> nodeById)
{
for(Map.Entry<Long, ComponentEntry> spanEntry : allSpans.entrySet())
{
AnnisNode span = nodeById.get(spanEntry.getKey());
// Check all covered token if there is already a coverage edge between the span
// and the token. If at least one edge already exists, it must have been
// a discontinuos span and we don't need to add any missing edges.
boolean anyTokenConnected = false;
for(long i=span.getLeftToken(); i <= span.getRightToken() && !anyTokenConnected; i++)
{
AnnisNode token = graph.getToken(i);
// the span border might be behind the result set, so ignore this entries
if(token != null)
{
for (Edge e : token.getIncomingEdges())
{
if (e.getSource() == span && e.getEdgeType() == EdgeType.COVERAGE)
{
anyTokenConnected = true;
break;
}
}
}
}
if(!anyTokenConnected)
{
long pre = 1;
for(long i=span.getLeftToken(); i <= span.getRightToken(); i++)
{
AnnisNode tok = graph.getToken(i);
if(tok != null)
{
Edge edge = new Edge();
ComponentEntry component = spanEntry.getValue();
edge.setPre(pre++);
edge.setComponentID(component.getId());
edge.setEdgeType(EdgeType.COVERAGE);
edge.setNamespace(component.getNamespace());
edge.setName(null);
edge.setDestination(tok);
edge.setSource(span);
graph.addEdge(edge);
span.addOutgoingEdge(edge);
tok.addIncomingEdge(edge);
}
}
}
} // end for each node
}
protected void fixNodes(Edge edge, Map<Long, Edge> edgeByRankID,
Map<Long, AnnisNode> nodeById)
{
// pull source node from parent edge
AnnisNode source = edge.getSource();
if (source == null)
{
return;
}
long nodeID = source.getId();
Edge parentEdge = edgeByRankID.get(nodeID);
AnnisNode parent = parentEdge != null
? parentEdge.getDestination() : null;
// log.debug("looking for node with rank.pre =
// " + pre + "; found: " + parent);
edge.setSource(parent);
// pull destination node from mapping function
long destinationId = edge.getDestination().getId();
edge.setDestination(nodeById.get(destinationId));
}
public TableAccessStrategy getOuterQueryTableAccessStrategy()
{
return outerQueryTableAccessStrategy;
}
public void setOuterQueryTableAccessStrategy(TableAccessStrategy outerQueryTableAccessStrategy)
{
this.outerQueryTableAccessStrategy = outerQueryTableAccessStrategy;
}
public static class ComponentEntry
{
private final long id;
private final char type;
private final String namespace;
private final String name;
public ComponentEntry(long id, char type, String namespace, String name)
{
this.id = id;
this.type = type;
this.namespace = namespace;
this.name = name;
}
public long getId()
{
return id;
}
public char getType()
{
return type;
}
public String getNamespace()
{
return namespace;
}
public String getName()
{
return name;
}
@Override
public String toString()
{
return "ComponentEntry{" + "id=" + id + ", type=" + type + ", namespace=" + namespace + ", name=" + name + '}';
}
}
}