Commit b5caf524 authored by R.W.Majeed's avatar R.W.Majeed
Browse files

Mapping-transformations working with conditions in XPath/ECMAScript and

simple choose (without otherwise)
parent 79aa5e0a
<!--
#%L
histream
%%
Copyright (C) 2013 - 2015 R.W.Majeed
%%
Licensed 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.
#L%
-->
<dwh-eav xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<!-- TODO namespace xmlns="http://sekmi.de/histream/dwh-eav..." -->
<!-- chronologisch impliziert, dass der zeitstempel eines nachfolgenden elementes gr��er als alle vorangehenden elemente sein muss. Der Zeitstempel kann vor dem Encounter-Start liegen -->
<meta>
<!-- Zeitpunkt, an dem der Export erstellt wurde bzw. Datenstand -->
<etl strategy="replace-visit" />
<source timestamp="2015-04-21T08:58:00" id="test"/>
<!-- weitere metadaten n�tig? wertebereich, datentypen, ontologie, ...? -->
</meta>
<visit>
<patid>XX12345</patid>
<surname>Dampf</surname>
<names>A</names>
<birthdate>2001-01-01</birthdate>
<deathdate>2020</deathdate>
<sex>F</sex>
<encounter start="2014-01-01T10:30:00" end="2014-01-05T10:30:00">XXE12345</encounter>
<location>Zuhause</location>
<!-- TODO inpatient/outpatient -->
<provider>xxxa</provider>
<facts>
<!-- test parsing of data types -->
<fact concept="source1"><value xsi:type="string">1</value></fact>
<fact concept="source1"><value xsi:type="string">2</value></fact>
<fact concept="source2"><value xsi:type="string">1</value></fact>
<fact concept="source3"><value xsi:type="string">1</value></fact>
<fact concept="source3"><value xsi:type="string">2</value></fact>
<fact concept="source3"><value xsi:type="string">99</value></fact>
</facts>
</visit>
<!-- weitere zeitstempel -->
</dwh-eav>
......@@ -20,8 +20,12 @@
skos:notation "T:t1" .
:t2 a dwh:Concept ;
skos:inScheme :target ;
skos:prefLabel "Target Concept 1"@en ;
skos:notation "T:t1" .
skos:prefLabel "Target Concept 2"@en ;
skos:notation "T:t2" .
:t3 a dwh:Concept ;
skos:inScheme :target ;
skos:prefLabel "Target Concept 3"@en ;
skos:notation "T:t3" .
# Source schema
......@@ -33,7 +37,7 @@
skos:notation "source1" ;
dwh:mapFact [
a dwh:MapRule ; # optional
dwh:condition "./value='1'"^^dwh:XPath ;
dwh:condition "fact/value='1'"^^dwh:XPath ;
dwh:target :t1 ;
dwh:modify dwh:removeValue
] .
......@@ -42,17 +46,17 @@
skos:notation "source2" ;
dwh:mapFact [
a dwh:MapRule ; # optional
dwh:condition "fact.value==1"^^dwh:Javascript ;
dwh:target :t1 ;
dwh:condition "fact.value.value==1"^^dwh:ECMAScript ;
dwh:target :t2 ;
dwh:modify dwh:removeValue
] .
:MapChooseValue a dwh:Concept ;
skos:inScheme :source ;
skos:notation "map2" ;
skos:notation "source3" ;
dwh:mapFact [
dwh:choose ([rdf:value "1"; dwh:target :t1] [rdf:value "1"; dwh:target :t2]) ;
dwh:otherwise [dwh:target snomed:11399002 ; dwh:modify dwh:removeValue]; # pulmonary arterial hypertension
dwh:choose ([rdf:value "1"; dwh:target :t1] [rdf:value "2"; dwh:target :t2]) ;
dwh:otherwise [dwh:target :t3 ; dwh:modify dwh:removeValue]; # pulmonary arterial hypertension
dwh:modify dwh:removeValue
] .
......
......@@ -28,6 +28,14 @@
<artifactId>histream-core</artifactId>
<version>0.2-alpha</version>
</dependency>
<!-- include tests from core for testing -->
<dependency>
<groupId>de.sekmi.histream</groupId>
<artifactId>histream-core</artifactId>
<version>0.2-alpha</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<!-- dependencies for RDF libraries -->
<dependency>
......
......@@ -30,7 +30,7 @@ public class ConceptImpl implements Concept {
return store.getNarrower(this);
}
Resource getResource(){
public Resource getResource(){
return res;
}
Store getStore(){
......
......@@ -14,6 +14,8 @@ public class HIStreamOntology {
public static final URI DWH_TARGET;
public static final URI DWH_MODIFY;
public static final URI DWH_OTHERWISE;
public static final URI DWH_XPATH;
public static final URI DWH_ECMASCRIPT;
static {
final ValueFactory f = ValueFactoryImpl.getInstance();
......@@ -24,6 +26,8 @@ public class HIStreamOntology {
DWH_TARGET = f.createURI(DWH_NAMESPACE, "target");
DWH_MODIFY = f.createURI(DWH_NAMESPACE, "modify");
DWH_OTHERWISE = f.createURI(DWH_NAMESPACE, "otherwise");
DWH_XPATH = f.createURI(DWH_NAMESPACE, "XPath");
DWH_ECMASCRIPT = f.createURI(DWH_NAMESPACE, "ECMAScript");
}
}
......@@ -17,6 +17,7 @@ import org.openrdf.model.Value;
import org.openrdf.model.util.Literals;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.SKOS;
import org.openrdf.model.vocabulary.XMLSchema;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
......@@ -30,6 +31,7 @@ import de.sekmi.histream.Plugin;
import de.sekmi.histream.ontology.Concept;
import de.sekmi.histream.ontology.Ontology;
import de.sekmi.histream.ontology.OntologyException;
import de.sekmi.histream.ontology.skos.transform.ConditionType;
import de.sekmi.histream.ontology.skos.transform.Rule;
import de.sekmi.histream.ontology.skos.transform.TransformationRules;
......@@ -255,7 +257,7 @@ public class Store implements Ontology, Plugin {
* TODO move method to ConceptImpl
* @param notation notation for the concept
* @param schemaURI schema in which to search for the notation. can be null
* @return transformation rules
* @return transformation rules or {@code null} if there are no rules
* @throws OntologyException for ontology errors
*/
public TransformationRules getConceptTransformations(String notation, String schemaURI) throws OntologyException{
......@@ -276,7 +278,8 @@ public class Store implements Ontology, Plugin {
} catch (RepositoryException e) {
throw new OntologyException(e);
}
return new TransformationRules(rules.toArray(new Rule[rules.size()]));
if( rules.size() == 0 )return null;
else return new TransformationRules(rules.toArray(new Rule[rules.size()]));
}
public void forEachRDFListItem(Resource rdfList, Consumer<Value> consumer) throws RepositoryException{
do{
......@@ -319,7 +322,7 @@ public class Store implements Ontology, Plugin {
v[4] = s.getObject();
}else if( s.getPredicate().equals(HIStreamOntology.DWH_OTHERWISE) ){
v[5] = s.getObject();
}//TODO: else if DWH_OTHERWISE
}
// TODO add exceptions to errors
});
if( v[0] != null ){
......@@ -332,7 +335,7 @@ public class Store implements Ontology, Plugin {
if( !(v[0] instanceof Literal) )throw new SKOSException(r, "dwh:condition needs literal object");
Literal condition = (Literal)v[0];
// TODO: load otherwise
return new Rule(condition, new ConceptImpl(this, (Resource)v[2]));
return Rule.forCondition(condition, new ConceptImpl(this, (Resource)v[2]));
}else if( v[1] != null ){
// choose specified
......@@ -350,7 +353,14 @@ public class Store implements Ontology, Plugin {
errors.add(e);
}
});
// TODO load otherwise
// load otherwise
if( v[5] != null ){
// TODO move to separate method to reuse this code
Value targ = RDFUtils.getObject(rc, (Resource)v[5], HIStreamOntology.DWH_TARGET);
if( targ == null )throw new SKOSException(r, "dwh:otherwise must include a dwh:target");
// TODO: load otherwise into a rule
}
if( errors.isEmpty() ){
return new Rule(list.toArray(new Rule[list.size()]), null);
}else{
......@@ -366,7 +376,9 @@ public class Store implements Ontology, Plugin {
if( !(v[4] instanceof Literal) )throw new SKOSException(r, "rdf:value needs literal object");
if( v[2] == null )throw new SKOSException(r, "dwh:mapFact with rdf:value must also contain dwh:target");
Literal value = (Literal)v[4];
return new Rule(value, new ConceptImpl(this, (Resource)v[2]));
if( !value.getDatatype().equals(XMLSchema.STRING) )throw new SKOSException(r, "rdf:value for comparison must have datatype xsd:string");
return new Rule(value.stringValue(), ConditionType.StringValueEquals, new ConceptImpl(this, (Resource)v[2]));
//return Rule.forCondition(value, new ConceptImpl(this, (Resource)v[2]));
}else{
// unsupported mapping
throw new SKOSException(r, "dwh:mapFact must contain one of dwh:condition, dwh:choose, dwh:value");
......
package de.sekmi.histream.ontology.skos.transform;
public enum ConditionType {
XPath, ECMAScript, StringValueEquals
}
......@@ -3,11 +3,19 @@ package de.sekmi.histream.ontology.skos.transform;
import java.util.HashMap;
import java.util.function.Consumer;
import javax.xml.bind.JAXBException;
import de.sekmi.histream.Observation;
import de.sekmi.histream.ObservationFactory;
import de.sekmi.histream.eval.ECMAEvaluator;
import de.sekmi.histream.eval.Engine;
import de.sekmi.histream.eval.ScriptException;
import de.sekmi.histream.eval.StringValueEqualsEngine;
import de.sekmi.histream.impl.XPathEvaluator;
import de.sekmi.histream.io.Transformation;
import de.sekmi.histream.io.TransformationException;
import de.sekmi.histream.ontology.OntologyException;
import de.sekmi.histream.ontology.skos.ConceptImpl;
import de.sekmi.histream.ontology.skos.Store;
public class RDFTransformation implements Transformation {
......@@ -17,13 +25,23 @@ public class RDFTransformation implements Transformation {
private String schema;
private boolean dropFactsWithoutRules;
private HashMap<String, TransformationRules> cache;
private Engine engineXPath;
private Engine engineES;
public RDFTransformation(ObservationFactory factory, Store store, String schema, boolean dropFactsWithoutRules){
public RDFTransformation(ObservationFactory factory, Store store, String schema, boolean dropFactsWithoutRules)throws TransformationException{
this.store = store;
this.factory = factory;
this.schema = schema;
this.dropFactsWithoutRules = dropFactsWithoutRules;
this.cache = new HashMap<>();
// TODO initialize evaluation engines for XPath and ECMAScript
try {
engineXPath = new XPathEvaluator();
} catch (JAXBException e) {
throw new TransformationException("Initialization error for XPath engine",e);
}
engineES = new ECMAEvaluator();
}
@Override
......@@ -53,11 +71,83 @@ public class RDFTransformation implements Transformation {
}
// TODO perform transformation
try {
Rule[] map = rules.getMapRules();
for( Rule rule : map ){
applyRule(rule, fact);
}
} catch (OntologyException e) {
throw new TransformationException("Rule evaluation failed", e);
}
// TODO perform generations
if( factory != null ){}
return fact;
}
private boolean applyRule(Rule rule, Observation fact) throws OntologyException, TransformationException{
ConceptImpl target;
if( rule.choose != null ){
int i = -1;
for( i=0; i<rule.choose.length; i++ ){
if( applyRule(rule.choose[i], fact) ){
// rule matched
target = rule.choose[i].target;
break;
}
}
if( i == rule.choose.length && rule.otherwise != null ){
// no match for choose rules, try otherwise
target = rule.otherwise.target;
}else{
return false;
}
}else if( rule.condition != null ){
Engine ng;
switch( rule.conditionType ){
case ECMAScript:
ng = engineES;
break;
case XPath:
ng = engineXPath;
break;
case StringValueEquals:
ng = StringValueEqualsEngine.ENGINE;
break;
default:
throw new TransformationException("Unsupported condition type: "+rule.conditionType);
}
boolean matched;
try {
matched = ng.test(rule.condition, fact);
} catch (ScriptException e) {
throw new TransformationException("Evaluation error: "+rule.condition, e);
}
if( matched ){
// match
target = rule.target;
}else if( rule.otherwise != null ){
// try otherwise
target = rule.otherwise.target;
}else{
// no match
return false;
}
}else{
throw new TransformationException("Rule without 'choose' or 'condition'");
}
// map to target
String[] ids = target.getIDs();
if( ids.length == 0 )throw new TransformationException("No notation found in target concept "+target);
fact.replaceConcept(ids[0]);
// TODO: is there a way to specify which notation should be used if there are multiple notations?
return true;
}
}
package de.sekmi.histream.ontology.skos.transform;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import de.sekmi.histream.ontology.skos.ConceptImpl;
import de.sekmi.histream.ontology.skos.HIStreamOntology;
import de.sekmi.histream.ontology.skos.SKOSException;
public class Rule {
protected String condition;
protected String conditionType;
protected ConditionType conditionType;
protected Rule[] choose;
protected ConceptImpl target;
protected Rule otherwise;
public Rule(Literal condition, ConceptImpl target){
this.condition = condition.stringValue();
this.conditionType = condition.getDatatype().stringValue();
public static Rule forCondition(Literal condition, ConceptImpl target)throws SKOSException{
URI datatype = condition.getDatatype();
if( datatype == null ){
throw new SKOSException(target.getResource(), "Expression without datatype");
}
else if( datatype.equals(HIStreamOntology.DWH_XPATH) ){
// set xpath
return new Rule(condition.stringValue(), ConditionType.XPath, target);
}else if( datatype.equals(HIStreamOntology.DWH_ECMASCRIPT) ){
// use ecmascript
return new Rule(condition.stringValue(), ConditionType.ECMAScript, target);
}else{
throw new SKOSException(target.getResource(), "Unsupported expression datatype: "+datatype.stringValue());
}
}
public Rule (String condition, ConditionType type, ConceptImpl target){
this.condition = condition;
this.conditionType = type;
this.target = target;
}
public Rule(Rule[] choose, Rule otherwise){
this.choose = choose;
}
......
......@@ -6,6 +6,9 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import de.sekmi.histream.Observation;
import de.sekmi.histream.ObservationSupplier;
import de.sekmi.histream.io.FileObservationProviderTest;
import de.sekmi.histream.ontology.OntologyException;
import de.sekmi.histream.ontology.skos.Store;
import de.sekmi.histream.ontology.skos.transform.Rule;
......@@ -29,6 +32,58 @@ public class TransformationRuleTest {
Rule rule = r.getMapRules()[0];
Assert.assertNotNull(rule.condition);
Assert.assertNotNull(rule.getTarget());
Assert.assertEquals(ConditionType.XPath, rule.conditionType);
}
// TODO perform transformation
@Test
public void testApplyRules() throws Exception{
TransformationRules r = store.getConceptTransformations("source1", TEST_PREFIX+"source");
Assert.assertNotNull(r);
RDFTransformation t = new RDFTransformation(null, store, TEST_PREFIX+"source", true);
FileObservationProviderTest p = new FileObservationProviderTest();
p.initializeObservationFactory();
ObservationSupplier s = p.getExampleSupplier("examples/test-mapping-facts.xml");
Observation o, o2;
o = s.get();
Assert.assertEquals("source1", o.getConceptId());
o2 = t.transform(o, null);
Assert.assertEquals("T:t1", o2.getConceptId());
o = s.get(); // no match (value != 1)
Assert.assertEquals("source1", o.getConceptId());
o2 = t.transform(o, null);
Assert.assertEquals("source1", o2.getConceptId());
o = s.get();
Assert.assertEquals("source2", o.getConceptId());
o2 = t.transform(o, null);
Assert.assertEquals("T:t2", o2.getConceptId());
o = s.get();
Assert.assertEquals("source3", o.getConceptId());
o2 = t.transform(o, null);
Assert.assertEquals("T:t1", o2.getConceptId());
o = s.get();
Assert.assertEquals("source3", o.getConceptId());
o2 = t.transform(o, null);
Assert.assertEquals("T:t2", o2.getConceptId());
// TODO implement choose+otherwise
/*
o = s.get();
Assert.assertEquals("source3", o.getConceptId());
o2 = t.transform(o, null);
Assert.assertEquals("T:t3", o2.getConceptId());
*/
s.close();
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment