/**
* Copyright 2014 Confluent Inc.
*
* 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 io.confluent.kafka.schemaregistry.client;
import org.apache.avro.Schema;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import io.confluent.kafka.schemaregistry.avro.AvroCompatibilityLevel;
import io.confluent.kafka.schemaregistry.client.rest.exceptions.RestClientException;
/**
* Mock implementation of SchemaRegistryClient that can be used for tests. This version is NOT
* thread safe. Schema data is stored in memory and is not persistent or shared across instances.
*/
public class MockSchemaRegistryClient implements SchemaRegistryClient {
private String defaultCompatibility = "BACKWARD";
private final Map<String, Map<Schema, Integer>> schemaCache;
private final Map<String, Map<Integer, Schema>> idCache;
private final Map<String, Map<Schema, Integer>> versionCache;
private final Map<String, String> compatibilityCache;
private final AtomicInteger ids;
public MockSchemaRegistryClient() {
schemaCache = new HashMap<String, Map<Schema, Integer>>();
idCache = new HashMap<String, Map<Integer, Schema>>();
versionCache = new HashMap<String, Map<Schema, Integer>>();
compatibilityCache = new HashMap<String, String>();
ids = new AtomicInteger(0);
idCache.put(null, new HashMap<Integer, Schema>());
}
private int getIdFromRegistry(String subject, Schema schema) throws IOException {
Map<Integer, Schema> idSchemaMap;
if (idCache.containsKey(subject)) {
idSchemaMap = idCache.get(subject);
for (Map.Entry<Integer, Schema> entry : idSchemaMap.entrySet()) {
if (entry.getValue().toString().equals(schema.toString())) {
generateVersion(subject, schema);
return entry.getKey();
}
}
} else {
idSchemaMap = new HashMap<Integer, Schema>();
}
int id = ids.incrementAndGet();
idSchemaMap.put(id, schema);
idCache.put(subject, idSchemaMap);
generateVersion(subject, schema);
return id;
}
private void generateVersion(String subject, Schema schema) {
ArrayList<Integer> versions = getAllVersions(subject);
Map<Schema, Integer> schemaVersionMap;
int currentVersion;
if (versions.isEmpty()) {
schemaVersionMap = new IdentityHashMap<Schema, Integer>();
currentVersion = 1;
} else {
schemaVersionMap = versionCache.get(subject);
currentVersion = versions.get(versions.size() - 1) + 1;
}
schemaVersionMap.put(schema, currentVersion);
versionCache.put(subject, schemaVersionMap);
}
private ArrayList<Integer> getAllVersions(String subject) {
ArrayList<Integer> versions = new ArrayList<Integer>();
if (versionCache.containsKey(subject)) {
versions.addAll(versionCache.get(subject).values());
Collections.sort(versions);
}
return versions;
}
private Schema getSchemaBySubjectAndIdFromRegistry(String subject, int id) throws IOException {
if (idCache.containsKey(subject)) {
Map<Integer, Schema> idSchemaMap = idCache.get(subject);
if (idSchemaMap.containsKey(id)) {
return idSchemaMap.get(id);
}
}
throw new IOException("Cannot get schema from schema registry!");
}
@Override
public synchronized int register(String subject, Schema schema)
throws IOException, RestClientException {
Map<Schema, Integer> schemaIdMap;
if (schemaCache.containsKey(subject)) {
schemaIdMap = schemaCache.get(subject);
} else {
schemaIdMap = new IdentityHashMap<Schema, Integer>();
schemaCache.put(subject, schemaIdMap);
}
if (schemaIdMap.containsKey(schema)) {
return schemaIdMap.get(schema);
} else {
int id = getIdFromRegistry(subject, schema);
schemaIdMap.put(schema, id);
idCache.get(null).put(id, schema);
return id;
}
}
@Override
public Schema getByID(final int id) throws IOException, RestClientException {
return getById(id);
}
@Override
public synchronized Schema getById(int id) throws IOException, RestClientException {
return getBySubjectAndId(null, id);
}
@Override
public Schema getBySubjectAndID(final String subject, final int id)
throws IOException, RestClientException {
return getBySubjectAndId(subject, id);
}
@Override
public synchronized Schema getBySubjectAndId(String subject, int id)
throws IOException, RestClientException {
Map<Integer, Schema> idSchemaMap;
if (idCache.containsKey(subject)) {
idSchemaMap = idCache.get(subject);
} else {
idSchemaMap = new HashMap<Integer, Schema>();
idCache.put(subject, idSchemaMap);
}
if (idSchemaMap.containsKey(id)) {
return idSchemaMap.get(id);
} else {
Schema schema = getSchemaBySubjectAndIdFromRegistry(subject, id);
idSchemaMap.put(id, schema);
return schema;
}
}
private int getLatestVersion(String subject)
throws IOException, RestClientException {
ArrayList<Integer> versions = getAllVersions(subject);
if (versions.isEmpty()) {
throw new IOException("No schema registered under subject!");
} else {
return versions.get(versions.size() - 1);
}
}
@Override
public synchronized SchemaMetadata getSchemaMetadata(String subject, int version) {
String schemaString = null;
Map<Schema, Integer> schemaVersionMap = versionCache.get(subject);
for (Map.Entry<Schema, Integer> entry : schemaVersionMap.entrySet()) {
if (entry.getValue() == version) {
schemaString = entry.getKey().toString();
}
}
int id = -1;
Map<Integer, Schema> idSchemaMap = idCache.get(subject);
for (Map.Entry<Integer, Schema> entry : idSchemaMap.entrySet()) {
if (entry.getValue().toString().equals(schemaString)) {
id = entry.getKey();
}
}
return new SchemaMetadata(id, version, schemaString);
}
@Override
public synchronized SchemaMetadata getLatestSchemaMetadata(String subject)
throws IOException, RestClientException {
int version = getLatestVersion(subject);
return getSchemaMetadata(subject, version);
}
@Override
public synchronized int getVersion(String subject, Schema schema)
throws IOException, RestClientException {
if (versionCache.containsKey(subject)) {
return versionCache.get(subject).get(schema);
} else {
throw new IOException("Cannot get version from schema registry!");
}
}
@Override
public boolean testCompatibility(String subject, Schema newSchema) throws IOException,
RestClientException {
String compatibility = compatibilityCache.get(subject);
if (compatibility == null) {
compatibility = defaultCompatibility;
}
AvroCompatibilityLevel compatibilityLevel = AvroCompatibilityLevel.forName(compatibility);
if (compatibilityLevel == null) {
return false;
}
List<Schema> schemaHistory = new ArrayList<>();
for (int version : getAllVersions(subject)) {
SchemaMetadata schemaMetadata = getSchemaMetadata(subject, version);
schemaHistory.add(getSchemaBySubjectAndIdFromRegistry(subject, schemaMetadata.getId()));
}
return compatibilityLevel.compatibilityChecker.isCompatible(newSchema, schemaHistory);
}
@Override
public String updateCompatibility(String subject, String compatibility)
throws IOException,
RestClientException {
if (subject == null) {
defaultCompatibility = compatibility;
return compatibility;
}
compatibilityCache.put(subject, compatibility);
return compatibility;
}
@Override
public String getCompatibility(String subject) throws IOException, RestClientException {
String compatibility = compatibilityCache.get(subject);
if (compatibility == null) {
compatibility = defaultCompatibility;
}
return compatibility;
}
@Override
public Collection<String> getAllSubjects() throws IOException, RestClientException {
List<String> results = new ArrayList<>();
results.addAll(this.schemaCache.keySet());
Collections.sort(results, String.CASE_INSENSITIVE_ORDER);
return results;
}
}