/**
* Copyright 2014 Sunny Gleason and 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 io.kazuki.v0.store.schema;
import io.kazuki.v0.internal.helper.LockManager;
import io.kazuki.v0.internal.helper.LogTranslation;
import io.kazuki.v0.internal.v2schema.SchemaValidator;
import io.kazuki.v0.store.KazukiException;
import io.kazuki.v0.store.Key;
import io.kazuki.v0.store.Version;
import io.kazuki.v0.store.keyvalue.KeyValuePair;
import io.kazuki.v0.store.keyvalue.KeyValueStore;
import io.kazuki.v0.store.keyvalue.KeyValueStoreConfiguration;
import io.kazuki.v0.store.keyvalue.KeyValueStoreIteration.SortDirection;
import io.kazuki.v0.store.management.ComponentDescriptor;
import io.kazuki.v0.store.management.ComponentRegistrar;
import io.kazuki.v0.store.management.KazukiComponent;
import io.kazuki.v0.store.management.impl.ComponentDescriptorImpl;
import io.kazuki.v0.store.management.impl.LateBindingComponentDescriptorImpl;
import io.kazuki.v0.store.schema.model.Schema;
import io.kazuki.v0.store.schema.model.TransformException;
import io.kazuki.v0.store.sequence.KeyImpl;
import io.kazuki.v0.store.sequence.ResolvedKey;
import io.kazuki.v0.store.sequence.SequenceService;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import javax.inject.Inject;
import org.slf4j.Logger;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
public class SchemaStoreImpl implements SchemaStore, SchemaStoreRegistration {
public static final String SCHEMA_PREFIX = "$schema";
private final Logger log = LogTranslation.getLogger(getClass());
private final LockManager lockManager;
private final SequenceService sequences;
private final List<SchemaStoreListener> ssListeners;
private final ComponentDescriptor<SchemaStore> componentDescriptor;
private KeyValueStore store;
@Inject
public SchemaStoreImpl(LockManager lockManager, SequenceService sequences,
KeyValueStoreConfiguration config) {
this.lockManager = lockManager;
this.sequences = sequences;
this.ssListeners = new ArrayList<SchemaStoreListener>();
this.componentDescriptor =
new ComponentDescriptorImpl<SchemaStore>("KZ:SchemaStore:" + config.getGroupName() + "-"
+ config.getStoreName(), SchemaStore.class, (SchemaStore) this,
new ImmutableList.Builder().add(
((KazukiComponent) lockManager).getComponentDescriptor(),
((KazukiComponent) sequences).getComponentDescriptor(),
(new LateBindingComponentDescriptorImpl<KeyValueStore>() {
@Override
public KazukiComponent<KeyValueStore> get() {
return (KazukiComponent<KeyValueStore>) SchemaStoreImpl.this.store;
}
})).build());
}
@Inject
public synchronized void setKeyValueStorage(KeyValueStore store) {
this.store = store;
}
@Override
public void addListener(SchemaStoreListener listener) {
this.ssListeners.add(listener);
}
@Override
public ComponentDescriptor<SchemaStore> getComponentDescriptor() {
return this.componentDescriptor;
}
@Override
@Inject
public void registerAsComponent(ComponentRegistrar manager) {
manager.register(this.componentDescriptor);
}
public Version createSchema(String type, Schema value) throws KazukiException {
if (store == null) {
throw new IllegalStateException("schemaManager not initialized with KV store");
}
try (LockManager toRelease = lockManager.acquire()) {
Integer typeId = getTypeIdPossiblyNull(type, true);
if (typeId == null) {
throw new KazukiException("unable to allocate new type id for Schema type: " + type);
}
Key realKey = KeyImpl.createInternal(SCHEMA_PREFIX, typeId.longValue());
KeyValuePair<Schema> existing = this.store.retrieveVersioned(realKey, Schema.class);
if (existing != null) {
return existing.getVersion();
}
try {
SchemaValidator.validate(value);
} catch (TransformException e) {
throw new KazukiException("invalid schema definition for type: " + type, e);
}
for (SchemaStoreListener ssListener : ssListeners) {
ssListener.onSchemaCreate(type, value);
}
ResolvedKey resolvedKey = sequences.resolveKey(realKey);
KeyValuePair<Schema> schemaKv =
store.create(SCHEMA_PREFIX, Schema.class, value, resolvedKey, TypeValidation.LAX);
return schemaKv.getVersion();
}
}
public KeyValuePair<Schema> retrieveSchema(String type) throws KazukiException {
if (store == null) {
throw new IllegalStateException("schemaManager not initialized with KV store");
}
try (LockManager toRelease = lockManager.acquire()) {
Integer typeId = getTypeIdPossiblyNull(type, false);
if (typeId == null || type.equals(SCHEMA_PREFIX)) {
return null;
}
return store.retrieveVersioned(KeyImpl.createInternal(SCHEMA_PREFIX, typeId.longValue()),
Schema.class);
}
}
public Version updateSchema(final String type, final Version version, final Schema value)
throws KazukiException {
if (store == null) {
throw new IllegalStateException("schemaManager not initialized with KV store");
}
try (LockManager toRelease = lockManager.acquire()) {
final Integer typeId = getTypeIdPossiblyNull(type, false);
if (typeId == null) {
return null;
}
Key theKey = KeyImpl.createInternal(SCHEMA_PREFIX, typeId.longValue());
final Schema original = store.retrieve(theKey, Schema.class);
if (original == null) {
return null;
}
try {
SchemaValidator.validate(value);
SchemaValidator.validateUpgrade(original, value);
} catch (TransformException e) {
throw new KazukiException("invalid Schema update for type: " + type, e);
}
for (SchemaStoreListener ssListener : ssListeners) {
ssListener.onSchemaUpdate(type, value, original,
this.store.iterators().entries(type, LinkedHashMap.class, SortDirection.ASCENDING));
}
return store.updateVersioned(theKey, version, Schema.class, value);
}
}
public boolean deleteSchema(final String type, final Version version) throws KazukiException {
if (store == null) {
throw new IllegalStateException("schemaManager not initialized with KV store");
}
try (LockManager toRelease = lockManager.acquire()) {
Integer typeId = getTypeIdPossiblyNull(type, true);
if (typeId == null) {
return false;
}
Schema original =
store.retrieve(KeyImpl.createInternal(SCHEMA_PREFIX, Long.valueOf(typeId)), Schema.class);
for (SchemaStoreListener ssListener : ssListeners) {
ssListener.onSchemaDelete(type, original);
}
Key theKey = KeyImpl.createInternal(SCHEMA_PREFIX, typeId.longValue());
return store.deleteHard(theKey);
}
}
public void clear() throws KazukiException {
try (LockManager toRelease = lockManager.acquire()) {
this.store.clear(SCHEMA_PREFIX);
}
}
private Integer getTypeIdPossiblyNull(String type, boolean val) {
try {
return sequences.getTypeId(type, val);
} catch (KazukiException e) {
return null;
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
}