From 03b19664ef7cc4924a1c392fe7f7bf52cb6362a2 Mon Sep 17 00:00:00 2001 From: Gary Helmling Date: Wed, 27 Jan 2010 17:30:38 -0500 Subject: [PATCH] Clean up some code structure --- src/java/meetup/beeno/Criteria.java | 16 +- src/java/meetup/beeno/EntityIndexer.java | 3 +- src/java/meetup/beeno/EntityMetadata.java | 486 ------------------ src/java/meetup/beeno/EntityService.java | 30 +- src/java/meetup/beeno/Query.java | 25 +- src/java/meetup/beeno/QueryOpts.java | 10 - src/java/meetup/beeno/QueryStrategy.java | 4 +- src/java/meetup/beeno/ScanByIndex.java | 10 +- src/java/meetup/beeno/ScanNoIndex.java | 3 +- src/java/meetup/beeno/mapping/EntityInfo.java | 179 +++++++ .../meetup/beeno/mapping/EntityMetadata.java | 192 +++++++ .../meetup/beeno/mapping/FieldMapping.java | 42 ++ .../meetup/beeno/mapping/IndexMapping.java | 46 ++ src/java/meetup/beeno/mapping/ListField.java | 28 + src/java/meetup/beeno/mapping/MapField.java | 33 ++ src/java/meetup/beeno/util/HUtil.java | 7 +- 16 files changed, 585 insertions(+), 529 deletions(-) delete mode 100644 src/java/meetup/beeno/EntityMetadata.java create mode 100644 src/java/meetup/beeno/mapping/EntityInfo.java create mode 100644 src/java/meetup/beeno/mapping/EntityMetadata.java create mode 100644 src/java/meetup/beeno/mapping/FieldMapping.java create mode 100644 src/java/meetup/beeno/mapping/IndexMapping.java create mode 100644 src/java/meetup/beeno/mapping/ListField.java create mode 100644 src/java/meetup/beeno/mapping/MapField.java diff --git a/src/java/meetup/beeno/Criteria.java b/src/java/meetup/beeno/Criteria.java index 2aaca06..200c976 100644 --- a/src/java/meetup/beeno/Criteria.java +++ b/src/java/meetup/beeno/Criteria.java @@ -9,6 +9,8 @@ import meetup.beeno.filter.ColumnMatchFilter; import meetup.beeno.filter.WhileMatchFilter; +import meetup.beeno.mapping.EntityInfo; +import meetup.beeno.mapping.FieldMapping; import meetup.beeno.util.IOUtil; import meetup.beeno.util.PBUtil; @@ -36,6 +38,10 @@ public Criteria add(Expression expr) { return this; } + public boolean isEmpty() { + return this.expressions.isEmpty(); + } + public List getExpressions() { return this.expressions; } @@ -102,7 +108,7 @@ public static abstract class Expression implements Externalizable { public Expression() { } - public abstract Filter getFilter(EntityMetadata.EntityInfo info) throws HBaseException; + public abstract Filter getFilter(EntityInfo info) throws HBaseException; public String toString() { return "["+this.getClass().getSimpleName()+"]"; @@ -157,8 +163,8 @@ public PropertyComparison(String prop, Object val, ColumnMatchFilter.CompareOp o this.op = op; } - public Filter getFilter(EntityMetadata.EntityInfo entityInfo) throws HBaseException { - EntityMetadata.FieldMapping mapping = entityInfo.getPropertyMapping(this.property); + public Filter getFilter(EntityInfo entityInfo) throws HBaseException { + FieldMapping mapping = entityInfo.getPropertyMapping(this.property); if (mapping == null) { throw new MappingException( entityInfo.getEntityClass(), String.format("No mapping for criteria! class=%s, property=%s", @@ -190,7 +196,7 @@ public RequireExpression(Expression required) { this.required = required; } - public Filter getFilter(EntityMetadata.EntityInfo entityInfo) throws HBaseException { + public Filter getFilter(EntityInfo entityInfo) throws HBaseException { if (log.isDebugEnabled()) log.debug(String.format("Adding filter: WhileMatchFilter for expr %s", this.required.toString())); @@ -233,7 +239,7 @@ public CompoundExpression(boolean reqAll) { this.oper = FilterList.Operator.MUST_PASS_ONE; } - public Filter getFilter(EntityMetadata.EntityInfo entityInfo) throws HBaseException { + public Filter getFilter(EntityInfo entityInfo) throws HBaseException { FilterList newFilter = new FilterList(this.oper, new ArrayList()); for (Expression expr : this.subconditions) { newFilter.addFilter(expr.getFilter(entityInfo)); diff --git a/src/java/meetup/beeno/EntityIndexer.java b/src/java/meetup/beeno/EntityIndexer.java index 973f2ea..a447a03 100644 --- a/src/java/meetup/beeno/EntityIndexer.java +++ b/src/java/meetup/beeno/EntityIndexer.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; +import meetup.beeno.mapping.IndexMapping; import meetup.beeno.util.HUtil; import meetup.beeno.util.PBUtil; @@ -42,7 +43,7 @@ public class EntityIndexer { private boolean invertDate = false; private List extraFields; - public EntityIndexer(EntityMetadata.IndexMapping mapping) { + public EntityIndexer(IndexMapping mapping) { this.indexTable = mapping.getTableName(); this.primaryField = new HUtil.HCol(mapping.getPrimaryField().getFamily(), mapping.getPrimaryField().getColumn()); diff --git a/src/java/meetup/beeno/EntityMetadata.java b/src/java/meetup/beeno/EntityMetadata.java deleted file mode 100644 index bfd61ce..0000000 --- a/src/java/meetup/beeno/EntityMetadata.java +++ /dev/null @@ -1,486 +0,0 @@ -package meetup.beeno; - -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.io.IOException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Pattern; - -import meetup.beeno.util.HUtil; - -import org.apache.hadoop.hbase.client.HTable; -import org.apache.hadoop.hbase.client.HTablePool; -import org.apache.log4j.Logger; - -/** - * Central cache for parsed mapping metadata on entity classes. Given an entity class reference, - * this will parse out {@link HBaseEntity}. {@link HRowKey}, {@link HProperty} annotations for the class - * and cache the results for future queries. - * - * @author garyh - * - */ -public class EntityMetadata { - - private static Logger log = Logger.getLogger(EntityMetadata.class.getName()); - - private static EntityMetadata instance = null; - - public static enum PropertyType { - string(String.class), - int_type(Integer.TYPE), - float_type(Float.TYPE), - double_type(Double.TYPE), - long_type(Long.TYPE); - - private Class clazz = null; - - PropertyType(Class clazz) { this.clazz = clazz; } - public Class getTypeClass() { return this.clazz; } - }; - - private Map mappings = new ConcurrentHashMap(); - private HTablePool pool = new HTablePool(); - - private EntityMetadata() { - - } - - /** - * Returns the annotated HBase metadata for the given entity class. If the class is not - * yet parsed, it will be parsed and added to the internal mapping. - * - * @param entityClass - * @return - */ - public EntityInfo getInfo(Class entityClass) throws MappingException { - EntityInfo info = this.mappings.get(entityClass); - - if (info == null) { - info = parseEntity(entityClass); - mappings.put(entityClass, info); - } - - return info; - } - - - /** - * Reads all mapping annotations from the passed in class, to build up - * a set of the HBase table and column associations. - * - * @param clazz - * @return - * @throws MappingException - */ - protected EntityInfo parseEntity(Class clazz) throws MappingException { - // lookup any class mappings - HBaseEntity classTable = (HBaseEntity) clazz.getAnnotation(HBaseEntity.class); - if (classTable == null) { - throw new MappingException(clazz, "Not an entity class!"); - } - EntityInfo info = new EntityInfo(clazz); - info.setTablename(classTable.name()); - - // lookup any property mappings for table fields and indexes - parseProperties(clazz, info); - - // make sure we have a mapping for the row key - if (info.getKeyProperty() == null) { - throw new MappingException(clazz, "Missing required annotation for HTable row key property"); - } - - return info; - } - - - /** - * Examines the java bean properties for the class, looking for column mappings - * and a mapping for the row key. - * @param clazz - * @param info - */ - protected void parseProperties(Class clazz, EntityInfo info) throws MappingException { - try { - BeanInfo clazzInfo = Introspector.getBeanInfo(clazz); - - for (PropertyDescriptor prop : clazzInfo.getPropertyDescriptors()) { - Method readMethod = prop.getReadMethod(); - if (readMethod != null) { - parseMethod(prop, readMethod, info); - } - - Method writeMethod = prop.getWriteMethod(); - if (writeMethod != null) { - parseMethod(prop, writeMethod, info); - } - } - } - catch (IntrospectionException ie) { - log.error("Failed to get BeanInfo: "+ie.getMessage()); - } - } - - protected void parseMethod(PropertyDescriptor prop, Method meth, EntityInfo info) - throws MappingException { - // see if this is mapped to the row key -- if so it's not allowed to be a field - HRowKey key = (HRowKey) meth.getAnnotation(HRowKey.class); - if (key != null) { - // check for a duplicate mapping (composite keys not supported) - if (info.getKeyProperty() != null && !prop.equals(info.getKeyProperty())) { - throw new MappingException( info.getEntityClass(), - String.format("Duplicate mappings for table row key: %s, %s", - info.getKeyProperty().getName(), prop.getName()) ); - } - info.setKeyProperty(prop); - return; - } - - // check for property mapping - HProperty propAnnotation = (HProperty) meth.getAnnotation(HProperty.class); - if (propAnnotation != null) { - String fieldname = fieldToString(propAnnotation); - PropertyDescriptor currentMapped = info.getFieldProperty(fieldname); - // check for a duplicate mapping - if (currentMapped != null && !prop.equals(currentMapped)) { - throw new MappingException( info.getEntityClass(), - String.format("Duplicate mappings for table field: %s, %s", currentMapped.getName(), prop.getName()) ); - } - String typeName = propAnnotation.type(); - PropertyType type = null; - if (typeName != null && !"".equals(typeName.trim())) { - try { - type = PropertyType.valueOf(typeName); - } - catch (IllegalArgumentException iae) { - throw new MappingException( info.getEntityClass(), - String.format("Invalid property type ('%s') for '%s'", typeName, prop.getName()) ); - } - } - - info.addProperty(propAnnotation, prop, type); - } - - } - - protected String fieldToString(HProperty prop) { - StringBuilder builder = new StringBuilder(prop.family()).append(":"); - if (prop.name() != null && !"*".equals(prop.name())) - builder.append(prop.name()); - - return builder.toString(); - } - - - /** - * Returns the main metadata instance. Ignoring synchronization here - * as having a few copies initially isn't too bad. - * @return - */ - public static EntityMetadata getInstance() { - if (instance == null) - instance = new EntityMetadata(); - - return instance; - } - - - /* *************** Mapping Metadata Classes *************** */ - - /** - * Stores an annotated {@link HProperty} mapping of a JavaBean property - * in the entity class to an HBase table column. This maps a single Java object - * instance to a single column. - */ - public static class FieldMapping { - protected String family = null; - protected String column = null; - protected String fieldname = null; - protected PropertyDescriptor beanProperty = null; - public FieldMapping(HProperty prop, PropertyDescriptor beanProperty) { - this.family = prop.family(); - this.column = prop.name(); - this.fieldname = this.family+":"+this.column; - this.beanProperty = beanProperty; - } - - public boolean matches(String fieldname) { return this.fieldname.equals(fieldname); } - public PropertyDescriptor getBeanProperty() { return this.beanProperty; } - public String getFamily() { return this.family; } - public String getColumn() { return this.column; } - public String getFieldName() { return this.fieldname; } - - public static FieldMapping get(HProperty prop, PropertyDescriptor beanProperty) { - if (Map.class.isAssignableFrom(beanProperty.getPropertyType())) { - return new MapField(prop, beanProperty); - } - else if (Collection.class.isAssignableFrom(beanProperty.getPropertyType())) { - return new ListField(prop, beanProperty); - } - - return new FieldMapping(prop, beanProperty); - } - } - - - /** - * Represents an annotated {@link HProperty} mapping of a JavaBean java.util.Collection type property - * to multiple indexed columns in an HBase table. The multiple values will be mapped to columns - * based on the value index in the collection: - * [column family]:[column name]_[index number] - * - * Due to the mapping to multiple columns, these property types are not easily covered by HBase - * secondary indexes and should not be used as query criteria. - * - * @author garyh - * - */ - public static class ListField extends FieldMapping { - protected Pattern fieldRegex = null; - public ListField(HProperty prop, PropertyDescriptor beanProperty) { - super(prop, beanProperty); - this.fieldRegex = Pattern.compile(this.family+":"+this.column+"_\\d+"); - } - - public boolean matches(String fieldname) { return this.fieldRegex.matcher(fieldname).matches(); } - } - - - /** - * Represents an annotated {@link HProperty} mapping of a JavaBean java.util.Map type property - * to multiple columns (one per Map key) in an HBase table. The Map entries will be mapped to columns - * in the specified column family, using the convention: - * [column family]:[entry key] - * - * Like the ListField mapping, these property types cannot easily be indexed using HBase secondary indexes, - * due to the dynamic nature of the column names. So these properties should not be used as query criteria. - * - * @author garyh - * - */ - public static class MapField extends FieldMapping { - protected Pattern fieldRegex = null; - public MapField(HProperty prop, PropertyDescriptor beanProperty) { - super(prop, beanProperty); - if (this.column == null || this.column.equals("*")) - this.column = ""; - - this.fieldRegex = Pattern.compile(this.family+":"+this.column+".+"); - // fieldname should ignore wildcard pattern - this.fieldname = this.family + ":"; - } - - public boolean matches(String fieldname) { return this.fieldRegex.matcher(fieldname).matches(); } - } - - /** - * Represents an index configuration annotated on an entity property - * @author garyh - * - */ - public static class IndexMapping { - protected String indexTable; - protected FieldMapping primaryField; - protected HUtil.HCol dateCol; - protected boolean invertDate = false; - protected List extraFields = new ArrayList(); - protected EntityIndexer generator; - - public IndexMapping(String baseTable, FieldMapping baseField, HIndex indexAnnotation) { - this.indexTable = String.format("%s-by_%s", baseTable, baseField.getColumn()); - this.primaryField = baseField; - for (String col : indexAnnotation.extra_cols()) { - HUtil.HCol hcol = HUtil.HCol.parse(col); - if (hcol != null) - this.extraFields.add( hcol ); - } - - if (indexAnnotation.date_col() != null && indexAnnotation.date_col().length() > 0) - this.dateCol = HUtil.HCol.parse(indexAnnotation.date_col()); - this.invertDate = indexAnnotation.date_invert(); - - this.generator = new EntityIndexer(this); - } - - public String getTableName() { return this.indexTable; } - public FieldMapping getPrimaryField() { return this.primaryField; } - public HUtil.HCol getDateField() { return this.dateCol; } - public boolean isDateInverted() { return this.invertDate; } - public List getExtraFields() { return this.extraFields; } - public EntityIndexer getGenerator() { return this.generator; } - } - - - /** - * Encapsulates the mapping of an entity class and its properties - * to an HBase table and columns. - * - * @author garyh - * - */ - public static class EntityInfo { - private Class entityClass = null; - private String table = null; - private PropertyDescriptor keyProperty = null; - - private List mappedProps = new ArrayList(); - private Map propertiesByName = new HashMap(); - private Map fieldsByProperty = new HashMap(); - private Map typesByProperty = new HashMap(); - private Map> indexesByProperty = new HashMap>(); - - public EntityInfo(Class clazz) { - this.entityClass = clazz; - } - - public Class getEntityClass() { return this.entityClass; } - - /** - * Returns the HBase table identified by the entity's {@link HBaseEntity} - * annotation. - * @return - */ - public String getTablename() { return this.table; } - public void setTablename(String tablename) { this.table = tablename; } - - /** - * Returns the java bean properties mapped by the entity's {@link HRowKey} - * annotation. - * - * @return - */ - public PropertyDescriptor getKeyProperty() { return this.keyProperty; } - public void setKeyProperty(PropertyDescriptor prop) { this.keyProperty = prop; } - - public void addProperty(HProperty mapping, PropertyDescriptor prop, PropertyType type) { - FieldMapping field = FieldMapping.get(mapping, prop); - this.mappedProps.add(field); - this.propertiesByName.put(prop.getName(), prop); - this.fieldsByProperty.put(prop, field); - if (type != null) - this.typesByProperty.put(prop, type); - - // !!! ADD HANDLING FOR INDEX ANNOTATIONS !!! - HIndex[] indexes = mapping.indexes(); - if (indexes != null && indexes.length > 0) { - for (HIndex idx : indexes) - addIndex( new IndexMapping(this.table, field, idx), prop ); - } - } - - public void addIndex(IndexMapping index, PropertyDescriptor prop) { - List curIndexes = this.indexesByProperty.get(prop); - if (curIndexes == null) { - curIndexes = new ArrayList(); - this.indexesByProperty.put(prop, curIndexes); - } - - curIndexes.add(index); - } - - public PropertyDescriptor getFieldProperty(String fieldname) { - for (FieldMapping mapping : this.mappedProps) { - if (mapping.matches(fieldname)) { - return mapping.getBeanProperty(); - } - } - - return null; - } - - public List getMappedFields() { return this.mappedProps; } - - public PropertyDescriptor getProperty(String propName) { - return this.propertiesByName.get(propName); - } - - public FieldMapping getPropertyMapping(String propName) { - if (this.propertiesByName.get(propName) != null) { - return this.fieldsByProperty.get( this.propertiesByName.get(propName) ); - } - - return null; - } - - public PropertyType getPropertyType(PropertyDescriptor prop) { - return this.typesByProperty.get(prop); - } - - public List getPropertyIndexes(String propname) { - PropertyDescriptor prop = getProperty(propname); - if (prop != null) - return getPropertyIndexes(prop); - - return null; - } - - public List getPropertyIndexes(PropertyDescriptor prop) { - return this.indexesByProperty.get(prop); - } - - public IndexMapping getFirstPropertyIndex(String propname) { - PropertyDescriptor prop = getProperty(propname); - if (prop != null) - return getFirstPropertyIndex(prop); - - return null; - } - - public IndexMapping getFirstPropertyIndex(PropertyDescriptor prop) { - List indexes = getPropertyIndexes(prop); - if (indexes != null && indexes.size() > 0) - return indexes.get(0); - - return null; - } - - public Map> getIndexesByProperty() { - return this.indexesByProperty; - } - - /** - * Returns all index mappings present on this entity class - * @return - */ - public List getMappedIndexes() { - List indexes = new ArrayList(this.indexesByProperty.size()); - for (List propIndexes : this.indexesByProperty.values()) - indexes.addAll(propIndexes); - - return indexes; - } - - public boolean isGettable(String fieldname) { - PropertyDescriptor prop = getFieldProperty(fieldname); - return (prop != null && prop.getReadMethod() != null); - } - - public boolean isSettable(String fieldname) { - PropertyDescriptor prop = getFieldProperty(fieldname); - return (prop != null && prop.getWriteMethod() != null); - } - - public Collection getColumnFamilyNames() { - if (this.columnFamilies == null) { - Set names = new HashSet(); - for (FieldMapping mapping : this.mappedProps) - names.add(mapping.getFamily()); - - this.columnFamilies = names; - } - - return this.columnFamilies; - } - private Set columnFamilies = null; - } -} diff --git a/src/java/meetup/beeno/EntityService.java b/src/java/meetup/beeno/EntityService.java index 4220956..3932b87 100644 --- a/src/java/meetup/beeno/EntityService.java +++ b/src/java/meetup/beeno/EntityService.java @@ -14,6 +14,10 @@ import java.util.SortedSet; import java.util.TreeSet; +import meetup.beeno.mapping.EntityInfo; +import meetup.beeno.mapping.EntityMetadata; +import meetup.beeno.mapping.FieldMapping; +import meetup.beeno.mapping.IndexMapping; import meetup.beeno.util.HUtil; import meetup.beeno.util.PBUtil; @@ -58,7 +62,7 @@ public class EntityService { } protected Class clazz; - private EntityMetadata.EntityInfo defaultInfo; + private EntityInfo defaultInfo; /** * Crappy duplication of class parameter to work around type erasure. @@ -73,7 +77,7 @@ public EntityService(Class clazz) { * instantiated so we don't block on HTable operations (to scan metadata) * unnecessarily. */ - protected EntityMetadata.EntityInfo getInfo() throws MappingException { + protected EntityInfo getInfo() throws MappingException { if (this.defaultInfo == null) { this.defaultInfo = EntityMetadata.getInstance().getInfo(this.clazz); } @@ -89,7 +93,7 @@ protected EntityMetadata.EntityInfo getInfo() throws MappingException { */ public T get(String rowKey) throws HBaseException { T entity = null; - EntityMetadata.EntityInfo info = getInfo(); + EntityInfo info = getInfo(); HTable table = null; try { table = HUtil.getTable( info.getTablename() ); @@ -160,7 +164,7 @@ protected T newEntityInstance(Result row) throws Exception { */ public void populate(T entity, Result res) throws HBaseException { // set the row key - EntityMetadata.EntityInfo info = EntityMetadata.getInstance().getInfo(entity.getClass()); + EntityInfo info = EntityMetadata.getInstance().getInfo(entity.getClass()); PropertyDescriptor keyProp = info.getKeyProperty(); writeProperty(entity, keyProp, res.getRow(), false); @@ -261,7 +265,7 @@ public Query query(Class entityClass, QueryOpts opts) throws Map */ public void save(T entity) throws HBaseException { Put update = getUpdateForEntity(entity); - EntityMetadata.EntityInfo info = EntityMetadata.getInstance().getInfo(entity.getClass()); + EntityInfo info = EntityMetadata.getInstance().getInfo(entity.getClass()); // commit the update List puts = new ArrayList(1); @@ -279,7 +283,7 @@ public void save(T entity) throws HBaseException { * @throws HBaseException */ public void delete(String rowKey) throws HBaseException { - EntityMetadata.EntityInfo info = getInfo(); + EntityInfo info = getInfo(); // commit the update HTable table = null; @@ -303,7 +307,7 @@ public void delete(String rowKey) throws HBaseException { /** * Updates any indexes based on entity annotations for the instance */ - public void index(Put update, EntityMetadata.EntityInfo info) throws HBaseException { + public void index(Put update, EntityInfo info) throws HBaseException { List uplist = new ArrayList(1); uplist.add(update); index(uplist, info); @@ -313,17 +317,17 @@ public void index(Put update, EntityMetadata.EntityInfo info) throws HBaseExcept /** * Updates any indexes based on entity annotations for the instance */ - public void index(List updates, EntityMetadata.EntityInfo info) throws HBaseException { + public void index(List updates, EntityInfo info) throws HBaseException { if (updates == null || updates.size() == 0 || info == null) { log.info("Updates or EntityInfo is NULL!"); return; } log.info("Calling index for entity "+info.getEntityClass().getName()); - List indexes = info.getMappedIndexes(); + List indexes = info.getMappedIndexes(); if (indexes != null && indexes.size() > 0) { Map> updatesByTable = new HashMap>(); - for (EntityMetadata.IndexMapping idx : indexes) { + for (IndexMapping idx : indexes) { EntityIndexer indexer = idx.getGenerator(); if (indexer != null) { @@ -392,7 +396,7 @@ public void saveAll(List entities) throws HBaseException { return; List updates = new ArrayList(entities.size()); - EntityMetadata.EntityInfo info = null; + EntityInfo info = null; for (T entity : entities) { if (info == null) info = EntityMetadata.getInstance().getInfo(entity.getClass()); @@ -429,7 +433,7 @@ public int update(Query query, EntityUpdate updater) throws HBaseException { protected Put getUpdateForEntity(T entity) throws HBaseException { // get the row key for the update - EntityMetadata.EntityInfo entityInfo = EntityMetadata.getInstance().getInfo(entity.getClass()); + EntityInfo entityInfo = EntityMetadata.getInstance().getInfo(entity.getClass()); PropertyDescriptor keyprop = entityInfo.getKeyProperty(); // row keys are _not_ encoded as proto bufs byte[] rowKey = readProperty(entity, keyprop, false); @@ -442,7 +446,7 @@ protected Put getUpdateForEntity(T entity) throws HBaseException { Put update = new Put(rowKey); // setup each field - for (EntityMetadata.FieldMapping field : entityInfo.getMappedFields()) { + for (FieldMapping field : entityInfo.getMappedFields()) { PropertyDescriptor prop = field.getBeanProperty(); String fieldname = field.getColumn(); // allow multiple values for collections diff --git a/src/java/meetup/beeno/Query.java b/src/java/meetup/beeno/Query.java index 3e50a20..6f6cc13 100644 --- a/src/java/meetup/beeno/Query.java +++ b/src/java/meetup/beeno/Query.java @@ -3,6 +3,9 @@ import java.util.ArrayList; import java.util.List; +import meetup.beeno.mapping.EntityInfo; +import meetup.beeno.mapping.EntityMetadata; + import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.filter.Filter; @@ -21,8 +24,10 @@ public class Query { public static Logger log = Logger.getLogger(Query.class); - protected EntityMetadata.EntityInfo entityInfo = null; + protected EntityInfo entityInfo = null; protected QueryOpts opts = null; + protected Criteria criteria = new Criteria(); + protected Criteria indexCriteria = new Criteria(); protected EntityService service = null; public Query(EntityService service, Class entityClass) throws MappingException { @@ -46,15 +51,25 @@ public void setOptions(QueryOpts opts) { this.opts = opts; } - public Query add(Criteria.Expression expression) { - this.opts.addCriteria(expression); + public Query where(Criteria.Expression expression) { + this.criteria.add(expression); + return this; + } + + /** + * Specifies an indexed expression to use for the query + * @return + * @throws HBaseException + */ + public Query using(Criteria.Expression expression) { + this.indexCriteria.add(expression); return this; } public List execute() throws HBaseException { long t1 = System.nanoTime(); List entities = new ArrayList(); - FilterList baseFilter = getCriteriaFilter(this.opts.getCriteria().getExpressions()); + FilterList baseFilter = getCriteriaFilter(this.criteria.getExpressions()); ResultScanner scanner = null; int processCnt = 0; @@ -101,7 +116,7 @@ public T executeSingle() throws HBaseException { protected QueryStrategy getStrategy() { QueryStrategy strat = null; - if (this.opts.shouldUseIndex()) + if (this.opts.shouldUseIndex() && !this.indexCriteria.isEmpty()) strat = new ScanByIndex(); else strat = new ScanNoIndex(); diff --git a/src/java/meetup/beeno/QueryOpts.java b/src/java/meetup/beeno/QueryOpts.java index 3c85c6f..434d07e 100644 --- a/src/java/meetup/beeno/QueryOpts.java +++ b/src/java/meetup/beeno/QueryOpts.java @@ -19,7 +19,6 @@ public class QueryOpts implements Externalizable { private Long startTime = null; private int pageSize = DEFAULT_PAGE_SIZE; private boolean useIndex = true; - private Criteria criteria = new Criteria(); public QueryOpts() {} @@ -56,13 +55,6 @@ public void setUseIndex( boolean useIndex ) { public int getPageSize() { return this.pageSize; } public void setPageSize(int size) { this.pageSize = size; } - public Criteria getCriteria() { return this.criteria; } - public void setCriteria(Criteria criteria) { this.criteria = criteria; } - - public void addCriteria(Criteria.Expression expression) { - this.criteria.add(expression); - } - @Override public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException { @@ -77,7 +69,6 @@ public void readExternal( ObjectInput in ) throws IOException, startTime = IOUtil.readLong(in); pageSize = in.readInt(); useIndex = in.readBoolean(); - criteria = (in.readBoolean() ? null : (Criteria)in.readObject()); } @Override @@ -90,7 +81,6 @@ public void writeExternal( ObjectOutput out ) throws IOException { IOUtil.writeNullable(out, this.startTime); out.writeInt(this.pageSize); out.writeBoolean(this.useIndex); - IOUtil.writeNullable(out, this.criteria); } } \ No newline at end of file diff --git a/src/java/meetup/beeno/QueryStrategy.java b/src/java/meetup/beeno/QueryStrategy.java index be0f897..cf96f51 100644 --- a/src/java/meetup/beeno/QueryStrategy.java +++ b/src/java/meetup/beeno/QueryStrategy.java @@ -3,6 +3,8 @@ */ package meetup.beeno; +import meetup.beeno.mapping.EntityInfo; + import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.filter.Filter; @@ -12,7 +14,7 @@ */ public interface QueryStrategy { - ResultScanner createScanner(EntityMetadata.EntityInfo info, QueryOpts opts, Filter baseFilter) + ResultScanner createScanner(EntityInfo info, QueryOpts opts, Filter baseFilter) throws QueryException; } diff --git a/src/java/meetup/beeno/ScanByIndex.java b/src/java/meetup/beeno/ScanByIndex.java index c376e0f..8c14578 100644 --- a/src/java/meetup/beeno/ScanByIndex.java +++ b/src/java/meetup/beeno/ScanByIndex.java @@ -9,6 +9,8 @@ import java.util.Iterator; import java.util.List; +import meetup.beeno.mapping.EntityInfo; +import meetup.beeno.mapping.IndexMapping; import meetup.beeno.util.HUtil; import meetup.beeno.util.PBUtil; @@ -34,7 +36,7 @@ public class ScanByIndex implements QueryStrategy { * @see com.meetup.db.hbase.QueryStrategy#createScanner(com.meetup.db.hbase.EntityMetadata.EntityInfo, org.apache.hadoop.hbase.filter.RowFilterInterface) */ @Override - public ResultScanner createScanner( EntityMetadata.EntityInfo info, QueryOpts opts, Filter baseFilter ) + public ResultScanner createScanner( EntityInfo info, QueryOpts opts, Filter baseFilter ) throws QueryException { ResultScanner scanner = null; @@ -47,7 +49,7 @@ public ResultScanner createScanner( EntityMetadata.EntityInfo info, QueryOpts op Criteria.PropertyExpression indexedExpr = selectIndexedExpression(info, opts.getCriteria().getExpressions()); if (indexedExpr != null) { log.debug("Using indexed expression: "+indexedExpr); - EntityMetadata.IndexMapping idx = info.getFirstPropertyIndex(indexedExpr.getProperty()); + IndexMapping idx = info.getFirstPropertyIndex(indexedExpr.getProperty()); if (idx != null) log.debug("Using index table: "+idx.getTableName()); @@ -130,7 +132,7 @@ protected ResultScanner getIndexScanner(String tablename, * @param expressions * @return */ - protected Criteria.PropertyExpression selectIndexedExpression(EntityMetadata.EntityInfo info, + protected Criteria.PropertyExpression selectIndexedExpression(EntityInfo info, List expressions) { // first look for a direct match expression for (Criteria.Expression e : expressions) { @@ -162,7 +164,7 @@ protected Criteria.PropertyExpression selectIndexedExpression(EntityMetadata.Ent } - protected byte[] getStartRow(QueryOpts opts, Criteria.PropertyExpression expr, EntityMetadata.IndexMapping idx) throws HBaseException { + protected byte[] getStartRow(QueryOpts opts, Criteria.PropertyExpression expr, IndexMapping idx) throws HBaseException { if (opts.getStartKey() != null) { return opts.getStartKey(); } diff --git a/src/java/meetup/beeno/ScanNoIndex.java b/src/java/meetup/beeno/ScanNoIndex.java index 4f9dd00..87fa129 100644 --- a/src/java/meetup/beeno/ScanNoIndex.java +++ b/src/java/meetup/beeno/ScanNoIndex.java @@ -2,6 +2,7 @@ import java.io.IOException; +import meetup.beeno.mapping.EntityInfo; import meetup.beeno.util.HUtil; import org.apache.hadoop.hbase.client.HTable; @@ -17,7 +18,7 @@ public ScanNoIndex() { } @Override - public ResultScanner createScanner( EntityMetadata.EntityInfo entityInfo, QueryOpts opts, Filter baseFilter ) + public ResultScanner createScanner( EntityInfo entityInfo, QueryOpts opts, Filter baseFilter ) throws QueryException { ResultScanner scanner = null; diff --git a/src/java/meetup/beeno/mapping/EntityInfo.java b/src/java/meetup/beeno/mapping/EntityInfo.java new file mode 100644 index 0000000..839fa12 --- /dev/null +++ b/src/java/meetup/beeno/mapping/EntityInfo.java @@ -0,0 +1,179 @@ +package meetup.beeno.mapping; + +import java.beans.PropertyDescriptor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import meetup.beeno.HBaseEntity; +import meetup.beeno.HIndex; +import meetup.beeno.HProperty; +import meetup.beeno.HRowKey; +import meetup.beeno.mapping.EntityMetadata.PropertyType; + +/** + * Encapsulates the mapping of an entity class and its properties + * to an HBase table and columns. + * + * @author garyh + * + */ +public class EntityInfo { + private Class entityClass = null; + private String table = null; + private PropertyDescriptor keyProperty = null; + + private List mappedProps = new ArrayList(); + private Map propertiesByName = new HashMap(); + private Map fieldsByProperty = new HashMap(); + private Map typesByProperty = new HashMap(); + private Map> indexesByProperty = new HashMap>(); + + public EntityInfo(Class clazz) { + this.entityClass = clazz; + } + + public Class getEntityClass() { return this.entityClass; } + + /** + * Returns the HBase table identified by the entity's {@link HBaseEntity} + * annotation. + * @return + */ + public String getTablename() { return this.table; } + public void setTablename(String tablename) { this.table = tablename; } + + /** + * Returns the java bean properties mapped by the entity's {@link HRowKey} + * annotation. + * + * @return + */ + public PropertyDescriptor getKeyProperty() { return this.keyProperty; } + public void setKeyProperty(PropertyDescriptor prop) { this.keyProperty = prop; } + + public void addProperty(HProperty mapping, PropertyDescriptor prop, PropertyType type) { + FieldMapping field = FieldMapping.get(mapping, prop); + this.mappedProps.add(field); + this.propertiesByName.put(prop.getName(), prop); + this.fieldsByProperty.put(prop, field); + if (type != null) + this.typesByProperty.put(prop, type); + + // !!! ADD HANDLING FOR INDEX ANNOTATIONS !!! + HIndex[] indexes = mapping.indexes(); + if (indexes != null && indexes.length > 0) { + for (HIndex idx : indexes) + addIndex( new IndexMapping(this.table, field, idx), prop ); + } + } + + public void addIndex(IndexMapping index, PropertyDescriptor prop) { + List curIndexes = this.indexesByProperty.get(prop); + if (curIndexes == null) { + curIndexes = new ArrayList(); + this.indexesByProperty.put(prop, curIndexes); + } + + curIndexes.add(index); + } + + public PropertyDescriptor getFieldProperty(String fieldname) { + for (FieldMapping mapping : this.mappedProps) { + if (mapping.matches(fieldname)) { + return mapping.getBeanProperty(); + } + } + + return null; + } + + public List getMappedFields() { return this.mappedProps; } + + public PropertyDescriptor getProperty(String propName) { + return this.propertiesByName.get(propName); + } + + public FieldMapping getPropertyMapping(String propName) { + if (this.propertiesByName.get(propName) != null) { + return this.fieldsByProperty.get( this.propertiesByName.get(propName) ); + } + + return null; + } + + public PropertyType getPropertyType(PropertyDescriptor prop) { + return this.typesByProperty.get(prop); + } + + public List getPropertyIndexes(String propname) { + PropertyDescriptor prop = getProperty(propname); + if (prop != null) + return getPropertyIndexes(prop); + + return null; + } + + public List getPropertyIndexes(PropertyDescriptor prop) { + return this.indexesByProperty.get(prop); + } + + public IndexMapping getFirstPropertyIndex(String propname) { + PropertyDescriptor prop = getProperty(propname); + if (prop != null) + return getFirstPropertyIndex(prop); + + return null; + } + + public IndexMapping getFirstPropertyIndex(PropertyDescriptor prop) { + List indexes = getPropertyIndexes(prop); + if (indexes != null && indexes.size() > 0) + return indexes.get(0); + + return null; + } + + public Map> getIndexesByProperty() { + return this.indexesByProperty; + } + + /** + * Returns all index mappings present on this entity class + * @return + */ + public List getMappedIndexes() { + List indexes = new ArrayList(this.indexesByProperty.size()); + for (List propIndexes : this.indexesByProperty.values()) + indexes.addAll(propIndexes); + + return indexes; + } + + public boolean isGettable(String fieldname) { + PropertyDescriptor prop = getFieldProperty(fieldname); + return (prop != null && prop.getReadMethod() != null); + } + + public boolean isSettable(String fieldname) { + PropertyDescriptor prop = getFieldProperty(fieldname); + return (prop != null && prop.getWriteMethod() != null); + } + + public Collection getColumnFamilyNames() { + if (this.columnFamilies == null) { + Set names = new HashSet(); + for (FieldMapping mapping : this.mappedProps) + names.add(mapping.getFamily()); + + this.columnFamilies = names; + } + + return this.columnFamilies; + } + private Set columnFamilies = null; +} \ No newline at end of file diff --git a/src/java/meetup/beeno/mapping/EntityMetadata.java b/src/java/meetup/beeno/mapping/EntityMetadata.java new file mode 100644 index 0000000..3e29196 --- /dev/null +++ b/src/java/meetup/beeno/mapping/EntityMetadata.java @@ -0,0 +1,192 @@ +package meetup.beeno.mapping; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import meetup.beeno.HBaseEntity; +import meetup.beeno.HProperty; +import meetup.beeno.HRowKey; +import meetup.beeno.MappingException; + +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.HTablePool; +import org.apache.log4j.Logger; + +/** + * Central cache for parsed mapping metadata on entity classes. Given an entity class reference, + * this will parse out {@link HBaseEntity}. {@link HRowKey}, {@link HProperty} annotations for the class + * and cache the results for future queries. + * + * @author garyh + * + */ +public class EntityMetadata { + + private static Logger log = Logger.getLogger(EntityMetadata.class.getName()); + + private static EntityMetadata instance = null; + + public static enum PropertyType { + string(String.class), + int_type(Integer.TYPE), + float_type(Float.TYPE), + double_type(Double.TYPE), + long_type(Long.TYPE); + + private Class clazz = null; + + PropertyType(Class clazz) { this.clazz = clazz; } + public Class getTypeClass() { return this.clazz; } + }; + + private Map mappings = new ConcurrentHashMap(); + private HTablePool pool = new HTablePool(); + + private EntityMetadata() { + + } + + /** + * Returns the annotated HBase metadata for the given entity class. If the class is not + * yet parsed, it will be parsed and added to the internal mapping. + * + * @param entityClass + * @return + */ + public EntityInfo getInfo(Class entityClass) throws MappingException { + EntityInfo info = this.mappings.get(entityClass); + + if (info == null) { + info = parseEntity(entityClass); + mappings.put(entityClass, info); + } + + return info; + } + + + /** + * Reads all mapping annotations from the passed in class, to build up + * a set of the HBase table and column associations. + * + * @param clazz + * @return + * @throws MappingException + */ + protected EntityInfo parseEntity(Class clazz) throws MappingException { + // lookup any class mappings + HBaseEntity classTable = (HBaseEntity) clazz.getAnnotation(HBaseEntity.class); + if (classTable == null) { + throw new MappingException(clazz, "Not an entity class!"); + } + EntityInfo info = new EntityInfo(clazz); + info.setTablename(classTable.name()); + + // lookup any property mappings for table fields and indexes + parseProperties(clazz, info); + + // make sure we have a mapping for the row key + if (info.getKeyProperty() == null) { + throw new MappingException(clazz, "Missing required annotation for HTable row key property"); + } + + return info; + } + + + /** + * Examines the java bean properties for the class, looking for column mappings + * and a mapping for the row key. + * @param clazz + * @param info + */ + protected void parseProperties(Class clazz, EntityInfo info) throws MappingException { + try { + BeanInfo clazzInfo = Introspector.getBeanInfo(clazz); + + for (PropertyDescriptor prop : clazzInfo.getPropertyDescriptors()) { + Method readMethod = prop.getReadMethod(); + if (readMethod != null) { + parseMethod(prop, readMethod, info); + } + + Method writeMethod = prop.getWriteMethod(); + if (writeMethod != null) { + parseMethod(prop, writeMethod, info); + } + } + } + catch (IntrospectionException ie) { + log.error("Failed to get BeanInfo: "+ie.getMessage()); + } + } + + protected void parseMethod(PropertyDescriptor prop, Method meth, EntityInfo info) + throws MappingException { + // see if this is mapped to the row key -- if so it's not allowed to be a field + HRowKey key = (HRowKey) meth.getAnnotation(HRowKey.class); + if (key != null) { + // check for a duplicate mapping (composite keys not supported) + if (info.getKeyProperty() != null && !prop.equals(info.getKeyProperty())) { + throw new MappingException( info.getEntityClass(), + String.format("Duplicate mappings for table row key: %s, %s", + info.getKeyProperty().getName(), prop.getName()) ); + } + info.setKeyProperty(prop); + return; + } + + // check for property mapping + HProperty propAnnotation = (HProperty) meth.getAnnotation(HProperty.class); + if (propAnnotation != null) { + String fieldname = fieldToString(propAnnotation); + PropertyDescriptor currentMapped = info.getFieldProperty(fieldname); + // check for a duplicate mapping + if (currentMapped != null && !prop.equals(currentMapped)) { + throw new MappingException( info.getEntityClass(), + String.format("Duplicate mappings for table field: %s, %s", currentMapped.getName(), prop.getName()) ); + } + String typeName = propAnnotation.type(); + PropertyType type = null; + if (typeName != null && !"".equals(typeName.trim())) { + try { + type = PropertyType.valueOf(typeName); + } + catch (IllegalArgumentException iae) { + throw new MappingException( info.getEntityClass(), + String.format("Invalid property type ('%s') for '%s'", typeName, prop.getName()) ); + } + } + + info.addProperty(propAnnotation, prop, type); + } + + } + + protected String fieldToString(HProperty prop) { + StringBuilder builder = new StringBuilder(prop.family()).append(":"); + if (prop.name() != null && !"*".equals(prop.name())) + builder.append(prop.name()); + + return builder.toString(); + } + + + /** + * Returns the main metadata instance. Ignoring synchronization here + * as having a few copies initially isn't too bad. + * @return + */ + public static EntityMetadata getInstance() { + if (instance == null) + instance = new EntityMetadata(); + + return instance; + } +} diff --git a/src/java/meetup/beeno/mapping/FieldMapping.java b/src/java/meetup/beeno/mapping/FieldMapping.java new file mode 100644 index 0000000..f77da09 --- /dev/null +++ b/src/java/meetup/beeno/mapping/FieldMapping.java @@ -0,0 +1,42 @@ +package meetup.beeno.mapping; + +import java.beans.PropertyDescriptor; +import java.util.Collection; +import java.util.Map; + +import meetup.beeno.HProperty; + +/** + * Stores an annotated {@link HProperty} mapping of a JavaBean property + * in the entity class to an HBase table column. This maps a single Java object + * instance to a single column. + */ +public class FieldMapping { + protected String family = null; + protected String column = null; + protected String fieldname = null; + protected PropertyDescriptor beanProperty = null; + public FieldMapping(HProperty prop, PropertyDescriptor beanProperty) { + this.family = prop.family(); + this.column = prop.name(); + this.fieldname = this.family+":"+this.column; + this.beanProperty = beanProperty; + } + + public boolean matches(String fieldname) { return this.fieldname.equals(fieldname); } + public PropertyDescriptor getBeanProperty() { return this.beanProperty; } + public String getFamily() { return this.family; } + public String getColumn() { return this.column; } + public String getFieldName() { return this.fieldname; } + + public static FieldMapping get(HProperty prop, PropertyDescriptor beanProperty) { + if (Map.class.isAssignableFrom(beanProperty.getPropertyType())) { + return new MapField(prop, beanProperty); + } + else if (Collection.class.isAssignableFrom(beanProperty.getPropertyType())) { + return new ListField(prop, beanProperty); + } + + return new FieldMapping(prop, beanProperty); + } +} \ No newline at end of file diff --git a/src/java/meetup/beeno/mapping/IndexMapping.java b/src/java/meetup/beeno/mapping/IndexMapping.java new file mode 100644 index 0000000..f7e16d2 --- /dev/null +++ b/src/java/meetup/beeno/mapping/IndexMapping.java @@ -0,0 +1,46 @@ +package meetup.beeno.mapping; + +import java.util.ArrayList; +import java.util.List; + +import meetup.beeno.EntityIndexer; +import meetup.beeno.HIndex; +import meetup.beeno.util.HUtil; +import meetup.beeno.util.HUtil.HCol; + +/** + * Represents an index configuration annotated on an entity property + * @author garyh + * + */ +public class IndexMapping { + protected String indexTable; + protected FieldMapping primaryField; + protected HUtil.HCol dateCol; + protected boolean invertDate = false; + protected List extraFields = new ArrayList(); + protected EntityIndexer generator; + + public IndexMapping(String baseTable, FieldMapping baseField, HIndex indexAnnotation) { + this.indexTable = String.format("%s-by_%s", baseTable, baseField.getColumn()); + this.primaryField = baseField; + for (String col : indexAnnotation.extra_cols()) { + HUtil.HCol hcol = HUtil.HCol.parse(col); + if (hcol != null) + this.extraFields.add( hcol ); + } + + if (indexAnnotation.date_col() != null && indexAnnotation.date_col().length() > 0) + this.dateCol = HUtil.HCol.parse(indexAnnotation.date_col()); + this.invertDate = indexAnnotation.date_invert(); + + this.generator = new EntityIndexer(this); + } + + public String getTableName() { return this.indexTable; } + public FieldMapping getPrimaryField() { return this.primaryField; } + public HUtil.HCol getDateField() { return this.dateCol; } + public boolean isDateInverted() { return this.invertDate; } + public List getExtraFields() { return this.extraFields; } + public EntityIndexer getGenerator() { return this.generator; } +} \ No newline at end of file diff --git a/src/java/meetup/beeno/mapping/ListField.java b/src/java/meetup/beeno/mapping/ListField.java new file mode 100644 index 0000000..b749bcd --- /dev/null +++ b/src/java/meetup/beeno/mapping/ListField.java @@ -0,0 +1,28 @@ +package meetup.beeno.mapping; + +import java.beans.PropertyDescriptor; +import java.util.regex.Pattern; + +import meetup.beeno.HProperty; + +/** + * Represents an annotated {@link HProperty} mapping of a JavaBean java.util.Collection type property + * to multiple indexed columns in an HBase table. The multiple values will be mapped to columns + * based on the value index in the collection: + * [column family]:[column name]_[index number] + * + * Due to the mapping to multiple columns, these property types are not easily covered by HBase + * secondary indexes and should not be used as query criteria. + * + * @author garyh + * + */ +public class ListField extends FieldMapping { + protected Pattern fieldRegex = null; + public ListField(HProperty prop, PropertyDescriptor beanProperty) { + super(prop, beanProperty); + this.fieldRegex = Pattern.compile(this.family+":"+this.column+"_\\d+"); + } + + public boolean matches(String fieldname) { return this.fieldRegex.matcher(fieldname).matches(); } +} \ No newline at end of file diff --git a/src/java/meetup/beeno/mapping/MapField.java b/src/java/meetup/beeno/mapping/MapField.java new file mode 100644 index 0000000..22775d3 --- /dev/null +++ b/src/java/meetup/beeno/mapping/MapField.java @@ -0,0 +1,33 @@ +package meetup.beeno.mapping; + +import java.beans.PropertyDescriptor; +import java.util.regex.Pattern; + +import meetup.beeno.HProperty; + +/** + * Represents an annotated {@link HProperty} mapping of a JavaBean java.util.Map type property + * to multiple columns (one per Map key) in an HBase table. The Map entries will be mapped to columns + * in the specified column family, using the convention: + * [column family]:[entry key] + * + * Like the ListField mapping, these property types cannot easily be indexed using HBase secondary indexes, + * due to the dynamic nature of the column names. So these properties should not be used as query criteria. + * + * @author garyh + * + */ +public class MapField extends FieldMapping { + protected Pattern fieldRegex = null; + public MapField(HProperty prop, PropertyDescriptor beanProperty) { + super(prop, beanProperty); + if (this.column == null || this.column.equals("*")) + this.column = ""; + + this.fieldRegex = Pattern.compile(this.family+":"+this.column+".+"); + // fieldname should ignore wildcard pattern + this.fieldname = this.family + ":"; + } + + public boolean matches(String fieldname) { return this.fieldRegex.matcher(fieldname).matches(); } +} \ No newline at end of file diff --git a/src/java/meetup/beeno/util/HUtil.java b/src/java/meetup/beeno/util/HUtil.java index 74cb77b..bbdec8a 100644 --- a/src/java/meetup/beeno/util/HUtil.java +++ b/src/java/meetup/beeno/util/HUtil.java @@ -5,7 +5,8 @@ import java.util.HashSet; import java.util.Set; -import meetup.beeno.EntityMetadata; +import meetup.beeno.mapping.EntityInfo; +import meetup.beeno.mapping.FieldMapping; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.KeyValue; @@ -174,9 +175,9 @@ else if (val instanceof Enum) { } - public static String[] getMappedFamilies(EntityMetadata.EntityInfo info) { + public static String[] getMappedFamilies(EntityInfo info) { Set families = new HashSet(); - for (EntityMetadata.FieldMapping field : info.getMappedFields()) { + for (FieldMapping field : info.getMappedFields()) { families.add(field.getFamily()+":"); }