/** * 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 com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; 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 java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import static org.deephacks.confit.internal.hbase.HBeanRow.PROP_COLUMN_FAMILY; public class HBeanProperties { /** Kryo is used for serializing properties */ private static final Kryo kryo = new Kryo(); private KeyValue properties; private UniqueIds uids; public HBeanProperties(UniqueIds uids) { this.uids = uids; } public HBeanProperties(KeyValue kv, UniqueIds uids) { this.properties = kv; this.uids = uids; } public HBeanProperties(final byte[] rowkey, String[][] properties, UniqueIds uids) { this.uids = uids; this.properties = getProperties(rowkey, properties); } public HBeanProperties(Bean bean, UniqueIds uids) { this.uids = uids; set(bean); } public void set(Bean bean) { byte[] rowkey = HBeanRow.getRowKey(bean.getId(), uids); String[][] props = getProperties(bean); final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); final Output out = new Output(bytes); try { kryo.writeObject(out, props); } finally { out.close(); try { bytes.close(); } catch (IOException e) { // ignore } } properties = new KeyValue(rowkey, PROP_COLUMN_FAMILY, PROP_COLUMN_FAMILY, bytes.toByteArray()); } public void merge(Bean bean, byte[] rowkey) { String[][] mergedProps = merge(getProperties(), HBeanProperties.getProperties(bean)); this.properties = getProperties(rowkey, mergedProps); properties = getProperties(rowkey, mergedProps); } public void merge(HBeanProperties properties, RowMutations mutations) { if (properties == null || properties.getProperties().length == 0) { return; } String[][] mergedProps = merge(getProperties(), properties.getProperties()); this.properties = getProperties(mutations.getRow(), mergedProps); Put p = new Put(mutations.getRow()); try { p.add(this.properties); mutations.add(p); } catch (IOException e) { throw new RuntimeException(e); } } public void set(HBeanProperties properties, RowMutations mutations) { if (properties == null || properties.getProperties() == null || properties.getProperties().length == 0) { Delete d = new Delete(mutations.getRow()); try { d.deleteColumn(this.properties.getFamily(), this.properties.getQualifier()); mutations.add(d); } catch (IOException e) { throw new RuntimeException(e); } return; } String[][] mergedProps = properties.getProperties(); this.properties = getProperties(mutations.getRow(), mergedProps); Put p = new Put(mutations.getRow()); try { p.add(this.properties); mutations.add(p); } catch (IOException e) { throw new RuntimeException(e); } } private static String[][] merge(String[][] source, String[][] target) { // merge properties HashMap<String, String[]> props = new HashMap<>(); for (String[] p : source) { props.put(p[0], Arrays.copyOfRange(p, 1, p.length)); } for (String[] p : target) { props.put(p[0], Arrays.copyOfRange(p, 1, p.length)); } String[][] mergedProps = new String[props.size()][]; int n = 0; for (String propertyName : props.keySet()) { String[] values = props.get(propertyName); String[] merged = new String[values.length + 1]; merged[0] = propertyName; System.arraycopy(values, 0, merged, 1, values.length); mergedProps[n++] = merged; } return mergedProps; } /** * @param props properties in key value form. * @return Properties converted into string matrix form. */ public String[][] getProperties() { if (properties == null) { return new String[0][]; } try (final Input in = new Input(properties.getValue())){ return kryo.readObject(in, String[][].class); } } /** * In order to reduce the amount of property data stored, we convert the * properties into a String matrix. The first element is the property name * followed by its values. * * @param bean Bean to lookup properties from. * @return String matrix */ public static String[][] getProperties(final Bean bean) { final List<String> propertyNames = bean.getPropertyNames(); final int psize = propertyNames.size(); final String[][] properties = new String[psize][]; for (int i = 0; i < psize; i++) { final String propertyName = propertyNames.get(i); final List<String> values = bean.getValues(propertyName); final int vsize = values.size(); properties[i] = new String[vsize + 1]; properties[i][0] = propertyName; for (int j = 0; j < vsize; j++) { properties[i][j + 1] = values.get(j); } } return properties; } public KeyValue getPropertiesKeyValue() { return properties; } private KeyValue getProperties(final byte[] rowkey, String[][] properties) { final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); final Output out = new Output(bytes); try { kryo.writeObject(out, properties); } finally { out.close(); try { bytes.close(); } catch (IOException e) { // ignore } } return new KeyValue(rowkey, PROP_COLUMN_FAMILY, PROP_COLUMN_FAMILY, bytes.toByteArray()); } /** * Set a string matrix of properties on a particular bean. * * @param bean Bean to set properties on. * @param properties in string matrix form. */ public void setPropertiesOn(final Bean bean) { String[][] properties = getProperties(); for (int i = 0; i < properties.length; i++) { if (properties[i].length < 2) { continue; } for (int j = 0; j < properties[i].length - 1; j++) { bean.addProperty(properties[i][0], properties[i][j + 1]); } } } /** * Extract the property name from a key value. */ public static String getPropertyName(KeyValue kv, UniqueIds uids) { final byte[] qualifier = kv.getQualifier(); final byte[] pid = new byte[] { qualifier[2], qualifier[3] }; final String propertyName = uids.getUsid().getName(pid); return propertyName; } /** * If this key value is of property familiy type. */ public static boolean isProperty(KeyValue kv) { if (Bytes.equals(kv.getFamily(), PROP_COLUMN_FAMILY)) { return true; } return false; } }