RDF4JのカスタムSPARQL関数のさらなる例として他の人を助けてくれることを期待して、自分の解決策を私の質問に投稿しています。私はこれをエレガントな解決策として(テスト条件の設定のせいで)保持していませんが、それはうまく動作し、私の要求を満たしています。このソリューションは、私は今、3または引数なしのどちらかを取ることができsoo:spectrumOpsDateTime()
と呼ばれる関数としてPREFIX soo: <http://www.disa.mil/dso/a2i/ontologies/PBSM/Sharing/SpectrumOperationsOntology#>
によって定義された名前空間で実装されたカスタムを持っているhttp://docs.rdf4j.org/custom-sparql-functions/ ...
に基づいて@jeen_broekstraから答えを拡張します。 3つの引数の場合、スケーリングされた日付時刻を次のように設定できます。
- まず引数:
xsd:boolean
...使用のシステム・クロックの場合true
またはfalse
- 第二引数場合スケールクロックを使用します。
xsd:dateTime
...スケーリングされたクロックの開始日/時間(最初の引数がtrueの場合は無視されます)操作
- 第三引数:
xsd:double
(最初の引数がtrueの場合は無視されます)...スケールクロック・レート(例えば2.0がスケーリングクロックが二回リアルタイムで、速く走る意味)
何が存在しない場合引数であるsoo:spectrumOpsDateTime()
は、Javaコードで指定された初期値または最後に指定された3引数の呼び出しに応じて、スケーリングされた日付/時刻またはシステムの日付/時刻を返します。テスト対象のSPARQLおよびSPINコードは引数のないバージョンのみを使用します。テストセットアップクエリは、特定のテストの時間条件を設定します。ここで
は今朝始まる2倍の速度を設定する例SPARQLのセットアップクエリです:
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 © 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();
}
}
}
}
私の場合、jarファイルのエクスポートEclipseからのedは私のApacheのインストールの下で実行され、C:\Apache\apache-tomcat-8.5.15\webapps\rdf4j-server\WEB-INF\lib\ScaledDateTime.jar
にあります。mofificationsを実行すると、このjarファイルを置き換えてApacheサーバーを再起動します。
はい、デバッグしていないときに実際の 'now()'関数の単純なラッパーにすることができます。ありがとう! –
私はあなたの提案を実装しようとしています。私はこの手順に問題があります」適切なJARファイルを作成したら、RDF4Jプロジェクトの実行時クラスパスを追加する必要があります(RDF4Jサーバーでこれを使用することを目指している場合は、RDF4Jサーバーに追加してください)。その後、あなたは完了です:RDF4Jはあなたの新しいカスタム関数を自動的に拾うべきです。今からSPARQLクエリでそれを使うことができます。 "私はサーバーのケースを持っていて、私はApacheの下で走っています。 RDF4J Server Webアプリケーションのクラスパスはどこに設定されていますか?環境変数? –
使用するサーブレットコンテナによって異なりますが、Apache Tomcatの場合は、 'tomcat/shared/lib'に追加するか、単に 'web-apps/rdf4j-server/WEB-INF/lib'に追加してください'限り、それはクラスローダーがそれをピックアップするどこかにインストールされています。 –