在 RDF4J 中操作 Date/Time 进行调试
Manipulate Date/Time in RDF4J for Debugging
我在 Windows 10 Professional 64 位上使用 RDF4J 2.2.1。我将有一些对 date/time 敏感的 SPIN 构造函数规则。例如,我可能想将包含 xsd:dateTime
数据类型 属性 的三元组与 SPARQL 的内置 now()
函数的输出进行比较。要调试此功能,以某种方式操纵 RDF4J 对 date/time 的感知比操纵系统时钟更方便。我知道有一些通用的商业软件(例如 Solution Soft 的 "Time Machine")通常可以操纵任何 Windows 进程的时间感知。然而,这个软件对于我们的小概念验证项目来说似乎太贵了。
我希望能够做的事情:
- 将 RDF4J 的 date/time 设置为任意 date/time 值。
- 让 RDF4J 的 date/time 在调试期间以实时速度或以某种可编程的更快速度进行。
有没有人对如何以这种方式 date/time 为 RDF4J 进行操作提出建议?这将使我对时间敏感的 SPIN 规则的调试更加高效。我不想与我的 PC 的系统时钟发生冲突,因为许多其他事情都依赖于它。我想 运行 整个虚拟 PC 并在虚拟 PC 上调试是另一种选择,但似乎应该有更简单的方法。
谢谢。
您可以通过实施 custom SPARQL function 并使用它代替 actual now()
函数来实现此目的。例如称它为 mock_now()
。由于您实施了它,因此您可以完全控制它的行为。
我发布了我的问题解决方案,希望它可以作为 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
(如果第一个参数为真则忽略)...缩放时钟操作的起始date/time
- 第三个参数:
xsd:double
(如果第一个参数为真则忽略)...比例时钟速率(例如 2.0 表示比例时钟运行得更快,两倍于实时)
如果没有参数,soo:spectrumOpsDateTime()
returns 缩放 date/time 或系统 date/time 取决于 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 查询示例,用于获取缩放 date/time:
PREFIX soo: <http://www.disa.mil/dso/a2i/ontologies/PBSM/Sharing/SpectrumOperationsOntology#>
SELECT DISTINCT *
WHERE {
BIND(soo:spectrumOpsDateTime() AS ?testDateTime) .
}
用于实现此自定义函数的单个class是:
/**
*
*/
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 © 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 服务器。
我在 Windows 10 Professional 64 位上使用 RDF4J 2.2.1。我将有一些对 date/time 敏感的 SPIN 构造函数规则。例如,我可能想将包含 xsd:dateTime
数据类型 属性 的三元组与 SPARQL 的内置 now()
函数的输出进行比较。要调试此功能,以某种方式操纵 RDF4J 对 date/time 的感知比操纵系统时钟更方便。我知道有一些通用的商业软件(例如 Solution Soft 的 "Time Machine")通常可以操纵任何 Windows 进程的时间感知。然而,这个软件对于我们的小概念验证项目来说似乎太贵了。
我希望能够做的事情:
- 将 RDF4J 的 date/time 设置为任意 date/time 值。
- 让 RDF4J 的 date/time 在调试期间以实时速度或以某种可编程的更快速度进行。
有没有人对如何以这种方式 date/time 为 RDF4J 进行操作提出建议?这将使我对时间敏感的 SPIN 规则的调试更加高效。我不想与我的 PC 的系统时钟发生冲突,因为许多其他事情都依赖于它。我想 运行 整个虚拟 PC 并在虚拟 PC 上调试是另一种选择,但似乎应该有更简单的方法。
谢谢。
您可以通过实施 custom SPARQL function 并使用它代替 actual now()
函数来实现此目的。例如称它为 mock_now()
。由于您实施了它,因此您可以完全控制它的行为。
我发布了我的问题解决方案,希望它可以作为 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
(如果第一个参数为真则忽略)...缩放时钟操作的起始date/time - 第三个参数:
xsd:double
(如果第一个参数为真则忽略)...比例时钟速率(例如 2.0 表示比例时钟运行得更快,两倍于实时)
如果没有参数,soo:spectrumOpsDateTime()
returns 缩放 date/time 或系统 date/time 取决于 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 查询示例,用于获取缩放 date/time:
PREFIX soo: <http://www.disa.mil/dso/a2i/ontologies/PBSM/Sharing/SpectrumOperationsOntology#>
SELECT DISTINCT *
WHERE {
BIND(soo:spectrumOpsDateTime() AS ?testDateTime) .
}
用于实现此自定义函数的单个class是:
/**
*
*/
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 © 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 服务器。