在RDF4J中操作日期/时间以进行调试

时间:2017-08-14 23:44:50

标签: date sparql rdf4j

我在Windows 10 Professional 64位上使用RDF4J 2.2.1。我将有一些对日期/时间敏感的SPIN构造函数规则。例如,我可能想要将包含xsd:dateTime数据类型属性的三元组与SPARQL的内置now()函数的输出进行比较。要调试此功能,以某种方式操纵RDF4J对日期/时间的感知将是方便的,而不是操纵系统时钟。我知道有一般的商业软件(例如Solution Soft的“Time Machine”),它通常可以操纵任何Windows进程的时间感。但是,对于我们的小概念验证项目来说,这个软件似乎太昂贵了。

我希望能够做到:

  • 将RDF4J的日期/时间设置为某个任意日期/时间值。
  • 让RDF4J的日期/时间以实时速度或在调试过程中以一些可编程的更快速度进行。

有没有人建议如何以这种方式操纵RDF4J的日期/时间?这将使我对时间敏感的SPIN规则的调试更加有效。我宁愿不打我的电脑的系统时钟,因为许多其他事情依赖于它。我认为运行整个虚拟PC并在虚拟PC上进行调试是另一种选择,但似乎应该有一种更简单的方法。

感谢。

2 个答案:

答案 0 :(得分:2)

您可以通过实施custom SPARQL function并使用该代替实际 now()函数来实现此目的。例如,将其称为mock_now()。自实施以来,您就可以完全控制其行为。

答案 1 :(得分:1)

我发布了我的问题的解决方案,希望它可以帮助其他人作为RDF4J下的自定义SPARQL函数的另一个示例。我没有把它作为优雅的解决方案(由于我如何设置测试条件),但它确实有效并满足我的要求。该解决方案基于http://docs.rdf4j.org/custom-sparql-functions/ ...

扩展了@jeen_broekstra的答案

我现在在PREFIX soo: <http://www.disa.mil/dso/a2i/ontologies/PBSM/Sharing/SpectrumOperationsOntology#>定义的名称空间中实现了一个自定义作为一个名为soo:spectrumOpsDateTime()的函数,可以使用三个参数或不参数。三个参数情况允许设置缩放日期时间,如下所示。

  • 第一个参数:xsd:boolean ...使用系统时钟true或使用缩放时钟false
  • 第二个参数:xsd:dateTime(如果第一个参数为真,则忽略)...缩放时钟操作的开始日期/时间
  • 第三个参数:xsd:double(如果第一个参数为真,则忽略)...缩放的时钟速率(例如2.0表示缩放的时钟运行速度更快,实时两倍)

如果没有参数,soo:spectrumOpsDateTime()将返回缩放日期/时间或系统日期/时间,具体取决于Java代码中指定的初始值或指定的最后三个参数调用的内容。正在测试的SPARQL和SPIN代码将仅使用无参数版本。测试设置查询将设置特定测试的时间条件。

这是一个示例SPARQL设置查询,从今天早上开始设置2倍速度:

PREFIX soo: <http://www.disa.mil/dso/a2i/ontologies/PBSM/Sharing/SpectrumOperationsOntology#>

SELECT DISTINCT *
WHERE {
  BIND(soo:spectrumOpsDateTime("false"^^xsd:boolean, "2017-08-22T10:49:21.019-05:00"^^xsd:dateTime, "2.0"^^xsd:double) AS ?testDateTime) .
}

以下是获取缩放日期/时间的示例SPARQL查询:

PREFIX soo: <http://www.disa.mil/dso/a2i/ontologies/PBSM/Sharing/SpectrumOperationsOntology#>

SELECT DISTINCT *
WHERE {
  BIND(soo:spectrumOpsDateTime() AS ?testDateTime) .
}

用于实现此自定义函数的单个类是:

/**
 * 
 */

package mil.disa.dso.spo.a2i.nsc.sharing2025.scaledDateTime;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.query.algebra.evaluation.ValueExprEvaluationException;
import org.eclipse.rdf4j.query.algebra.evaluation.function.Function;

