diff --git a/silk-core/src/main/resources/org/silkframework/LinkSpecificationLanguage.xsd b/silk-core/src/main/resources/org/silkframework/LinkSpecificationLanguage.xsd index 90f6c25d5d..04844ea63a 100644 --- a/silk-core/src/main/resources/org/silkframework/LinkSpecificationLanguage.xsd +++ b/silk-core/src/main/resources/org/silkframework/LinkSpecificationLanguage.xsd @@ -308,6 +308,8 @@ + + diff --git a/silk-core/src/main/scala/org/silkframework/runtime/plugin/ParameterType.scala b/silk-core/src/main/scala/org/silkframework/runtime/plugin/ParameterType.scala index 10ed89885b..a199c40868 100644 --- a/silk-core/src/main/scala/org/silkframework/runtime/plugin/ParameterType.scala +++ b/silk-core/src/main/scala/org/silkframework/runtime/plugin/ParameterType.scala @@ -183,7 +183,7 @@ object StringParameterType { private val allStaticTypes: Seq[StringParameterType[_]] = { Seq(StringType, CharType, IntType, DoubleType, BooleanType, IntOptionType, StringMapType, UriType, ResourceType, WritableResourceType, ResourceOptionType, DurationType, ProjectReferenceType, TaskReferenceType, MultilineStringParameterType, SparqlEndpointDatasetParameterType, LongType, - PasswordParameterType, IdentifierType, IdentifierOptionType, StringTraversableParameterType, RestrictionType) + PasswordParameterType, IdentifierType, IdentifierOptionType, StringTraversableParameterType, RestrictionType, StringOptionType) } /** @@ -325,6 +325,25 @@ object StringParameterType { } } + object StringOptionType extends StringParameterType[StringOptionParameter] { + + override def name: String = "option[string]" + + override def description: String = "An optional non-empty string" + + override def fromString(str: String)(implicit prefixes: Prefixes, resourceLoader: ResourceManager): StringOptionParameter = { + if(str.isEmpty) { + StringOptionParameter(None) + } else { + StringOptionParameter(Some(str)) + } + } + + override def toString(value: StringOptionParameter)(implicit prefixes: Prefixes): String = { + value.getOrElse("") + } + } + object IdentifierOptionType extends StringParameterType[IdentifierOptionParameter] { override def name: String = "option[identifier]" diff --git a/silk-core/src/main/scala/org/silkframework/runtime/plugin/StringOptionParameter.scala b/silk-core/src/main/scala/org/silkframework/runtime/plugin/StringOptionParameter.scala new file mode 100644 index 0000000000..591304bf54 --- /dev/null +++ b/silk-core/src/main/scala/org/silkframework/runtime/plugin/StringOptionParameter.scala @@ -0,0 +1,11 @@ +package org.silkframework.runtime.plugin + +/** + * An optional string. Empty strings are interpreted as missing value. + */ +case class StringOptionParameter(value: Option[String]) + +object StringOptionParameter { + implicit def toStringOptionParameter(v: Option[String]): StringOptionParameter = StringOptionParameter(v) + implicit def fromStringOptionParameter(v: StringOptionParameter): Option[String] = v.value +} diff --git a/silk-plugins/silk-serialization-json/src/main/scala/org/silkframework/serialization/json/JsonSerializers.scala b/silk-plugins/silk-serialization-json/src/main/scala/org/silkframework/serialization/json/JsonSerializers.scala index ebc8cf4b87..b436528599 100644 --- a/silk-plugins/silk-serialization-json/src/main/scala/org/silkframework/serialization/json/JsonSerializers.scala +++ b/silk-plugins/silk-serialization-json/src/main/scala/org/silkframework/serialization/json/JsonSerializers.scala @@ -931,6 +931,7 @@ object JsonSerializers { final val REFERENCE_LINKS = "referenceLinks" final val LINK_LIMIT = "linkLimit" final val MATCHING_EXECUTION_TIMEOUT = "matchingExecutionTimeout" + final val APPLICATION_DATA = "applicationData" /** Deprecated properties */ final val DEPRECATED_OUTPUTS = "outputs" @@ -952,7 +953,8 @@ object JsonSerializers { output = stringValueOption(parametersObj, OUTPUT).filter(_.trim.nonEmpty).map(o => Identifier(o.trim)), referenceLinks = optionalValue(parametersObj, REFERENCE_LINKS).map(fromJson[ReferenceLinks]).getOrElse(ReferenceLinks.empty), linkLimit = numberValueOption(parametersObj, LINK_LIMIT).map(_.intValue).getOrElse(LinkSpec.DEFAULT_LINK_LIMIT), - matchingExecutionTimeout = numberValueOption(parametersObj, MATCHING_EXECUTION_TIMEOUT).map(_.intValue).getOrElse(LinkSpec.DEFAULT_EXECUTION_TIMEOUT_SECONDS) + matchingExecutionTimeout = numberValueOption(parametersObj, MATCHING_EXECUTION_TIMEOUT).map(_.intValue).getOrElse(LinkSpec.DEFAULT_EXECUTION_TIMEOUT_SECONDS), + applicationData = stringValueOption(parametersObj, APPLICATION_DATA) ) } } @@ -967,23 +969,30 @@ object JsonSerializers { output = mustBeJsArray(mustBeDefined(value, DEPRECATED_OUTPUTS))(_.value.map(v => Identifier(v.as[JsString].value))).headOption, referenceLinks = optionalValue(value, REFERENCE_LINKS).map(fromJson[ReferenceLinks]).getOrElse(ReferenceLinks.empty), linkLimit = numberValueOption(value, LINK_LIMIT).map(_.intValue()).getOrElse(LinkSpec.DEFAULT_LINK_LIMIT), - matchingExecutionTimeout = numberValueOption(value, MATCHING_EXECUTION_TIMEOUT).map(_.intValue()).getOrElse(LinkSpec.DEFAULT_EXECUTION_TIMEOUT_SECONDS) + matchingExecutionTimeout = numberValueOption(value, MATCHING_EXECUTION_TIMEOUT).map(_.intValue()).getOrElse(LinkSpec.DEFAULT_EXECUTION_TIMEOUT_SECONDS), + applicationData = stringValueOption(value, APPLICATION_DATA) ) } override def write(value: LinkSpec)(implicit writeContext: WriteContext[JsValue]): JsValue = { + var parameters = Json.obj( + SOURCE -> toJson(value.dataSelections.source), + TARGET -> toJson(value.dataSelections.target), + RULE -> toJson(value.rule), + OUTPUT -> JsString(value.output.map(_.toString).getOrElse("")), + REFERENCE_LINKS -> toJson(value.referenceLinks), + LINK_LIMIT -> JsString(value.linkLimit.toString), + MATCHING_EXECUTION_TIMEOUT -> JsString(value.matchingExecutionTimeout.toString) + ) + + for(data <- value.applicationData) { + parameters += (APPLICATION_DATA -> JsString(data)) + } + Json.obj( TASKTYPE -> TASK_TYPE_LINKING, TYPE -> "linking", - PARAMETERS -> Json.obj( - SOURCE -> toJson(value.dataSelections.source), - TARGET -> toJson(value.dataSelections.target), - RULE -> toJson(value.rule), - OUTPUT -> JsString(value.output.map(_.toString).getOrElse("")), - REFERENCE_LINKS -> toJson(value.referenceLinks), - LINK_LIMIT -> JsString(value.linkLimit.toString), - MATCHING_EXECUTION_TIMEOUT -> JsString(value.matchingExecutionTimeout.toString) - ) + PARAMETERS -> parameters ) } } diff --git a/silk-rules/src/main/scala/org/silkframework/rule/LinkSpec.scala b/silk-rules/src/main/scala/org/silkframework/rule/LinkSpec.scala index 78162b928d..b2dc1f6268 100644 --- a/silk-rules/src/main/scala/org/silkframework/rule/LinkSpec.scala +++ b/silk-rules/src/main/scala/org/silkframework/rule/LinkSpec.scala @@ -25,7 +25,7 @@ import org.silkframework.rule.evaluation.ReferenceLinks import org.silkframework.rule.input.{Input, PathInput, TransformInput} import org.silkframework.rule.similarity.{Aggregation, Comparison, SimilarityOperator} import org.silkframework.runtime.activity.UserContext -import org.silkframework.runtime.plugin.IdentifierOptionParameter +import org.silkframework.runtime.plugin.{IdentifierOptionParameter, StringOptionParameter} import org.silkframework.runtime.plugin.annotations.{Param, Plugin} import org.silkframework.runtime.resource.Resource import org.silkframework.runtime.serialization.XmlSerialization._ @@ -35,7 +35,7 @@ import org.silkframework.util._ import org.silkframework.workspace.project.task.DatasetTaskReferenceAutoCompletionProvider import scala.collection.mutable -import scala.xml.Node +import scala.xml.{Node, NodeSeq} /** * Represents a Silk Link Specification. @@ -63,7 +63,10 @@ case class LinkSpec(@Param(label = "Source input", value = "The source input to linkLimit: Int = LinkSpec.DEFAULT_LINK_LIMIT, @Param(label = "Matching timeout", value = "The timeout for the matching phase. If the matching takes longer the execution will be stopped.", advanced = true) - matchingExecutionTimeout: Int = LinkSpec.DEFAULT_EXECUTION_TIMEOUT_SECONDS) extends TaskSpec { + matchingExecutionTimeout: Int = LinkSpec.DEFAULT_EXECUTION_TIMEOUT_SECONDS, + @Param(value = "Additional data that is set by the application managing the linking task.", + visibleInDialog = false) + applicationData: StringOptionParameter = None) extends TaskSpec { assert(linkLimit >= 0, "The link limit must be greater equal 0!") assert(matchingExecutionTimeout >= 0, "The matching execution timeout must be greater equal 0!") @@ -238,7 +241,8 @@ object LinkSpec { Identifier(id) }, linkLimit = (node \ "@linkLimit").headOption.map(_.text.toInt).getOrElse(LinkSpec.DEFAULT_LINK_LIMIT), - matchingExecutionTimeout = (node \ "@matchingExecutionTimeout").headOption.map(_.text.toInt).getOrElse(LinkSpec.DEFAULT_EXECUTION_TIMEOUT_SECONDS) + matchingExecutionTimeout = (node \ "@matchingExecutionTimeout").headOption.map(_.text.toInt).getOrElse(LinkSpec.DEFAULT_EXECUTION_TIMEOUT_SECONDS), + applicationData = (node \ "ApplicationData").headOption.map(_.text) ) } @@ -253,6 +257,7 @@ object LinkSpec { {spec.output.value.toSeq.map(o => )} + { spec.applicationData.map(data => {data}).getOrElse(NodeSeq.Empty) } } } diff --git a/silk-workbench/silk-workbench-workspace/test/controllers/workspace/TaskApiTest.scala b/silk-workbench/silk-workbench-workspace/test/controllers/workspace/TaskApiTest.scala index 6be4d8382d..ebd3000d5e 100644 --- a/silk-workbench/silk-workbench-workspace/test/controllers/workspace/TaskApiTest.scala +++ b/silk-workbench/silk-workbench-workspace/test/controllers/workspace/TaskApiTest.scala @@ -260,6 +260,7 @@ class TaskApiTest extends PlaySpec with IntegrationTestTrait with MustMatchers { (response.json \ DATA \ PARAMETERS \ "source" \ "typeUri").get mustBe JsString("") (response.json \ DATA \ PARAMETERS \ "target" \ "typeUri").get mustBe JsString("") (response.json \ DATA \ PARAMETERS \ "rule" \ "linkType").get mustBe JsString("owl:sameAs") + (response.json \ DATA \ PARAMETERS \ "applicationData").isEmpty mustBe true } "patch linking task" in { @@ -278,7 +279,8 @@ class TaskApiTest extends PlaySpec with IntegrationTestTrait with MustMatchers { | "inputId": "$datasetId", | "typeUri": "", | "restriction": "" - | } + | }, + | "applicationData": "Some Data" | } | } | } @@ -297,6 +299,7 @@ class TaskApiTest extends PlaySpec with IntegrationTestTrait with MustMatchers { (response.json \ DATA \ PARAMETERS \ "source" \ "typeUri").get mustBe JsString("owl:Class") (response.json \ DATA \ PARAMETERS \ "target" \ "typeUri").get mustBe JsString("") (response.json \ DATA \ PARAMETERS \ "rule" \ "linkType").get mustBe JsString("owl:sameAs") + (response.json \ DATA \ PARAMETERS \ "applicationData").get mustBe JsString("Some Data") } "post workflow task" in { diff --git a/silk-workspace/src/test/scala/org/silkframework/workspace/WorkspaceProviderTestTrait.scala b/silk-workspace/src/test/scala/org/silkframework/workspace/WorkspaceProviderTestTrait.scala index ab06ea6da9..45aceba146 100644 --- a/silk-workspace/src/test/scala/org/silkframework/workspace/WorkspaceProviderTestTrait.scala +++ b/silk-workspace/src/test/scala/org/silkframework/workspace/WorkspaceProviderTestTrait.scala @@ -80,6 +80,10 @@ trait WorkspaceProviderTestTrait extends FlatSpec with Matchers with MockitoSuga description = Some("Updated Task Description") ) + val applicationData = "Some Data" + + val applicationDataUpdated = "Updated Data" + val dataset = PlainTask(DATASET_ID, DatasetSpec(MockDataset("default")), metaData = MetaData(DATASET_ID, Some(DATASET_ID + " description"))) val datasetUpdated = PlainTask(DATASET_ID, DatasetSpec(MockDataset("updated"), uriAttribute = Some("uri")), metaData = MetaData(DATASET_ID)) @@ -88,11 +92,11 @@ trait WorkspaceProviderTestTrait extends FlatSpec with Matchers with MockitoSuga private val dummyRestriction = Restriction.custom(" ?a 1 .\n\n ?a true .\n")(Prefixes.default) val linkSpec = LinkSpec(rule = rule, source = DatasetSelection(DUMMY_DATASET, dummyType, dummyRestriction), target = DatasetSelection(DUMMY_DATASET, dummyType, dummyRestriction), - linkLimit = LinkSpec.DEFAULT_LINK_LIMIT + 1, matchingExecutionTimeout = LinkSpec.DEFAULT_EXECUTION_TIMEOUT_SECONDS + 1) + linkLimit = LinkSpec.DEFAULT_LINK_LIMIT + 1, matchingExecutionTimeout = LinkSpec.DEFAULT_EXECUTION_TIMEOUT_SECONDS + 1, applicationData = Some(applicationData)) val linkTask = PlainTask(LINKING_TASK_ID, linkSpec, metaData) - val linkTaskUpdated = PlainTask(LINKING_TASK_ID, LinkSpec(rule = rule.copy(operator = None)), metaDataUpdated) + val linkTaskUpdated = PlainTask(LINKING_TASK_ID, LinkSpec(rule = rule.copy(operator = None), applicationData = Some(applicationDataUpdated)), metaDataUpdated) val transformTask = PlainTask(