package com.linkedin.camus.schemaregistry;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.Properties;
/**
* Not thread safe.
*
* @param <S>
* The type of the schema that this registry manages.
*/
public class FileSchemaRegistry<S> implements SchemaRegistry<S> {
private final File root;
private final Serde<S> serde;
public void init(Properties props) {
}
public FileSchemaRegistry(File root, Serde<S> serde) {
this.root = root;
this.serde = serde;
}
@Override
public String register(String topic, S schema) {
FileOutputStream out = null;
File topicDir = getTopicPath(topic);
if (!topicDir.exists()) {
topicDir.mkdirs();
}
try {
byte[] bytes = serde.toBytes(schema);
String id = SHAsum(bytes);
File file = getSchemaPath(topic, id, true);
out = new FileOutputStream(file);
out.write(bytes);
// move any old "latest" files to be regular schema files
for (File fileToRename : topicDir.listFiles()) {
if (!fileToRename.equals(file) && fileToRename.getName().endsWith(".latest")) {
String oldName = fileToRename.getName();
// 7 = len(.latest)
File renameTo = new File(fileToRename.getParentFile(), oldName.substring(0, oldName.length() - 7));
fileToRename.renameTo(renameTo);
}
}
return id;
} catch (Exception e) {
throw new SchemaRegistryException(e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
throw new SchemaRegistryException(e);
}
}
}
}
@Override
public S getSchemaByID(String topic, String id) {
File file = getSchemaPath(topic, id, false);
if (!file.exists()) {
file = getSchemaPath(topic, id, true);
}
if (!file.exists()) {
throw new SchemaNotFoundException("No matching schema found for topic " + topic + " and id " + id + ".");
}
return serde.fromBytes(readBytes(file));
}
@Override
public SchemaDetails<S> getLatestSchemaByTopic(String topicName) {
File topicDir = getTopicPath(topicName);
if (topicDir.exists()) {
for (File file : topicDir.listFiles()) {
if (file.getName().endsWith(".latest")) {
String id = file.getName().replace(".schema.latest", "");
return new SchemaDetails<S>(topicName, id, serde.fromBytes(readBytes(file)));
}
}
}
throw new SchemaNotFoundException("Unable to find a latest schema for topic " + topicName + ".");
}
private byte[] readBytes(File file) {
DataInputStream dis = null;
byte[] bytes = new byte[(int) file.length()];
try {
dis = new DataInputStream(new FileInputStream(file));
dis.readFully(bytes);
} catch (Exception e) {
throw new SchemaRegistryException(e);
} finally {
if (dis != null) {
try {
dis.close();
} catch (Exception e) {
throw new SchemaRegistryException(e);
}
}
}
return bytes;
}
private File getTopicPath(String topic) {
return new File(root, topic);
}
private File getSchemaPath(String topic, String id, boolean latest) {
return new File(getTopicPath(topic), id + ".schema" + ((latest) ? ".latest" : ""));
}
public static String SHAsum(byte[] convertme) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
return byteArray2Hex(md.digest(convertme));
}
private static String byteArray2Hex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash) {
formatter.format("%02x", b);
}
String hex = formatter.toString();
formatter.close();
return hex;
}
}