/*
Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.mysql.clusterj.openjpa;
import com.mysql.clusterj.ClusterJException;
import com.mysql.clusterj.ClusterJFatalInternalException;
import com.mysql.clusterj.ClusterJUserException;
import com.mysql.clusterj.core.metadata.AbstractDomainFieldHandlerImpl;
import com.mysql.clusterj.core.query.QueryDomainTypeImpl;
import com.mysql.clusterj.core.spi.DomainTypeHandler;
import com.mysql.clusterj.core.spi.QueryExecutionContext;
import com.mysql.clusterj.core.spi.SessionSPI;
import com.mysql.clusterj.core.spi.ValueHandler;
import com.mysql.clusterj.core.store.IndexScanOperation;
import com.mysql.clusterj.core.store.IndexScanOperation.BoundType;
import com.mysql.clusterj.core.store.Dictionary;
import com.mysql.clusterj.core.store.Operation;
import com.mysql.clusterj.core.store.PartitionKey;
import com.mysql.clusterj.core.store.ResultData;
import com.mysql.clusterj.core.store.ScanFilter;
import com.mysql.clusterj.core.store.Table;
import com.mysql.clusterj.core.store.ScanFilter.BinaryCondition;
import com.mysql.clusterj.core.util.I18NHelper;
import com.mysql.clusterj.core.util.Logger;
import com.mysql.clusterj.core.util.LoggerFactoryService;
import com.mysql.clusterj.query.Predicate;
import com.mysql.clusterj.query.PredicateOperand;
import com.mysql.clusterj.query.QueryDomainType;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
import org.apache.openjpa.jdbc.meta.strats.RelationFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.RelationStrategies;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Index;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.util.IntId;
import org.apache.openjpa.util.LongId;
import org.apache.openjpa.util.ObjectId;
import org.apache.openjpa.util.OpenJPAId;
import org.apache.openjpa.util.StringId;
/**
*
*/
public class NdbOpenJPADomainFieldHandlerImpl extends AbstractDomainFieldHandlerImpl {
/** My message translator */
static final I18NHelper local = I18NHelper.getInstance(NdbOpenJPADomainFieldHandlerImpl.class);
/** My logger */
static final Logger logger = LoggerFactoryService.getFactory().getInstance(NdbOpenJPADomainFieldHandlerImpl.class);
private static com.mysql.clusterj.core.store.Column[] emptyStoreColumns = new com.mysql.clusterj.core.store.Column[] {};
/** The openjpa field mapping for this field */
private FieldMapping fieldMapping;
/** For single-column mappings, the mapped column */
private Column column;
/** The openjpa javaType of this field from org.apache.openjpa.meta.JavaTypes */
private int javaType;
/** The java.lang.reflect.Field in the oid class corresponding to the pk field */
private Field oidField;
/** The name of the class of this type, used for messages */
private String javaTypeName;
/** True if this field is a relation */
private boolean isRelation = false;
/** True if this field is mapped by the relation field in the other class */
private boolean isMappedBy = false;
/** True if the field is mapped to a single-valued field in the other class */
private boolean isToOne = false;
/** True if the field is embedded */
private boolean isEmbedded;
/** The openjpa class mapping of the related class */
private ClassMapping relatedTypeMapping = null;
/** The domain type handler of the related class */
private NdbOpenJPADomainTypeHandlerImpl<?> relatedDomainTypeHandler = null;
/** The openjpa field mapping for the related field */
private FieldMapping relatedFieldMapping;
/** The domain field handler for the related field */
private FieldMapping mappedByMapping;
/** The class of the related type */
private Class<?> relatedType = null;
/** The name of the class of the related type */
private String relatedTypeName = null;
/** These fields are to manage composite key relationships */
/** The openjpa columns mapped by this relationship */
private Column[] columns;
/** The store Columns mapped by this relationship */
private com.mysql.clusterj.core.store.Column[] storeColumns = emptyStoreColumns;
/** The name of the related field */
private String relatedFieldName;
/** If this field is supported for clusterjpa */
private boolean supported = true;
/** The reason the field is not supported */
private String reason = "";
private RelatedFieldLoadManager relatedFieldLoadManager;
public FieldMapping getFieldMapping() {
return fieldMapping;
}
public NdbOpenJPADomainFieldHandlerImpl(Dictionary dictionary, NdbOpenJPADomainTypeHandlerImpl<?> domainTypeHandler,
NdbOpenJPAConfigurationImpl domainTypeHandlerFactory, final FieldMapping fieldMapping) {
String message = null;
this.fieldMapping = fieldMapping;
this.domainTypeHandler = domainTypeHandler;
this.name = fieldMapping.getName();
this.fieldNumber = fieldMapping.getIndex();
this.primaryKey = fieldMapping.isPrimaryKey();
this.columns = fieldMapping.getColumns();
this.objectOperationHandlerDelegate = objectOperationHandlerUnsupportedType;
this.mappedByMapping = fieldMapping.getMappedByMapping();
this.isMappedBy = mappedByMapping != null;
this.isToOne = fieldMapping.getStrategy() instanceof RelationFieldStrategy;
if (isMappedBy) {
relatedType = mappedByMapping.getDeclaringType();
relatedFieldMapping = fieldMapping.getMappedByMapping();
}
// TODO are these valid for every field?
this.relatedTypeMapping = fieldMapping.getDeclaredTypeMapping();
if (relatedTypeMapping != null) {
relatedType = relatedTypeMapping.getDescribedType();
}
if (relatedType != null) {
relatedTypeName = relatedType.getName();
}
// TODO: the following might not be definitive to identify a relationship
this.isRelation = fieldMapping.getStrategy().getClass().getName().contains("Relation");
this.isEmbedded = fieldMapping.getStrategy().getClass().getName().contains("Embed");
if (logger.isDetailEnabled()) logger.detail(
"field: " + name + " strategy: " + fieldMapping.getStrategy().getClass().getName() + " with " + columns.length + " columns.");
if ((!(isRelation | isEmbedded))) {
if (columns.length == 1) {
// each field is mapped to one column
this.column = columns[0];
this.columnName = column.getName();
Table table = domainTypeHandler.getTable();
if (table == null) {
message = local.message("ERR_No_Mapped_Table", domainTypeHandler.getName());
setUnsupported(message);
return;
}
this.storeColumn = table.getColumn(columnName);
if (storeColumn == null) {
message = local.message("ERR_No_Column", name, table.getName(), columnName);
setUnsupported(message);
return;
}
this.storeColumns = new com.mysql.clusterj.core.store.Column[] {storeColumn};
charsetName = storeColumn.getCharsetName();
// set up the default object operation handler for the column type
// TODO this might better use the "Class type;" field in superclass
this.javaType = column.getJavaType();
this.objectOperationHandlerDelegate = getObjectOperationHandler(javaType);
if (objectOperationHandlerUnsupportedType.equals(objectOperationHandlerDelegate)) {
message = local.message("ERR_Unsupported_Meta_Type", javaType);
setUnsupported(message);
return;
} else {
this.javaTypeName = NdbOpenJPAUtility.getJavaTypeName(javaType);
if (storeColumn.isPrimaryKey()) {
domainTypeHandler.registerPrimaryKeyColumn(this, storeColumn.getName());
}
}
} else if (columns.length > 1) {
// error, no support
StringBuffer buffer = new StringBuffer();
String separator = "";
for (Column errorColumn : columns) {
buffer.append(separator);
buffer.append(errorColumn.getName());
separator = ", ";
}
message = local.message("ERR_More_Than_One_Column_Mapped_To_A_Field",
domainTypeHandler.getName(), name, buffer);
logger.info(message);
setUnsupported(message);
return;
} else if (columns.length == 0) {
message = local.message("ERR_No_Column_Mapped_To_A_Field",
domainTypeHandler.getName(), name, fieldMapping.getTable(), fieldMapping.getStrategy().getClass().getName());
logger.info(message);
setUnsupported(message);
return;
}
if (this.primaryKey) {
// each field is mapped to its own column
// if using a user-defined openJPAId class, set up the value handler
oidField = getFieldForOidClass(this, domainTypeHandler.getOidClass(), name);
indexNames.add("PRIMARY");
switch (javaType) {
case JavaTypes.INT:
this.objectOperationHandlerDelegate = objectOperationHandlerKeyInt;
break;
case JavaTypes.INT_OBJ:
this.objectOperationHandlerDelegate = objectOperationHandlerKeyObjectInteger;
break;
case JavaTypes.LONG:
this.objectOperationHandlerDelegate = objectOperationHandlerKeyLong;
break;
case JavaTypes.LONG_OBJ:
this.objectOperationHandlerDelegate = objectOperationHandlerKeyObjectLong;
break;
case JavaTypes.STRING: this.objectOperationHandlerDelegate =
objectOperationHandlerKeyString;
break;
default:
message = local.message("ERR_Illegal_Primary_Key_Type",
domainTypeHandler.getName(), name, columnName, javaTypeName);
logger.info(message);
setUnsupported(message);
}
}
} else if (isRelation) {
// relationships might have zero, one, or more columns
if (columns.length == 1) {
this.column = columns[0];
this.columnName = column.getName();
this.columnNames = new String[] {columnName};
Table table = domainTypeHandler.getTable();
this.storeColumn = table.getColumn(columnName);
if (storeColumn == null) {
message = local.message("ERR_No_Column", name, table.getName(), columnName);
setUnsupported(message);
return;
}
this.storeColumns = new com.mysql.clusterj.core.store.Column[] {storeColumn};
// set up the default object operation handler for the column type
this.javaType = column.getJavaType();
this.javaTypeName = NdbOpenJPAUtility.getJavaTypeName(javaType);
this.objectOperationHandlerDelegate = getObjectOperationHandlerRelationDelegate(javaType);
if (objectOperationHandlerDelegate == null) {
// unsupported primary key type
return;
}
} else if (columns.length == 0) {
if (isMappedBy) {
// this is the case of a OneToMany field mapped by columns in another table
this.objectOperationHandlerDelegate = objectOperationHandlerVirtualType;
} else {
message = local.message("ERR_No_Columns_And_Not_Mapped_By",
this.domainTypeHandler.getName(), this.name);
logger.info(message);
setUnsupported(message);
}
} else {
// multiple columns for related object
if (isMappedBy) {
// this is the case of OneToOne field mapped by columns in another table
this.objectOperationHandlerDelegate = objectOperationHandlerVirtualType;
} else {
// create an array of NdbOpenJPADomainFieldHandlerImpl
// one for each column in the foreign key
// each one needs to be able to extract the foreign key
// value from the openJPAId instance of the related instance
// using the oidField object
this.relatedTypeMapping = fieldMapping.getDeclaredTypeMapping();
this.relatedType = relatedTypeMapping.getDescribedType();
Class<?> oid = relatedTypeMapping.getObjectIdType();
if (logger.isDetailEnabled()) logger.detail(
"For class: " + domainTypeHandler.getName() +
" field: " + name + " related type is: " + relatedType.getName() +
" objectid type: " + oid.getName());
// create the domain field handlers for each column
this.compositeDomainFieldHandlers = new NdbOpenJPADomainFieldHandlerImpl[columns.length];
this.columnNames = new String[columns.length];
this.storeColumns = new com.mysql.clusterj.core.store.Column[columns.length];
for (int i = 0; i < columns.length; ++i) {
StringBuffer detailMessage = new StringBuffer();
Column localColumn = columns[i];
String localColumnName = localColumn.getName();
Table table = domainTypeHandler.getTable();
com.mysql.clusterj.core.store.Column localStoreColumn = table.getColumn(localColumnName);
if (localStoreColumn == null) {
message = local.message("ERR_No_Column", name, table.getName(), localColumnName);
logger.info(message);
setUnsupported(message);
return;
}
this.storeColumns[i] = localStoreColumn;
this.columnNames[i] = localColumnName;
ForeignKey foreignKey = fieldMapping.getForeignKey();
// get the primary key column corresponding to the local column
Column pkColumn = foreignKey.getPrimaryKeyColumn(localColumn);
if (logger.isDetailEnabled()) {
detailMessage.append(" column: " + localColumnName);
detailMessage.append(" fk-> " + foreignKey);
detailMessage.append(" pkColumn-> " + pkColumn);
logger.detail(detailMessage.toString());
}
NdbOpenJPADomainFieldHandlerImpl relatedFieldHandler =
new NdbOpenJPADomainFieldHandlerImpl(this, localColumn, pkColumn);
if (relatedFieldHandler.isSupported()) {
this.compositeDomainFieldHandlers[i] = relatedFieldHandler;
} else {
message = relatedFieldHandler.getReason();
setUnsupported(message);
return;
}
}
this.objectOperationHandlerDelegate =
objectOperationHandlerRelationCompositeField;
}
}
} else {
// embedded field
message = local.message("ERR_Embedded_Fields_Not_Supported",
this.domainTypeHandler.getName(), this.name);
logger.info(message);
setUnsupported(message);
return;
}
// now handle indexes, for supported field types
Index index = fieldMapping.getJoinIndex();
// TODO: where is this annotation used?
if (index != null) {
String indexName = index.getName();
Column[] indexColumns = index.getColumns();
if (logger.isDetailEnabled()) {
StringBuilder buffer = new StringBuilder("Found index name ");
buffer.append(indexName);
buffer.append(" [");
for (Column indexColumn : indexColumns) {
if (logger.isDetailEnabled()) {
buffer.append(indexColumn.getName());
buffer.append(" ");
}
}
buffer.append("]");
logger.detail(buffer.toString());
}
}
index = fieldMapping.getValueIndex();
// Value indexes are used for ManyToOne and OneToOne relationship indexes on the mapped side
if (index != null) {
StringBuffer buffer = null;
if (logger.isDetailEnabled()) buffer = new StringBuffer("Found index ");
String indexName = index.getName();
if (logger.isDetailEnabled()) buffer.append(indexName + " [ ");
Column[] indexColumns = index.getColumns();
for (Column indexColumn : indexColumns) {
if (logger.isDetailEnabled()) buffer.append(indexColumn.getName() + " ");
}
if (logger.isDetailEnabled()) buffer.append("]");
if (logger.isDetailEnabled()) logger.detail(buffer.toString());
// create an index entry for clusterj queries
// Create an index handler and register this instance with the domain type handler
indices = domainTypeHandler.createIndexHandler(this, dictionary, indexName);
}
this.type = fieldMapping.getType();
if (logger.isTraceEnabled()) {
logger.trace(
" number: " + this.fieldNumber +
" name: " + this.name +
" column: " + this.columnName +
" Java type: " + javaType +
" strategy: " + toString(fieldMapping.getStrategy()) +
" ObjectOperationHandler: " + objectOperationHandlerDelegate.handler());
}
}
/** Initialize relationship handling. There are three types of relationships
* supported by clusterjpa:
* <ul><li>direct ToOne relationship mapped by a foreign key on this side
* </li><li>ToOne relationship mapped by a foreign key on the other side
* </li><li>ToMany relationship mapped by a foreign key on the other side
* </li></ul>
*
*/
public void initializeRelations() {
if (isRelation) {
// set up related field load handler
if (isMappedBy && isToOne) {
// mapped by other side with one instance
this.relatedDomainTypeHandler = ((NdbOpenJPADomainTypeHandlerImpl<?>)domainTypeHandler)
.registerDependency(relatedTypeMapping);
this.relatedFieldName = relatedFieldMapping.getName();
relatedFieldLoadManager = new RelatedFieldLoadManager() {
public void load(OpenJPAStateManager sm, NdbOpenJPAStoreManager store,
JDBCFetchConfiguration fetch) throws SQLException {
SessionSPI session = store.getSession();
session.startAutoTransaction();
NdbOpenJPAResult queryResult = queryRelated(sm, store);
Object related = null;
try {
if (queryResult.next()) {
// instantiate the related object from the result of the query
related = store.load(relatedTypeMapping, fetch, (BitSet) null, queryResult);
}
if (logger.isDetailEnabled()) logger.detail("related object is: " + related);
// store the value of the related object in this field
sm.storeObjectField(fieldNumber, related);
session.endAutoTransaction();
} catch (Exception e) {
session.failAutoTransaction();
}
}
};
if (logger.isDetailEnabled()) logger.detail("Single-valued relationship field " + name
+ " is mapped by " + relatedTypeName + " field " + relatedFieldName
+ " with relatedDomainTypeHandler " + relatedDomainTypeHandler.getName());
} else if (isMappedBy && !isToOne) {
// mapped by other side with multiple instances
this.relatedTypeMapping = mappedByMapping.getDeclaringMapping();
this.relatedDomainTypeHandler = ((NdbOpenJPADomainTypeHandlerImpl<?>)domainTypeHandler)
.registerDependency(relatedTypeMapping);
this.relatedFieldName = mappedByMapping.getName();
relatedTypeName = relatedDomainTypeHandler.getName();
if (logger.isDetailEnabled()) logger.detail("Multi-valued relationship field " + name
+ " is mapped by " + relatedTypeName + " field " + relatedFieldName);
relatedFieldLoadManager = new RelatedFieldLoadManager() {
public void load(OpenJPAStateManager sm, NdbOpenJPAStoreManager store,
JDBCFetchConfiguration fetch) throws SQLException {
SessionSPI session = store.getSession();
session.startAutoTransaction();
try {
NdbOpenJPAResult queryResult = queryRelated(sm, store);
while (queryResult.next()) {
if (logger.isDetailEnabled()) logger.detail("loading related instance of type: " + relatedTypeMapping.getDescribedType().getName());
store.load(relatedTypeMapping, fetch, (BitSet) null, queryResult);
}
fieldMapping.load(sm, store, fetch);
session.endAutoTransaction();
} catch (Exception e) {
session.failAutoTransaction();
throw new ClusterJException(local.message("ERR_Exception_While_Loading"), e);
}
}
};
} else {
// this side contains foreign key to other side
if (logger.isDetailEnabled()) logger.detail("NdbOpenJPADomainFieldHandlerImpl.initializeRelations for "
+ fieldMapping.getName() + " column " + (column==null?"null":column.getName())
+ " relatedFieldName " + relatedFieldName
+ " relatedFieldMapping " + relatedFieldMapping
+ " relatedTypeMapping " + relatedTypeMapping);
// record dependency to related type if not null
if (relatedTypeMapping != null) {
this.relatedDomainTypeHandler = ((NdbOpenJPADomainTypeHandlerImpl<?>)domainTypeHandler)
.registerDependency(relatedTypeMapping);
relatedFieldLoadManager = new RelatedFieldLoadManager() {
public void load(OpenJPAStateManager sm, NdbOpenJPAStoreManager store,
JDBCFetchConfiguration fetch) throws SQLException {
if (logger.isDetailEnabled()) logger.detail("Loading field " + name + "from stored key");
fieldMapping.load(sm, store, fetch);
}
};
}
}
}
}
/** Get the object operation handler for the specific java field type.
* @param javaType the java type from JavaTypes or JavaSQLTypes
* @return the object operation handler
*/
private ObjectOperationHandler getObjectOperationHandler(int javaType) {
// the default is unsupported
ObjectOperationHandler result = objectOperationHandlerUnsupportedType;
// if a known java type, the default is from the table of object operation handlers
if (javaType < objectOperationHandlers.length) {
result = objectOperationHandlers[javaType];
}
// handle exceptions, including all JavaSQLTypes and special handling for String
switch (javaType) {
case JavaSQLTypes.SQL_DATE:
return objectOperationHandlerJavaSqlDate;
case JavaSQLTypes.TIME:
return objectOperationHandlerJavaSqlTime;
case JavaSQLTypes.TIMESTAMP:
return objectOperationHandlerJavaSqlTimestamp;
case JavaSQLTypes.BYTES:
switch(storeColumn.getType()) {
case Blob:
case Longvarbinary:
return objectOperationHandlerBytesLob;
case Binary:
case Varbinary:
return objectOperationHandlerBytes;
default:
}
case JavaTypes.STRING:
switch(storeColumn.getType()) {
case Text:
return objectOperationHandlerStringLob;
case Char:
case Varchar:
case Longvarchar:
return objectOperationHandlerString;
default:
}
default:
}
return result;
}
/** This field handler is used with compound "foreign keys". Each column of
* the compound "foreign key" has its own field handler. The parent
* field handler has the relationship field but no columns.
*
* @param parent the field handler with the relationship field
* @param localColumn the "foreign key" column in this table
* @param pkColumn the primary key column in the other table
*/
public NdbOpenJPADomainFieldHandlerImpl(NdbOpenJPADomainFieldHandlerImpl parent,
Column localColumn, Column pkColumn) {
String message = null;
if (logger.isDetailEnabled()) logger.detail("NdbOpenJPADomainFieldHandlerImpl<init> for localColumn: " + localColumn + " pkColumn: " + pkColumn);
this.column = localColumn;
Table table = parent.domainTypeHandler.getStoreTable();
this.storeColumn = table.getColumn(localColumn.getName());
if (storeColumn == null) {
message = local.message("ERR_No_Column", parent.getName(), table.getName(), columnName);
setUnsupported(message);
logger.info(message);
return;
}
this.javaType = column.getJavaType();
this.objectOperationHandlerDelegate = getObjectOperationHandlerRelationDelegate(javaType);
if (objectOperationHandlerDelegate == null) {
// unsupported primary key type
return;
}
this.columnName = column.getName();
this.fieldNumber = parent.fieldNumber;
this.domainTypeHandler = parent.domainTypeHandler;
this.relatedTypeMapping = parent.relatedTypeMapping;
if (relatedTypeMapping != null) {
relatedType = relatedTypeMapping.getDescribedType();
if (relatedType != null) {
relatedTypeName = relatedType.getName();
}
}
// now find the field in the related class corresponding to this column
FieldMapping[] relatedFieldMappings = relatedTypeMapping.getPrimaryKeyFieldMappings();
for (FieldMapping rfm: relatedFieldMappings) {
Column[] rcs = rfm.getColumns();
if (logger.isDetailEnabled()) logger.detail("NdbOpenJPADomainFieldHandlerImpl<init> trying primary key column: " + rcs[0]);
if (rcs.length == 1 && rcs[0].equals(pkColumn)) {
// found the corresponding pk field
String pkFieldName = rfm.getName();
oidField = getFieldForOidClass(this, relatedTypeMapping.getObjectIdType(), pkFieldName);
if (logger.isDetailEnabled()) logger.detail("NdbOpenJPADomainFieldHandlerImpl<init> found primary key column: " + rcs[0] + " for field: " + pkFieldName);
break;
}
}
if (oidField == null) {
message = local.message("ERR_No_Oid_Field", pkColumn);
setUnsupported(message);
logger.info(message);
return;
}
if (logger.isTraceEnabled()) {
logger.trace(" Relation Field Handler for column: " + columnName +
" number: " + this.fieldNumber +
" name: " + this.name +
" column: " + this.columnName +
" Java type: " + javaType +
" ObjectOperationHandler: " + objectOperationHandlerDelegate.handler());
}
}
interface RelatedFieldLoadManager {
public void load(OpenJPAStateManager sm, NdbOpenJPAStoreManager store,
JDBCFetchConfiguration fetch) throws SQLException ;
}
/** Load the value of this field. This will be done here for relationship
* since basic fields are loaded when the instance is first initialized.
*
* @param sm the openjpa state manager for the instance
* @param store the store manager
* @param fetch the fetch configuration, presently unused
* @throws SQLException
*/
public void load(OpenJPAStateManager sm, NdbOpenJPAStoreManager store, JDBCFetchConfiguration fetch)
throws SQLException {
if (isRelation) {
relatedFieldLoadManager.load(sm, store, fetch);
} else {
throw new ClusterJFatalInternalException("load called for non-relationship field "
+ this.getName() + " mapped to column " + this.columnName);
}
}
/** Query the related type for instance(s) whose related field refers to this instance.
* @param sm the state manager
* @param store the store manager
* @return the result of executing a query for the related type based on this instance's primary key
*/
private NdbOpenJPAResult queryRelated(OpenJPAStateManager sm, NdbOpenJPAStoreManager store) {
// get the Oid object for this sm
OpenJPAId openJPAId = (OpenJPAId)sm.getObjectId();
Object thisOid = openJPAId.getIdObject();
QueryDomainType<?> queryDomainType = store.createQueryDomainType(relatedType);
if (logger.isDetailEnabled()) logger.detail("created query for " + queryDomainType.getType().getName());
// query for related type equals this pk oid value
Predicate predicate = queryDomainType.get(relatedFieldName).equal(queryDomainType.param(relatedFieldName));
queryDomainType.where(predicate);
Map<String, Object> parameterList = new HashMap<String, Object>();
parameterList.put(relatedFieldName, thisOid);
if (logger.isDetailEnabled()) logger.detail(parameterList.toString());
NdbOpenJPAResult queryResult = store.executeQuery(relatedDomainTypeHandler, queryDomainType, parameterList);
// debug query result
if (logger.isDetailEnabled()) {
DomainTypeHandler<?> handler = queryResult.domainTypeHandler;
Set<String> columnNames = queryResult.getColumnNames();
StringBuffer buffer = new StringBuffer("Executed query for ");
buffer.append(handler.getName());
buffer.append(" returned columns: ");
buffer.append(Arrays.toString(columnNames.toArray()));
logger.detail(buffer.toString());
}
return queryResult;
}
public void load(OpenJPAStateManager sm, NdbOpenJPAStoreManager store, JDBCFetchConfiguration fetch,
NdbOpenJPAResult result) throws SQLException {
fieldMapping.load(sm, store, fetch, result);
}
/** Add filters to the query and return the values to be used for the filter.
*
* @param queryDomainType the QueryDomainType
* @param thisOid the object id to be used to query foreign keys
* @return the parameter map for the query with bound data values
*/
public Map<String, Object> createParameterMap(QueryDomainType<?> queryDomainType, Object thisOid) {
return ((ObjectOperationHandlerRelationField)objectOperationHandlerDelegate).createParameterMap(this, queryDomainType, thisOid);
}
public ObjectOperationHandler[] objectOperationHandlers =
new ObjectOperationHandler[] {
objectOperationHandlerBoolean, /* 0: boolean */
objectOperationHandlerByte, /* 1: byte */
objectOperationHandlerUnsupportedType, /* 2: char */
objectOperationHandlerDouble, /* 3: double */
objectOperationHandlerFloat, /* 4: float */
objectOperationHandlerInt, /* 5: int */
objectOperationHandlerLong, /* 6: long */
objectOperationHandlerShort, /* 7: short */
objectOperationHandlerUnsupportedType, /* 8: Object */
objectOperationHandlerString, /* 9: String */
objectOperationHandlerUnsupportedType, /* 10: Number */
objectOperationHandlerUnsupportedType, /* 11: Array */
objectOperationHandlerUnsupportedType, /* 12: Collection */
objectOperationHandlerUnsupportedType, /* 13: Map */
objectOperationHandlerJavaUtilDate, /* 14: java.util.Date */
objectOperationHandlerUnsupportedType, /* 15: PC */
objectOperationHandlerObjectBoolean, /* 16: Boolean */
objectOperationHandlerObjectByte, /* 17: Byte */
objectOperationHandlerUnsupportedType, /* 18: Character */
objectOperationHandlerObjectDouble, /* 19: Double */
objectOperationHandlerObjectFloat, /* 20: Float */
objectOperationHandlerObjectInteger, /* 21: Integer */
objectOperationHandlerObjectLong, /* 22: Long */
objectOperationHandlerObjectShort, /* 23: Short */
objectOperationHandlerDecimal, /* 24: BigDecimal */
objectOperationHandlerBigInteger, /* 25: BigInteger */
objectOperationHandlerUnsupportedType, /* 26: Locale */
objectOperationHandlerUnsupportedType, /* 27: PC Untyped */
objectOperationHandlerUnsupportedType, /* 28: Calendar */
objectOperationHandlerUnsupportedType, /* 29: OID */
objectOperationHandlerUnsupportedType, /* 30: InputStream */
objectOperationHandlerUnsupportedType /* 31: InputReader */
};
public int compareTo(Object o) {
return compareTo((NdbOpenJPADomainFieldHandlerImpl)o);
}
protected String toString(Object o) {
return o.getClass().getSimpleName();
}
Column[] getColumns() {
return fieldMapping.getColumns();
}
protected Object getKeyValue(Object keys) {
Object key = keys;
if (keys instanceof ObjectId) {
key = ((ObjectId)keys).getId();
}
return getKeyValue(oidField, key);
}
protected static Object getKeyValue(Field field, Object keys) {
try {
Object result;
String fieldName = "none";
if (field != null) {
result = field.get(keys);
fieldName = field.getName();
} else {
result = keys;
}
if (logger.isDetailEnabled()) logger.detail("For field " + fieldName + " keys: " + keys + " value returned is " + result);
return result;
} catch (IllegalArgumentException ex) {
String message = "IllegalArgumentException, field " + field.getDeclaringClass().getName() + ":" + field.getName() + " keys: " + keys;
logger.error(message);
throw new ClusterJUserException(message, ex);
} catch (IllegalAccessException ex) {
String message = "IllegalAccessException, field " + field.getDeclaringClass().getName() + ":" + field.getName() + " keys: " + keys;
throw new ClusterJUserException(message, ex);
}
}
public Field getOidField() {
return oidField;
}
protected static Field getFieldForOidClass(
NdbOpenJPADomainFieldHandlerImpl ndbOpenJPADomainFieldHandlerImpl,
Class<?> oidClass, String fieldName) {
String message = null;
Field result = null;
if (logger.isDetailEnabled()) logger.detail("Oid class: " + oidClass.getName());
// the openJPAId class might be a simple type or a user-defined type
if (OpenJPAId.class.isAssignableFrom(oidClass)) {
return null;
} else {
try {
// user-defined class; get Field to extract values at runtime
result = oidClass.getField(fieldName);
if (logger.isDetailEnabled()) logger.detail("OidField: " + result);
return result;
} catch (NoSuchFieldException ex) {
message = local.message("ERR_No_Field_In_Oid_Class", oidClass.getName(), fieldName);
logger.info(message);
ndbOpenJPADomainFieldHandlerImpl.setUnsupported(message);
return null;
} catch (SecurityException ex) {
message = local.message("ERR_Security_Violation_For_Oid_Class", oidClass.getName());
logger.info(message);
ndbOpenJPADomainFieldHandlerImpl.setUnsupported(message);
return null;
}
}
}
protected ObjectOperationHandler getObjectOperationHandlerRelationDelegate(int javaType) {
String message;
switch (javaType) {
case JavaTypes.INT:
case JavaTypes.INT_OBJ:
return objectOperationHandlerRelationIntField;
case JavaTypes.LONG:
case JavaTypes.LONG_OBJ:
return objectOperationHandlerRelationLongField;
case JavaTypes.STRING:
return objectOperationHandlerRelationStringField;
default:
message = local.message("ERR_Illegal_Foreign_Key_Type",
domainTypeHandler.getName(), name, columnName, javaType);
setUnsupported(message);
return null;
}
}
static abstract class ObjectOperationHandlerRelationField implements ObjectOperationHandler {
public boolean isPrimitive() {
return false;
}
/** Add the filter to the query and create the parameter map with the bound value of the oid.
*
* @param domainFieldHandler the domain field handler
* @param queryDomainObject the query domain object
* @param oid the object id value to bind to the filter
* @return the map with the bound parameter
*/
public Map<String, Object> createParameterMap(NdbOpenJPADomainFieldHandlerImpl domainFieldHandler, QueryDomainType<?> queryDomainObject, Object oid) {
String name = domainFieldHandler.name;
PredicateOperand parameter = queryDomainObject.get(name);
Predicate predicate = parameter.equal(parameter);
queryDomainObject.where(predicate);
// construct a map of parameter binding to the value in oid
Map<String, Object> result = new HashMap<String, Object>();
Object value = domainFieldHandler.getKeyValue(oid);
result.put(name, value);
if (logger.isDetailEnabled()) logger.detail("Map.Entry key: " + name + ", value: " + value);
return result;
}
public void objectInitializeJavaDefaultValue(AbstractDomainFieldHandlerImpl fmd, ValueHandler handler) {
throw new ClusterJFatalInternalException(
local.message("ERR_Implementation_Should_Not_Occur"));
}
public void operationGetValue(AbstractDomainFieldHandlerImpl fmd, Operation op) {
op.getValue(fmd.getStoreColumn());
}
public Object getDefaultValueFor(AbstractDomainFieldHandlerImpl fmd, String columnDefaultValue) {
throw new ClusterJFatalInternalException(
local.message("ERR_Implementation_Should_Not_Occur"));
}
public void operationSetValue(AbstractDomainFieldHandlerImpl fmd, Object value, Operation op) {
throw new ClusterJFatalInternalException(
local.message("ERR_Implementation_Should_Not_Occur"));
}
public void objectSetValue(AbstractDomainFieldHandlerImpl fmd, ResultData rs, ValueHandler handler) {
throw new ClusterJFatalInternalException(
local.message("ERR_Implementation_Should_Not_Occur"));
}
public void operationSetBounds(AbstractDomainFieldHandlerImpl fmd, Object value, BoundType type, IndexScanOperation op) {
throw new ClusterJFatalInternalException(
local.message("ERR_Implementation_Should_Not_Occur"));
}
public void filterCompareValue(AbstractDomainFieldHandlerImpl fmd, Object value, BinaryCondition condition, ScanFilter filter) {
throw new ClusterJFatalInternalException(
local.message("ERR_Implementation_Should_Not_Occur"));
}
public void operationEqual(AbstractDomainFieldHandlerImpl fmd, Object value, Operation op) {
throw new ClusterJFatalInternalException(
local.message("ERR_Implementation_Should_Not_Occur"));
}
public boolean isValidIndexType(AbstractDomainFieldHandlerImpl fmd, boolean hashNotOrdered) {
// relationships can use either hash or btree indexes
return true;
}
protected int getInt(Object objectId) {
// get an int value from an objectId
if (objectId instanceof IntId) {
return ((IntId)objectId).getId();
} else if (objectId instanceof OpenJPAId) {
OpenJPAId openJPAId = (OpenJPAId)objectId;
Object id = openJPAId.getIdObject();
if (id instanceof Integer) {
return ((Integer)id).intValue();
}
throw new UnsupportedOperationException(
local.message("ERR_Unsupported_Object_Id_Type", "int key", "OpenJPAId"));
} else {
String message = (objectId == null)?"<null>":objectId.getClass().getName();
throw new UnsupportedOperationException(
local.message("ERR_Unsupported_Object_Id_Type", "int key", message));
}
}
protected long getLong(Object objectId) {
// get a long value from an objectId
if (objectId instanceof LongId) {
return ((LongId)objectId).getId();
} else if (objectId instanceof OpenJPAId) {
OpenJPAId openJPAId = (OpenJPAId)objectId;
Object id = openJPAId.getIdObject();
if (id instanceof Long) {
return ((Long)id).longValue();
}
throw new UnsupportedOperationException(
local.message("ERR_Unsupported_Object_Id_Type", "long key", "OpenJPAId"));
} else {
String message = (objectId == null)?"<null>":objectId.getClass().getName();
throw new UnsupportedOperationException(
local.message("ERR_Unsupported_Object_Id_Type", "long key", message));
}
}
protected String getString(Object objectId) {
// get a String value from an objectId
if (objectId instanceof StringId) {
return ((StringId)objectId).getId();
} else if (objectId instanceof OpenJPAId) {
OpenJPAId openJPAId = (OpenJPAId)objectId;
Object id = openJPAId.getIdObject();
if (id instanceof String) {
return (String)id;
}
throw new UnsupportedOperationException(
local.message("ERR_Unsupported_Object_Id_Type", "String key", "OpenJPAId"));
} else {
String message = (objectId == null)?"<null>":objectId.getClass().getName();
throw new UnsupportedOperationException(
local.message("ERR_Unsupported_Object_Id_Type", "String key", message));
}
}
protected OpenJPAStateManager getRelatedStateManager(ValueHandler handler, AbstractDomainFieldHandlerImpl fmd) {
// get related object
OpenJPAStateManager sm = ((NdbOpenJPAValueHandler) handler).getStateManager();
NdbOpenJPAStoreManager store = ((NdbOpenJPAValueHandler) handler).getStoreManager();
OpenJPAStateManager rel = RelationStrategies.getStateManager(sm.fetchObjectField(fmd.getFieldNumber()), store.getContext());
return rel;
}
public void partitionKeySetPart(AbstractDomainFieldHandlerImpl fmd,
PartitionKey partitionKey, ValueHandler keyValueHandler) {
throw new ClusterJFatalInternalException(
local.message("ERR_Operation_Not_Supported","partitionKeySetPart", "non-key fields"));
}
public Object getValue(QueryExecutionContext context, String index) {
return context.getObject(index);
}
};
static ObjectOperationHandler objectOperationHandlerRelationIntField =
new ObjectOperationHandlerRelationField() {
public void operationSetValue(AbstractDomainFieldHandlerImpl fmd, ValueHandler handler, Operation op) {
OpenJPAStateManager rel = getRelatedStateManager(handler, fmd);
// get openJPAId from related object
if (rel == null) {
if (logger.isDetailEnabled()) logger.detail("Related object is null");
op.setNull(fmd.getStoreColumn());
} else {
Object objid = rel.getObjectId();
if (objid == null) {
// TODO: doesn't seem right
op.setNull(fmd.getStoreColumn());
if (logger.isDetailEnabled()) logger.detail("Related object class: " + rel.getMetaData().getTypeAlias() + " object id: " + objid);
} else {
int oid = getInt(objid);
if (logger.isDetailEnabled()) logger.detail("Related object class: " + rel.getMetaData().getTypeAlias() + " key: " + oid);
op.setInt(fmd.getStoreColumn(), oid);
}
}
}
public String handler() {
return "Object ToOne Int key.";
}
@Override
public void operationSetValue(AbstractDomainFieldHandlerImpl fmd, Object value, Operation op) {
if (value == null) {
op.setNull(fmd.getStoreColumn());
} else {
op.setInt(fmd.getStoreColumn(),(Integer) value);
}
}
@Override
public void filterCompareValue(AbstractDomainFieldHandlerImpl fmd, Object oid, BinaryCondition condition, ScanFilter filter) {
Field field = ((NdbOpenJPADomainFieldHandlerImpl)fmd).getOidField();
Object value = getKeyValue(field, oid);
if (logger.isDetailEnabled()) logger.detail("For column: " + fmd.getColumnName() + " oid: " + oid + " value: " + value);
filter.cmpInt(condition, fmd.getStoreColumn(), ((Integer) value).intValue());
}
/** Set bounds for an index operation.
*
* @param fmd the domain field handler
* @param value the value to set
* @param type the bound type (i.e. EQ, NE, LE, LT, GE, GT)
* @param op the index operation
*/
public void operationSetBounds(AbstractDomainFieldHandlerImpl fmd, Object value, BoundType type, IndexScanOperation op) {
op.setBoundInt(fmd.getStoreColumn(), type, (Integer)value);
}
};
static ObjectOperationHandler objectOperationHandlerRelationLongField =
new ObjectOperationHandlerRelationField() {
public void operationSetValue(AbstractDomainFieldHandlerImpl fmd, ValueHandler handler, Operation op) {
OpenJPAStateManager rel = getRelatedStateManager(handler, fmd);
// get openJPAId from related object
if (rel == null) {
if (logger.isDetailEnabled()) logger.detail("Related object is null");
op.setNull(fmd.getStoreColumn());
} else {
long oid = getLong(rel.getObjectId());
if (logger.isDetailEnabled()) logger.detail("Related object class: " + rel.getMetaData().getTypeAlias() + " key: " + oid);
op.setLong(fmd.getStoreColumn(), oid);
}
}
public String handler() {
return "Object ToOne Long key.";
}
@Override
public void operationSetValue(AbstractDomainFieldHandlerImpl fmd, Object value, Operation op) {
if (value == null) {
op.setNull(fmd.getStoreColumn());
} else {
op.setLong(fmd.getStoreColumn(),(Long) value);
}
}
@Override
public void filterCompareValue(AbstractDomainFieldHandlerImpl fmd, Object oid, BinaryCondition condition, ScanFilter filter) {
Field field = ((NdbOpenJPADomainFieldHandlerImpl)fmd).getOidField();
Object value = getKeyValue(field, oid);
if (logger.isDetailEnabled()) logger.detail("For column: " + fmd.getColumnName() + " oid: " + oid + " value: " + value);
filter.cmpLong(condition, fmd.getStoreColumn(), ((Long) value).longValue());
}
/** Set bounds for an index operation.
*
* @param fmd the domain field handler
* @param value the value to set
* @param type the bound type (i.e. EQ, NE, LE, LT, GE, GT)
* @param op the index operation
*/
public void operationSetBounds(AbstractDomainFieldHandlerImpl fmd, Object value, BoundType type, IndexScanOperation op) {
op.setBoundLong(fmd.getStoreColumn(), type, (Long)value);
}
};
static ObjectOperationHandler objectOperationHandlerRelationStringField =
new ObjectOperationHandlerRelationField() {
public void operationSetValue(AbstractDomainFieldHandlerImpl fmd, ValueHandler handler, Operation op) {
OpenJPAStateManager rel = getRelatedStateManager(handler, fmd);
// get openJPAId from related object
if (rel == null) {
if (logger.isDetailEnabled()) logger.detail("Related object is null");
op.setNull(fmd.getStoreColumn());
} else {
String oid = getString(rel.getObjectId());
if (logger.isDetailEnabled()) logger.detail("Related object class: " + rel.getMetaData().getTypeAlias() + " key: " + oid);
op.setString(fmd.getStoreColumn(), oid);
}
}
public String handler() {
return "Object ToOne String key.";
}
@Override
public void operationSetValue(AbstractDomainFieldHandlerImpl fmd, Object value, Operation op) {
if (value == null) {
op.setNull(fmd.getStoreColumn());
} else {
op.setString(fmd.getStoreColumn(),(String) value);
}
}
@Override
public void filterCompareValue(AbstractDomainFieldHandlerImpl fmd, Object oid, BinaryCondition condition, ScanFilter filter) {
Field field = ((NdbOpenJPADomainFieldHandlerImpl)fmd).getOidField();
Object value = getKeyValue(field, oid);
if (logger.isDetailEnabled()) logger.detail("For column: " + fmd.getColumnName() + " oid: " + oid + " filter.cmpString: " + value);
filter.cmpString(condition, fmd.getStoreColumn(), (String) value);
}
/** Set bounds for an index operation.
*
* @param fmd the domain field handler
* @param value the value to set
* @param type the bound type (i.e. EQ, NE, LE, LT, GE, GT)
* @param op the index operation
*/
public void operationSetBounds(AbstractDomainFieldHandlerImpl fmd, Object value, BoundType type, IndexScanOperation op) {
op.setBoundString(fmd.getStoreColumn(), type, (String)value);
}
};
static ObjectOperationHandlerRelationField objectOperationHandlerRelationCompositeField = new ObjectOperationHandlerRelationField() {
public String handler() {
return "Composite key.";
}
@Override
public void operationGetValue(AbstractDomainFieldHandlerImpl fmd, Operation op) {
for (AbstractDomainFieldHandlerImpl localHandler: fmd.compositeDomainFieldHandlers) {
localHandler.operationGetValue(op);
}
}
@Override
public void operationSetValue(AbstractDomainFieldHandlerImpl fmd, Object value, Operation op) {
throw new ClusterJFatalInternalException(
local.message("ERR_Implementation_Should_Not_Occur"));
}
/** Set the filter for this composite key relationship field. This only works for equal conditions.
* Set the filter to AND, and for each field in the composite key, extract the value
* from the oid and add it to the filter.
*/
@Override
public void filterCompareValue(AbstractDomainFieldHandlerImpl fmd, Object value, BinaryCondition condition, ScanFilter filter) {
if (!BinaryCondition.COND_EQ.equals(condition)) {
throw new ClusterJFatalInternalException(
local.message("ERR_Illegal_Filter_Condition", condition));
}
filter.begin();
for (AbstractDomainFieldHandlerImpl localHandler : fmd.compositeDomainFieldHandlers) {
if (value != null) {
// extract the value from the oid and add the filter condition
localHandler.filterCompareValue(value, condition, filter);
} else {
// set null for each local column
localHandler.filterCompareValue((Object)null, condition, filter);
}
}
filter.end();
}
public void operationSetValue(AbstractDomainFieldHandlerImpl fmd, ValueHandler handler, Operation op) {
OpenJPAStateManager rel = getRelatedStateManager(handler, fmd);
OpenJPAId openJPAId = null;
Object oid = null;
if (rel == null) {
if (logger.isDetailEnabled()) logger.detail("Related object is null");
} else {
if (logger.isDetailEnabled()) logger.detail("Related object class: " + rel.getMetaData().getTypeAlias() + " key: " + openJPAId);
openJPAId = (OpenJPAId) rel.getObjectId();
oid = openJPAId.getIdObject();
}
for (AbstractDomainFieldHandlerImpl localHandler : fmd.compositeDomainFieldHandlers) {
Object value = null;
if (rel != null) {
// get the value from the related object
Field field = ((NdbOpenJPADomainFieldHandlerImpl)localHandler).getOidField();
value = getKeyValue(field, oid);
localHandler.operationSetValue(value, op);
} else {
// set null for each local column
localHandler.operationSetValue((Object)null, op);
}
}
}
@Override
public Map<String, Object> createParameterMap(NdbOpenJPADomainFieldHandlerImpl domainFieldHandler,
QueryDomainType<?> queryDomainObject, Object oid) {
Map<String, Object> result = new HashMap<String, Object>();
Predicate predicate = null;
for (AbstractDomainFieldHandlerImpl localHandler: domainFieldHandler.compositeDomainFieldHandlers) {
String name = localHandler.getColumnName();
PredicateOperand parameter = queryDomainObject.param(name);
PredicateOperand field = queryDomainObject.get(name);
if (predicate == null) {
predicate = field.equal(parameter);
} else {
predicate.and(field.equal(parameter));
}
// construct a map of parameter binding to the value in oid
Object value = domainFieldHandler.getKeyValue(oid);
result.put(name, value);
if (logger.isDetailEnabled()) logger.detail("Map.Entry key: " + name + ", value: " + value);
}
queryDomainObject.where(predicate);
return result;
}
/** Set bounds for an index operation. Delegate this to each domain field handler which
* will extract the column data from the object id.
*
* @param fmd the domain field handler
* @param oid the value to set
* @param type the bound type (i.e. EQ, LE, LT, GE, GT)
* @param op the index operation
*/
public void operationSetBounds(AbstractDomainFieldHandlerImpl fmd, Object oid, BoundType type, IndexScanOperation op) {
for (AbstractDomainFieldHandlerImpl localHandler : fmd.compositeDomainFieldHandlers) {
Field field = ((NdbOpenJPADomainFieldHandlerImpl)localHandler).getOidField();
Object columnData = getKeyValue(field, oid);
localHandler.operationSetBounds(columnData, type, op);
}
}
};
public com.mysql.clusterj.core.store.Column[] getStoreColumns() {
return storeColumns;
}
public boolean isSupported() {
return supported;
}
public boolean isRelation() {
return isRelation;
}
public String getReason() {
return reason;
}
private void setUnsupported(String reason) {
this.supported = false;
this.reason = reason;
}
}