/** * 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.deephacks.confit.internal.hbase; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.RowMutations; import org.deephacks.confit.model.Bean; import org.deephacks.confit.model.BeanId; import java.io.IOException; import java.util.AbstractList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static org.deephacks.confit.internal.hbase.HBeanRow.IID_WIDTH; import static org.deephacks.confit.internal.hbase.HBeanRow.REF_COLUMN_FAMILY; public class HBeanReferences { private Map<String, KeyValue> references = new HashMap<>(); private final UniqueIds uids; public HBeanReferences(UniqueIds uids) { this.uids = uids; } public HBeanReferences(final Bean bean, final UniqueIds uids) { this.uids = uids; byte[] rowkey = HBeanRow.getRowKey(bean.getId(), uids); Map<String, KeyValue> references = new HashMap<>(); for (String propertyName : bean.getReferenceNames()) { List<BeanId> refs = bean.getReference(propertyName); String schemaName = bean.getSchema().getReferenceSchemaName(propertyName); KeyValue kv = getReferenceKeyValue(rowkey, propertyName, schemaName, refs, uids); references.put(propertyName, kv); } this.references = references; } public HBeanReferences(Map<String, KeyValue> kvs, UniqueIds uids) { this.references = kvs; this.uids = uids; } public Map<String, KeyValue> getReferences(final byte[] rowkey, final Bean bean, final UniqueIds uids) { Map<String, KeyValue> references = new HashMap<>(); for (String propertyName : bean.getReferenceNames()) { List<BeanId> refs = bean.getReference(propertyName); if (refs == null || refs.size() == 0) { continue; } String schemaName = bean.getSchema().getReferenceSchemaName(propertyName); KeyValue kv = getReferenceKeyValue(rowkey, propertyName, schemaName, refs, uids); references.put(propertyName, kv); } return references; } /** * Get a particular type of references identified by propertyName into key value * form. * @param schemaName */ public static KeyValue getReferenceKeyValue(byte[] rowkey, String propertyName, String schemaName, List<BeanId> refs, UniqueIds uids) { final byte[] pid = uids.getUsid().getId(propertyName); final byte[] sid = uids.getUsid().getId(schemaName); final byte[] qual = new byte[] { sid[0], sid[1], pid[0], pid[1] }; final byte[] iids = getIids2(refs, uids); return new KeyValue(rowkey, REF_COLUMN_FAMILY, qual, iids); } public void merge(Bean bean, byte[] rowkey) { // merge references Map<String, KeyValue> mergedRefs = new HashMap<>(); for (String propertyName : references.keySet()) { String schemaName = bean.getSchema().getReferenceSchemaName(propertyName); KeyValue kv = references.get(propertyName); final List<BeanId> refs = bean.getReference(propertyName); KeyValue mergekv = null; if (refs == null) { mergekv = kv.deepCopy(); } else { mergekv = HBeanReferences.getReferenceKeyValue(rowkey, propertyName, schemaName, refs, uids); } mergedRefs.put(propertyName, mergekv); } references = mergedRefs; } public Set<HBeanRow> merge(HBeanReferences provided, RowMutations mutations) { if (provided == null) { return new HashSet<>(); } // merge references Set<HBeanRow> removedRowKeys = new HashSet<>(); Map<String, KeyValue> providedRefs = provided.getReferencesKeyValue(); for (String propertyName : providedRefs.keySet()) { KeyValue providedkv = providedRefs.get(propertyName); KeyValue existingkv = references.get(propertyName); if (existingkv != null) { Map<String, HBeanRow> providedRowKeys = getRowKeys(providedkv); Map<String, HBeanRow> existingRowKeys = getRowKeys(existingkv); removedRowKeys = getDeletedKeys(providedRowKeys, existingRowKeys); } try { if (providedkv != null) { Put p = new Put(mutations.getRow()); p.add(providedkv); mutations.add(p); } } catch (IOException e) { throw new RuntimeException(e); } } return removedRowKeys; } public Set<HBeanRow> set(HBeanReferences provided, RowMutations mutations) { if (provided == null) { return new HashSet<>(); } Set<HBeanRow> removedRowKeys = merge(provided, mutations); Map<String, KeyValue> providedRefs = provided.getReferencesKeyValue(); for (String propertyName : references.keySet()) { KeyValue providedkv = providedRefs.get(propertyName); if (providedkv == null) { KeyValue existingkv = references.get(propertyName); try { Delete d = new Delete(mutations.getRow()); d.deleteColumns(existingkv.getFamily(), existingkv.getQualifier()); mutations.add(d); Map<String, HBeanRow> existingRowKeys = getRowKeys(existingkv); removedRowKeys.addAll(existingRowKeys.values()); } catch (IOException e) { throw new RuntimeException(e); } } } return removedRowKeys; } private Set<HBeanRow> getDeletedKeys(Map<String, HBeanRow> providedRowKeys, Map<String, HBeanRow> existingRowKeys) { HashSet<HBeanRow> deleted = new HashSet<>(); for (String instanceId : existingRowKeys.keySet()) { if (providedRowKeys.get(instanceId) == null) { deleted.add(existingRowKeys.get(instanceId)); } } return deleted; } private Map<String, HBeanRow> getRowKeys(KeyValue existing) { Map<String, HBeanRow> rowkeys = new HashMap<>(); final byte[] sidpid = existing.getQualifier(); final byte[] iids = existing.getValue(); final byte[] sid = new byte[] { sidpid[0], sidpid[1] }; for (int i = 0; i < iids.length; i += IID_WIDTH) { final byte[] iid = new byte[] { iids[i], iids[i + 1], iids[i + 2], iids[i + 3] }; final byte[] rowkey = new byte[] { sid[0], sid[1], iids[i], iids[i + 1], iids[i + 2], iids[i + 3] }; final String instanceId = uids.getUiid().getName(iid); rowkeys.put(instanceId, new HBeanRow(rowkey, uids)); } return rowkeys; } public void set(Bean bean, byte[] rowkey) { // overwrite row references and nullify existing ones Map<String, KeyValue> setRefs = new HashMap<>(); for (String propertyName : references.keySet()) { String schemaName = bean.getSchema().getReferenceSchemaName(propertyName); final KeyValue kv = references.get(propertyName); final List<BeanId> refs = bean.getReference(propertyName); KeyValue setkv = null; if (refs == null) { setkv = new KeyValue(rowkey, kv.getQualifier(), null); } else { setkv = HBeanReferences.getReferenceKeyValue(rowkey, propertyName, schemaName, refs, uids); } setRefs.put(propertyName, setkv); } this.references = setRefs; } /** * Set references on a bean using a set of key values containing references. */ public void setReferencesOn(Bean bean) { for (KeyValue ref : references.values()) { final byte[] sidpid = ref.getQualifier(); final byte[] iids = ref.getValue(); final byte[] sid = new byte[] { sidpid[0], sidpid[1] }; final byte[] pid = new byte[] { sidpid[2], sidpid[3] }; final String schemaName = uids.getUsid().getName(sid); final String propertyName = uids.getUsid().getName(pid); for (int i = 0; i < iids.length; i += IID_WIDTH) { final byte[] iid = new byte[] { iids[i], iids[i + 1], iids[i + 2], iids[i + 3] }; final String instanceId = uids.getUiid().getName(iid); bean.addReference(propertyName, BeanId.create(instanceId, schemaName)); } } } public Map<String, KeyValue> getReferencesKeyValue() { return references; } public Set<HBeanRow> getReferences() { final Set<HBeanRow> rows = new HashSet<>(); for (KeyValue ref : references.values()) { byte[] sid = ref.getQualifier(); byte[] iids = ref.getValue(); for (int i = 0; i < iids.length; i += IID_WIDTH) { final byte[] rowkey = new byte[] { sid[0], sid[1], iids[i], iids[i + 1], iids[i + 2], iids[i + 3] }; rows.add(new HBeanRow(rowkey, uids)); } } return rows; } /** * If this key value is of reference familiy type. */ public static boolean isReference(KeyValue kv) { if (Bytes.equals(kv.getFamily(), REF_COLUMN_FAMILY)) { return true; } return false; } public void add(KeyValue kv) { String propertyName = HBeanProperties.getPropertyName(kv, uids); references.put(propertyName, kv); } /** * Compress a set of instances ids into a byte array where each id consist of * 4 bytes each. */ public static byte[] getIids(final List<String> ids, final UniqueIds uids) { final int size = ids.size(); final byte[] iids = new byte[IID_WIDTH * size]; for (int i = 0; i < size; i++) { final String instanceId = ids.get(i); final byte[] iid = uids.getUiid().getId(instanceId); System.arraycopy(iid, 0, iids, i * IID_WIDTH, IID_WIDTH); } return iids; } /** * Second version of getIids that takes a set of bean ids instead. */ private static byte[] getIids2(final List<BeanId> ids, final UniqueIds uids) { if (ids == null) { return new byte[0]; } final AbstractList<String> list = new AbstractList<String>() { @Override public String get(int index) { return ids.get(index).getInstanceId(); } @Override public int size() { return ids.size(); } }; return getIids(list, uids); } }