/**
* CloudGraph Community Edition (CE) License
*
* This is a community release of CloudGraph, a dual-license suite of
* Service Data Object (SDO) 2.1 services designed for relational and
* big-table style "cloud" databases, such as HBase and others.
* This particular copy of the software is released under the
* version 2 of the GNU General Public License. CloudGraph was developed by
* TerraMeta Software, Inc.
*
* Copyright (c) 2013, TerraMeta Software, Inc. All rights reserved.
*
* General License information can be found below.
*
* This distribution may include materials developed by third
* parties. For license and attribution notices for these
* materials, please refer to the documentation that accompanies
* this distribution (see the "Licenses for Third-Party Components"
* appendix) or view the online documentation at
* <http://cloudgraph.org/licenses/>.
*/
package org.cloudgraph.config;
import java.math.BigInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.plasma.sdo.DataFlavor;
import org.plasma.sdo.DataType;
import org.plasma.sdo.PlasmaProperty;
import org.plasma.sdo.ValueConstraint;
import org.plasma.sdo.helper.DataConverter;
import commonj.sdo.DataObject;
import commonj.sdo.Type;
/**
* Encapsulates logic related to access of a configured
* user-defined row key field.
* @author Scott Cinnamond
* @since 0.5.1
*/
public class UserDefinedRowKeyFieldConfig extends KeyFieldConfig {
private static Log log = LogFactory.getLog(UserDefinedRowKeyFieldConfig.class);
private DataGraphConfig dataGraph;
private UserDefinedField userDefinedField;
/** The simple property path with any XPath traversal elements removed */
private String propertyPath;
private PlasmaProperty endpointProperty;
private int maxLength;
public UserDefinedRowKeyFieldConfig(DataGraphConfig dataGraph,
UserDefinedField userDefinedField,
int sequenceNum, int totalFields) {
super(userDefinedField, sequenceNum, totalFields);
this.dataGraph = dataGraph;
this.userDefinedField = userDefinedField;
try {
construct(this.userDefinedField.getPath());
}
catch (IllegalArgumentException e) {
throw new CloudGraphConfigurationException(e);
}
finally {
}
}
/**
*
* Note; not using SDO XPath here as this operates
* on data not metadata and at this point we have only
* metadata to work with. This seems to surface the need
* for XPath processing for SDO types.
* @param xpath
*/
private void construct(String xpath) {
Type contextType = this.getDataGraph().getRootType();
StringBuilder buf = new StringBuilder();
String[] tokens = xpath.split("/");
for (int i = 0; i < tokens.length; i++) {
if (i > 0)
buf.append("/");
String token = tokens[i];
int right = token.indexOf("[");
if (right >= 0) // remove predicate - were just after the path
token = token.substring(0, right);
int attr = token.indexOf("@");
if (attr == 0)
token = token.substring(1);
PlasmaProperty prop = (PlasmaProperty)contextType.getProperty(token);
if (!prop.getType().isDataType()) {
contextType = prop.getType(); // traverse
if (i == tokens.length-1)
throw new CloudGraphConfigurationException("expected xpath '"+xpath+"' termination with a data property not, "
+ prop);
}
else
this.endpointProperty = prop;
buf.append(prop.getName());
}
this.propertyPath = buf.toString();
DataFlavor flavor = this.endpointProperty.getDataFlavor();
DataType dataType = DataType.valueOf(
this.endpointProperty.getType().getName());
switch (flavor) {
case string:
this.maxLength = getStringTypeMaxLength(this.endpointProperty, dataType, flavor);
break;
case integral:
this.maxLength = getIntegralTypeMaxLength(this.endpointProperty,
dataType, flavor);
break;
case real:
this.maxLength = getRealTypeMaxLength(this.endpointProperty, dataType, flavor);
break;
case temporal:
this.maxLength = getTemporalTypeMaxLength(this.endpointProperty, dataType, flavor);
break;
case other:
throw new IllegalArgumentException("data flavor '"
+ flavor + "' not supported for row key fields");
}
}
private int getRealTypeMaxLength(PlasmaProperty prop,
DataType dataType, DataFlavor flavor) {
if (prop.getValueConstraint() == null)
throw new MissingRequiredConstraintException("expected value contraint for property, "
+ prop.getContainingType().toString() + "." + prop.getName() + " - row key fields are fixed length and real properties used in row key fields must be annotated with a value constraint and the total-digits set");
ValueConstraint constraint = prop.getValueConstraint();
if (constraint.getTotalDigits() == null || constraint.getTotalDigits().length() == 0)
throw new MissingRequiredConstraintException("expected value contraint total-digits for real property, "
+ prop.getContainingType().toString() + "." + prop.getName() + " - row key fields are fixed length and real properties used in row key fields must be annotated with a value constraint with the total-digits set");
int totalDigits = Integer.parseInt(constraint.getTotalDigits());
if (totalDigits == 0)
throw new MissingRequiredConstraintException("expected value contraint with non-zero total-digits for real property, "
+ prop.getContainingType().toString() + "." + prop.getName() + " - row key fields are fixed length and real properties used in row key fields must be annotated with a value constraint with the total-digits set");
return Integer.parseInt(constraint.getTotalDigits());
}
private int getStringTypeMaxLength(PlasmaProperty prop,
DataType dataType, DataFlavor flavor) {
if (prop.getValueConstraint() == null)
throw new MissingRequiredConstraintException("expected value contraint for string property, "
+ prop.getContainingType().toString() + "." + prop.getName() + " - row key fields are fixed length and string properties used in row key fields must be annotated with a value constraint and the max-length set");
ValueConstraint constraint = prop.getValueConstraint();
if (constraint.getMaxLength() == null || constraint.getMaxLength().length() == 0)
throw new MissingRequiredConstraintException("expected value contraint max-length for string property, "
+ prop.getContainingType().toString() + "." + prop.getName() + " - row key fields are fixed length and string properties used in row key fields must be annotated with a value constraint with the max-length set");
int maxLength = Integer.parseInt(constraint.getMaxLength());
if (maxLength == 0)
throw new MissingRequiredConstraintException("expected value contraint with non-zero max-length for string property, "
+ prop.getContainingType().toString() + "." + prop.getName() + " - row key fields are fixed length and string properties used in row key fields must be annotated with a value constraint with the max-length set");
return Integer.parseInt(constraint.getMaxLength());
}
private int getIntegralTypeMaxLength(PlasmaProperty prop,
DataType dataType, DataFlavor flavor) {
int result = 0;
if (prop.getValueConstraint() != null && prop.getValueConstraint().getTotalDigits() != null && prop.getValueConstraint().getTotalDigits().length() > 0) {
result = Integer.parseInt(prop.getValueConstraint().getTotalDigits());
if (result == 0) {
log.warn("expected value contraint with non-zero total-digits for integral property, "
+ prop.getContainingType().toString() + "." + prop.getName()
+ " - row key fields are fixed length and integral properties used in row key fields must be annotated with a "
+ "value constraint with the total-digits set - ignoring, using default");
result = getDefaultIntegralTypeMaxLength(prop, dataType, flavor);
}
}
else { // default it and warn
result = getDefaultIntegralTypeMaxLength(prop, dataType, flavor);
}
return result;
}
private int getDefaultIntegralTypeMaxLength(PlasmaProperty prop,
DataType dataType, DataFlavor flavor)
{
int result;
switch (dataType) {
case Short:
log.warn("expected value contraint total-digits for integral property, "
+ prop.getContainingType().toString() + "." + prop.getName()
+ " - row key fields are fixed length and integral properties used in "
+ "row key fields should be annotated with a value constraint with the total-digits set"
+ " - defaulting to max size for datatype, " + dataType
+ ".");
result = String.valueOf(Short.MAX_VALUE).length();
break;
case Int:
log.warn("expected value contraint total-digits for integral property, "
+ prop.getContainingType().toString() + "." + prop.getName()
+ " - row key fields are fixed length and integral properties used in "
+ "row key fields should be annotated with a value constraint with the total-digits set"
+ " - defaulting to max size for datatype, " + dataType
+ ".");
result = String.valueOf(Integer.MAX_VALUE).length();
break;
case Long:
log.warn("expected value contraint total-digits for integral property, "
+ prop.getContainingType().toString() + "." + prop.getName()
+ " - row key fields are fixed length and integral properties used in "
+ "row key fields should be annotated with a value constraint with the total-digits set"
+ " - defaulting to max size for datatype, " + dataType
+ ".");
result = String.valueOf(Long.MAX_VALUE).length();
break;
default:
log.warn("cannot default max-length for datatype, "
+ dataType);
throw new MissingRequiredConstraintException("expected value contraint total-digits for integral "+dataType+" property, "
+ prop.getContainingType().toString() + "." + prop.getName()
+ " - row key fields are fixed length and integral properties used in row key fields must be annotated with a value constraint with the total-digits set");
}
return result;
}
private int getTemporalTypeMaxLength(PlasmaProperty prop,
DataType dataType, DataFlavor flavor) {
switch (dataType) {
case Date:
return DataConverter.FORMAT_PATTERN_DATE.length();
case DateTime:
return DataConverter.FORMAT_PATTERN_DATETIME.length();
case Day:
return DataConverter.FORMAT_PATTERN_DAY.length();
case Month:
return DataConverter.FORMAT_PATTERN_MONTH.length();
case MonthDay:
return DataConverter.FORMAT_PATTERN_MONTHDAY.length();
case Year:
return DataConverter.FORMAT_PATTERN_YEAR.length();
case YearMonth:
return DataConverter.FORMAT_PATTERN_YEARMONTH.length();
case YearMonthDay:
return DataConverter.FORMAT_PATTERN_YEARMONTHDAY.length();
case Time:
return DataConverter.FORMAT_PATTERN_TIME.length();
case Duration:
default:
throw new IllegalArgumentException("temporal datatype '"
+ dataType + "' not supported for row key fields");
}
}
public boolean equals(Object obj) {
UserDefinedRowKeyFieldConfig other = (UserDefinedRowKeyFieldConfig)obj;
return (this.sequenceNum == other.sequenceNum);
}
public int getSequenceNum() {
return sequenceNum;
}
public DataGraphConfig getDataGraph() {
return dataGraph;
}
public UserDefinedField getUserToken() {
return userDefinedField;
}
public String getPathExpression() {
return this.userDefinedField.getPath();
}
public boolean isHash() {
return this.userDefinedField.isHash();
}
public String getPropertyPath() {
return propertyPath;
}
public PlasmaProperty getEndpointProperty() {
return endpointProperty;
}
/**
* Returns a token value from the given Data Graph
* @param dataGraph the data graph
* @return the token value
*/
public byte[] getKeyBytes(
commonj.sdo.DataGraph dataGraph) {
return this.getKeyBytes(dataGraph.getRootObject());
}
/**
* Returns a user defined key value from the
* given data object.
* @param dataObject the root data object
* @return the token value
* @throws UnresolvedPathExpressionException if the
* configured XPath expression resolves to a null value
*/
public byte[] getKeyBytes(
DataObject dataObject)
{
return getKey(dataObject).getBytes(this.charset);
}
public String getKey(
commonj.sdo.DataGraph dataGraph) {
return this.getKey(dataGraph.getRootObject());
}
/**
* Returns a user defined key value from the
* given data object.
* @param dataObject the root data object
* @return the token value
* @throws UnresolvedPathExpressionException if the
* configured XPath expression resolves to a null value
*/
public String getKey(
DataObject dataObject)
{
// FIXME: do we want to invoke a converter here?
// FIXME: do we want to transform this value somehow?
String result = dataObject.getString(
this.getPathExpression());
if (result == null)
throw new UnresolvedPathExpressionException(
"the configured XPath expression '"
+ this.getPathExpression() + "'"
+ " for graph root type '" + dataGraph.getRootType().getName() + "'"
+ " within table '" + dataGraph.getTable().getName() + "'"
+ " resolved to a null value - "
+ "use an XPath expressions which terminate with a mandatory property, "
+ "and return mandatory properties for data graph root types");
return result;
}
/**
* Returns the maximum length allowed for this
* key field.
* @return the maximum length allowed for this
* key field.
*/
@Override
public int getMaxLength() {
return maxLength;
}
@Override
public DataFlavor getDataFlavor() {
return ((PlasmaProperty)this.endpointProperty).getDataFlavor();
}
}