All Downloads are FREE. Search and download functionalities are using the official Maven repository.

step.core.yaml.schema.YamlJsonSchemaHelper Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (C) 2020, exense GmbH
 *
 * This file is part of STEP
 *
 * STEP is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * STEP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with STEP.  If not, see .
 ******************************************************************************/
package step.core.yaml.schema;

import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.spi.JsonProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import step.core.ReflectionUtils;
import step.core.yaml.YamlFields;
import step.handlers.javahandler.jsonschema.*;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

public class YamlJsonSchemaHelper {

	private static final Logger log = LoggerFactory.getLogger(YamlJsonSchemaHelper.class);

	private static final String DYNAMIC_EXPRESSION_DEF = "DynamicExpressionDef";

	public static final String SMART_DYNAMIC_VALUE_STRING_DEF = "SmartDynamicValueStringDef";
	public static final String SMART_DYNAMIC_VALUE_NUM_DEF = "SmartDynamicValueNumDef";
	public static final String SMART_DYNAMIC_VALUE_BOOLEAN_DEF = "SmartDynamicValueBooleanDef";

	public static final String DYNAMIC_KEYWORD_INPUTS_DEF = "DynamicKeywordInputsDef";
	public static final String PLAN_DEF = "PlanDef";
	public static final String COMPOSITE_PLAN_DEF = "CompositePlanDef";
	public static final String DEFS_PREFIX = "#/$defs/";
	private final JsonProvider jsonProvider;

	public YamlJsonSchemaHelper(JsonProvider jsonProvider) {
		this.jsonProvider = jsonProvider;
	}

	public Map createDynamicValueImplDefs() {
		Map res = new HashMap<>();
		res.put(DYNAMIC_EXPRESSION_DEF, createDynamicValueDef());
		res.put(SMART_DYNAMIC_VALUE_STRING_DEF, createSmartDynamicValueDef("string"));
		res.put(SMART_DYNAMIC_VALUE_NUM_DEF, createSmartDynamicValueDef("number"));
		res.put(SMART_DYNAMIC_VALUE_BOOLEAN_DEF, createSmartDynamicValueDef("boolean"));
		res.put(DYNAMIC_KEYWORD_INPUTS_DEF, createDynamicKeywordInputsDef());
		return res;
	}

