/*
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.core.metadata;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.mysql.clusterj.ClusterJException;
import com.mysql.clusterj.ClusterJFatalInternalException;
import com.mysql.clusterj.ClusterJUserException;
import com.mysql.clusterj.core.CacheManager;
import com.mysql.clusterj.core.query.CandidateIndexImpl;
import com.mysql.clusterj.core.spi.DomainFieldHandler;
import com.mysql.clusterj.core.spi.DomainTypeHandler;
import com.mysql.clusterj.core.spi.ValueHandler;
import com.mysql.clusterj.core.store.Column;
import com.mysql.clusterj.core.store.Dictionary;
import com.mysql.clusterj.core.store.IndexOperation;
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.Table;
import com.mysql.clusterj.core.util.I18NHelper;
import com.mysql.clusterj.core.util.Logger;
import com.mysql.clusterj.core.util.LoggerFactoryService;
/** Abstract class implementing DomainTypeHandler. This class implements common
* behavior to manage persistent representations of tables, including field
* handlers for persistent field values. Subclasses will implement behavior
* specific to the actual representations of persistence.
*/
public abstract class AbstractDomainTypeHandlerImpl<T> implements DomainTypeHandler<T> {
/** My message translator */
protected static final I18NHelper local = I18NHelper.getInstance(AbstractDomainTypeHandlerImpl.class);
/** My logger */
protected static final Logger logger = LoggerFactoryService.getFactory().getInstance(AbstractDomainTypeHandlerImpl.class);
/** The name of the class. */
protected String name;
/** The table for the class. */
protected String tableName;
/** The NDB table for the class. */
protected Table table;
/** The number of id fields for the class. */
protected int numberOfIdFields;
/** The field numbers of the id fields. */
protected int[] idFieldNumbers;
/** The id field(s) for the class, mapped to primary key columns */
protected DomainFieldHandler[] idFieldHandlers;
/** The PrimaryKey column names. */
protected String[] primaryKeyColumnNames;
/** The number of partition key columns */
protected int numberOfPartitionKeyColumns = 0;
/** The partition key fields */
protected DomainFieldHandler[] partitionKeyFieldHandlers;
/** The names of the partition key columns */
protected String[] partitionKeyColumnNames;
/** The number of fields. Dynamically created as fields are added. */
protected int numberOfFields = 0;
/** Persistent fields. */
protected List<DomainFieldHandler> persistentFieldHandlers = new ArrayList<DomainFieldHandler>();
/** Non PK fields. */
protected List<DomainFieldHandler> nonPKFieldHandlers = new ArrayList<DomainFieldHandler>();
/** Primitive fields. */
protected List<DomainFieldHandler> primitiveFieldHandlers = new ArrayList<DomainFieldHandler>();
/** Map of field names to field numbers. */
protected Map<String, Integer> fieldNameToNumber = new HashMap<String, Integer>();
/** Field names */
protected String[] fieldNames;
/** All index handlers defined for the mapped class. The position in this
* array is significant. Each DomainFieldHandlerImpl contains the index into
* this array and the index into the fields array within the
* IndexHandlerImpl.
*/
protected List<IndexHandlerImpl> indexHandlerImpls = new ArrayList<IndexHandlerImpl>();
/** Set of index names to check for duplicates. */
protected Set<String> indexNames = new HashSet<String>();
/** Register a primary key column field. This is used to associate
* primary key and partition key column names with field handlers.
* This method is called by the DomainFieldHandlerImpl constructor
* after the mapped column name is known. It is only called by fields
* that are mapped to primary key columns.
* @param fmd the field handler instance calling us
* @param columnName the name of the column
*/
public void registerPrimaryKeyColumn(DomainFieldHandler fmd, String columnName) {
// find the primary key column that matches the primary key column
for (int i = 0; i < primaryKeyColumnNames.length; ++i) {
if (primaryKeyColumnNames[i].equals(columnName)) {
idFieldHandlers[i] = fmd;
idFieldNumbers[i] = fmd.getFieldNumber();
if (logger.isDetailEnabled()) logger.detail("registerPrimaryKeyColumn found primary key " + columnName);
}
}
// find the partition key column that matches the primary key column
for (int j = 0; j < partitionKeyColumnNames.length; ++j) {
if (partitionKeyColumnNames[j].equals(columnName)) {
partitionKeyFieldHandlers[j] = fmd;
if (logger.isDetailEnabled()) logger.detail("registerPrimaryKeyColumn found partition key " + columnName);
}
}
return;
}
/** Create and register an index from a field and return a special int[][] that
* contains all indexHandlerImpls in which the
* field participates. The int[][] is used by the query optimizer to determine
* which if any index can be used. This method is called by the
* DomainFieldHandlerImpl constructor after the mapped column name is known.
* @see AbstractDomainFieldHandlerImpl#indices
* @param fmd the FieldHandler
* @param columnName the column name mapped to the field
* @return the array of array identifying the indexes into the IndexHandler
* list and columns in the IndexHandler corresponding to the field
*/
public int[][] registerIndices(AbstractDomainFieldHandlerImpl fmd, String columnName) {
// Find all the indexes that this field belongs to, by iterating
// the list of indexes and comparing column names.
List<int[]> result =new ArrayList<int[]>();
for (int i = 0; i < indexHandlerImpls.size(); ++i) {
IndexHandlerImpl indexHandler = indexHandlerImpls.get(i);
String[] columns = indexHandler.getColumnNames();
for (int j = 0; j < columns.length; ++j) {
if (fmd.getColumnName().equals(columns[j])) {
if (logger.isDetailEnabled()) logger.detail("Found field " + fmd.getName()
+ " column " + fmd.getColumnName() + " matching " + indexHandler.getIndexName());
indexHandler.setDomainFieldHandlerFor(j, fmd);
result.add(new int[]{i,j});
}
}
}
if (logger.isDebugEnabled()) logger.debug("Found " + result.size() + " indexes for " + columnName);
return result.toArray(new int[result.size()][]);
}
/** Return the list of index names corresponding to the array of indexes.
* This method is called by the DomainFieldHandlerImpl constructor after the
* registerIndices method is called.
* @param indexArray the result of registerIndices
* @return all index names for the corresponding indexes
*/
public Set<String> getIndexNames(int[][] indexArray) {
Set<String> result = new HashSet<String>();
for (int[] index: indexArray) {
result.add(indexHandlerImpls.get(index[0]).getIndexName());
}
return result;
}
/** Extract the column names from store Columns.
*
* @param indexName the index name (for error messages)
* @param columns the store Column instances
* @return an array of column names
*/
protected String[] getColumnNames(String indexName, Column[] columns) {
Set<String> columnNames = new HashSet<String>();
for (Column column : columns) {
String columnName = column.getName();
if (columnNames.contains(columnName)) {
// error: the column name is duplicated
throw new ClusterJUserException(
local.message("ERR_Duplicate_Column",
name, indexName, columnName));
}
columnNames.add(columnName);
}
return columnNames.toArray(new String[columnNames.size()]);
}
/** Create a list of candidate indexes to evaluate query terms and
* decide what type of operation to use. The result must correspond
* one to one with the indexHandlerImpls.
* @return a new array of CandidateIndexImpl
*/
public CandidateIndexImpl[] createCandidateIndexes() {
CandidateIndexImpl[] result = new CandidateIndexImpl[indexHandlerImpls.size()];
int i = 0;
for (IndexHandlerImpl indexHandler: indexHandlerImpls) {
result[i++] = indexHandler.toCandidateIndexImpl();
}
return result;
}
public String getTableName() {
return tableName;
}
public int getNumberOfFields() {
return numberOfFields;
}
public DomainFieldHandler[] getIdFieldHandlers() {
return idFieldHandlers;
}
public DomainFieldHandler getFieldHandler(String fieldName) {
for (DomainFieldHandler fmd: persistentFieldHandlers) {
if (fmd.getName().equals(fieldName)) {
return fmd;
}
}
throw new ClusterJUserException(
local.message("ERR_Not_A_Member", fieldName, name));
}
public int getFieldNumber(String fieldName) {
Integer fieldNumber = fieldNameToNumber.get(fieldName);
if (fieldNumber == null) {
throw new ClusterJFatalInternalException(
local.message("ERR_No_Field_Number", fieldName, name));
}
return fieldNumber.intValue();
}
public void operationSetNonPKValues(ValueHandler handler, Operation op) {
for (DomainFieldHandler fmd: nonPKFieldHandlers) {
if (handler.isModified(fmd.getFieldNumber())) {
fmd.operationSetValue(handler, op);
}
}
}
public void operationSetValues(ValueHandler handler, Operation op) {
for (DomainFieldHandler fmd: persistentFieldHandlers) {
if (logger.isDetailEnabled()) logger.detail("operationSetValues field: " + fmd.getName());
fmd.operationSetValue(handler, op);
}
}
public void operationSetModifiedNonPKValues(ValueHandler handler, Operation op) {
for (DomainFieldHandler fmd: nonPKFieldHandlers) {
fmd.operationSetModifiedValue(handler, op);
}
}
public void operationSetModifiedValues(ValueHandler handler, Operation op) {
for (DomainFieldHandler fmd: persistentFieldHandlers) {
fmd.operationSetModifiedValue(handler, op);
}
}
public void operationSetKeys(ValueHandler handler, Operation op) {
for (DomainFieldHandler fmd: idFieldHandlers) {
fmd.operationSetValue(handler, op);
}
}
public void operationGetValues(Operation op) {
for (DomainFieldHandler fmd: persistentFieldHandlers) {
fmd.operationGetValue(op);
}
}
public void operationGetValues(Operation op, BitSet fields) {
if (fields == null) {
operationGetValues(op);
} else {
int i = 0;
for (DomainFieldHandler fmd: persistentFieldHandlers) {
if (fields.get(i++)) {
fmd.operationGetValue(op);
}
}
}
}
public void operationGetValuesExcept(IndexOperation op, String index) {
for (DomainFieldHandler fmd: persistentFieldHandlers) {
if (!fmd.includedInIndex(index)) {
if (logger.isDetailEnabled()) logger.detail("operationGetValuesExcept index: " + index);
fmd.operationGetValue(op);
}
}
}
public void objectSetValues(ResultData rs, ValueHandler handler) {
for (DomainFieldHandler fmd: persistentFieldHandlers) {
fmd.objectSetValue(rs, handler);
}
}
public void objectSetValuesExcept(ResultData rs, ValueHandler handler, String indexName) {
for (DomainFieldHandler fmd: persistentFieldHandlers) {
fmd.objectSetValueExceptIndex(rs, handler, indexName);
}
}
protected Table getTable(Dictionary dictionary) {
Table result;
try {
result = dictionary.getTable(tableName);
} catch (Exception ex) {
throw new ClusterJException(
local.message("ERR_Get_NdbTable", name, tableName), ex);
}
return result;
}
public int[] getKeyFieldNumbers() {
return idFieldNumbers;
}
public Table getStoreTable() {
return table;
}
/** Create a partition key for a find by primary key.
* @param handler the handler that contains the values of the primary key
*/
public PartitionKey createPartitionKey(ValueHandler handler) {
// create the partition key based on the mapped table
PartitionKey result = table.createPartitionKey();
// add partition key part value for each partition key field
for (DomainFieldHandler fmd: partitionKeyFieldHandlers) {
if (logger.isDetailEnabled()) logger.detail(
"Field number " + fmd.getFieldNumber()
+ " column name " + fmd.getName() + " field name " + fmd.getName());
fmd.partitionKeySetPart(result, handler);
}
return result;
}
public String getName() {
return name;
}
public Set<String> getColumnNames(BitSet fields) {
throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
}
public Set<com.mysql.clusterj.core.store.Column> getStoreColumns(BitSet fields) {
throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
}
public ValueHandler createKeyValueHandler(Object keys) {
throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
}
public T getInstance(ValueHandler handler) {
throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
}
public Class<?> getOidClass() {
throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
}
public Class<T> getProxyClass() {
throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
}
public ValueHandler getValueHandler(Object instance) {
throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
}
public boolean isSupportedType() {
throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
}
public T newInstance() {
throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
}
public void objectMarkModified(ValueHandler handler, String fieldName) {
throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
}
public void objectResetModified(ValueHandler handler) {
throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
}
public void objectSetCacheManager(CacheManager cm, Object instance) {
throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
}
protected String removeUniqueSuffix(String indexName) {
int beginIndex = indexName.lastIndexOf("$unique");
if (beginIndex < 0) {
// there's no $unique suffix
return indexName;
}
String result = indexName.substring(0, beginIndex);
return result;
}
public String[] getFieldNames() {
return fieldNames;
}
}