Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to keep linking rule draft of possibly invalid linking rules #403

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,8 @@

<xs:element ref="Outputs" minOccurs="0"/>

<xs:element name="ApplicationData" type="xs:string" minOccurs="0"/>

</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="linkLimit" type="xs:string" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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]"
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
)
}
}
Expand All @@ -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
)
}
}
Expand Down
13 changes: 9 additions & 4 deletions silk-rules/src/main/scala/org/silkframework/rule/LinkSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand All @@ -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.
Expand Down Expand Up @@ -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!")
Expand Down Expand Up @@ -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)
)
}

Expand All @@ -253,6 +257,7 @@ object LinkSpec {
<Outputs>
{spec.output.value.toSeq.map(o => <Output id={o}></Output>)}
</Outputs>
{ spec.applicationData.map(data => <ApplicationData>{data}</ApplicationData>).getOrElse(NodeSeq.Empty) }
</Interlink>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ class TaskApiTest extends PlaySpec with IntegrationTestTrait with MustMatchers {
(response.json \ DATA \ PARAMETERS \ "source" \ "typeUri").get mustBe JsString("<http://dbpedia.org/ontology/Film>")
(response.json \ DATA \ PARAMETERS \ "target" \ "typeUri").get mustBe JsString("<http://data.linkedmdb.org/resource/movie/film>")
(response.json \ DATA \ PARAMETERS \ "rule" \ "linkType").get mustBe JsString("owl:sameAs")
(response.json \ DATA \ PARAMETERS \ "applicationData").isEmpty mustBe true
}

"patch linking task" in {
Expand All @@ -278,7 +279,8 @@ class TaskApiTest extends PlaySpec with IntegrationTestTrait with MustMatchers {
| "inputId": "$datasetId",
| "typeUri": "<urn:schema:targetType>",
| "restriction": ""
| }
| },
| "applicationData": "Some Data"
| }
| }
| }
Expand All @@ -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("<urn:schema:targetType>")
(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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -88,11 +92,11 @@ trait WorkspaceProviderTestTrait extends FlatSpec with Matchers with MockitoSuga
private val dummyRestriction = Restriction.custom(" ?a <urn:test:prop1> 1 .\n\n ?a <urn:test:prop2> 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(
Expand Down