001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.schemagen.json; 007 008import com.fasterxml.jackson.databind.node.ObjectNode; 009 010import gov.nist.secauto.metaschema.core.model.IDefinition; 011import gov.nist.secauto.metaschema.core.util.ObjectUtils; 012import gov.nist.secauto.metaschema.schemagen.SchemaGenerationException; 013import gov.nist.secauto.metaschema.schemagen.json.impl.AbstractDefinitionJsonSchema.SimpleKey; 014import gov.nist.secauto.metaschema.schemagen.json.impl.AbstractModelDefinitionJsonSchema.ComplexKey; 015 016import java.util.Comparator; 017 018import edu.umd.cs.findbugs.annotations.NonNull; 019import edu.umd.cs.findbugs.annotations.Nullable; 020 021/** 022 * Represents a JSON schema that is a global definition or an inline schema. 023 * <p> 024 * A schema of this type will be a global definition if 025 * {@link #isInline(IJsonGenerationState)} is {@code false}. 026 */ 027public interface IDefineableJsonSchema { 028 029 /** 030 * Determine if the schema is defined inline or as a global definition. 031 * 032 * @param state 033 * the schema generation state used for context 034 * @return {@code true} if the schema is to be defined inline or {@code false} 035 * if the schema is to be defined globally 036 */ 037 boolean isInline(@NonNull IJsonGenerationState state); 038 039 default void generateSchemaOrRef( 040 @NonNull ObjectNode obj, 041 @NonNull IJsonGenerationState state) { 042 if (isInline(state)) { 043 generateInlineSchema(obj, state); 044 } else { 045 generateRef(obj, state); 046 } 047 } 048 049 /** 050 * Generate the schema within the provided JSON object node. 051 * 052 * @param obj 053 * the JSON object to populate 054 * @param state 055 * the schema generation state used for context and writing 056 * @throws SchemaGenerationException 057 * if an error occurred while writing the type 058 */ 059 void generateInlineSchema( 060 @NonNull ObjectNode obj, 061 @NonNull IJsonGenerationState state); 062 063 /** 064 * Get the definition's name. 065 * 066 * @param state 067 * the schema generation state used for context and writing 068 * @return the definition name 069 * @throws IllegalStateException 070 * if the JSON schema object is not a definition 071 */ 072 @NonNull 073 String getDefinitionName(@NonNull IJsonGenerationState state); 074 075 /** 076 * Get the definition's reference URI. 077 * 078 * @param state 079 * the schema generation state used for context and writing 080 * @return the definition's reference URI 081 * @throws IllegalStateException 082 * if the JSON schema object is not a definition 083 */ 084 default String getDefinitionRef(@NonNull IJsonGenerationState state) { 085 return ObjectUtils.notNull(new StringBuilder() 086 .append("#/definitions/") 087 .append(getDefinitionName(state)) 088 .toString()); 089 } 090 091 /** 092 * Generate a reference to a globally defined schema, within the provided JSON 093 * object node. 094 * 095 * @param obj 096 * the JSON object to populate 097 * @param state 098 * the schema generation state used for context and writing 099 * @throws SchemaGenerationException 100 * if an error occurred while writing the type 101 */ 102 default void generateRef( 103 @NonNull ObjectNode obj, 104 @NonNull IJsonGenerationState state) { 105 obj.put("$ref", getDefinitionRef(state)); 106 } 107 108 // /** 109 // * Determine if the JSON schema object is a JSON definition. 110 // * 111 // * @param state 112 // * the schema generation state used for context and writing 113 // * @return {@code true} if the SON schema object is a definition or 114 // * {@code false} otherwise 115 // */ 116 // default boolean isDefinition(@NonNull IJsonGenerationState state) { 117 // return !isInline(state); 118 // } 119 120 // REFACTOR: move to abstract implementation? 121 default void generateDefinition(@NonNull IJsonGenerationState state, @NonNull ObjectNode definitionsObject) { 122 123 // create the definition property 124 ObjectNode definitionObj = ObjectUtils.notNull( 125 definitionsObject.putObject(getDefinitionName(state))); 126 127 // Add identifier, see usnistgov/metaschema#160 128 definitionObj.put("$id", getDefinitionRef(state)); 129 130 // generate the definition object contents 131 generateInlineSchema(definitionObj, state); 132 } 133 134 @SuppressWarnings("PMD.ShortClassName") 135 interface IKey extends Comparable<IKey> { 136 Comparator<IKey> KEY_COMPARATOR = Comparator 137 .<IKey, String>comparing(key -> key.getDefinition().getContainingModule().getShortName()) 138 .thenComparing(key -> key.getDefinition().getEffectiveName()) 139 .thenComparing(IKey::getJsonKeyFlagName, Comparator.nullsFirst(Comparator.naturalOrder())) 140 .thenComparing(IKey::getDiscriminatorProperty, Comparator.nullsFirst(Comparator.naturalOrder())) 141 .thenComparing(IKey::getDiscriminatorValue, Comparator.nullsFirst(Comparator.naturalOrder())); 142 143 @SuppressWarnings("PMD.ShortMethodName") 144 @NonNull 145 static IKey of(@NonNull IDefinition definition) { 146 return new SimpleKey(definition); 147 } 148 149 @SuppressWarnings("PMD.ShortMethodName") 150 @NonNull 151 static IKey of( 152 @NonNull IDefinition definition, 153 @Nullable String jsonKeyFlagName, 154 @Nullable String discriminatorProperty, 155 @Nullable String discriminatorValue) { 156 IKey retval; 157 if (jsonKeyFlagName != null || discriminatorProperty != null || discriminatorValue != null) { 158 retval = new ComplexKey(definition, jsonKeyFlagName, discriminatorProperty, discriminatorValue); 159 } else { 160 retval = of(definition); 161 } 162 return retval; 163 } 164 165 @NonNull 166 IDefinition getDefinition(); 167 168 @Nullable 169 String getJsonKeyFlagName(); 170 171 @Nullable 172 String getDiscriminatorProperty(); 173 174 @Nullable 175 String getDiscriminatorValue(); 176 177 @Override 178 default int compareTo(IKey other) { 179 return KEY_COMPARATOR.compare(this, other); 180 } 181 } 182}