Skip to content

Commit

Permalink
Merge branch 'fix/optionalLabels-CMEM-1897' into release/3.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
robertisele committed Nov 17, 2021
2 parents 2a30b87 + f1b540b commit 95903ee
Show file tree
Hide file tree
Showing 82 changed files with 273 additions and 238 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.silkframework.config

import org.silkframework.util.Identifier

/**
* Inherited by classes that provide an identifier and metadata.
*/
trait HasMetaData {

/**
* The unique identifier for this object.
*/
def id: Identifier

/**
* The metadata for this object.
*/
def metaData: MetaData

/**
* Returns a label for this object.
* Per default, it will fall back to generating a label from the identifier, if no label is defined.
* Subclasses may override this behaviour.
* Truncates the label to maxLength characters.
*
* @param maxLength the max length in characters
*/
def label(maxLength: Int = MetaData.DEFAULT_LABEL_MAX_LENGTH)(implicit prefixes: Prefixes = Prefixes.empty): String = {
metaData.formattedLabel(MetaData.labelFromId(id), maxLength)
}

/**
* Returns a label for this object with no length restriction.
*/
def fullLabel(implicit prefixes: Prefixes = Prefixes.empty): String = label(Int.MaxValue)

}
17 changes: 9 additions & 8 deletions silk-core/src/main/scala/org/silkframework/config/MetaData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import scala.xml._
/**
* Holds meta data about a task.
*/
case class MetaData(label: String,
case class MetaData(label: Option[String],
description: Option[String] = None,
modified: Option[Instant] = None,
created: Option[Instant] = None,
Expand All @@ -26,10 +26,11 @@ case class MetaData(label: String,
*/
def formattedLabel(defaultLabel: String, maxLength: Int = MetaData.DEFAULT_LABEL_MAX_LENGTH): String = {
assert(maxLength > 5, "maxLength for task label must be at least 5 chars long")
val trimmedLabel = if(label.trim != "") {
label.trim
} else {
defaultLabel
val trimmedLabel = label match {
case Some(l) if l.trim != "" =>
l.trim
case _ =>
defaultLabel
}
if(trimmedLabel.length > maxLength) {
val sideLength = (maxLength - 2) / 2
Expand Down Expand Up @@ -72,7 +73,7 @@ object MetaData {

val DEFAULT_LABEL_MAX_LENGTH = 50

def empty: MetaData = MetaData("", None, None, None, None, None)
def empty: MetaData = MetaData(None, None, None, None, None, None)

/**
* Generates a nice label from an identifier.
Expand Down Expand Up @@ -114,7 +115,7 @@ object MetaData {
*/
def read(node: Node)(implicit readContext: ReadContext): MetaData = {
MetaData(
label = (node \ "Label").text,
label = Some((node \ "Label").text).filter(_.nonEmpty),
description = Some((node \ "Description").text).filter(_.nonEmpty),
modified = (node \ "Modified").headOption.map(node => Instant.parse(node.text)),
created = (node \ "Created").headOption.map(node => Instant.parse(node.text)),
Expand All @@ -129,7 +130,7 @@ object MetaData {
def write(data: MetaData)(implicit writeContext: WriteContext[Node]): Node = {
val descriptionPCData = PCData(data.description.getOrElse(""))
<MetaData>
<Label>{data.label}</Label>
<Label>{data.label.getOrElse("")}</Label>
<Description xml:space="preserve">{descriptionPCData}</Description>
{ data.modified.map(instant => <Modified>{instant.toString}</Modified>).toSeq }
{ data.created.map(instant => <Created>{instant.toString}</Created>).toSeq }
Expand Down
12 changes: 1 addition & 11 deletions silk-core/src/main/scala/org/silkframework/config/Task.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import scala.xml._
*
* @tparam TaskType The type of this task, e.g., TransformSpec.
*/
trait Task[+TaskType <: TaskSpec] {
trait Task[+TaskType <: TaskSpec] extends HasMetaData {
/** The id of this task. */
def id: Identifier

Expand Down Expand Up @@ -46,16 +46,6 @@ trait Task[+TaskType <: TaskSpec] {
/** Find tasks that are either input or output to this task. */
def findRelatedTasksInsideWorkflows()(implicit userContext: UserContext): Set[Identifier] = Set.empty

/**
* Returns the label if defined or the task ID. Truncates the label to maxLength characters.
* @param maxLength the max length in characters
*/
def taskLabel(maxLength: Int = MetaData.DEFAULT_LABEL_MAX_LENGTH): String = {
metaData.formattedLabel(id, maxLength)
}

def fullTaskLabel: String = taskLabel(Int.MaxValue)

override def equals(obj: scala.Any): Boolean = obj match {
case task: Task[_] =>
id == task.id &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ trait ExecutionReport {
/**
* A user-friendly label for this report, usually just the task label.
*/
def label: String = task.taskLabel()
def label: String = task.label()

/**
* Short label for the executed operation, e.g., read or write (optional).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
package org.silkframework.workspace

import java.util.logging.Logger

import com.typesafe.config.ConfigException

import javax.inject.Inject
import org.silkframework.config.{Config, DefaultConfig, MetaData, Prefixes}
import org.silkframework.config.{Config, DefaultConfig, HasMetaData, MetaData, Prefixes}
import org.silkframework.util.Identifier

/**
Expand All @@ -32,7 +32,8 @@ import org.silkframework.util.Identifier
case class ProjectConfig(id: Identifier = Identifier.random,
prefixes: Prefixes = Prefixes.default,
projectResourceUriOpt: Option[String] = None,
metaData: MetaData = MetaData.empty) {
metaData: MetaData = MetaData.empty) extends HasMetaData {

def withMetaData(metaData: MetaData): ProjectConfig = this.copy(metaData = metaData)

def generateDefaultUri: String = {
Expand All @@ -43,6 +44,10 @@ case class ProjectConfig(id: Identifier = Identifier.random,
def resourceUriOrElseDefaultUri: String = {
projectResourceUriOpt.getOrElse(generateDefaultUri)
}

override def label(maxLength: Int)(implicit prefixes: Prefixes): String = {
metaData.formattedLabel(id, maxLength)
}
}

object ProjectConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ case class DatasetTaskReferenceAutoCompletionProvider() extends PluginParameterA
workspace: WorkspaceReadTrait)
(implicit userContext: UserContext): Traversable[AutoCompletionResult] = {
val taskProject = dependOnParameterValues.headOption.getOrElse(projectId)
val allDatasets = workspace.project(taskProject).tasks[GenericDatasetSpec].map(spec => AutoCompletionResult(spec.id, Some(spec.metaData.label)))
val allDatasets = workspace.project(taskProject).tasks[GenericDatasetSpec].map(spec => AutoCompletionResult(spec.id, spec.metaData.label))
filterResults(searchQuery, allDatasets)
}

Expand All @@ -29,6 +29,6 @@ case class DatasetTaskReferenceAutoCompletionProvider() extends PluginParameterA
workspace: WorkspaceReadTrait)
(implicit userContext: UserContext): Option[String] = {
val taskProject = dependOnParameterValues.headOption.getOrElse(projectId)
workspace.project(taskProject).taskOption[GenericDatasetSpec](value).map(_.metaData.label)
workspace.project(taskProject).taskOption[GenericDatasetSpec](value).flatMap(_.metaData.label)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package org.silkframework.plugins.dataset.rdf.endpoint

import java.io._
import java.util.logging.Logger

import org.apache.jena.query.{Dataset, Query, QueryExecution, QueryExecutionFactory}
import org.apache.jena.rdf.model.{Model, ModelFactory}
import org.apache.jena.riot.{Lang, RDFLanguages}
import org.apache.jena.update.{UpdateExecutionFactory, UpdateFactory, UpdateProcessor}
import org.silkframework.dataset.rdf.{GraphStoreTrait, SparqlEndpoint, SparqlParams}
import org.silkframework.runtime.activity.UserContext

import scala.util.control.NonFatal

/**
* A SPARQL endpoint which executes all queries on a Jena Dataset.
*/
Expand All @@ -20,7 +21,13 @@ class JenaDatasetEndpoint(dataset: Dataset, val sparqlParams: SparqlParams = Spa
}

override def createUpdateExecution(query: String): UpdateProcessor = {
UpdateExecutionFactory.create(UpdateFactory.create(query), dataset)
try {
UpdateExecutionFactory.create(UpdateFactory.create(query), dataset)
}
catch {
case NonFatal(ex) =>
throw ex
}
}

override def graphStoreEndpoint(graph: String): String = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ case class SparqlEndpointDatasetAutoCompletionProvider() extends PluginParameter
(implicit userContext: UserContext): Traversable[AutoCompletionResult] = {
val allResults = workspace.project(projectId).tasks[GenericDatasetSpec]
.filter(datasetSpec => datasetSpec.data.plugin.isInstanceOf[RdfDataset])
.map(datasetSpec => AutoCompletionResult(datasetSpec.id, Some(datasetSpec.metaData.label)))
.map(datasetSpec => AutoCompletionResult(datasetSpec.id, datasetSpec.metaData.label))
filterResults(searchQuery, allResults)
}

override def valueToLabel(projectId: String, value: String, dependOnParameterValues: Seq[String], workspace: WorkspaceReadTrait)
(implicit userContext: UserContext): Option[String] = {
workspace.project(projectId).taskOption[GenericDatasetSpec](value).map(_.metaData.label)
workspace.project(projectId).taskOption[GenericDatasetSpec](value).flatMap(_.metaData.label)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ object ExecutionReportSerializers {

def serializeBasicValues(value: ExecutionReport)(implicit writeContext: WriteContext[JsValue]): JsObject = {
Json.obj(
LABEL -> value.task.taskLabel(),
LABEL -> value.task.label(),
OPERATION -> value.operation,
OPERATION_DESC -> value.operationDesc,
TASK -> GenericTaskJsonFormat.write(value.task),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,14 @@ object JsonSerializers {
final val CREATED_BY = "createdBy"
final val LAST_MODIFIED_BY = "lastModifiedBy"

override def read(value: JsValue)(implicit readContext: ReadContext): MetaData = {
read(value, "")
}

/**
* Reads meta data. Generates a label if no label is provided in the json.
*
* @param value The json to read the meta data from.
* @param identifier If no label is provided in the json, use this identifier to generate a label.
*/
def read(value: JsValue, identifier: String)(implicit readContext: ReadContext): MetaData = {
override def read(value: JsValue)(implicit readContext: ReadContext): MetaData = {
MetaData(
label = stringValueOption(value, LABEL).getOrElse(MetaData.labelFromId(identifier)),
label = stringValueOption(value, LABEL).filter(_.nonEmpty),
description = stringValueOption(value, DESCRIPTION),
modified = stringValueOption(value, MODIFIED).map(Instant.parse),
created = stringValueOption(value, CREATED).map(Instant.parse),
Expand All @@ -112,10 +107,10 @@ object JsonSerializers {
}

override def write(value: MetaData)(implicit writeContext: WriteContext[JsValue]): JsValue = {
var json =
Json.obj(
LABEL -> JsString(value.label)
)
var json = Json.obj()
for(label <- value.label if label.nonEmpty) {
json += LABEL -> JsString(label)
}
for(description <- value.description if description.nonEmpty) {
json += DESCRIPTION -> JsString(description)
}
Expand Down Expand Up @@ -387,10 +382,9 @@ object JsonSerializers {
*/
override def read(value: JsValue)(implicit readContext: ReadContext): RootMappingRule = {
val mappingRules = fromJson[MappingRules](mustBeDefined(value, RULES_PROPERTY))
val typeName = mappingRules.typeRules.flatMap(_.typeUri.localName).headOption
val id = identifier(value, RootMappingRule.defaultId)
val mappingTarget = optionalValue(value, MAPPING_TARGET).map(fromJson[MappingTarget]).getOrElse(RootMappingRule.defaultMappingTarget)
RootMappingRule(id = id, rules = mappingRules, mappingTarget = mappingTarget, metaData = metaData(value, typeName.getOrElse("RootMapping")))
RootMappingRule(id = id, rules = mappingRules, mappingTarget = mappingTarget, metaData = metaData(value))
}

/**
Expand Down Expand Up @@ -422,7 +416,7 @@ object JsonSerializers {
val typeUri = Uri.parse(stringValue(value, TYPE_PROPERTY), readContext.prefixes)
val typeName = typeUri.localName.getOrElse("type")
val name = identifier(value, typeName)
TypeMapping(name,typeUri, metaData(value, typeName))
TypeMapping(name,typeUri, metaData(value))
}

/**
Expand Down Expand Up @@ -456,7 +450,7 @@ object JsonSerializers {
if(readContext.validationEnabled) {
UriPatternParser.parseIntoSegments(pattern, allowIncompletePattern = false).validateAndThrow()
}
PatternUriMapping(name, pattern.trim(), metaData(value, "URI"), readContext.prefixes)
PatternUriMapping(name, pattern.trim(), metaData(value), readContext.prefixes)
}

/**
Expand Down Expand Up @@ -486,7 +480,7 @@ object JsonSerializers {
ComplexUriMapping(
id = identifier(value, "uri"),
operator = fromJson[Input]((value \ OPERATOR).get),
metaData(value, "URI")
metaData(value)
)
}

Expand Down Expand Up @@ -547,7 +541,7 @@ object JsonSerializers {
val mappingName = mappingTarget.propertyUri.localName.getOrElse("ValueMapping")
val id = identifier(value, mappingName)
val sourcePath = silkPath(id, stringValue(value, SOURCE_PATH_PROPERTY))
DirectMapping(id, sourcePath, mappingTarget, metaData(value, mappingName))
DirectMapping(id, sourcePath, mappingTarget, metaData(value))
}

/**
Expand Down Expand Up @@ -583,7 +577,7 @@ object JsonSerializers {
val mappingName = mappingTarget.flatMap(_.propertyUri.localName).getOrElse("ObjectMapping")
val id = identifier(value, mappingName)
val sourcePath = silkPath(id, stringValue(value, SOURCE_PATH))
ObjectMapping(id, sourcePath, mappingTarget, children, metaData(value, mappingName), readContext.prefixes)
ObjectMapping(id, sourcePath, mappingTarget, children, metaData(value), readContext.prefixes)
}

/**
Expand Down Expand Up @@ -638,7 +632,7 @@ object JsonSerializers {
id = id,
operator = fromJson[Input]((jsValue \ OPERATOR).get),
target = mappingTarget,
metaData(jsValue, mappingName)
metaData(jsValue)
)
TransformRule.simplify(complex)(readContext.prefixes)
}
Expand Down Expand Up @@ -1061,19 +1055,20 @@ object JsonSerializers {
}
val id: Identifier = stringValueOption(value, ID).map(_.trim).filter(_.nonEmpty).map(Identifier.apply).getOrElse {
// Generate unique ID from label if no ID was supplied
val md = metaData(value, "id")
val label = md.label.trim
if(label.isEmpty) {
throw BadUserInputException("The label must not be empty if no ID is provided!")
val md = metaData(value)
md.label match {
case Some(label) if label.trim.nonEmpty =>
generateTaskId(label)
case _ =>
throw BadUserInputException("The label must not be empty if no ID is provided!")
}
generateTaskId(label)
}
// In older serializations the task data has been directly attached to this JSON object
val dataJson = optionalValue(value, DATA).getOrElse(value)
PlainTask(
id = id,
data = fromJson[T](dataJson),
metaData = metaData(value, id)
metaData = metaData(value)
)
}

Expand Down Expand Up @@ -1348,12 +1343,12 @@ object JsonSerializers {
* @param json The json to read the meta data from.
* @param identifier If no label is provided in the json, use this identifier to generate a label.
*/
private def metaData(json: JsValue, identifier: String)(implicit readContext: ReadContext): MetaData = {
private def metaData(json: JsValue)(implicit readContext: ReadContext): MetaData = {
optionalValue(json, METADATA) match {
case Some(metaDataJson) =>
MetaDataJsonFormat.read(metaDataJson, identifier)
MetaDataJsonFormat.read(metaDataJson)
case None =>
MetaData(MetaData.labelFromId(identifier))
MetaData.empty
}
}

Expand Down
Loading

0 comments on commit 95903ee

Please sign in to comment.