package org.ff4j.hbase.store; import static org.ff4j.hbase.HBaseConstants.B_FEATURES_CF_CORE; import static org.ff4j.hbase.HBaseConstants.B_FEATURES_CF_PROPERTIES; import static org.ff4j.hbase.HBaseConstants.FEATURES_CF_CORE; import static org.ff4j.hbase.HBaseConstants.FEATURES_CF_PROPERTIES; import static org.ff4j.hbase.HBaseConstants.FEATURES_TABLENAME; import static org.ff4j.hbase.HBaseConstants.FEATURES_TABLENAME_ID; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.client.Table; import org.ff4j.core.Feature; import org.ff4j.core.FeatureStore; import org.ff4j.exception.FeatureAccessException; import org.ff4j.hbase.HBaseConnection; import org.ff4j.hbase.mapper.HBaseFeatureMapper; import org.ff4j.hbase.mapper.HBaseQueryBuilder; import org.ff4j.store.AbstractFeatureStore; import org.ff4j.utils.Util; /** * Implementation of {@link FeatureStore} to work with Cassandra Storage. * * Minimize the Number of Writes : * Writes in Cassandra aren’t free, but they’re awfully cheap. Cassandra is optimized for high write throughput, * and almost all writes are equally efficient [1]. If you can perform extra writes to improve the efficiency of * your read queries, it’s almost always a good tradeoff. Reads tend to be more expensive and are much more * difficult to tune. * * Minimize Data Duplication * Denormalization and duplication of data is a fact of life with Cassandra. Don’t be afraid of it. Disk space * is generally the cheapest resource (compared to CPU, memory, disk IOPs, or network), and Cassandra is * architected around that fact. In order to get the most efficient reads, you often need to duplicate data. * * Rule 1: Spread Data Evenly Around the Cluster * Rule 2: Minimize the Number of Partitions Read * * Ces familles de colonnes contiennent des colonnes ainsi qu'un ensemble de colonnes connexes qui sont identifiées par une clé de ligne * * * @author Cedrick Lunven (@clunven) */ public class FeatureStoreHBase extends AbstractFeatureStore { /** Mapper. */ private static final HBaseFeatureMapper MAPPER = new HBaseFeatureMapper(); /** Connection to store Cassandra. */ private HBaseConnection conn; /** * Default constructor. */ public FeatureStoreHBase() { } /** * Initialization through {@link HBaseConnection}. * * @param conn * current client to cassandra db */ public FeatureStoreHBase(HBaseConnection conn) { this.conn = conn; } /** {@inheritDoc} */ @Override public void createSchema() { conn.createTable(FEATURES_TABLENAME_ID, Util.set(FEATURES_CF_CORE, FEATURES_CF_PROPERTIES)); } /** {@inheritDoc} */ @Override public boolean exist(String featId) { Util.assertHasLength(featId); try (Connection hbConn = ConnectionFactory.createConnection(conn.getConfig())) { try(Table table = hbConn.getTable(FEATURES_TABLENAME)) { return !table.get(HBaseQueryBuilder.getFeatureById(featId)).isEmpty(); } } catch (IOException e) { throw new FeatureAccessException("Cannot check feature existence", e); } } private void executePutCommand(Put putQuery) { try (Connection hbConn = ConnectionFactory.createConnection(conn.getConfig())) { try(Table table = hbConn.getTable(FEATURES_TABLENAME)) { table.put(putQuery); } } catch (IOException e) { throw new FeatureAccessException("Cannot execute command", e); } } /** {@inheritDoc} */ @Override public void create(Feature fp) { assertFeatureNotNull(fp); assertFeatureNotExist(fp.getUid()); executePutCommand(MAPPER.toStore(fp)); } /** {@inheritDoc} */ @Override public void enable(String uid) { assertFeatureExist(uid); executePutCommand(HBaseQueryBuilder.queryEnableFeature(uid)); } /** {@inheritDoc} */ @Override public void disable(String uid) { assertFeatureExist(uid); executePutCommand(HBaseQueryBuilder.queryDisableFeature(uid)); } /** {@inheritDoc} */ @Override public Feature read(String uid) { assertFeatureExist(uid); try (Connection hbConn = ConnectionFactory.createConnection(conn.getConfig())) { try(Table table = hbConn.getTable(FEATURES_TABLENAME)) { Result result = table.get(HBaseQueryBuilder.getFeatureById(uid)); return MAPPER.fromStore(result); } } catch (IOException e) { throw new FeatureAccessException("Cannot check feature existence", e); } } /** {@inheritDoc} */ @Override public Map<String, Feature> readAll() { Map<String, Feature> mapOfFeature = new HashMap<>(); try (Connection hbConn = ConnectionFactory.createConnection(conn.getConfig())) { try(Table table = hbConn.getTable(FEATURES_TABLENAME)) { Scan scan = new Scan(); scan.setCaching(100); scan.setBatch(100); scan.addFamily(B_FEATURES_CF_CORE); scan.addFamily(B_FEATURES_CF_PROPERTIES); try(ResultScanner resultScanner = table.getScanner(scan)) { Iterator<Result> iterator = resultScanner.iterator(); while (iterator.hasNext()) { Feature f = MAPPER.fromStore(iterator.next()); mapOfFeature.put(f.getUid(), f); } } } } catch (IOException e) { throw new FeatureAccessException("Cannot read all features", e); } return mapOfFeature; } /** {@inheritDoc} */ @Override public void delete(String uid) { assertFeatureExist(uid); try (Connection hbConn = ConnectionFactory.createConnection(conn.getConfig())) { try(Table table = hbConn.getTable(FEATURES_TABLENAME)) { List<Delete> list = new ArrayList<Delete>(); Delete del = new Delete(uid.getBytes()); list.add(del); table.delete(list); } } catch (IOException e) { throw new FeatureAccessException("Cannot delete feature ", e); } } /** {@inheritDoc} */ @Override public void update(Feature fp) { assertFeatureNotNull(fp); assertFeatureExist(fp.getUid()); delete(fp.getUid()); create(fp); } /** {@inheritDoc} */ @Override public void grantRoleOnFeature(String flipId, String roleName) { } /** {@inheritDoc} */ @Override public void removeRoleFromFeature(String flipId, String roleName) { } /** {@inheritDoc} */ @Override public void enableGroup(String groupName) { } /** {@inheritDoc} */ @Override public void disableGroup(String groupName) { } /** {@inheritDoc} */ @Override public boolean existGroup(String groupName) { return false; } /** {@inheritDoc} */ @Override public Map<String, Feature> readGroup(String groupName) { return null; } /** {@inheritDoc} */ @Override public void addToGroup(String featureId, String groupName) { } /** {@inheritDoc} */ @Override public void removeFromGroup(String featureId, String groupName) { } /** {@inheritDoc} */ @Override public Set<String> readAllGroups() { return null; } /** {@inheritDoc} */ @Override public void clear() { conn.truncateTable(FEATURES_TABLENAME_ID); } /** * Getter accessor for attribute 'conn'. * * @return * current value of 'conn' */ public HBaseConnection getConn() { return conn; } /** * Setter accessor for attribute 'conn'. * * @param conn * new value for 'conn ' */ public void setConn(HBaseConnection conn) { this.conn = conn; } }