	/**
	 * Prepares the json schema for class.
	 * @return the following structure:
	 * 
{@code
	 * {
	 *   "type": "object",
	 *   "properties": {
	 *     fields extracted from class via reflection
	 *   },
	 *   "additionalProperties": false,
	 *   "required": [...]
	 * }
	 *}
*/ public JsonObjectBuilder createJsonSchemaForClass(JsonSchemaCreator jsonSchemaCreator, Class clazz, boolean additionalProperties) throws JsonSchemaPreparationException { JsonObjectBuilder res = jsonProvider.createObjectBuilder(); res.add("type", "object"); JsonObjectBuilder propertiesBuilder = jsonProvider.createObjectBuilder(); List requiredProperties = new ArrayList<>(); extractPropertiesFromClass(jsonSchemaCreator, clazz, propertiesBuilder, requiredProperties, null); res.add("properties", propertiesBuilder); if(!additionalProperties) { res.add("additionalProperties", additionalProperties); } addRequiredProperties(requiredProperties, res); return res; } /** * Analyzes the class hierarchy and writes all applicable fields to the json schema (output) */ public void extractPropertiesFromClass(JsonSchemaCreator jsonSchemaCreator, Class clazz, JsonObjectBuilder output, List requiredPropertiesOutput, Class untilParentClassIs) throws JsonSchemaPreparationException { log.info("Preparing json schema for class {}...", clazz); // analyze the class hierarchy List allFieldsInHierarchy = ReflectionUtils.getAllFieldsInHierarchy(clazz, untilParentClassIs); // for each field we want either build the json schema via reflection // or use some predefined schemas for some special classes (like step.core.dynamicbeans.DynamicValue) try { jsonSchemaCreator.processFields(clazz, output, allFieldsInHierarchy, requiredPropertiesOutput); } catch (Exception ex) { throw new JsonSchemaPreparationException("Unable to prepare json schema for " + clazz, ex); } } public void addRequiredProperties(List requiredProperties, JsonObjectBuilder propertiesBuilder) { if (requiredProperties != null && !requiredProperties.isEmpty()) { JsonArrayBuilder requiredBuilder = jsonProvider.createArrayBuilder(); for (String requiredProperty : requiredProperties) { requiredBuilder.add(requiredProperty); } propertiesBuilder.add("required", requiredBuilder); } } /** *
{@code
	 * {
	 *   "type": "array",
	 *   "items": {
	 *     "type": "object",
	 *     "patternProperties": {
	 *       ".*": {
	 *         "anyOf": [
	 *           {
	 *             "type": "number"
	 *           },
	 *           {
	 *             "type": "boolean"
	 *           },
	 *           {
	 *             "type": "string"
	 *           },
	 *           {
	 *             "type": "object"
	 *           },
	 *           {
	 *             "$ref": "#/$defs/DynamicExpressionDef"
	 *           }
	 *         ]
	 *       }
	 *     },
	 *     "additionalProperties": false
	 *   }
	 * }
	 *}
*/ private JsonObjectBuilder createDynamicKeywordInputsDef(){ JsonObjectBuilder res = jsonProvider.createObjectBuilder(); res.add("type", "array"); JsonObjectBuilder arrayItemDef = createPatternPropertiesWithDynamicValues(); res.add("items", arrayItemDef); return res; } public JsonObjectBuilder createPatternPropertiesWithDynamicValues() { JsonObjectBuilder arrayItemDef = jsonProvider.createObjectBuilder(); arrayItemDef.add("type", "object"); JsonArrayBuilder anyOfArray = jsonProvider.createArrayBuilder() .add(jsonProvider.createObjectBuilder().add("type", "number")) .add(jsonProvider.createObjectBuilder().add("type", "boolean")) .add(jsonProvider.createObjectBuilder().add("type", "string")) .add(jsonProvider.createObjectBuilder().add("type", "object")) .add(jsonProvider.createObjectBuilder().add("type", "array")) .add(addRef(jsonProvider.createObjectBuilder(), YamlJsonSchemaHelper.DYNAMIC_EXPRESSION_DEF)) .add(jsonProvider.createObjectBuilder().add("type", "null")); JsonObjectBuilder properties = jsonProvider.createObjectBuilder() .add(".*", jsonProvider.createObjectBuilder().add("anyOf", anyOfArray)); arrayItemDef.add("patternProperties", properties); arrayItemDef.add("additionalProperties", false); return arrayItemDef; } private JsonObjectBuilder createDynamicValueDef() { JsonObjectBuilder res = jsonProvider.createObjectBuilder(); res.add("type", "object"); JsonObjectBuilder properties = jsonProvider.createObjectBuilder(); properties.add(YamlFields.DYN_VALUE_EXPRESSION_FIELD, jsonProvider.createObjectBuilder().add("type", "string")); res.add("properties", properties); res.add("additionalProperties", false); return res; } private JsonObjectBuilder createSmartDynamicValueDef(String smartValueType) { JsonObjectBuilder res = jsonProvider.createObjectBuilder(); JsonArrayBuilder oneOfArray = jsonProvider.createArrayBuilder(); oneOfArray.add(jsonProvider.createObjectBuilder().add("type", smartValueType)); oneOfArray.add(addRef(jsonProvider.createObjectBuilder(), YamlJsonSchemaHelper.DYNAMIC_EXPRESSION_DEF)); res.add("oneOf", oneOfArray); return res; } /** * Uses a reference to one of dynamic value definitions (depending on generic type of field) */ public void applyDynamicValueDefForField(Field field, FieldMetadata fieldMetadata, JsonObjectBuilder propertiesBuilder){ // for dynamic value we need to reference the definition prepared above // the definition depends on generic type used in dynamic value (string, integer, boolean, etc) Type genericType = field.getGenericType(); if (genericType instanceof ParameterizedType) { Type[] arguments = ((ParameterizedType) genericType).getActualTypeArguments(); Type dynamicValueClass = arguments[0]; String dynamicValueType; if (!(dynamicValueClass instanceof Class)) { // for undefined type or for generic parameter types like "?" dynamicValueType = "object"; } else { dynamicValueType = JsonInputConverter.resolveJsonPropertyType((Class) dynamicValueClass); } String defaultValue = fieldMetadata.getDefaultValue(); if (defaultValue != null) { propertiesBuilder.add("default", defaultValue); } switch (dynamicValueType){ case "string": addRef(propertiesBuilder, SMART_DYNAMIC_VALUE_STRING_DEF); break; case "boolean": addRef(propertiesBuilder, SMART_DYNAMIC_VALUE_BOOLEAN_DEF); break; case "number": addRef(propertiesBuilder, SMART_DYNAMIC_VALUE_NUM_DEF); break; case "object": log.warn("Unknown dynamic value type for field " + field.getName()); addRef(propertiesBuilder, SMART_DYNAMIC_VALUE_STRING_DEF); break; default: throw new IllegalArgumentException("Unsupported dynamic value type: " + dynamicValueType); } } else { throw new IllegalArgumentException("Unsupported dynamic value generic field " + genericType); } } /** * Prepares the json schema for named entity (i.e. there is the top-level node with class name and all nested * fields are written to nested fields) * * @param yamlName the top-level node name (entity name) * @return the following structure: *
{@code
	 * {
	 *   "type" : "object",
	 *   "properties" : {
	 *     "${yamlName}" : {
	 *       "type" : "object",
	 *       "properties" : {
	 *           fields extracted from class via reflection
	 *       },
	 *       "required": [...],
	 *       "additionalProperties": false
	 *     }
	 *   },
	 *   "additionalProperties" : false
	 * }
	 *}
*/ public JsonObjectBuilder createNamedObjectImplDef(String yamlName, Class clazz, JsonSchemaCreator jsonSchemaCreator, boolean additionalProperties) throws JsonSchemaPreparationException { JsonObjectBuilder res = jsonProvider.createObjectBuilder(); res.add("type", "object"); // on the top level there is a name only JsonObjectBuilder schemaBuilder = jsonProvider.createObjectBuilder(); // other properties are located in nested object and automatically prepared via reflection JsonObjectBuilder propertiesBuilder = createJsonSchemaForClass(jsonSchemaCreator, clazz, additionalProperties); schemaBuilder.add(yamlName, propertiesBuilder); res.add("properties", schemaBuilder); res.add("additionalProperties", false); return res; } public static JsonObjectBuilder addRef(JsonObjectBuilder builder, String refValue){ return builder.add("$ref", DEFS_PREFIX + refValue); } public static List prepareDefaultFieldProcessors(List additionalProcessors) { List result = new ArrayList<>(); // -- FILTERED FIELDS result.add(new CommonFilteredFieldProcessor()); // -- CUSTOM EXTENSIONS if (additionalProcessors != null) { result.addAll(additionalProcessors); } // -- DEFAULT LOGIC result.add(new DynamicValueFieldProcessor()); result.add(new EnumFieldProcessor()); return result; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy