Add Avro serialization and schema management support

- Add schema server implementation
- Add schema client abstraction
- Add schema client implementation for own schema registry server
- Add schema client supporting Confluent schema registry
- Add Avro-based message converter supporting a static schema resource
- Add Avro-based message converter with schema evolution support, via
  schema registry client.
- On serialization, the converter register writer schemas with the schema
  registry server and augment the content type of outbound message with
  schema information.
  On deserialization, the reading converter will fetch the schema from the server
  if not available locally.

Use class information if schema is not specified

In the case of SpecificRecord and Reflective readers/writers, the class information can be used instead

Make subtype prefix configurable and shorten the subject

- Subtype prefix is now configurable and subject is the lowercase schema name
- Enhance/correct javadoc

Refine AbstractAvroMessageConverter

- distinguish between writer and reader schema when reader is created

Add schema registry and schema registry client docs
This commit is contained in:
Vinicius Carvalho
2016-07-28 18:33:53 -04:00
committed by Marius Bogoevici
parent 8dd22ebca0
commit 4422b21438
50 changed files with 3261 additions and 32 deletions

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.cloud.stream.schema.server.config.SchemaServerConfiguration;
import org.springframework.context.annotation.Import;
/**
* Enables the schema registry server enpoints.
*
* @author Vinicius Carvalho
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(SchemaServerConfiguration.class)
public @interface EnableSchemaRegistryServer {
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Vinicius Carvalho
*/
@SpringBootApplication
@EnableSchemaRegistryServer
public class SchemaRegistryServerApplication {
public static void main(String[] args) {
SpringApplication.run(SchemaRegistryServerApplication.class, args);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server.config;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.stream.schema.server.controllers.ServerController;
import org.springframework.cloud.stream.schema.server.repository.SchemaRepository;
import org.springframework.cloud.stream.schema.server.support.AvroSchemaValidator;
import org.springframework.cloud.stream.schema.server.support.SchemaValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
/**
* @author Vinicius Carvalho
*/
@Configuration
@EnableJpaRepositories(basePackageClasses = SchemaRepository.class)
@EnableConfigurationProperties(SchemaServerProperties.class)
public class SchemaServerConfiguration {
@Bean
public ServerController serverController(SchemaRepository repository) {
return new ServerController(repository, schemaValidators());
}
@Bean
public Map<String, SchemaValidator> schemaValidators() {
Map<String, SchemaValidator> validatorMap = new HashMap<>();
validatorMap.put("avro", new AvroSchemaValidator());
return validatorMap;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Vinicius Carvalho
*/
@ConfigurationProperties("spring.cloud.stream.schema.server")
public class SchemaServerProperties {
/**
* Prefix for configuration resource paths (default is empty). Useful when embedding
* in another application when you don't want to change the context path or servlet
* path.
*/
private String path;
public String getPath() {
return this.path;
}
public void setPath(String path) {
this.path = path;
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server.controllers;
import java.util.List;
import java.util.Map;
import org.springframework.cloud.stream.schema.server.model.Schema;
import org.springframework.cloud.stream.schema.server.repository.SchemaRepository;
import org.springframework.cloud.stream.schema.server.support.InvalidSchemaException;
import org.springframework.cloud.stream.schema.server.support.SchemaNotFoundException;
import org.springframework.cloud.stream.schema.server.support.SchemaValidator;
import org.springframework.cloud.stream.schema.server.support.UnsupportedFormatException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
/**
* @author Vinicius Carvalho
*/
@RestController
@RequestMapping(path = "${spring.cloud.stream.schema.server.prefix:}")
public class ServerController {
private final SchemaRepository repository;
private final Map<String, SchemaValidator> validators;
public ServerController(SchemaRepository repository,
Map<String, SchemaValidator> validators) {
Assert.notNull(repository, "cannot be null");
Assert.notEmpty(validators, "cannot be empty");
this.repository = repository;
this.validators = validators;
}
@RequestMapping(method = RequestMethod.POST, path = "/", consumes = "application/json", produces = "application/json")
public synchronized ResponseEntity<Schema> register(@RequestBody Schema schema,
UriComponentsBuilder builder) {
SchemaValidator validator = this.validators.get(schema.getFormat());
if (validator == null) {
throw new UnsupportedFormatException(String.format(
"Invalid format, supported types are: %s",
StringUtils.collectionToCommaDelimitedString(this.validators.keySet())));
}
if (!validator.isValid(schema.getDefinition())) {
throw new InvalidSchemaException("Invalid schema");
}
Schema result;
List<Schema> registeredEntities = this.repository.findBySubjectAndFormatOrderByVersion(
schema.getSubject(), schema.getFormat());
if (registeredEntities == null || registeredEntities.size() == 0) {
schema.setVersion(1);
result = this.repository.save(schema);
}
else {
result = validator.match(registeredEntities, schema.getDefinition());
if (result == null) {
schema.setVersion(
registeredEntities.get(registeredEntities.size() - 1).getVersion()
+ 1);
result = this.repository.save(schema);
}
}
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.LOCATION,
builder.path("/{subject}/{format}/v{version}")
.buildAndExpand(result.getSubject(), result.getFormat(),
result.getVersion())
.toString());
ResponseEntity<Schema> response = new ResponseEntity<>(result, headers,
HttpStatus.CREATED);
return response;
}
@RequestMapping(method = RequestMethod.GET, produces = "application/json", path = "/{subject}/{format}/v{version}")
public ResponseEntity<Schema> findOne(@PathVariable("subject") String subject,
@PathVariable("format") String format,
@PathVariable("version") Integer version) {
Schema schema = this.repository.findOneBySubjectAndFormatAndVersion(subject, format,
version);
if (schema == null) {
throw new SchemaNotFoundException("Could not find Schema");
}
return new ResponseEntity<>(schema, HttpStatus.OK);
}
@RequestMapping(method = RequestMethod.GET, produces = "application/json", path = "/schemas/{id}")
public ResponseEntity<Schema> findOne(@PathVariable("id") Integer id) {
Schema schema = this.repository.findOne(id);
if (schema == null) {
throw new SchemaNotFoundException("Could not find Schema");
}
return new ResponseEntity<>(schema, HttpStatus.OK);
}
@ExceptionHandler(UnsupportedFormatException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Format not supported")
public void unsupportedFormat(UnsupportedFormatException ex) {
}
@ExceptionHandler(InvalidSchemaException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Invalid schema")
public void invalidSchema(InvalidSchemaException ex) {
}
@ExceptionHandler(SchemaNotFoundException.class)
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Schema not found")
public void schemaNotFound(SchemaNotFoundException ex) {
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server.model;
/**
* @author Vinicius Carvalho
*/
public enum Compatibility {
BACKWARD, FORWARD, FULL, INCOMPATIBLE;
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
/**
* @author Vinicius Carvalho
*
* Represents a persisted schema entity.
*/
@Entity
public class Schema {
@Id
@GeneratedValue
@Column(name = "ID")
private Integer id;
@Column(name = "VERSION", nullable = false)
private Integer version;
@Column(name = "SUBJECT", nullable = false)
private String subject;
@Column(name = "FORMAT", nullable = false)
private String format;
@Lob
@Column(name = "DEFINITION", nullable = false, length = 8192)
private String definition;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String getDefinition() {
return definition;
}
public void setDefinition(String definition) {
this.definition = definition;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server.repository;
import java.util.List;
import org.springframework.cloud.stream.schema.server.model.Schema;
import org.springframework.data.repository.PagingAndSortingRepository;
/**
* @author Vinicius Carvalho
*/
public interface SchemaRepository extends PagingAndSortingRepository<Schema, Integer> {
List<Schema> findBySubjectAndFormatOrderByVersion(String subject,
String format);
Schema findOneBySubjectAndFormatAndVersion(String subject, String format,
Integer version);
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server.support;
import java.util.List;
import org.apache.avro.SchemaParseException;
import org.springframework.cloud.stream.schema.server.model.Compatibility;
import org.springframework.cloud.stream.schema.server.model.Schema;
/**
* @author Vinicius Carvalho
*/
public class AvroSchemaValidator implements SchemaValidator {
@Override
public boolean isValid(String definition) {
boolean result = true;
try {
new org.apache.avro.Schema.Parser().parse(definition);
}
catch (SchemaParseException ex) {
result = false;
}
return result;
}
@Override
public Compatibility compatibilityCheck(String source, String other) {
return null;
}
@Override
public Schema match(List<Schema> schemas, String definition) {
Schema result = null;
org.apache.avro.Schema source = new org.apache.avro.Schema.Parser()
.parse(definition);
for (Schema s : schemas) {
org.apache.avro.Schema target = new org.apache.avro.Schema.Parser()
.parse(s.getDefinition());
if (target.equals(source)) {
result = s;
break;
}
}
return result;
}
@Override
public String getFormat() {
return "avro";
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server.support;
/**
* @author Vinicius Carvalho
*/
public class InvalidSchemaException extends RuntimeException {
public InvalidSchemaException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server.support;
/**
* @author Vinicius Carvalho
*/
public class SchemaNotFoundException extends RuntimeException {
public SchemaNotFoundException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server.support;
import java.util.List;
import org.springframework.cloud.stream.schema.server.model.Compatibility;
import org.springframework.cloud.stream.schema.server.model.Schema;
/**
* @author Vinicius Carvalho
*
* Provides utility methods to validate, check compatibility and match schemas of
* different implementations
*/
public interface SchemaValidator {
/**
* Verifies if a definition is a valid schema
* @param definition - The textual representation of the schema file
* @return
*/
boolean isValid(String definition);
/**
* Checks for compatibility between two schemas @see Compatibility class for types
* This method may not be supported for certain formats
* @param source - The textual representation of the schema to tested
* @param other - The textual representation of the other schema to tested
* @return
*/
Compatibility compatibilityCheck(String source, String other);
/**
* Return the Schema that is represented by the definition.
* @param schemas List of schemas to be tested
* @param definition Textual representation of the schema
* @return A full Schema object with identifier and subject properties
*/
Schema match(List<Schema> schemas, String definition);
String getFormat();
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server.support;
/**
* @author Vinicius Carvalho
*/
public class UnsupportedFormatException extends RuntimeException {
public UnsupportedFormatException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,5 @@
spring:
application:
name: SchemaRegistryServer
server:
port: 8990

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.schema.server;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.cloud.stream.schema.server.model.Schema;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Vinicius Carvalho
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class SchemaRegistryServerAvroTests {
final String USER_SCHEMA_V1 = "{\"namespace\": \"example.avro\",\n"
+ " \"type\": \"record\",\n" + " \"name\": \"User\",\n" + " \"fields\": [\n"
+ " {\"name\": \"name\", \"type\": \"string\"},\n"
+ " {\"name\": \"favorite_number\", \"type\": [\"int\", \"null\"]}\n"
+ " ]\n" + "}";
final String USER_SCHEMA_V2 = "{\"namespace\": \"example.avro\",\n"
+ " \"type\": \"record\",\n" + " \"name\": \"User\",\n" + " \"fields\": [\n"
+ " {\"name\": \"name\", \"type\": \"string\"},\n"
+ " {\"name\": \"favorite_number\", \"type\": [\"int\", \"null\"]},\n"
+ " {\"name\": \"favorite_color\", \"type\": [\"string\", \"null\"]}\n"
+ " ]\n" + "}";
@Autowired
private TestRestTemplate client;
@Test
public void testUnsupportedFormat() throws Exception {
Schema schema = new Schema();
schema.setFormat("spring");
schema.setSubject("boot");
ResponseEntity<Schema> response = client.postForEntity("http://localhost:8990/",
schema, Schema.class);
Assert.assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
}
@Test
public void testInvalidSchema() throws Exception {
Schema schema = new Schema();
schema.setFormat("avro");
schema.setSubject("boot");
schema.setDefinition("{}");
ResponseEntity<Schema> response = client.postForEntity("http://localhost:8990/",
schema, Schema.class);
Assert.assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
}
@Test
public void testUserSchemaV1() throws Exception {
Schema schema = new Schema();
schema.setFormat("avro");
schema.setSubject("org.springframework.cloud.stream.schema.User");
schema.setDefinition(USER_SCHEMA_V1);
ResponseEntity<Schema> response = client.postForEntity("http://localhost:8990/",
schema, Schema.class);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
Assert.assertEquals(new Integer(1), response.getBody().getVersion());
List<String> location = response.getHeaders().get(HttpHeaders.LOCATION);
Assert.assertNotNull(location);
ResponseEntity<Schema> persistedSchema = client.getForEntity(location.get(0),
Schema.class);
Assert.assertEquals(response.getBody().getId(),
persistedSchema.getBody().getId());
}
@Test
public void testUserSchemaV2() throws Exception {
Schema schema = new Schema();
schema.setFormat("avro");
schema.setSubject("org.springframework.cloud.stream.schema.User");
schema.setDefinition(USER_SCHEMA_V1);
Schema schema2 = new Schema();
schema2.setFormat("avro");
schema2.setSubject("org.springframework.cloud.stream.schema.User");
schema2.setDefinition(USER_SCHEMA_V2);
ResponseEntity<Schema> response = client.postForEntity("http://localhost:8990/",
schema, Schema.class);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
Assert.assertEquals(new Integer(1), response.getBody().getVersion());
List<String> location = response.getHeaders().get(HttpHeaders.LOCATION);
Assert.assertNotNull(location);
ResponseEntity<Schema> response2 = client.postForEntity("http://localhost:8990/",
schema2, Schema.class);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
Assert.assertEquals(new Integer(2), response2.getBody().getVersion());
List<String> location2 = response2.getHeaders().get(HttpHeaders.LOCATION);
Assert.assertNotNull(location2);
}
@Test
public void testIdempotentRegistration() throws Exception {
Schema schema = new Schema();
schema.setFormat("avro");
schema.setSubject("org.springframework.cloud.stream.schema.User");
schema.setDefinition(USER_SCHEMA_V1);
ResponseEntity<Schema> response = client.postForEntity("http://localhost:8990/",
schema, Schema.class);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
Assert.assertEquals(new Integer(1), response.getBody().getVersion());
List<String> location = response.getHeaders().get(HttpHeaders.LOCATION);
Assert.assertNotNull(location);
ResponseEntity<Schema> response2 = client.postForEntity("http://localhost:8990/",
schema, Schema.class);
Assert.assertEquals(response.getBody().getId(), response2.getBody().getId());
}
@Test
public void testSchemaNotfound() throws Exception {
ResponseEntity<Schema> response = client
.getForEntity("http://localhost:8990/foo/avro/v42", Schema.class);
Assert.assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
}
@TestConfiguration
static class Config {
@Bean
public TestRestTemplate testRestTemplate() {
return new TestRestTemplate();
}
}
}