commit 3fc1b5040826e317263e0b2dd260a74e3d1c8109
parent 32c9a81a8af415aa0b9af868293034d5cc7d3f52
Author: Dan Callaghan <djc@djc.id.au>
Date: Sun, 1 Nov 2009 15:45:18 +1000
datetime data type which converts to joda DateTime instances; adaptation for formatting dates/datetimes
Diffstat:
9 files changed, 218 insertions(+), 9 deletions(-)
diff --git a/src/main/antlr3/au/com/miskinhill/rdftemplate/selector/Selector.g b/src/main/antlr3/au/com/miskinhill/rdftemplate/selector/Selector.g
@@ -65,7 +65,7 @@ selector returns [Selector<?> result]
throw new InvalidSelectorSyntaxException("No adaptation named " + $adaptationName.text);
}
( '('
- startIndex=INTEGER {
+ ( startIndex=INTEGER {
try {
adaptation = adaptationClass.getConstructor(Integer.class)
.newInstance(Integer.parseInt($startIndex.text));
@@ -73,6 +73,15 @@ selector returns [Selector<?> result]
throw new InvalidSelectorSyntaxException(e);
}
}
+ | sq=SINGLE_QUOTED {
+ try {
+ adaptation = adaptationClass.getConstructor(String.class)
+ .newInstance($sq.text);
+ } catch (Exception e) {
+ throw new InvalidSelectorSyntaxException(e);
+ }
+ }
+ )
')'
| {
try {
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/datatype/DateTimeDataType.java b/src/main/java/au/com/miskinhill/rdftemplate/datatype/DateTimeDataType.java
@@ -0,0 +1,98 @@
+package au.com.miskinhill.rdftemplate.datatype;
+
+import com.hp.hpl.jena.datatypes.DatatypeFormatException;
+import com.hp.hpl.jena.datatypes.RDFDatatype;
+import com.hp.hpl.jena.datatypes.TypeMapper;
+import com.hp.hpl.jena.graph.impl.LiteralLabel;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DateTimeDataType implements RDFDatatype {
+
+ public static final String URI = "http://www.w3.org/TR/xmlschema-2/#datetime";
+
+ @SuppressWarnings("unused")
+ private static DateTimeDataType instance;
+ public static void registerStaticInstance() {
+ instance = new DateTimeDataType();
+ }
+
+ private final DateTimeFormatter parser = ISODateTimeFormat.dateTimeNoMillis();
+
+ public DateTimeDataType() {
+ TypeMapper.getInstance().registerDatatype(this);
+ }
+
+ @Override
+ public String getURI() {
+ return URI;
+ }
+
+ @Override
+ public Class<DateTime> getJavaClass() {
+ return DateTime.class;
+ }
+
+ @Override
+ public String unparse(Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object cannonicalise(Object value) {
+ return value;
+ }
+
+ @Override
+ public Object extendedTypeDefinition() {
+ return null;
+ }
+
+ @Override
+ public int getHashCode(LiteralLabel lit) {
+ return lit.getValue().hashCode();
+ }
+
+ @Override
+ public boolean isEqual(LiteralLabel left, LiteralLabel right) {
+ return left.getValue().equals(right.getValue());
+ }
+
+ @Override
+ public DateTime parse(String lexicalForm) throws DatatypeFormatException {
+ try {
+ return parser.parseDateTime(lexicalForm);
+ } catch (IllegalArgumentException e) {
+ throw new DatatypeFormatException(lexicalForm, this, "Parser barfed");
+ }
+ }
+
+ @Override
+ public boolean isValid(String lexicalForm) {
+ try {
+ parse(lexicalForm);
+ return true;
+ } catch (DatatypeFormatException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isValidLiteral(LiteralLabel lit) {
+ return lit.getDatatypeURI().equals(URI) && isValid(lit.getLexicalForm());
+ }
+
+ @Override
+ public boolean isValidValue(Object valueForm) {
+ return (valueForm instanceof DateTime);
+ }
+
+ @Override
+ public RDFDatatype normalizeSubType(Object value, RDFDatatype dt) {
+ return dt;
+ }
+
+}
+\ No newline at end of file
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/DefaultAdaptationResolver.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/DefaultAdaptationResolver.java
@@ -11,6 +11,7 @@ public class DefaultAdaptationResolver implements AdaptationResolver {
ADAPTATIONS.put("uri-slice", UriSliceAdaptation.class);
ADAPTATIONS.put("lv", LiteralValueAdaptation.class);
ADAPTATIONS.put("comparable-lv", ComparableLiteralValueAdaptation.class);
+ ADAPTATIONS.put("formatted-dt", FormattedDateTimeAdaptation.class);
}
@Override
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/FormattedDateTimeAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/FormattedDateTimeAdaptation.java
@@ -0,0 +1,52 @@
+package au.com.miskinhill.rdftemplate.selector;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+import com.hp.hpl.jena.rdf.model.Literal;
+import com.hp.hpl.jena.rdf.model.RDFNode;
+import org.joda.time.ReadableInstant;
+import org.joda.time.ReadablePartial;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+public class FormattedDateTimeAdaptation implements Adaptation<String> {
+
+ private final String pattern;
+
+ public FormattedDateTimeAdaptation(String pattern) {
+ this.pattern = pattern;
+ }
+
+ public String getPattern() {
+ return pattern;
+ }
+
+ @Override
+ public Class<String> getDestinationType() {
+ return String.class;
+ }
+
+ @Override
+ public String adapt(RDFNode node) {
+ if (!node.isLiteral()) {
+ throw new SelectorEvaluationException("Attempted to apply #formatted-dt to non-literal node " + node);
+ }
+ DateTimeFormatter formatter = DateTimeFormat.forPattern(pattern);
+ Object lv = ((Literal) node).getValue();
+ if (lv instanceof ReadableInstant) {
+ ReadableInstant instant = (ReadableInstant) lv;
+ return formatter.print(instant);
+ } else if (lv instanceof ReadablePartial) {
+ ReadablePartial instant = (ReadablePartial) lv;
+ return formatter.print(instant);
+ } else {
+ throw new SelectorEvaluationException("Attempted to apply #formatted-dt to non-datetime literal " + lv);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).append("pattern", pattern).toString();
+ }
+
+}
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/LiteralValueAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/LiteralValueAdaptation.java
@@ -13,7 +13,7 @@ public class LiteralValueAdaptation implements Adaptation<Object> {
@Override
public Object adapt(RDFNode node) {
if (!node.isLiteral()) {
- throw new SelectorEvaluationException("Attempted to apply #comparable-lv to non-literal node " + node);
+ throw new SelectorEvaluationException("Attempted to apply #lv to non-literal node " + node);
}
return ((Literal) node).getValue();
}
diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/AdaptationMatcher.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/AdaptationMatcher.java
@@ -26,5 +26,11 @@ public class AdaptationMatcher<T extends Adaptation<?>> extends BeanPropertyMatc
public static AdaptationMatcher<ComparableLiteralValueAdaptation> comparableLVAdaptation() {
return new AdaptationMatcher<ComparableLiteralValueAdaptation>(ComparableLiteralValueAdaptation.class);
}
+
+ public static AdaptationMatcher<FormattedDateTimeAdaptation> formattedDTAdaptation(String pattern) {
+ AdaptationMatcher<FormattedDateTimeAdaptation> m = new AdaptationMatcher<FormattedDateTimeAdaptation>(FormattedDateTimeAdaptation.class);
+ m.addRequiredProperty("pattern", equalTo(pattern));
+ return m;
+ }
}
diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationUnitTest.java
@@ -1,10 +1,5 @@
package au.com.miskinhill.rdftemplate.selector;
-import static au.com.miskinhill.rdftemplate.selector.AdaptationMatcher.comparableLVAdaptation;
-import static au.com.miskinhill.rdftemplate.selector.SelectorComparatorMatcher.selectorComparator;
-import static au.com.miskinhill.rdftemplate.selector.SelectorMatcher.selector;
-import static au.com.miskinhill.rdftemplate.selector.TraversalMatcher.traversal;
-
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.matchers.JUnitMatchers.hasItems;
@@ -22,16 +17,18 @@ import org.junit.BeforeClass;
import org.junit.Test;
import au.com.miskinhill.rdftemplate.datatype.DateDataType;
+import au.com.miskinhill.rdftemplate.datatype.DateTimeDataType;
public class SelectorEvaluationUnitTest {
private Model m;
- private Resource journal, issue, article, citedArticle, author, anotherAuthor, book, review, anotherReview, obituary, en, ru;
+ private Resource journal, issue, article, citedArticle, author, anotherAuthor, book, review, anotherReview, obituary, en, ru, forum;
private SelectorFactory selectorFactory;
@BeforeClass
public static void ensureDatatypesRegistered() {
DateDataType.registerStaticInstance();
+ DateTimeDataType.registerStaticInstance();
}
@Before
@@ -51,6 +48,7 @@ public class SelectorEvaluationUnitTest {
obituary = m.createResource("http://miskinhill.com.au/journals/test/1:1/in-memoriam-john-doe");
en = m.createResource("http://www.lingvoj.org/lang/en");
ru = m.createResource("http://www.lingvoj.org/lang/ru");
+ forum = m.createResource("http://miskinhill.com.au/");
selectorFactory = new AntlrSelectorFactory();
}
@@ -177,4 +175,11 @@ public class SelectorEvaluationUnitTest {
assertThat(results.get(2), equalTo((RDFNode) review));
}
+ @Test
+ public void shouldEvaluateFormattedDTAdaptation() throws Exception {
+ String result = selectorFactory.get("!sioc:has_container/dc:created#formatted-dt('d MMMM yyyy')")
+ .withResultType(String.class).singleResult(forum);
+ assertThat(result, equalTo("15 June 2009"));
+ }
+
}
diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorParserUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorParserUnitTest.java
@@ -166,6 +166,13 @@ public class SelectorParserUnitTest {
selectorComparator(selector(traversal("mhs", "startPage")).withAdaptation(comparableLVAdaptation())))));
}
+ @Test
+ public void shouldRecogniseFormattedDTAdaptation() throws Exception {
+ Selector<?> selector = factory.get("dc:created#formatted-dt('d MMMM yyyy')");
+ assertThat(selector, selector(traversal("dc", "created"))
+ .withAdaptation(formattedDTAdaptation("d MMMM yyyy")));
+ }
+
@Test(expected = InvalidSelectorSyntaxException.class)
public void shouldThrowForInvalidSyntax() throws Exception {
factory.get("dc:creator]["); // this is a parser error
diff --git a/src/test/resources/au/com/miskinhill/rdftemplate/test-data.xml b/src/test/resources/au/com/miskinhill/rdftemplate/test-data.xml
@@ -5,7 +5,37 @@
xmlns:contact="http://www.w3.org/2000/10/swap/pim/contact#"
xmlns:geonames="http://www.geonames.org/ontology#"
xmlns:lingvoj="http://www.lingvoj.org/ontology#"
- xmlns:mhs="http://miskinhill.com.au/rdfschema/1.0/">
+ xmlns:mhs="http://miskinhill.com.au/rdfschema/1.0/"
+ xmlns:sioc="http://rdfs.org/sioc/ns#"
+ xmlns:awol="http://bblfish.net/work/atom-owl/2006-06-06/#">
+
+ <sioc:Forum rdf:about="http://miskinhill.com.au/">
+ <dc:title xml:lang="en">News</dc:title>
+ </sioc:Forum>
+ <sioc:Post rdf:about="http://miskinhill.com.au/news/data-feeds">
+ <dc:created rdf:datatype="http://www.w3.org/TR/xmlschema-2/#datetime">2009-06-15T18:21:32+10:00</dc:created>
+ <dc:title xml:lang="en">New data feeds available</dc:title>
+ <sioc:content>
+ <awol:Content>
+ <awol:body rdf:parseType="Literal">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <p>To coincide with the publication of our second issue, the <a
+ href="/journals/asees/22:1-2/">2008 volume</a> of <a
+ href="/journals/asees/"><em>Australian Slavonic and East European
+ Studies</em></a>, we are making available two new data feeds: an <a
+ href="/feeds/issues">Atom feed of all journal issues</a> published
+ on this site, and the <a href="/feeds/world">complete RDF
+ dataset</a> underlying the site.</p>
+ <p>We hope this helps our users and aggregators to discover new
+ content as it is published.</p>
+ </div>
+ </awol:body>
+ <awol:type>application/xhtml+xml</awol:type>
+ </awol:Content>
+ </sioc:content>
+ <sioc:has_container rdf:resource="http://miskinhill.com.au/" />
+ </sioc:Post>
+
<mhs:Journal rdf:about="http://miskinhill.com.au/journals/test/">
<dc:title xml:lang="en">Test Journal of Good Stuff</dc:title>
<dc:publisher rdf:nodeID="pub"/>