/**
 * Class for generating a configurable date/time clock that can either be a pass-through of the
 * system clock or a scaled clock starting at a specified date/time running at a specified
 * rate from that specified time (first call). 
 * @author Greg Cox of Roberson and Associates &copy Copyright 2017 Roberson and Associates, All Right Reserved
 *
 */
public class DateTimeGenerator implements Function {
    private static final String thisClassName = "RDF4JCustomSPARQLFunction." + DateTimeGenerator.class.getSimpleName();
    private static final String thisClassFullName = DateTimeGenerator.class.getName();
    private static final boolean errorMessages = true;
    private static final boolean verboseMessages = true;

    private double clockPace = 2.0;                     // the speed of the clock, 1.0 is real time, 2.0 is 2x real time (double speed)
    private boolean useSystemClock = false;             // flag to indicate whether to use scaled clock or pass through the system clock

    private ZonedDateTime startingRealDateTime = null;  // the real time stamp at the first call to the evaluate function
    private ZonedDateTime startingScaledDateTime =      // the scaled time stamp (starting scaled time) at the first call to the evaluate function
            ZonedDateTime.parse("2016-08-21T17:29:37.568-05:00");

    // define a constant for the namespace of custom function
    private static String NAMESPACE = "http://www.disa.mil/dso/a2i/ontologies/PBSM/Sharing/SpectrumOperationsOntology#";    // defined as soo: elsewhere





