Skip to content

Commit

Permalink
Merge pull request #53 from openepcis/EDG-58_supporting_random_values…
Browse files Browse the repository at this point in the history
…_in_user_extensions

EDG-58: supporting random values in user extensions and refactoring c…
  • Loading branch information
sboeckelmann authored Feb 7, 2025
2 parents a7e89e6 + 0da6325 commit f9cf218
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import io.openepcis.testdata.generator.reactivestreams.StreamingEPCISDocument;
import io.openepcis.testdata.generator.reactivestreams.StreamingEPCISDocumentOutput;
import io.openepcis.testdata.generator.template.RandomGenerators;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
import lombok.Getter;
Expand Down Expand Up @@ -80,6 +81,11 @@ public class UserExtensionSyntax implements Serializable {
)
private String expression;

@Schema(type = SchemaType.OBJECT,
description = "Random information associated with the custom user extension (Ex: { \"type\": \"static\", \"staticValue\": 10, \"randomID\": \"\" })"
)
private ValueTypeSyntax random;

@Schema(
type = SchemaType.STRING,
enumeration = {
Expand All @@ -96,80 +102,98 @@ public class UserExtensionSyntax implements Serializable {
)
private transient Object rawJsonld;

@Setter
private static List<RandomGenerators> randomGenerators;


//Method to format the UserExtensions based on the Web Vocabulary or Custom extensions by recursively reading them
public Map<String, Object> toMap() {
final Map<String, Object> map = new HashMap<>();
// Converts UserExtensionSyntax to a Map representation, handling both raw JSON-LD and structured data.
public Map<String, Object> toMap() {
final Map<String, Object> result = new HashMap<>();

if (this.rawJsonld == null) {
//Add the context prefix and url to the document context
buildContextInfo(this.prefix, this.contextURL);
// If raw JSON-LD is provided, use it (excluding any "@context")
if (rawJsonld instanceof Map<?, ?>) {
@SuppressWarnings("unchecked") final Map<String, Object> rawMap = (Map<String, Object>) rawJsonld;
rawMap.remove("@context");
result.putAll(rawMap);
return result;
}

// Determine the key based on property or label
final String key = StringUtils.startsWith(property, "@")
? property
: StringUtils.defaultIfBlank(label, prefix + ":" + property);

// If there are child extensions, process them recursively
if (CollectionUtils.isNotEmpty(children)) {
final Map<String, Object> childrenMap = children.stream()
.flatMap(child -> child.toMap().entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
result.put(key, childrenMap);
return result;
}

// Process the leaf node based on the available data
if (StringUtils.isNotBlank(expression)) {
result.put(key, expression);
// Flag that Jinja processing is needed after event creation
StreamingEPCISDocumentOutput.setShouldRunJinjaTemplate(true);
} else if (random != null) {
// Generate a random value using the configured randomGenerators
result.put(key, random.getValue(randomGenerators));
} else if (StringUtils.isNotBlank(numberData)) {
// Parse numeric data, handling decimals and scientific notation
final Number number = (numberData.contains(".") || numberData.toLowerCase().contains("e"))
? new BigDecimal(numberData)
: NumberUtils.createNumber(numberData);
result.put(key, number);
} else {
// Default: use string data (or empty string if null)
result.put(key, StringUtils.defaultIfBlank(stringData, ""));
}

// Determine the key based on Web Vocabulary or Custom Extensions
final String key = StringUtils.startsWith(this.property, "@")
? this.property
: StringUtils.defaultIfBlank(this.label, this.prefix + ":" + this.property);
return result;
}

// Recursively processes namespaces from raw JSON-LD data and child extensions.
public void extractNamespaces() {
if (this.rawJsonld == null) {
buildContextInfo(prefix, contextURL);

// If children are present, recursively process them
// Loop over children and complex values to extract namespaces
if (CollectionUtils.isNotEmpty(children)) {
final Map<String, Object> complexMap = children.stream()
.flatMap(c -> c.toMap().entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
map.put(key, complexMap);
} else {
// If expression is present, store it in the map
if(StringUtils.isNotBlank(this.expression)){
map.put(key, expression);

// Flag to indicate Jinja processing is needed after event creation
StreamingEPCISDocumentOutput.setShouldRunJinjaTemplate(true);
}else if(StringUtils.isNotBlank(this.numberData)){
final Number number = this.numberData.contains(".") || this.numberData.toLowerCase().contains("e")
? new BigDecimal(this.numberData)
: NumberUtils.createNumber(this.numberData);
map.put(key, number);
}else {
// If only data is present, store it as a string (even if it looks like a number)
map.put(key, stringData);
}
children.forEach(UserExtensionSyntax::extractNamespaces);
}
} else if (this.rawJsonld instanceof Map<?, ?>) {
// Check if rawJsonld is a Map if so extract context info and append others to map
@SuppressWarnings("unchecked") final Map<String, Object> genExtMap = (Map<String, Object>) this.rawJsonld;

// Extract @context if present and append it to the StreamingEPCISDocument @context of EPCIS Document
Optional.ofNullable(genExtMap.get("@context"))
.filter(obj -> obj instanceof List)
.filter(List.class::isInstance)
.map(obj -> (List<Map<String, String>>) obj)
.ifPresent(contextList -> contextList.stream()
.flatMap(contextMap -> contextMap.entrySet().stream())
.forEach(entry -> buildContextInfo(entry.getKey(), entry.getValue())));


genExtMap.remove("@context"); // exclude the @context from the rawJsonld
map.putAll(genExtMap); // add remaining entries to the map
}

return map;
}


//Function to add the context url and the prefix to the StreamingEPCISDocument context to generate the @context
private void buildContextInfo(final String prefix, final String contextURL) {
// Check if the namespace already exist within the context if not then only add.
if (StreamingEPCISDocument.getContext() != null
&& !StreamingEPCISDocument.getContext().containsKey(prefix)
&& StringUtils.isNotBlank(prefix)
&& StringUtils.isNotBlank(contextURL)) {
//Function to add the context url and the prefix to the StreamingEPCISDocument context to generate the @context
private void buildContextInfo(final String prefix, final String contextURL) {
// Check if the namespace already exist within the context if not then only add.
if (StreamingEPCISDocument.getContext() != null
&& !StreamingEPCISDocument.getContext().containsKey(prefix)
&& StringUtils.isNotBlank(prefix)
&& StringUtils.isNotBlank(contextURL)) {

//Check if the namespace matches any of the default namespaces if so omit them
boolean isDefaultContext = EPCIS_DEFAULT_NAMESPACES.entrySet().stream().anyMatch(entry -> entry.getKey().equals(prefix) && entry.getValue().equals(contextURL));
//Check if the namespace matches any of the default namespaces if so omit them
boolean isDefaultContext = EPCIS_DEFAULT_NAMESPACES.entrySet().stream()
.anyMatch(entry -> entry.getKey().equals(prefix) && entry.getValue().equals(contextURL));

//if no matches to default namespace then add
if (!isDefaultContext) {
StreamingEPCISDocument.getContext().put(prefix, contextURL);
}
//if no matches to default namespace then add
if (!isDefaultContext) {
StreamingEPCISDocument.getContext().put(prefix, contextURL);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.openepcis.testdata.generator.model;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
Expand Down Expand Up @@ -76,6 +77,8 @@ public AbstractEventCreationModel(final T typeInfo, final List<Identifier> ident
this.randomGenerators = randomGenerators;
this.randomSerialNumberGenerator = RandomSerialNumberGenerator.getInstance(typeInfo.getSeed()); //Since instance of MersenneTwister for each model
RandomValueGenerator.generateInstance(randomGenerators); // Generate Random instance for each of the randomGenerators
// Configures the random number generators that will be used when processing extension/ilmd
UserExtensionSyntax.setRandomGenerators(randomGenerators);
}

@Override
Expand Down Expand Up @@ -142,7 +145,7 @@ protected void configure(final E epcisEvent, final List<EventIdentifierTracker>
}
}

// User extensions addition
// Processes user extensions from TypeInfo and sets them on the EPCIS event.
if (typeInfo.getUserExtensions() != null && !typeInfo.getUserExtensions().isEmpty()) {
epcisEvent.setUserExtensions(
typeInfo.getUserExtensions().stream()
Expand All @@ -153,6 +156,16 @@ protected void configure(final E epcisEvent, final List<EventIdentifierTracker>
} else if (root.getRawJsonld() instanceof Map) {
// Stream over rawJsonld if it's a Map
return root.toMap().entrySet().stream();
}else if(root.getRawJsonld() instanceof String) {
// Handle raw JSON-LD string - processes JSON-LD format with context removal
try {
final Map<String, Object> rawMap = objectMapper.readValue(root.getRawJsonld().toString(), new TypeReference<>() {});
rawMap.remove("@context");
return rawMap.entrySet().stream();
}
catch (Exception e){
throw new TestDataGeneratorException("Error during the conversion of RawJsonLD : " + typeInfo.getEventType() + e.getMessage(), e);
}
}
// Return an empty stream if neither are present
return Stream.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public static void storeContextUrls(final List<CustomContextUrl> customContextUr
* @param extensions list of extensions from ilmd, error and userExtensions
*/
public static void extractNamespaces(final List<UserExtensionSyntax> extensions) {
extensions.forEach(UserExtensionSyntax::toMap);
extensions.forEach(UserExtensionSyntax::extractNamespaces);
}

/**
Expand Down

0 comments on commit f9cf218

Please sign in to comment.