Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) 2024 Source Auditor Inc.
*/
package org.spdx.v3jsonldstore;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spdx.storage.PropertyDescriptor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Optional;
import java.util.Set;
import net.jimblackler.jsonschemafriend.GenerationException;
import net.jimblackler.jsonschemafriend.Schema;
import net.jimblackler.jsonschemafriend.SchemaException;
import net.jimblackler.jsonschemafriend.SchemaStore;
import net.jimblackler.jsonschemafriend.Validator;
/**
* @author Gary O'Neall
*
* Represents the JSON Schema for SPDX 3.X includes a number of convenience methods
*
*/
public class JsonLDSchema {
static final Logger logger = LoggerFactory.getLogger(JsonLDSchema.class);
private static final String ANY_CLASS_URI_SUFFIX = "/$defs/AnyClass";
public static final Map RESERVED_JAVA_WORDS = new HashMap<>();
public static final Map REVERSE_JAVA_WORDS = new HashMap<>();
public static final Set BOOLEAN_TYPES = new HashSet<>();
public static final Set INTEGER_TYPES = new HashSet<>();
public static final Set DOUBLE_TYPES = new HashSet<>();
public static final Set STRING_TYPES = new HashSet<>();
static {
RESERVED_JAVA_WORDS.put("Package", "SpdxPackage");
REVERSE_JAVA_WORDS.put("SpdxPackage", "Package");
RESERVED_JAVA_WORDS.put("package", "spdxPackage");
REVERSE_JAVA_WORDS.put("spdxPackage", "package");
RESERVED_JAVA_WORDS.put("File", "SpdxFile");
REVERSE_JAVA_WORDS.put("SpdxFile", "File");
RESERVED_JAVA_WORDS.put("file", "spdxFile");
REVERSE_JAVA_WORDS.put("spdxFile", "file");
RESERVED_JAVA_WORDS.put("import", "spdxImport");
REVERSE_JAVA_WORDS.put("spdxImport", "import");
BOOLEAN_TYPES.add("http://www.w3.org/2001/XMLSchema#boolean");
INTEGER_TYPES.add("http://www.w3.org/2001/XMLSchema#integer");
INTEGER_TYPES.add("http://www.w3.org/2001/XMLSchema#nonPositiveInteger");
INTEGER_TYPES.add("http://www.w3.org/2001/XMLSchema#nonNegativeInteger");
INTEGER_TYPES.add("http://www.w3.org/2001/XMLSchema#positiveInteger");
INTEGER_TYPES.add("http://www.w3.org/2001/XMLSchema#negativeInteger");
INTEGER_TYPES.add("http://www.w3.org/2001/XMLSchema#long");
DOUBLE_TYPES.add("http://www.w3.org/2001/XMLSchema#decimal");
DOUBLE_TYPES.add("http://www.w3.org/2001/XMLSchema#float");
DOUBLE_TYPES.add("http://www.w3.org/2001/XMLSchema#double");
STRING_TYPES.add("http://www.w3.org/2001/XMLSchema#duration");
STRING_TYPES.add("http://www.w3.org/2001/XMLSchema#dateType");
STRING_TYPES.add("http://www.w3.org/2001/XMLSchema#dateTimeStamp");
STRING_TYPES.add("http://www.w3.org/2001/XMLSchema#time");
STRING_TYPES.add("http://www.w3.org/2001/XMLSchema#date");
STRING_TYPES.add("http://www.w3.org/2001/XMLSchema#string");
STRING_TYPES.add("http://www.w3.org/2001/XMLSchema#normalizedString");
STRING_TYPES.add("http://www.w3.org/2001/XMLSchema#token");
STRING_TYPES.add("http://www.w3.org/2001/XMLSchema#language");
STRING_TYPES.add("http://www.w3.org/2001/XMLSchema#anyURI");
}
static final ObjectMapper JSON_MAPPER = new ObjectMapper();
private static final String OBJECT_TYPE = "http://www.w3.org/2002/07/owl#ObjectProperty";
private static final String INDIVIDUAL_TYPE = "http://www.w3.org/2002/07/owl#NamedIndividual";
private JsonNode contexts;
private Schema spdxRootSchema;
private Map modelStatements = new HashMap<>(); // maps the ID to the statement
private Validator validator = new Validator();
private List elementTypes;
private List anyLicenseInfoTypes;
/**
* @param schemaFileName File name for the schema file in the resources directory
* @param contextFileName File name for the context file in the resources directory
* @param modelFileName File name for the model file in the resources directory
* @throws GenerationException on schema loading error
*/
public JsonLDSchema(String schemaFileName, String contextFileName, String modelFileName) throws GenerationException {
SchemaStore schemaStore = new SchemaStore();
spdxRootSchema = schemaStore.loadSchema(JsonLDSchema.class.getResourceAsStream("/resources/"+schemaFileName));
try (InputStream is = JsonLDSchema.class.getResourceAsStream("/resources/"+contextFileName)) {
if (Objects.isNull(is)) {
throw new GenerationException("Unable to open JSON LD context file");
}
JsonNode root = JSON_MAPPER.readTree(is);
this.contexts = root.get("@context");
if (Objects.isNull(contexts)) {
throw new GenerationException("Missing contexts");
}
if (!contexts.isObject()) {
throw new GenerationException("Contexts is not an object");
}
} catch (IOException e1) {
throw new GenerationException("I/O Error loading JSON LD Context file", e1);
}
try (InputStream is = JsonLDSchema.class.getResourceAsStream("/resources/"+modelFileName)) {
if (Objects.isNull(is)) {
throw new GenerationException("Unable to open JSON LD model file");
}
JsonNode root = JSON_MAPPER.readTree(is);
for (Iterator iter = root.elements(); iter.hasNext(); ) {
JsonNode modelStatement = iter.next();
if (modelStatement.has("@id")) {
modelStatements.put(modelStatement.get("@id").asText(), modelStatement);
}
}
} catch (IOException e1) {
throw new GenerationException("I/O Error loading JSON LD model file", e1);
}
elementTypes = collectTypes("Element");
anyLicenseInfoTypes = collectTypes("simplelicensing_AnyLicenseInfo");
}
/**
* @return a list of all element types that are subclasses of superClass
*/
private List collectTypes(String superClass) {
List retval = new ArrayList<>();
for (Schema classSchema:getAllClasses()) {
try {
if (isSubclassOf(superClass, classSchema)) {
Optional typeUri = getTypeUri(classSchema);
if (typeUri.isPresent()) {
retval.add(classUriToType(typeUri.get()));
} else {
logger.warn("No class type found for {}", classSchema.getUri());
}
}
} catch (URISyntaxException e) {
throw new RuntimeException("Unexpected URI syntax error", e);
}
}
return retval;
}
/**
* @param classUri URI for the class
* @return type name used in the SPDX 3 model
*/
private String classUriToType(URI classUri) {
String strClassUri = classUri.toString();
String nameSpace = strClassUri.substring(0, classUri.toString().lastIndexOf('/'));
String profile = nameSpace.substring(nameSpace.lastIndexOf('/') + 1);
profile = RESERVED_JAVA_WORDS.getOrDefault(profile, profile);
String className = strClassUri.substring(strClassUri.lastIndexOf('/') + 1);
className = RESERVED_JAVA_WORDS.getOrDefault(className, className);
return profile + "." + className;
}
/**
* @return a list of schemas for all classes defined in the SPDX schema
*/
public Collection getAllClasses() {
for (Entry entry:spdxRootSchema.getSubSchemas().entrySet()) {
if (entry.getKey().toString().endsWith(ANY_CLASS_URI_SUFFIX)) {
return entry.getValue().getAnyOf();
}
}
return Collections.emptyList();
}
/**
* @param superClassType superclass type
* @param subClass schema for the subclass
* @return true if the subClass schema contains the property restrictions for the superclass types
* @throws URISyntaxException on a bad superClassType string
*/
public boolean isSubclassOf(String superClassType, Schema subClass) throws URISyntaxException {
URI superClassPropertyUri = new URI("#/$defs/" + superClassType + "_props");
for (Schema allOfSchema:subClass.getAllOf()) {
if (superClassPropertyUri.equals(allOfSchema.getUri())) {
return true;
}
if (Objects.nonNull(allOfSchema.getUri()) && allOfSchema.getUri().toString().endsWith("_props") &&
isSubclassOf(superClassType, allOfSchema)) {
return true;
}
}
return false;
}
/**
* @param propertyName name of the property to check
* @param schema schema containing property restrictions
* @return true if the schema requires a property named propertyName via properties, subSchemas, or allOf
*/
public boolean hasProperty(String propertyName, Schema schema) {
return hasProperty(propertyName, schema, new HashSet<>());
}
/**
* @param propertyName name of the property to check
* @param schema schema containing property restrictions
* @param checkSchemas set of schemas which has already been checked
* @return true if the schema requires a property named propertyName via properties, subSchemas, or allOf
*/
private boolean hasProperty(String propertyName, Schema schema, Set checkedSchemas) {
if (checkedSchemas.contains(schema)) {
return false;
}
checkedSchemas.add(schema);
if (schema.getProperties().containsKey(propertyName)) {
return true;
}
for (Schema subSchema:schema.getSubSchemas().values()) {
if (hasProperty(propertyName, subSchema, checkedSchemas)) {
return true;
}
}
for (Schema allOfSchema:schema.getAllOf()) {
if (hasProperty(propertyName, allOfSchema, checkedSchemas)) {
return true;
}
}
return false;
}
/**
* @param className name of the class
* @return schema for the class if it exists
*/
public Optional getClassSchema(String className) {
for (Entry entry:spdxRootSchema.getSubSchemas().entrySet()) {
if (entry.getKey().toString().endsWith("/$defs/"+className)) {
return Optional.of(entry.getValue());
}
}
return Optional.empty();
}
/**
* @param classSchema schema for a class
* @return type URI for the type of the class from the JSON LD Context
*/
public Optional getTypeUri(Schema classSchema) {
Optional type = getType(classSchema);
if (type.isPresent()) {
JsonNode typeUriNode = contexts.get(type.get());
if (Objects.isNull(typeUriNode)) {
logger.warn("No context entry for {}", type.get());
return Optional.empty();
}
if (!typeUriNode.isTextual()) {
logger.warn("Wrong context type for {}", type.get());
return Optional.empty();
}
try {
URI retval = new URI(typeUriNode.asText());
return Optional.of(retval);
} catch (URISyntaxException e) {
logger.warn("Invalid URI string in context file for {}", type.get());
return Optional.empty();
}
} else {
return Optional.empty();
}
}
/**
* @param classSchema Schema for the class
* @return JSON Schema type name for the class
*/
public Optional getType(Schema classSchema) {
Collection allOfs = classSchema.getAllOf();
if (Objects.isNull(allOfs)) {
logger.warn("No allOfs for {}", classSchema.getUri());
return Optional.empty();
}
Schema typeProperty = null;
for (Schema allOfSchema:allOfs) {
Map properties = allOfSchema.getProperties();
typeProperty = properties.get("type");
if (Objects.nonNull(typeProperty)) {
break;
}
}
if (Objects.isNull(typeProperty)) {
return Optional.empty();
}
Collection oneOf = typeProperty.getOneOf();
if (Objects.isNull(oneOf) || oneOf.isEmpty()) {
logger.warn("No OneOf for class schema type property {}", classSchema.getUri());
return Optional.empty();
}
if (oneOf.size() > 1) {
logger.warn("Too many OneOfs for class schema type property {}", classSchema.getUri());
return Optional.empty();
}
for (Schema oneOfSchema:oneOf) {
Object typeString = oneOfSchema.getConst();
if (Objects.isNull(typeString)) {
logger.warn("Type string is null {}", classSchema.getUri());
return Optional.empty();
}
if (typeString instanceof String) {
return Optional.of((String)typeString);
} else {
logger.warn("Type string is not of type string {}", classSchema.getUri());
return Optional.empty();
}
}
return Optional.empty();
}
/**
* @param root Root JSON node of the JSON representation of an SPDX serialization
* @return true if the JSON node is valid
*/
public boolean validate(JsonNode root) {
try {
validator.validate(spdxRootSchema, JSON_MAPPER.treeToValue(root, Map.class));
return true;
} catch (SchemaException e) {
logger.error("JSON object does not match schema: {}", e.getMessage());
return false;
} catch (JsonProcessingException e) {
logger.error("Unable to parse JSON object: {}", e.getMessage());
return false;
} catch (IllegalArgumentException e) {
logger.error(e.getMessage());
return false;
}
}
/**
* @param spdxJsonFile file containing SPDX JSON LD content
* @return true if the JSON in file is valid according to the schema
* @throws IOException on file IO errors
*/
public boolean validate(File spdxJsonFile) throws IOException {
try {
validator.validate(spdxRootSchema, spdxJsonFile);
return true;
} catch (SchemaException e) {
logger.error("JSON object does not match schema: {}", e.getMessage());
return false;
} catch (JsonProcessingException e) {
logger.error("Unable to parse JSON object: {}", e.getMessage());
return false;
} catch (IllegalArgumentException e) {
logger.error(e.getMessage());
return false;
}
}
/**
* @return the elementTypes
*/
public List getElementTypes() {
return elementTypes;
}
/**
* @return the anyLicenseInfoTypes
*/
public List getAnyLicenseInfoTypes() {
return anyLicenseInfoTypes;
}
/**
* @param propertyName
* @return the JSON property type if it exists in the JSON-LD context
*/
public Optional getPropertyType(String propertyName) {
JsonNode propertytNode = contexts.get(propertyName);
if (Objects.isNull(propertytNode)) {
return Optional.empty();
}
JsonNode typeNode = propertytNode.get("@type");
return Objects.isNull(typeNode) ? Optional.empty() : Optional.of(typeNode.asText());
}
/**
* @param propertyName name of a property in the JSON LD schema
* @return the vocab definition
*/
public Optional getVocab(String propertyName) {
JsonNode propertyNode = contexts.get(propertyName);
if (Objects.isNull(propertyNode)) {
return Optional.empty();
}
JsonNode contextNode = propertyNode.get("@context");
if (Objects.isNull(contextNode)) {
return Optional.empty();
}
JsonNode vocabNode = contextNode.get("@vocab");
return Objects.isNull(vocabNode) ? Optional.empty() : Optional.of(vocabNode.asText());
}
/**
* @param fieldName name of a JSON field / property
* @return the SPDX model property descriptor for the JSON property
*/
public Optional getPropertyDescriptor(String fieldName) {
String propName = REVERSE_JAVA_WORDS.getOrDefault(fieldName, fieldName);
JsonNode propertyNode = contexts.get(propName);
if (Objects.isNull(propertyNode)) {
return Optional.empty();
}
JsonNode idNode = propertyNode.get("@id");
if (Objects.isNull(idNode)) {
return Optional.empty();
}
String propertyUri = idNode.asText();
String namespace = propertyUri.substring(0, propertyUri.lastIndexOf('/')+1);
String name = propertyUri.substring(propertyUri.lastIndexOf('/')+1);
return Optional.of(new PropertyDescriptor(name, namespace));
}
/**
* @param propertyName property name
* @return the SHACL statement from the model if it exists
*/
private Optional getPropertyShacl(String propertyName) {
JsonNode propertytNode = contexts.get(propertyName);
if (Objects.isNull(propertytNode)) {
return Optional.empty();
}
JsonNode idNode = propertytNode.get("@id");
if (Objects.isNull(idNode)) {
return Optional.empty();
}
return Optional.ofNullable(modelStatements.get(idNode.asText()));
}
/**
* @param propertyName Name of the property
* @return true if the value associated with the property is an ID representing an SPDX Object
*/
public boolean isSpdxObject(String propertyName) {
if (isEnum(propertyName)) {
return false;
}
Optional shacl = getPropertyShacl(propertyName);
if (!shacl.isPresent()) {
return false;
}
boolean objectType = false;
JsonNode types = shacl.get().get("@type");
if (Objects.isNull(types)) {
return false;
}
for (Iterator iter = types.iterator(); iter.hasNext();) {
if (OBJECT_TYPE.equals(iter.next().asText())) {
objectType = true;
}
}
return objectType;
}
/**
* @param propertyName Name of the property
* @param propertyValue property value
* @return true if the propertyValue represents an Individual from the vocabulary
*/
public boolean isIndividual(String propertyName, String propertyValue) {
if (!isSpdxObject(propertyName)) {
return false;
}
String id;
JsonNode context = contexts.get(propertyValue);
if (Objects.nonNull(context) && context.has("@id")) {
id = context.get("@id").asText();
} else {
id = propertyValue;
}
JsonNode shacl = modelStatements.get(id);
if (Objects.isNull(shacl)) {
return false;
}
JsonNode types = shacl.get("@type");
for (Iterator iter = types.elements(); iter.hasNext(); ) {
if (INDIVIDUAL_TYPE.equals(iter.next().asText())) {
return true;
}
}
return false;
}
/**
* @param propertyName Name of the property
* @return true if the propertyValue represents an enumeration
*/
public boolean isEnum(String propertyName) {
Optional type = getPropertyType(propertyName);
if (!type.isPresent()) {
return false;
}
if (!"@vocab".equals(type.get())) {
return false;
}
Optional vocab = getVocab(propertyName);
return vocab.isPresent() && !vocab.get().endsWith("terms/Core/Element/");
}
}