RYA-392-Datetime-Within. Closes #237.
Project: http://git-wip-us.apache.org/repos/asf/incubator-rya/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-rya/commit/29a8e6b7 Tree: http://git-wip-us.apache.org/repos/asf/incubator-rya/tree/29a8e6b7 Diff: http://git-wip-us.apache.org/repos/asf/incubator-rya/diff/29a8e6b7 Branch: refs/heads/master Commit: 29a8e6b75c0ed903df2da8112f30f4941448582a Parents: a3b2042 Author: Caleb Meier <[email protected]> Authored: Wed Oct 4 12:06:55 2017 -0700 Committer: jdasch <[email protected]> Committed: Thu Oct 12 12:56:26 2017 -0400 ---------------------------------------------------------------------- .../rya/api/functions/DateTimeWithinPeriod.java | 130 ++++++++++++++ .../org/apache/rya/api/functions/OWLTime.java | 111 ++++++++++++ ...f.query.algebra.evaluation.function.Function | 17 ++ .../api/functions/DateTimeWithinPeriodTest.java | 180 +++++++++++++++++++ .../pcj/fluo/app/FilterResultUpdater.java | 14 +- .../pcj/fluo/app/util/FilterSerializer.java | 27 ++- .../pcj/fluo/app/util/FilterSerializerTest.java | 48 +++++ .../indexing/pcj/fluo/integration/QueryIT.java | 89 +++++++++ 8 files changed, 611 insertions(+), 5 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/29a8e6b7/common/rya.api/src/main/java/org/apache/rya/api/functions/DateTimeWithinPeriod.java ---------------------------------------------------------------------- diff --git a/common/rya.api/src/main/java/org/apache/rya/api/functions/DateTimeWithinPeriod.java b/common/rya.api/src/main/java/org/apache/rya/api/functions/DateTimeWithinPeriod.java new file mode 100644 index 0000000..aedeea7 --- /dev/null +++ b/common/rya.api/src/main/java/org/apache/rya/api/functions/DateTimeWithinPeriod.java @@ -0,0 +1,130 @@ +/* + * 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.rya.api.functions; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.time.Duration; +import java.time.Instant; + +import org.openrdf.model.Literal; +import org.openrdf.model.URI; +import org.openrdf.model.Value; +import org.openrdf.model.ValueFactory; +import org.openrdf.model.datatypes.XMLDatatypeUtil; +import org.openrdf.model.vocabulary.FN; +import org.openrdf.model.vocabulary.XMLSchema; +import org.openrdf.query.algebra.evaluation.ValueExprEvaluationException; +import org.openrdf.query.algebra.evaluation.function.Function; + +/** + * This {@link Function} determines whether two {@link XMLSchema#DATETIME}s occur within a specified period of time of + * one another. The method {@link Function#evaluate(ValueFactory, Value...)} expects four values, where the first two + * values are the datetimes, the third value is an integer indicating the period, and the fourth value is a URI + * indicating the time unit of the period. The URI must be of Type DurationDescription in the OWL-Time ontology (see + * <a href ="https://www.w3.org/TR/owl-time/">https://www.w3.org/TR/owl-time/</a>). Examples of valid time unit URIs can + * be found in the class {@link OWLTime} and below + * <ul> + * <li>http://www.w3.org/2006/time#days</li> + * <li>http://www.w3.org/2006/time#hours</li> + * <li>http://www.w3.org/2006/time#minutes</li> + * <li>http://www.w3.org/2006/time#seconds</li> + * </ul> + * + */ +public class DateTimeWithinPeriod implements Function { + + private static final String FUNCTION_URI = FN.NAMESPACE + "dateTimeWithin"; + + @Override + public String getURI() { + return FUNCTION_URI; + } + + /** + * Determines whether two datetimes occur within a specified period of time of one another. This method expects four + * values, where the first two values are the datetimes, the third value is an integer indicating the period, and + * the fourth value is a URI indicating the time unit of the period. The URI must be of Type DurationDescription in + * the OWL-Time ontology (see <a href ="https://www.w3.org/TR/owl-time/">https://www.w3.org/TR/owl-time/</a>). + * Examples of valid time unit URIs can be found in the class {@link OWLTime} and below + * <ul> + * <li>http://www.w3.org/2006/time#days</li> + * <li>http://www.w3.org/2006/time#hours</li> + * <li>http://www.w3.org/2006/time#minutes</li> + * <li>http://www.w3.org/2006/time#seconds</li> + * </ul> + * + * @param valueFactory - factory for creating values (not null) + * @param values - array of Value arguments for this Function (not null). + */ + @Override + public Value evaluate(ValueFactory valueFactory, Value... values) throws ValueExprEvaluationException { + checkNotNull(valueFactory); + checkNotNull(values); + try { + // general validation of input + checkArgument(values.length == 4); + checkArgument(values[0] instanceof Literal); + checkArgument(values[1] instanceof Literal); + checkArgument(values[2] instanceof Literal); + checkArgument(values[3] instanceof URI); + + Instant dateTime1 = convertToInstant((Literal) values[0]); + Instant dateTime2 = convertToInstant((Literal) values[1]); + long periodMillis = convertPeriodToMillis((Literal) values[2], (URI) values[3]); + long timeBetween = Math.abs(Duration.between(dateTime1, dateTime2).toMillis()); + + return valueFactory.createLiteral(timeBetween < periodMillis); + } catch (Exception e) { + throw new ValueExprEvaluationException(e); + } + } + + private Instant convertToInstant(Literal literal) { + String stringVal = literal.getLabel(); + URI dataType = literal.getDatatype(); + checkArgument(dataType.equals(XMLSchema.DATETIME) || dataType.equals(XMLSchema.DATE), + String.format("Invalid data type for date time. Data Type must be of type %s or %s .", XMLSchema.DATETIME, XMLSchema.DATE)); + checkArgument(XMLDatatypeUtil.isValidDateTime(stringVal) || XMLDatatypeUtil.isValidDate(stringVal), "Invalid date time value."); + return literal.calendarValue().toGregorianCalendar().toInstant(); + } + + private long convertPeriodToMillis(Literal literal, URI unit) { + String stringVal = literal.getLabel(); + URI dataType = literal.getDatatype(); + checkArgument(dataType.equals(XMLSchema.INTEGER) || dataType.equals(XMLSchema.INT), String + .format("Invalid data type for period duration. Data Type must be of type %s or %s .", XMLSchema.INTEGER, XMLSchema.INT)); + checkArgument(XMLDatatypeUtil.isValidInteger(stringVal) || XMLDatatypeUtil.isValidInt(stringVal), "Invalid duration value."); + return convertToMillis(Integer.parseInt(stringVal), unit); + } + + /** + * Converts the period duration to milliseconds. + * + * @param duration - duration of temporal period + * @param unit - URI indicating the time unit (URI must be of type DurationDescription in the OWL-Time ontology + * indicated by the namespace <http://www.w3.org/2006/time#>) + * @return - duration in milliseconds + */ + private long convertToMillis(int duration, URI unit) { + checkArgument(duration > 0); + return OWLTime.getMillis(duration, unit); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/29a8e6b7/common/rya.api/src/main/java/org/apache/rya/api/functions/OWLTime.java ---------------------------------------------------------------------- diff --git a/common/rya.api/src/main/java/org/apache/rya/api/functions/OWLTime.java b/common/rya.api/src/main/java/org/apache/rya/api/functions/OWLTime.java new file mode 100644 index 0000000..5ffc4ee --- /dev/null +++ b/common/rya.api/src/main/java/org/apache/rya/api/functions/OWLTime.java @@ -0,0 +1,111 @@ +/* + * 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.rya.api.functions; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.openrdf.model.URI; +import org.openrdf.model.ValueFactory; +import org.openrdf.model.impl.ValueFactoryImpl; + +/** + * Constants for OWL-Time primitives in the OWL-Time namespace. + * + */ +public class OWLTime { + + private static final ValueFactory FACTORY = ValueFactoryImpl.getInstance(); + + /** + * Indicates namespace of OWL-Time ontology + */ + public static final String NAMESPACE = "http://www.w3.org/2006/time#"; + /** + * Seconds class of type DurationDescription in OWL-Time ontology + */ + public static final URI SECONDS_URI = FACTORY.createURI(NAMESPACE, "seconds"); + /** + * Minutes class of type DurationDescription in OWL-Time ontology + */ + public static final URI MINUTES_URI = FACTORY.createURI(NAMESPACE, "minutes"); + /** + * Hours class of type DurationDescription in OWL-Time ontology + */ + public static final URI HOURS_URI = FACTORY.createURI(NAMESPACE, "hours"); + /** + * Days class of type DurationDescription in OWL-Time ontology + */ + public static final URI DAYS_URI = FACTORY.createURI(NAMESPACE, "days"); + /** + * Weeks class of type DurationDescription in OWL-Time ontology + */ + public static final URI WEEKS_URI = FACTORY.createURI(NAMESPACE, "weeks"); + + private static final Map<URI, ChronoUnit> DURATION_MAP = new HashMap<>(); + + static { + DURATION_MAP.put(SECONDS_URI, ChronoUnit.SECONDS); + DURATION_MAP.put(MINUTES_URI, ChronoUnit.MINUTES); + DURATION_MAP.put(HOURS_URI, ChronoUnit.HOURS); + DURATION_MAP.put(DAYS_URI, ChronoUnit.DAYS); + DURATION_MAP.put(WEEKS_URI, ChronoUnit.WEEKS); + } + + /** + * Verifies whether URI is a valid OWL-Time URI that is supported by this class. + * @param durationURI - OWLTime URI indicating the time unit (not null) + * @return - {@code true} if this URI indicates a supported OWLTime time unit + */ + public static boolean isValidDurationType(URI durationURI) { + checkNotNull(durationURI); + return DURATION_MAP.containsKey(durationURI); + } + + /** + * Returns the duration in milliseconds + * + * @param duration - amount of time in the units indicated by the provided {@link OWLTime} URI + * @param uri - OWLTime URI indicating the time unit of duration (not null) + * @return - the amount of time in milliseconds + * @throws IllegalArgumentException if provided {@link URI} is not a valid, supported OWL-Time time unit. + */ + public static long getMillis(int duration, URI uri) throws IllegalArgumentException { + Optional<ChronoUnit> unit = getChronoUnitFromURI(uri); + checkArgument(unit.isPresent(), + String.format("URI %s does not indicate a valid OWLTime time unit. URI must of be of type %s, %s, %s, %s, or %s .", uri, + SECONDS_URI, MINUTES_URI, HOURS_URI, DAYS_URI, WEEKS_URI)); + return duration * unit.get().getDuration().toMillis(); + } + + /** + * Converts the {@link OWLTime} URI time unit to a {@link ChronoUnit} time unit + * + * @param durationURI - OWLTime time unit URI (not null) + * @return - corresponding ChronoUnit time unit + */ + public static Optional<ChronoUnit> getChronoUnitFromURI(URI durationURI) { + return Optional.ofNullable(DURATION_MAP.get(durationURI)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/29a8e6b7/common/rya.api/src/main/resources/META-INF/services/org.openrdf.query.algebra.evaluation.function.Function ---------------------------------------------------------------------- diff --git a/common/rya.api/src/main/resources/META-INF/services/org.openrdf.query.algebra.evaluation.function.Function b/common/rya.api/src/main/resources/META-INF/services/org.openrdf.query.algebra.evaluation.function.Function new file mode 100644 index 0000000..104a13c --- /dev/null +++ b/common/rya.api/src/main/resources/META-INF/services/org.openrdf.query.algebra.evaluation.function.Function @@ -0,0 +1,17 @@ + # 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. +org.apache.rya.api.functions.DateTimeWithinPeriod \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/29a8e6b7/common/rya.api/src/test/java/org/apache/rya/api/functions/DateTimeWithinPeriodTest.java ---------------------------------------------------------------------- diff --git a/common/rya.api/src/test/java/org/apache/rya/api/functions/DateTimeWithinPeriodTest.java b/common/rya.api/src/test/java/org/apache/rya/api/functions/DateTimeWithinPeriodTest.java new file mode 100644 index 0000000..0fb0f2a --- /dev/null +++ b/common/rya.api/src/test/java/org/apache/rya/api/functions/DateTimeWithinPeriodTest.java @@ -0,0 +1,180 @@ +/* + * 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.rya.api.functions; + +import static org.junit.Assert.assertEquals; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; + +import org.junit.Test; +import org.openrdf.model.Literal; +import org.openrdf.model.ValueFactory; +import org.openrdf.model.impl.ValueFactoryImpl; +import org.openrdf.query.algebra.evaluation.ValueExprEvaluationException; + +public class DateTimeWithinPeriodTest { + + private static final ValueFactory vf = new ValueFactoryImpl(); + private static final Literal TRUE = vf.createLiteral(true); + private static final Literal FALSE = vf.createLiteral(false); + + @Test + public void testSeconds() throws DatatypeConfigurationException, ValueExprEvaluationException { + DatatypeFactory dtf = DatatypeFactory.newInstance(); + + ZonedDateTime zTime = ZonedDateTime.now(); + String time = zTime.format(DateTimeFormatter.ISO_INSTANT); + + ZonedDateTime zTime1 = zTime.minusSeconds(1); + String time1 = zTime1.format(DateTimeFormatter.ISO_INSTANT); + + Literal now = vf.createLiteral(dtf.newXMLGregorianCalendar(time)); + Literal nowMinusOne = vf.createLiteral(dtf.newXMLGregorianCalendar(time1)); + + DateTimeWithinPeriod func = new DateTimeWithinPeriod(); + + assertEquals(TRUE, func.evaluate(vf, now, now, vf.createLiteral(1), OWLTime.SECONDS_URI)); + assertEquals(FALSE, func.evaluate(vf, now, nowMinusOne,vf.createLiteral(1), OWLTime.SECONDS_URI)); + assertEquals(TRUE, func.evaluate(vf, now, nowMinusOne,vf.createLiteral(2), OWLTime.SECONDS_URI)); + } + + @Test + public void testMinutes() throws DatatypeConfigurationException, ValueExprEvaluationException { + + DatatypeFactory dtf = DatatypeFactory.newInstance(); + + ZonedDateTime zTime = ZonedDateTime.now(); + String time = zTime.format(DateTimeFormatter.ISO_INSTANT); + + ZonedDateTime zTime1 = zTime.minusMinutes(1); + String time1 = zTime1.format(DateTimeFormatter.ISO_INSTANT); + + Literal now = vf.createLiteral(dtf.newXMLGregorianCalendar(time)); + Literal nowMinusOne = vf.createLiteral(dtf.newXMLGregorianCalendar(time1)); + + DateTimeWithinPeriod func = new DateTimeWithinPeriod(); + + assertEquals(TRUE, func.evaluate(vf, now, now,vf.createLiteral(1),OWLTime.MINUTES_URI)); + assertEquals(FALSE, func.evaluate(vf, now, nowMinusOne,vf.createLiteral(1),OWLTime.MINUTES_URI)); + assertEquals(TRUE, func.evaluate(vf, now, nowMinusOne,vf.createLiteral(2),OWLTime.MINUTES_URI)); + } + + + @Test + public void testHours() throws DatatypeConfigurationException, ValueExprEvaluationException { + DatatypeFactory dtf = DatatypeFactory.newInstance(); + + ZonedDateTime zTime = ZonedDateTime.now(); + String time = zTime.format(DateTimeFormatter.ISO_INSTANT); + + ZonedDateTime zTime1 = zTime.minusHours(1); + String time1 = zTime1.format(DateTimeFormatter.ISO_INSTANT); + + Literal now = vf.createLiteral(dtf.newXMLGregorianCalendar(time)); + Literal nowMinusOne = vf.createLiteral(dtf.newXMLGregorianCalendar(time1)); + + DateTimeWithinPeriod func = new DateTimeWithinPeriod(); + + assertEquals(TRUE, func.evaluate(vf, now, now,vf.createLiteral(1),OWLTime.HOURS_URI)); + assertEquals(FALSE, func.evaluate(vf, now, nowMinusOne,vf.createLiteral(1),OWLTime.HOURS_URI)); + assertEquals(TRUE, func.evaluate(vf, now, nowMinusOne,vf.createLiteral(2),OWLTime.HOURS_URI)); + } + + + @Test + public void testDays() throws DatatypeConfigurationException, ValueExprEvaluationException { + DatatypeFactory dtf = DatatypeFactory.newInstance(); + + ZonedDateTime zTime = ZonedDateTime.now(); + String time = zTime.format(DateTimeFormatter.ISO_INSTANT); + + ZonedDateTime zTime1 = zTime.minusDays(1); + String time1 = zTime1.format(DateTimeFormatter.ISO_INSTANT); + + Literal now = vf.createLiteral(dtf.newXMLGregorianCalendar(time)); + Literal nowMinusOne = vf.createLiteral(dtf.newXMLGregorianCalendar(time1)); + + DateTimeWithinPeriod func = new DateTimeWithinPeriod(); + + assertEquals(TRUE, func.evaluate(vf, now, now, vf.createLiteral(1), OWLTime.DAYS_URI)); + assertEquals(FALSE, func.evaluate(vf, now, nowMinusOne, vf.createLiteral(1), OWLTime.DAYS_URI)); + assertEquals(TRUE, func.evaluate(vf, now, nowMinusOne, vf.createLiteral(2), OWLTime.DAYS_URI)); + } + + @Test + public void testWeeks() throws DatatypeConfigurationException, ValueExprEvaluationException { + DatatypeFactory dtf = DatatypeFactory.newInstance(); + + ZonedDateTime zTime = ZonedDateTime.now(); + String time = zTime.format(DateTimeFormatter.ISO_INSTANT); + + ZonedDateTime zTime1 = zTime.minusWeeks(1); + String time1 = zTime1.format(DateTimeFormatter.ISO_INSTANT); + + ZonedDateTime zTime2 = zTime.minusWeeks(7); + String time2 = zTime2.format(DateTimeFormatter.ISO_INSTANT); + + Literal now = vf.createLiteral(dtf.newXMLGregorianCalendar(time)); + Literal nowMinusOne = vf.createLiteral(dtf.newXMLGregorianCalendar(time1)); + Literal nowMinusSeven = vf.createLiteral(dtf.newXMLGregorianCalendar(time2)); + + DateTimeWithinPeriod func = new DateTimeWithinPeriod(); + + assertEquals(TRUE, func.evaluate(vf, now, now, vf.createLiteral(1), OWLTime.WEEKS_URI)); + assertEquals(FALSE, func.evaluate(vf, now, nowMinusOne, vf.createLiteral(1), OWLTime.WEEKS_URI)); + assertEquals(TRUE, func.evaluate(vf, now, nowMinusOne, vf.createLiteral(2), OWLTime.WEEKS_URI)); + assertEquals(FALSE, func.evaluate(vf, now, nowMinusSeven, vf.createLiteral(7), OWLTime.WEEKS_URI)); + } + + @Test + public void testTimeZone() throws DatatypeConfigurationException, ValueExprEvaluationException { + DatatypeFactory dtf = DatatypeFactory.newInstance(); + + ZonedDateTime now = ZonedDateTime.now(); + String time = now.format(DateTimeFormatter.ISO_INSTANT); + + ZonedDateTime zTime1 = now.withZoneSameInstant(ZoneId.of("Europe/London")); + String time1 = zTime1.format(DateTimeFormatter.ISO_INSTANT); + + ZonedDateTime zTime2 = now.withZoneSameInstant(ZoneId.of("Australia/Sydney")); + String time2 = zTime2.format(DateTimeFormatter.ISO_INSTANT); + + ZonedDateTime zTime3 = now.minusDays(1).withZoneSameInstant(ZoneId.of("Asia/Seoul")); + String time3 = zTime3.format(DateTimeFormatter.ISO_INSTANT); + + Literal nowLocal = vf.createLiteral(dtf.newXMLGregorianCalendar(time)); + Literal nowEuropeTZ = vf.createLiteral(dtf.newXMLGregorianCalendar(time1)); + Literal nowAustraliaTZ = vf.createLiteral(dtf.newXMLGregorianCalendar(time2)); + Literal nowAsiaTZMinusOne = vf.createLiteral(dtf.newXMLGregorianCalendar(time3)); + + DateTimeWithinPeriod func = new DateTimeWithinPeriod(); + + assertEquals(TRUE, func.evaluate(vf, nowLocal, nowEuropeTZ, vf.createLiteral(1), OWLTime.SECONDS_URI)); + assertEquals(TRUE, func.evaluate(vf, nowLocal, nowAustraliaTZ, vf.createLiteral(1), OWLTime.SECONDS_URI)); + assertEquals(FALSE, func.evaluate(vf, nowLocal, nowAsiaTZMinusOne, vf.createLiteral(1), OWLTime.DAYS_URI)); + assertEquals(TRUE, func.evaluate(vf, nowLocal, nowAsiaTZMinusOne, vf.createLiteral(2), OWLTime.DAYS_URI)); + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/29a8e6b7/extras/rya.pcj.fluo/pcj.fluo.app/src/main/java/org/apache/rya/indexing/pcj/fluo/app/FilterResultUpdater.java ---------------------------------------------------------------------- diff --git a/extras/rya.pcj.fluo/pcj.fluo.app/src/main/java/org/apache/rya/indexing/pcj/fluo/app/FilterResultUpdater.java b/extras/rya.pcj.fluo/pcj.fluo.app/src/main/java/org/apache/rya/indexing/pcj/fluo/app/FilterResultUpdater.java index 7cfa216..17ed158 100644 --- a/extras/rya.pcj.fluo/pcj.fluo.app/src/main/java/org/apache/rya/indexing/pcj/fluo/app/FilterResultUpdater.java +++ b/extras/rya.pcj.fluo/pcj.fluo.app/src/main/java/org/apache/rya/indexing/pcj/fluo/app/FilterResultUpdater.java @@ -39,9 +39,11 @@ import org.openrdf.model.impl.ValueFactoryImpl; import org.openrdf.query.BindingSet; import org.openrdf.query.QueryEvaluationException; import org.openrdf.query.algebra.Filter; +import org.openrdf.query.algebra.FunctionCall; import org.openrdf.query.algebra.ValueExpr; import org.openrdf.query.algebra.evaluation.TripleSource; import org.openrdf.query.algebra.evaluation.ValueExprEvaluationException; +import org.openrdf.query.algebra.evaluation.function.FunctionRegistry; import org.openrdf.query.algebra.evaluation.impl.EvaluationStrategyImpl; import org.openrdf.query.algebra.evaluation.util.QueryEvaluationUtil; @@ -130,16 +132,20 @@ public class FilterResultUpdater { * @param condition - The filter condition. (not null) * @param bindings - The binding set to evaluate. (not null) * @return {@code true} if the binding set is accepted by the filter; otherwise {@code false}. - * @throws QueryEvaluationException The condition couldn't be evaluated. + * @throws QueryEvaluationException The condition couldn't be evaluated. In the case that the ValueExpr is a + * {@link FunctionCall}, this Exception is thrown because the Function could not be found in the + * {@link FunctionRegistry}. */ private static boolean isTrue(final ValueExpr condition, final BindingSet bindings) throws QueryEvaluationException { try { final Value value = evaluator.evaluate(condition, bindings); return QueryEvaluationUtil.getEffectiveBooleanValue(value); } catch (final ValueExprEvaluationException e) { - // XXX Hack: If filtering a statement that does not have the right bindings, return true. - // When would this ever come up? Should we actually return true? - return true; + //False returned because for whatever reason, the ValueExpr could not be evaluated. + //In the event that the ValueExpr is a FunctionCall, this Exception will be generated if + //the Function URI is a valid URI that was found in the FunctionRegistry, but the arguments + //for that Function could not be parsed. + return false; } } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/29a8e6b7/extras/rya.pcj.fluo/pcj.fluo.app/src/main/java/org/apache/rya/indexing/pcj/fluo/app/util/FilterSerializer.java ---------------------------------------------------------------------- diff --git a/extras/rya.pcj.fluo/pcj.fluo.app/src/main/java/org/apache/rya/indexing/pcj/fluo/app/util/FilterSerializer.java b/extras/rya.pcj.fluo/pcj.fluo.app/src/main/java/org/apache/rya/indexing/pcj/fluo/app/util/FilterSerializer.java index 73f3447..6c99809 100644 --- a/extras/rya.pcj.fluo/pcj.fluo.app/src/main/java/org/apache/rya/indexing/pcj/fluo/app/util/FilterSerializer.java +++ b/extras/rya.pcj.fluo/pcj.fluo.app/src/main/java/org/apache/rya/indexing/pcj/fluo/app/util/FilterSerializer.java @@ -23,6 +23,8 @@ import java.util.Set; import org.openrdf.query.algebra.Filter; import org.openrdf.query.algebra.SingletonSet; +import org.openrdf.query.algebra.evaluation.function.Function; +import org.openrdf.query.algebra.evaluation.function.FunctionRegistry; import org.openrdf.query.algebra.helpers.QueryModelVisitorBase; import org.openrdf.query.parser.ParsedQuery; import org.openrdf.query.parser.ParsedTupleQuery; @@ -52,7 +54,7 @@ public class FilterSerializer { Filter clone = filter.clone(); clone.setArg(new SingletonSet()); try { - return renderer.render(new ParsedTupleQuery(clone)); + return removeAngularBracketsFromNonUriFunctions(renderer.render(new ParsedTupleQuery(clone))); } catch (Exception e) { throw new FilterParseException("Unable to parse Filter.", e); } @@ -98,6 +100,29 @@ public class FilterSerializer { public void meet(Filter node) { filters.add(node); } + + } + + /** + * There are a number of Functions in the FunctionRegistry whose getURI() method does not return a valid URI (NOW() + * is one such method). The SPARQLQueryRender adds angular brackets to the result returned by + * {@link Function#getURI()} by default, which leads to a MalformedQueryException when the SPARQLParser attempts to + * parse the SPARQL created by the renderer. Therefore, a call to serialize and then deserialize for a Filter + * containing a Function that returns an invalid URI will generate an exception. This method removes the angular + * brackets from the result returned by {@link Function#getURI()} if it is not a valid URI so that the parser will + * parse it. + * + * @param query - query generated by query renderer + * @return - String with angular brackets removed from around all invalid Function URI + */ + private static String removeAngularBracketsFromNonUriFunctions(String query) { + FunctionRegistry registry = FunctionRegistry.getInstance(); + for(String key: registry.getKeys()) { + if (key.indexOf(':') < 0) { + query = query.replace("<"+key.trim()+">", key); + } + } + return query; } public static class FilterParseException extends Exception { http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/29a8e6b7/extras/rya.pcj.fluo/pcj.fluo.app/src/test/java/org/apache/rya/indexing/pcj/fluo/app/util/FilterSerializerTest.java ---------------------------------------------------------------------- diff --git a/extras/rya.pcj.fluo/pcj.fluo.app/src/test/java/org/apache/rya/indexing/pcj/fluo/app/util/FilterSerializerTest.java b/extras/rya.pcj.fluo/pcj.fluo.app/src/test/java/org/apache/rya/indexing/pcj/fluo/app/util/FilterSerializerTest.java new file mode 100644 index 0000000..b2efa96 --- /dev/null +++ b/extras/rya.pcj.fluo/pcj.fluo.app/src/test/java/org/apache/rya/indexing/pcj/fluo/app/util/FilterSerializerTest.java @@ -0,0 +1,48 @@ +/* + * 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.rya.indexing.pcj.fluo.app.util; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.openrdf.query.algebra.Filter; +import org.openrdf.query.algebra.Projection; +import org.openrdf.query.parser.ParsedQuery; +import org.openrdf.query.parser.sparql.SPARQLParser; + +public class FilterSerializerTest { + + @Test + public void nowTest() throws Exception { + + //tests to see if NOW function is correctly serialized and deserialized + //by FilterSerializer + String query = "select * {Filter(NOW())}"; + SPARQLParser parser = new SPARQLParser(); + ParsedQuery pq = parser.parseQuery(query, null); + Filter filter = (Filter) ((Projection) pq.getTupleExpr()).getArg(); + String filterString = FilterSerializer.serialize(filter); + Filter deserializedFilter = FilterSerializer.deserialize(filterString); + + assertEquals(filter, deserializedFilter); + + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/29a8e6b7/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/QueryIT.java ---------------------------------------------------------------------- diff --git a/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/QueryIT.java b/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/QueryIT.java index 4974aee..beaef32 100644 --- a/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/QueryIT.java +++ b/extras/rya.pcj.fluo/pcj.fluo.integration/src/test/java/org/apache/rya/indexing/pcj/fluo/integration/QueryIT.java @@ -37,6 +37,8 @@ import org.apache.fluo.core.client.FluoClientImpl; import org.apache.rya.api.client.CreatePCJ.ExportStrategy; import org.apache.rya.api.client.RyaClient; import org.apache.rya.api.client.accumulo.AccumuloRyaClientFactory; +import org.apache.rya.api.functions.DateTimeWithinPeriod; +import org.apache.rya.api.functions.OWLTime; import org.apache.rya.indexing.pcj.fluo.api.CreateFluoPcj; import org.apache.rya.indexing.pcj.fluo.app.query.UnsupportedQueryException; import org.apache.rya.indexing.pcj.storage.PeriodicQueryResultStorage; @@ -54,6 +56,7 @@ import org.openrdf.model.ValueFactory; import org.openrdf.model.datatypes.XMLDatatypeUtil; import org.openrdf.model.impl.BooleanLiteralImpl; import org.openrdf.model.impl.ValueFactoryImpl; +import org.openrdf.model.vocabulary.FN; import org.openrdf.model.vocabulary.XMLSchema; import org.openrdf.query.BindingSet; import org.openrdf.query.algebra.evaluation.ValueExprEvaluationException; @@ -423,6 +426,92 @@ public class QueryIT extends RyaExportITBase { runTest(sparql, statements, expectedResults, ExportStrategy.RYA); } + + @Test + public void dateTimeWithin() throws Exception { + + final ValueFactory vf = new ValueFactoryImpl(); + DatatypeFactory dtf = DatatypeFactory.newInstance(); + FunctionRegistry.getInstance().add(new DateTimeWithinPeriod()); + + final String sparql = "PREFIX fn: <" + FN.NAMESPACE +">" + + "SELECT ?event ?startTime ?endTime WHERE { ?event <uri:startTime> ?startTime; <uri:endTime> ?endTime. " + + "FILTER(fn:dateTimeWithin(?startTime, ?endTime, 2,<" + OWLTime.HOURS_URI + "> ))}"; + + ZonedDateTime zTime = ZonedDateTime.now(); + String time = zTime.format(DateTimeFormatter.ISO_INSTANT); + + ZonedDateTime zTime1 = zTime.minusHours(1); + String time1 = zTime1.format(DateTimeFormatter.ISO_INSTANT); + + ZonedDateTime zTime2 = zTime.minusHours(2); + String time2 = zTime2.format(DateTimeFormatter.ISO_INSTANT); + + Literal lit = vf.createLiteral(dtf.newXMLGregorianCalendar(time)); + Literal lit1 = vf.createLiteral(dtf.newXMLGregorianCalendar(time1)); + Literal lit2 = vf.createLiteral(dtf.newXMLGregorianCalendar(time2)); + + // Create the Statements that will be loaded into Rya. + final Collection<Statement> statements = Sets.newHashSet( + vf.createStatement(vf.createURI("uri:event1"), vf.createURI("uri:startTime"), lit), + vf.createStatement(vf.createURI("uri:event1"), vf.createURI("uri:endTime"), lit1), + vf.createStatement(vf.createURI("uri:event2"), vf.createURI("uri:startTime"), lit), + vf.createStatement(vf.createURI("uri:event2"), vf.createURI("uri:endTime"), lit2) + ); + + // Create the expected results of the SPARQL query once the PCJ has been computed. + final Set<BindingSet> expectedResults = new HashSet<>(); + + MapBindingSet bs = new MapBindingSet(); + bs.addBinding("event", vf.createURI("uri:event1")); + bs.addBinding("startTime", lit); + bs.addBinding("endTime", lit1); + expectedResults.add(bs); + + // Verify the end results of the query match the expected results. + runTest(sparql, statements, expectedResults, ExportStrategy.RYA); + } + + @Test + public void dateTimeWithinNow() throws Exception { + + final ValueFactory vf = new ValueFactoryImpl(); + DatatypeFactory dtf = DatatypeFactory.newInstance(); + FunctionRegistry.getInstance().add(new DateTimeWithinPeriod()); + + final String sparql = "PREFIX fn: <" + FN.NAMESPACE +">" + + "SELECT ?event ?startTime WHERE { ?event <uri:startTime> ?startTime. " + + "FILTER(fn:dateTimeWithin(?startTime, NOW(), 15, <" + OWLTime.SECONDS_URI + "> ))}"; + + ZonedDateTime zTime = ZonedDateTime.now(); + String time = zTime.format(DateTimeFormatter.ISO_INSTANT); + + ZonedDateTime zTime1 = zTime.minusSeconds(30); + String time1 = zTime1.format(DateTimeFormatter.ISO_INSTANT); + + Literal lit = vf.createLiteral(dtf.newXMLGregorianCalendar(time)); + Literal lit1 = vf.createLiteral(dtf.newXMLGregorianCalendar(time1)); + + // Create the Statements that will be loaded into Rya. + final Collection<Statement> statements = Sets.newHashSet( + vf.createStatement(vf.createURI("uri:event1"), vf.createURI("uri:startTime"), lit), + vf.createStatement(vf.createURI("uri:event2"), vf.createURI("uri:startTime"), lit1) + ); + + // Create the expected results of the SPARQL query once the PCJ has been computed. + final Set<BindingSet> expectedResults = new HashSet<>(); + + MapBindingSet bs = new MapBindingSet(); + bs.addBinding("event", vf.createURI("uri:event1")); + bs.addBinding("startTime", lit); + expectedResults.add(bs); + + // Verify the end results of the query match the expected results. + runTest(sparql, statements, expectedResults, ExportStrategy.RYA); + } + + + @Test public void periodicQueryTestWithoutAggregation() throws Exception { String query = "prefix function: <http://org.apache.rya/function#> " // n
