diff --git a/app/src/test/java/io/apicurio/registry/noprofile/serde/JsonSchemaSerdeTest.java b/app/src/test/java/io/apicurio/registry/noprofile/serde/JsonSchemaSerdeTest.java index 3213155a4a..d83e7cad83 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/serde/JsonSchemaSerdeTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/serde/JsonSchemaSerdeTest.java @@ -134,6 +134,54 @@ public void testJsonSchemaSerde() throws Exception { } } + @Test + public void testJsonSchemaSerdeAutoRegister() throws Exception { + String groupId = TestUtils.generateGroupId(); + String artifactId = generateArtifactId(); + + Person person = new Person("Carles", "Arnal", 30); + + try (JsonSchemaKafkaSerializer serializer = new JsonSchemaKafkaSerializer<>(restClient, true); + Deserializer deserializer = new JsonSchemaKafkaDeserializer<>(restClient, true)) { + + Map config = new HashMap<>(); + config.put(SerdeConfig.EXPLICIT_ARTIFACT_GROUP_ID, groupId); + config.put(SerdeConfig.ARTIFACT_RESOLVER_STRATEGY, SimpleTopicIdStrategy.class.getName()); + config.put(SerdeConfig.SCHEMA_LOCATION, "/io/apicurio/registry/util/json-schema.json"); + config.put(SerdeConfig.AUTO_REGISTER_ARTIFACT, true); + serializer.configure(config, false); + + deserializer.configure(Collections.emptyMap(), false); + + Headers headers = new RecordHeaders(); + byte[] bytes = serializer.serialize(artifactId, headers, person); + + person = deserializer.deserialize(artifactId, headers, bytes); + + Assertions.assertEquals("Carles", person.getFirstName()); + Assertions.assertEquals("Arnal", person.getLastName()); + Assertions.assertEquals(30, person.getAge()); + + person.setAge(-1); + + try { + serializer.serialize(artifactId, new RecordHeaders(), person); + Assertions.fail(); + } catch (Exception ignored) { + } + + serializer.setValidationEnabled(false); // disable validation + // create invalid person bytes + bytes = serializer.serialize(artifactId, headers, person); + + try { + deserializer.deserialize(artifactId, headers, bytes); + Assertions.fail(); + } catch (Exception ignored) { + } + } + } + @Test public void testJsonSchemaSerdeHeaders() throws Exception { InputStream jsonSchema = getClass().getResourceAsStream("/io/apicurio/registry/util/json-schema.json"); diff --git a/schema-resolver/src/main/java/io/apicurio/registry/resolver/DefaultSchemaResolver.java b/schema-resolver/src/main/java/io/apicurio/registry/resolver/DefaultSchemaResolver.java index 3f78be22e2..6f73950b1b 100644 --- a/schema-resolver/src/main/java/io/apicurio/registry/resolver/DefaultSchemaResolver.java +++ b/schema-resolver/src/main/java/io/apicurio/registry/resolver/DefaultSchemaResolver.java @@ -106,16 +106,23 @@ private Optional> getSchemaFromCache(ArtifactReference art private SchemaLookupResult getSchemaFromRegistry(ParsedSchema parsedSchema, Record data, ArtifactReference artifactReference) { - if (autoCreateArtifact && schemaParser.supportsExtractSchemaFromData()) { - if (parsedSchema == null) { - parsedSchema = schemaParser.getSchemaFromData(data, dereference); - } - - if (parsedSchema.hasReferences()) { - //List of references lookup, to be used to create the references for the artifact - final List> schemaLookupResults = handleArtifactReferences(data, parsedSchema); - return handleAutoCreateArtifact(parsedSchema, artifactReference, schemaLookupResults); - } else { + if (autoCreateArtifact) { + + if (schemaParser.supportsExtractSchemaFromData()) { + + if (parsedSchema == null) { + parsedSchema = schemaParser.getSchemaFromData(data, dereference); + } + + if (parsedSchema.hasReferences()) { + //List of references lookup, to be used to create the references for the artifact + final List> schemaLookupResults = handleArtifactReferences(data, parsedSchema); + return handleAutoCreateArtifact(parsedSchema, artifactReference, schemaLookupResults); + } else { + return handleAutoCreateArtifact(parsedSchema, artifactReference); + } + } else if (config.getExplicitSchemaLocation() != null && schemaParser.supportsGetSchemaFromLocation()) { + parsedSchema = schemaParser.getSchemaFromLocation(config.getExplicitSchemaLocation()); return handleAutoCreateArtifact(parsedSchema, artifactReference); } } diff --git a/schema-resolver/src/main/java/io/apicurio/registry/resolver/SchemaParser.java b/schema-resolver/src/main/java/io/apicurio/registry/resolver/SchemaParser.java index b618738eb6..b10b3ca845 100644 --- a/schema-resolver/src/main/java/io/apicurio/registry/resolver/SchemaParser.java +++ b/schema-resolver/src/main/java/io/apicurio/registry/resolver/SchemaParser.java @@ -50,10 +50,27 @@ public interface SchemaParser { */ ParsedSchema getSchemaFromData(Record data, boolean dereference); + /** + * In some artifact types, such as Json, we allow defining a local place for the schema. + * + * @param location the schema location + * @return the ParsedSchema, containing both the raw schema (bytes) and the parsed schema. Can be null. + */ + default ParsedSchema getSchemaFromLocation(String location) { + return null; + } + /** * Flag that indicates if {@link SchemaParser#getSchemaFromData(Record)} is implemented or not. */ default boolean supportsExtractSchemaFromData() { return true; } + + /** + * Flag that indicates if {@link SchemaParser#getSchemaFromLocation(String)} is implemented or not. + */ + default boolean supportsGetSchemaFromLocation() { + return false; + } } diff --git a/schema-resolver/src/main/java/io/apicurio/registry/resolver/SchemaResolverConfig.java b/schema-resolver/src/main/java/io/apicurio/registry/resolver/SchemaResolverConfig.java index eae1e76f83..0bd769c9cc 100644 --- a/schema-resolver/src/main/java/io/apicurio/registry/resolver/SchemaResolverConfig.java +++ b/schema-resolver/src/main/java/io/apicurio/registry/resolver/SchemaResolverConfig.java @@ -93,6 +93,12 @@ public class SchemaResolverConfig { */ public static final String EXPLICIT_ARTIFACT_ID = "apicurio.registry.artifact.artifact-id"; + /** + * Only applicable for serializers + * Optional, set explicitly the schema location in the classpath for the schema to be used for serializing the data. + */ + public static final String SCHEMA_LOCATION = "apicurio.registry.artifact.schema.location"; + /** * Only applicable for serializers * Optional, set explicitly the version used for querying/creating an artifact. diff --git a/schema-resolver/src/main/java/io/apicurio/registry/resolver/config/DefaultSchemaResolverConfig.java b/schema-resolver/src/main/java/io/apicurio/registry/resolver/config/DefaultSchemaResolverConfig.java index 66f3ee5ba3..6f334b6136 100644 --- a/schema-resolver/src/main/java/io/apicurio/registry/resolver/config/DefaultSchemaResolverConfig.java +++ b/schema-resolver/src/main/java/io/apicurio/registry/resolver/config/DefaultSchemaResolverConfig.java @@ -132,6 +132,10 @@ public String getExplicitArtifactId() { return getString(EXPLICIT_ARTIFACT_ID); } + public String getExplicitSchemaLocation() { + return getString(SCHEMA_LOCATION); + } + public String getExplicitArtifactVersion() { return getString(EXPLICIT_ARTIFACT_VERSION); } diff --git a/serdes/jsonschema-serde/src/main/java/io/apicurio/registry/serde/jsonschema/JsonSchemaKafkaSerializer.java b/serdes/jsonschema-serde/src/main/java/io/apicurio/registry/serde/jsonschema/JsonSchemaKafkaSerializer.java index 0006d4199a..ef95c09689 100644 --- a/serdes/jsonschema-serde/src/main/java/io/apicurio/registry/serde/jsonschema/JsonSchemaKafkaSerializer.java +++ b/serdes/jsonschema-serde/src/main/java/io/apicurio/registry/serde/jsonschema/JsonSchemaKafkaSerializer.java @@ -124,7 +124,6 @@ public SchemaParser schemaParser() { */ @Override protected void serializeData(ParsedSchema schema, T data, OutputStream out) throws IOException { - //TODO add property to specify a jsonschema to allow for auto-register json schemas serializeData(null, schema, data, out); } diff --git a/serdes/jsonschema-serde/src/main/java/io/apicurio/registry/serde/jsonschema/JsonSchemaKafkaSerializerConfig.java b/serdes/jsonschema-serde/src/main/java/io/apicurio/registry/serde/jsonschema/JsonSchemaKafkaSerializerConfig.java index dcde2cd221..e274d20d5d 100644 --- a/serdes/jsonschema-serde/src/main/java/io/apicurio/registry/serde/jsonschema/JsonSchemaKafkaSerializerConfig.java +++ b/serdes/jsonschema-serde/src/main/java/io/apicurio/registry/serde/jsonschema/JsonSchemaKafkaSerializerConfig.java @@ -32,9 +32,8 @@ public class JsonSchemaKafkaSerializerConfig extends BaseKafkaSerDeConfig { private static ConfigDef configDef() { - ConfigDef configDef = new ConfigDef() + return new ConfigDef() .define(VALIDATION_ENABLED, Type.BOOLEAN, VALIDATION_ENABLED_DEFAULT, Importance.MEDIUM, "Whether to validate the data against the json schema"); - return configDef; } /** @@ -49,5 +48,4 @@ public JsonSchemaKafkaSerializerConfig(Map originals) { public boolean validationEnabled() { return this.getBoolean(VALIDATION_ENABLED); } - } diff --git a/serdes/jsonschema-serde/src/main/java/io/apicurio/registry/serde/jsonschema/JsonSchemaParser.java b/serdes/jsonschema-serde/src/main/java/io/apicurio/registry/serde/jsonschema/JsonSchemaParser.java index 14d68518c3..c8fc57c1be 100644 --- a/serdes/jsonschema-serde/src/main/java/io/apicurio/registry/serde/jsonschema/JsonSchemaParser.java +++ b/serdes/jsonschema-serde/src/main/java/io/apicurio/registry/serde/jsonschema/JsonSchemaParser.java @@ -17,6 +17,7 @@ package io.apicurio.registry.serde.jsonschema; import io.apicurio.registry.resolver.ParsedSchema; +import io.apicurio.registry.resolver.ParsedSchemaImpl; import io.apicurio.registry.resolver.SchemaParser; import io.apicurio.registry.resolver.data.Record; import io.apicurio.registry.types.ArtifactType; @@ -44,11 +45,6 @@ public JsonSchema parseSchema(byte[] rawSchema, Map e.getValue().getParsedSchema())), 0); } - //TODO we could implement some way of providing the jsonschema beforehand: - // - via annotation in the object being serialized - // - via config property - //if we do this users will be able to automatically registering the schema when using this serde - /** * @see io.apicurio.registry.resolver.SchemaParser#getSchemaFromData(java.lang.Object) */ @@ -64,8 +60,22 @@ public ParsedSchema getSchemaFromData(Record data, boolean derefe return null; } + @Override + public ParsedSchema getSchemaFromLocation(String location) { + String rawSchema = IoUtil.toString(Thread.currentThread().getContextClassLoader().getResourceAsStream(location)); + + return new ParsedSchemaImpl() + .setParsedSchema(new JsonSchema(rawSchema)) + .setRawSchema(rawSchema.getBytes()); + } + @Override public boolean supportsExtractSchemaFromData() { return false; } + + @Override + public boolean supportsGetSchemaFromLocation() { + return true; + } } diff --git a/serdes/serde-common/src/main/java/io/apicurio/registry/serde/SerdeConfig.java b/serdes/serde-common/src/main/java/io/apicurio/registry/serde/SerdeConfig.java index befcff59c6..745aa9b938 100644 --- a/serdes/serde-common/src/main/java/io/apicurio/registry/serde/SerdeConfig.java +++ b/serdes/serde-common/src/main/java/io/apicurio/registry/serde/SerdeConfig.java @@ -94,6 +94,12 @@ public class SerdeConfig { */ public static final String EXPLICIT_ARTIFACT_ID = SchemaResolverConfig.EXPLICIT_ARTIFACT_ID; + /** + * Only applicable for serializers + * Optional, set explicitly the schema used for serialization. + */ + public static final String SCHEMA_LOCATION = SchemaResolverConfig.SCHEMA_LOCATION; + /** * Only applicable for serializers * Optional, set explicitly the version used for querying/creating an artifact.