    // this is the evaluate function needed to implement the RDF4J Function interface
    //  it can take 0 or 3 arguments
    //  0 - get the current scaled time (starting by first call)
    //  3 - useSystemClock flag (true/false), starting date/time (xsd:dateTime), clock pace (non-negative real w/ 1.0 meaning 1sec = 1sec)
    @SuppressWarnings("unused")
    @Override
    public Value evaluate(ValueFactory valueFactory, Value... args) throws ValueExprEvaluationException {
        String thisMethodMessagePrefix = "";

        if (errorMessages || verboseMessages ) {
            String thisMethodName = ".evaluate: ";
            thisMethodMessagePrefix = thisClassName + thisMethodName;
        }


        if (args.length == 3) {
            // Three arguments --> attempting to set mode/parameters, so attempt to parse/check them
            if (verboseMessages) System.out.println(thisMethodMessagePrefix + "attempting to set scaled clock mode/parameters");

            boolean argErrFlag = false;
            boolean newUseSystemClock = false;
            String argErrMessage = "";

            // first argument should be true/false on whether to use system clock (true) or scaled clock (false)
            if (!(args[0] instanceof Literal)) {
                argErrFlag = true;
                argErrMessage += "first argument must be a literal true/false value... ";
            } else {
                String useSystemClockString = args[0].stringValue();
                if (useSystemClockString.equalsIgnoreCase("true")) {
                    if (verboseMessages) System.out.println(thisMethodMessagePrefix + "use system clock specified");
                    newUseSystemClock = true;
                } else if (useSystemClockString.equalsIgnoreCase("false")) {
                    if (verboseMessages) System.out.println(thisMethodMessagePrefix + "use scaled clock specified");
                    newUseSystemClock = false;
                }
                else {
                    argErrFlag = true;
                    argErrMessage += "first argument must be a literal true/false value... ";
                }
            }

            // second argument should be starting date/time for scaled clock (ignore if using system clock)
            ZonedDateTime startTime = null;
            if (!newUseSystemClock) { 
                if (!(args[1] instanceof Literal)) {
                    argErrFlag = true;
                    argErrMessage += "second argument must be literal xsd:dateTime value for start of scaled date/time... ";
                } else {
                    String startDateTimeString = args[1].stringValue();
                    try {
                        startTime = ZonedDateTime.parse(startDateTimeString);
                    } catch (Exception e) {
                        argErrFlag = true;
                        argErrMessage += "could not parse starting date/time... " + e.getMessage() + "... ";
                    }
                }
            }

            // third argument should be clock pace for scaled clock (ignore if using system clock)
            Double newClockPace = null;
            if (!newUseSystemClock) {
                if (!(args[2] instanceof Literal)) {
                    argErrFlag = true;
                    argErrMessage += "third argument must be literal xsd:double value for clock pace... ";
                } else {
                    String clockPaceString = args[2].stringValue();
                    try {
                        newClockPace = Double.parseDouble(clockPaceString);
                    } catch (Exception e) {
                        argErrFlag = true;
                        argErrMessage += "could not parse clock pace which should be a positive xsd:double... ";
                    }
                    if ((newClockPace != null) && (newClockPace <= 0.0)) {
                        argErrFlag = true;
                        argErrMessage += "clock pace must be positive, got " + newClockPace + "... ";
                    }
                }
            }

            // check for errors and set up the generator if no errors...
            if (argErrFlag) {
                if (errorMessages) System.err.println(thisMethodMessagePrefix + "ERROR - " + argErrMessage);
                if (errorMessages) System.err.println(thisMethodMessagePrefix + "throwing exception...");
                throw new ValueExprEvaluationException(
                        "spectrum operations time function soo:spectrumOpsDateTime() encountered errors in function arguments... " +
                                argErrMessage);
            } else if (newUseSystemClock) {
                if (verboseMessages) System.out.println(thisMethodMessagePrefix + "using unscaled system clock");
                useSystemClock = newUseSystemClock;
            } else if (!newUseSystemClock) {
                if (verboseMessages) System.out.println(thisMethodMessagePrefix + "using scaled time");
                useSystemClock = newUseSystemClock;
                startingRealDateTime = ZonedDateTime.now();
                if (verboseMessages) System.out.println(thisMethodMessagePrefix + "setting starting real time to " + startingRealDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
                if (verboseMessages) System.out.println(thisMethodMessagePrefix + "setting start time to " + startTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
                startingScaledDateTime = startTime;
                if (verboseMessages) System.out.println(thisMethodMessagePrefix + "setting clock pace to " + String.format("%5.2f", newClockPace * 100.0) + "%");
                clockPace = newClockPace;
            }

        } else if (args.length != 0) {  // can only have no arguments or three arguments...
            throw new ValueExprEvaluationException(
                    "spectrum operations time function soo:spectrumOpsDateTime() requires "
                            + "zero arguments or three arguments, got "
                            + args.length + " arguments");
        }

        // now run the generator and return the result...

        IRI xsdDateTimeIRI = valueFactory.createIRI("http://www.w3.org/2001/XMLSchema#dateTime");  // long-form equivalent to xsd:dateTime

        if (useSystemClock) {
            String unscaledTimeString = millisTrailingZeroes(ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
            return valueFactory.createLiteral(unscaledTimeString, xsdDateTimeIRI);
        } else {
            errString = null;
            String scaledTimeString = millisTrailingZeroes(getScaledDateTime().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
            if (scaledTimeString == null) {
                if (errorMessages) System.err.println(thisMethodMessagePrefix + "ERROR - scaled time returned null");
                if (errorMessages) System.err.println(thisMethodMessagePrefix + "thowing exception...");
                throw new ValueExprEvaluationException("could not generate valid scaled time string" + ((errString == null) ? "" : "... " + errString));
            }
            return valueFactory.createLiteral(scaledTimeString, xsdDateTimeIRI);
        }
    }

    private static String errString = null;

    /**
     * Utility method to make all the millisecond fields of an <tt>ISO_OFFSET_DATE_TIME</tt> three digits by
     * adding trailing zeroes as needed.  Why? Because of trouble with various implementations interpreting
     * 1 and 2 digit milliseconds differently.  Should be standard decimal, but sometimes interpreted 
     * as number of milliseconds (e.g. .39T interpreted as 39 millieconds inststead of 390 milliseconds)
     * @param <tt>ISO_OFFSET_DATE_TIME</tt> string to check for millisecond field length
     * @return <tt>ISO_OFFSET_DATE_TIME</tt> strnig with trailing zeroes in milliseconds field
     * as require to make the field three digits or <tt>null</tt> on error
     */
    private static String millisTrailingZeroes(String isoDateTimeString) {
        if (isoDateTimeString == null) {
            errString = "DateTimeGenerator.millisTrailingZeroes: got null isoDateTimeString argument, returning null...";
            return null;
        }

        String[] ss_l1 = isoDateTimeString.split("\\.");    // Example: 2017-08-18T13:01:05.39-05:00 --> 2017-08-18T13:01:05 AND 39-05:00
        if (ss_l1.length != 2) {
            errString = "DateTImeGenerator.millisTrailingZeros: first parsing split of isoDateTimeString=" + isoDateTimeString + " by '.' got unexpected number of parts=" + ss_l1.length;
            return null;
        }

        String[] ss_l2 = ss_l1[1].split("-");               // 39-05:00 --> 39 AND 05:00
        if (ss_l2.length != 2) {
            errString = "DateTImeGenerator.millisTrailingZeros: second parsing split of " + ss_l1[1] + " by '-' got unexpected number of parts=" + ss_l2.length;
            return null;
        }

        if (ss_l2[0].length() == 1) {
            ss_l2[0] = ss_l2[0] + "00";
        } else if (ss_l2[0].length() == 2)
            ss_l2[0] = ss_l2[0] + "0";                      // 39 --> 390

        return ss_l1[0] + "." + ss_l2[0] + "-" + ss_l2[1];  // 2017-08-18T13:01:05.390-05:00
    }

    /**
     * Method to get the current scaled date time according to the state of this DateTimeGenerator.
     * If <tt>useSystemClock</tt> is <tt>true</tt>, then time is not 
     * scaled and system time is returned instead of scaled time.
     * @return scaled date time if <tt>useSystemClock</tt> is <tt>true</tt> or
     * system date time if <tt>useSystemClock</tt> is <tt>false</tt>
     */
    private ZonedDateTime getScaledDateTime() {
        ZonedDateTime scaledDateTime = null;

        if (useSystemClock) {
            scaledDateTime = ZonedDateTime.now();
        } else {
            if (startingRealDateTime == null) 
                startingRealDateTime = ZonedDateTime.now();
            long realMillisFromFirstCall = ChronoUnit.MILLIS.between(startingRealDateTime, ZonedDateTime.now());
            long scaledMillisFromFirstCall = (long) ((double) realMillisFromFirstCall * clockPace);

            scaledDateTime = ChronoUnit.MILLIS.addTo(startingScaledDateTime, scaledMillisFromFirstCall);
        }

        return scaledDateTime;
    }


    @Override
    public String getURI() {
        return NAMESPACE + "spectrumOpsDateTime";
    }

    /**
     * Test main method
     * @param args command line arguments (ignored)
     */
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        String thisMethodMessagePrefix = "";

        if (errorMessages || verboseMessages ) {
            String thisMethodName = ".main: ";
            thisMethodMessagePrefix = thisClassName + thisMethodName;
        }

        DateTimeGenerator testGen = new DateTimeGenerator();

        if (verboseMessages) System.out.println(thisMethodMessagePrefix + "custom SPARQL method URI: " + testGen.getURI());
        if (verboseMessages) System.out.println(thisMethodMessagePrefix + "fully-qualified class name: " + thisClassFullName);

        ValueFactory testVF = SimpleValueFactory.getInstance();
        Value testValues[] = new Value[0];

        while (true) {

            if (verboseMessages) System.out.println(thisMethodMessagePrefix + "scaled: " + testGen.evaluate(testVF, testValues).stringValue() +
                    " current real: " + millisTrailingZeroes(ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

在我的例子中,从Eclipse导出的jar文件在我的Apache安装下执行并驻留在C:\Apache\apache-tomcat-8.5.15\webapps\rdf4j-server\WEB-INF\lib\ScaledDateTime.jar我在更改这个jar文件时重新启动Apache服务器。