/** * 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.index; import io.kazuki.v0.internal.helper.OpaquePaginationHelper; import io.kazuki.v0.store.KazukiException; import io.kazuki.v0.store.Key; import io.kazuki.v0.store.index.query.QueryEvaluator; import io.kazuki.v0.store.index.query.QueryHelper; import io.kazuki.v0.store.index.query.QueryTerm; import io.kazuki.v0.store.index.query.ValueHolder; import io.kazuki.v0.store.index.query.ValueType; import io.kazuki.v0.store.keyvalue.KeyValueIterable; 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.keyvalue.KeyValueStoreRegistration; 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.schema.SchemaStore; import io.kazuki.v0.store.schema.SchemaStoreRegistration; import io.kazuki.v0.store.schema.model.IndexDefinition; import io.kazuki.v0.store.schema.model.Schema; import io.kazuki.v0.store.sequence.ResolvedKey; import io.kazuki.v0.store.sequence.SequenceService; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Inject; import org.skife.jdbi.v2.Handle; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; public class SecondaryIndexStoreBruteForceImpl implements SecondaryIndexSupport { private final SequenceService sequenceService; private final KeyValueStore kvStore; private final SchemaStore schemaStore; private final ComponentDescriptor<SecondaryIndexStore> componentDescriptor; @Inject public SecondaryIndexStoreBruteForceImpl(KeyValueStoreConfiguration config, SequenceService sequenceService, KeyValueStore kvStore, SchemaStore schemaStore) { this.sequenceService = sequenceService; this.kvStore = kvStore; this.schemaStore = schemaStore; this.componentDescriptor = new ComponentDescriptorImpl<SecondaryIndexStore>("KZ:SecondaryIndexStore:" + config.getGroupName() + "-" + config.getStoreName(), SecondaryIndexStore.class, (SecondaryIndexStore) this, new ImmutableList.Builder().add( ((KazukiComponent) this.sequenceService).getComponentDescriptor(), ((KazukiComponent) this.kvStore).getComponentDescriptor(), ((KazukiComponent) this.schemaStore).getComponentDescriptor()).build()); } @Override public ComponentDescriptor<SecondaryIndexStore> getComponentDescriptor() { return this.componentDescriptor; } @Override public void registerAsComponent(ComponentRegistrar manager) { manager.register(this.componentDescriptor); } @Inject public void registerKeyValueStore(KeyValueStoreRegistration kvStore) { kvStore.addListener(this); } @Inject public void registerSchemaStore(SchemaStoreRegistration schemaStore) { schemaStore.addListener(this); } @Override public void onSchemaCreate(String type, Schema schema) {} @Override public void onSchemaUpdate(String type, Schema newSchema, Schema oldSchema, KeyValueIterable<KeyValuePair<LinkedHashMap>> entityCollection) {} @Override public void onSchemaDelete(String type, Schema oldSchema) {} @Override public <T> void enforceUnique(String type, Class<T> clazz, Schema schema, ResolvedKey resolvedKey, Map<String, Object> instance) throws KazukiException { IndexDefinition uniqueIndexDef = getUniqueIndexDef(schema); if (uniqueIndexDef != null) { Map<String, ValueHolder> values = new LinkedHashMap<String, ValueHolder>(); for (String attr : uniqueIndexDef.getAttributeNames()) { values.put(attr, new ValueHolder(ValueType.STRING, instance.get(attr).toString())); } UniqueEntityDescription uniqueDesc = new UniqueEntityDescription(type, clazz, uniqueIndexDef.getName(), schema, values); Key maybeExists = this.multiRetrieveUniqueKeys(ImmutableList.of(uniqueDesc)).get(uniqueDesc); if (maybeExists != null && !sequenceService.resolveKey(maybeExists).equals(resolvedKey)) { throw new KazukiException("unique index constraint violation"); } } } @Override public <T> void onCreate(Handle handle, String type, Class<T> clazz, Schema schema, ResolvedKey resolvedKey, Map<String, Object> instance) throws KazukiException {} @Override public <T> void onUpdate(Handle handle, String type, Class<T> clazz, Schema schema, ResolvedKey resolvedKey, Map<String, Object> newInstance, Map<String, Object> oldInstance) throws KazukiException {} @Override public <T> void onDelete(Handle handle, String type, Class<T> clazz, Schema schema, ResolvedKey resolvedKey, Map<String, Object> oldInstance) {} @Override public void clear(Handle handle, Map<String, Schema> typeToSchemaMap, boolean preserveSchema) {} @SuppressWarnings({"rawtypes", "unchecked"}) @Override public Map<UniqueEntityDescription, Object> multiRetrieveUniqueEntities( Collection<UniqueEntityDescription> entityDefinitions) { Map<UniqueEntityDescription, Key> keys = multiRetrieveUniqueKeys(entityDefinitions); Map<UniqueEntityDescription, Object> resultMap = new LinkedHashMap<>(); for (Map.Entry<UniqueEntityDescription, Key> entry : keys.entrySet()) { UniqueEntityDescription desc = entry.getKey(); Key key = entry.getValue(); try { resultMap.put(desc, kvStore.retrieve(key, desc.getClazz())); } catch (KazukiException e) { throw Throwables.propagate(e); } } return Collections.unmodifiableMap(resultMap); } @SuppressWarnings({"rawtypes", "unchecked"}) @Override public Map<UniqueEntityDescription, Key> multiRetrieveUniqueKeys( Collection<UniqueEntityDescription> entityDefinitions) { Map<String, Schema> schemaMap = new LinkedHashMap<String, Schema>(); Map<String, Map<String, Set<String>>> todo = new HashMap<String, Map<String, Set<String>>>(); Map<String, UniqueEntityDescription> backMap = new HashMap<String, UniqueEntityDescription>(); try { for (UniqueEntityDescription<?> desc : entityDefinitions) { String type = desc.getType(); String indexName = desc.getIndexName(); Schema schema = schemaMap.get(type); if (schema == null) { schema = schemaStore.retrieveSchema(type).getValue(); schemaMap.put(type, schema); } if (!todo.containsKey(type)) { todo.put(type, new HashMap<String, Set<String>>()); } Map<String, Set<String>> indexDesc = todo.get(type); if (!indexDesc.containsKey(indexName)) { indexDesc.put(indexName, new HashSet<String>()); } Set<String> toFind = indexDesc.get(indexName); Map<String, Object> valMap = new LinkedHashMap<String, Object>(); for (Map.Entry<String, QueryTerm> entry : desc.getColumnDefinitions().entrySet()) { valMap.put(entry.getKey(), entry.getValue().getValue()); } String uKey = SecondaryIndexTableHelper.getUniqueIndexKey(type, schema, indexName, valMap); toFind.add(uKey); backMap.put(uKey, desc); } } catch (KazukiException e) { throw Throwables.propagate(e); } Map<UniqueEntityDescription, Key> resultMap = new LinkedHashMap<UniqueEntityDescription, Key>(); TYPE: for (String type : todo.keySet()) { Schema schema = schemaMap.get(type); try (KeyValueIterable<KeyValuePair<LinkedHashMap>> iter = kvStore.iterators().entries(type, LinkedHashMap.class, SortDirection.ASCENDING)) { for (KeyValuePair<LinkedHashMap> kvPair : iter) { Key key = kvPair.getKey(); LinkedHashMap entity = kvPair.getValue(); Map<String, Set<String>> indexDesc = todo.get(type); for (Map.Entry<String, Set<String>> entry : indexDesc.entrySet()) { String indexName = entry.getKey(); Set<String> toFind = entry.getValue(); String candidate = SecondaryIndexTableHelper.getUniqueIndexKey(type, schema, indexName, entity); if (toFind.contains(candidate)) { resultMap.put(backMap.get(candidate), key); toFind.remove(candidate); if (toFind.isEmpty()) { indexDesc.remove(type); if (indexDesc.isEmpty()) { continue TYPE; } } } } } } } LinkedHashMap<UniqueEntityDescription, Key> inOrderResultMap = new LinkedHashMap<>(); for (UniqueEntityDescription desc : entityDefinitions) { inOrderResultMap.put(desc, resultMap.get(desc)); } return Collections.unmodifiableMap(inOrderResultMap); } @Override public <T> KeyValueIterable<Key> queryWithoutPagination(final String type, final Class<T> clazz, final String indexName, final List<QueryTerm> query, final SortDirection sortDirection, final Long offset, final Long limit) { Schema schema = null; try { schema = schemaStore.retrieveSchema(type).getValue(); } catch (KazukiException e) { throw Throwables.propagate(e); } Preconditions.checkNotNull(schema, "schema"); SecondaryIndexQueryValidation.validateQuery(indexName, query, schema); final QueryEvaluator eval = new QueryEvaluator(); return new FilteredKeyValueIterable<Key>(kvStore.iterators().entries(type, LinkedHashMap.class, sortDirection), new Predicate<Object>() { @SuppressWarnings("unchecked") @Override public boolean apply(Object instance) { return eval.matches((LinkedHashMap<String, Object>) instance, query); } }, new Function<KeyValuePair<?>, Key>() { @Override public Key apply(KeyValuePair<?> instance) { return instance.getKey(); } }, offset, limit); } @Override public <T> KeyValueIterable<Key> queryWithoutPagination(final String type, final Class<T> clazz, final String indexName, final String queryString, final SortDirection sortDirection, final Long offset, final Long limit) { Preconditions.checkNotNull(queryString, "query"); return queryWithoutPagination(type, clazz, indexName, QueryHelper.parseQuery(queryString), sortDirection, offset, limit); } @Override public <T> QueryResultsPage<T> queryWithPagination(final String type, final Class<T> clazz, final String indexName, final List<QueryTerm> query, final SortDirection sortDirection, final Boolean loadResults, final PageToken token, final Long limit) { try { KeyValueIterable<Key> kvIter = queryWithoutPagination(type, clazz, indexName, query, sortDirection, OpaquePaginationHelper.decodeOpaqueCursor(token.getToken()), limit); List<KeyValuePair<T>> kvPairs = new ArrayList<KeyValuePair<T>>(); if (loadResults) { List<Key> toRetrieve = new ArrayList<Key>(); Iterables.addAll(toRetrieve, kvIter); Map<Key, KeyValuePair<T>> resultMap = kvStore.multiRetrieveVersioned(toRetrieve, clazz); for (Map.Entry<Key, KeyValuePair<T>> entry : resultMap.entrySet()) { kvPairs.add(new KeyValuePair<T>(entry.getKey(), entry.getValue().getVersion(), entry .getValue().getSchemaVersion(), entry.getValue().getValue())); } } else { for (Key key : kvIter) { kvPairs.add(new KeyValuePair<T>(key, null, null, null)); } } return new QueryResultsPageImpl<T>(kvPairs, loadResults); } catch (Exception e) { throw Throwables.propagate(e); } } @Override public <T> QueryResultsPage<T> queryWithPagination(String type, Class<T> clazz, String indexName, String queryString, SortDirection sortDirection, Boolean loadResults, PageToken token, Long limit) { return queryWithPagination(type, clazz, indexName, QueryHelper.parseQuery(queryString), sortDirection, loadResults, token, limit); } private IndexDefinition getUniqueIndexDef(Schema schema) { for (IndexDefinition indexDef : schema.getIndexes()) { if (indexDef.isUnique()) { return indexDef; } } return null; } }