/*
   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, version 2.0,
   as published by the Free Software Foundation.

   This program is also distributed with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have included with MySQL.

   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, version 2.0, 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 java.lang.reflect.Modifier;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashSet;
import java.util.List;
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.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.PCState;

import com.mysql.clusterj.ClusterJFatalInternalException;
import com.mysql.clusterj.core.CacheManager;
import com.mysql.clusterj.core.metadata.IndexHandlerImpl;
import com.mysql.clusterj.core.metadata.KeyValueHandlerImpl;
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.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.Table;
import com.mysql.clusterj.core.util.I18NHelper;
import com.mysql.clusterj.core.util.Logger;
import com.mysql.clusterj.core.util.LoggerFactoryService;

/**
 *
 */
public class NdbOpenJPADomainTypeHandlerImpl<T> implements DomainTypeHandler<T> {

    /** My message translator */
    static final I18NHelper local = I18NHelper.getInstance(NdbOpenJPADomainTypeHandlerImpl.class);

    /** My logger */
    static final Logger logger = LoggerFactoryService.getFactory().getInstance(NdbOpenJPADomainTypeHandlerImpl.class);

    private String typeName;
    private Class<T> describedType;
    private Class<?> oidClass;
    private Table storeTable;
    private String tableName;
    private List<NdbOpenJPADomainFieldHandlerImpl> primaryKeyFields =
            new ArrayList<NdbOpenJPADomainFieldHandlerImpl>();
    private int[] primaryKeyFieldNumbers;
    private List<NdbOpenJPADomainFieldHandlerImpl> fields =
            new ArrayList<NdbOpenJPADomainFieldHandlerImpl>();
    private ClassMapping classMapping;

    /** The field handlers for this persistent type*/
    private NdbOpenJPADomainFieldHandlerImpl[] fieldHandlers;

    /** All columns in the mapped table. */
    private Set<com.mysql.clusterj.core.store.Column> allStoreColumns = 
        new HashSet<com.mysql.clusterj.core.store.Column>();

    /** The indexes defined for this domain type */
    private List<IndexHandlerImpl> indexHandlerImpls = new ArrayList<IndexHandlerImpl>();

    private String[] partitionKeyColumnNames;

    private int numberOfPartitionKeyColumns;

    private NdbOpenJPADomainFieldHandlerImpl[] partitionKeyFieldHandlers;

    private NdbOpenJPAConfigurationImpl domainTypeHandlerFactory;

    private Dictionary dictionary;

    private Set<NdbOpenJPADomainTypeHandlerImpl<?>> dependencies =
        new HashSet<NdbOpenJPADomainTypeHandlerImpl<?>>();

    private Status status = Status.IN_PROCESS;

    /** Reasons why this class is not supported by clusterjpa */
    private List<String> reasons = new ArrayList<String>();

    @SuppressWarnings("unchecked")
    NdbOpenJPADomainTypeHandlerImpl(
            Dictionary dictionary, ClassMapping classMapping, 
            NdbOpenJPAConfigurationImpl domainTypeHandlerFactory) {
        String message = null;
        this.dictionary = dictionary;
        this.domainTypeHandlerFactory = domainTypeHandlerFactory;
        this.classMapping = classMapping;
        classMapping.resolve(0xffffffff);
        oidClass = classMapping.getObjectIdType();
        this.describedType = (Class<T>)classMapping.getDescribedType();
        if (classMapping.getPCSuperclass() != null) {
            // persistent subclasses are not supported
            message = local.message("ERR_Subclass", this.describedType);
            setUnsupported(message);
        }
        if (classMapping.getPCSubclasses() != null && classMapping.getPCSubclasses().length > 0) {
            // persistent superclasses are not supported
            message = local.message("ERR_Superclass", this.describedType);
            setUnsupported(message);
        }
        int modifiers = describedType.getClass().getModifiers();
        if (Modifier.isAbstract(modifiers)) {
            // abstract classes are not supported
            message = local.message("ERR_Abstract_Class", describedType.getClass().getName());
            setUnsupported(message);
        }
        this.typeName = describedType.getName();
        org.apache.openjpa.jdbc.schema.Table table = classMapping.getTable();
        if (table != null) {
            this.tableName = table.getFullName();
            this.storeTable = dictionary.getTable(tableName);
            if (storeTable == null) {
                // classes with mapped tables not in cluster are not supported
                message = local.message("ERR_No_Table", describedType.getClass().getName(), tableName);
                logger.info(message);
                setUnsupported(message);
            } else {
                if (logger.isTraceEnabled()) {
                    logger.trace("initialize for class: " + typeName
                            + " mapped to table: " + storeTable.getName());
                }
                // set up the partition keys
                // the partition key field handlers will be initialized via registerPrimaryKeyColumn
                partitionKeyColumnNames = storeTable.getPartitionKeyColumnNames();
                numberOfPartitionKeyColumns = partitionKeyColumnNames.length;
                partitionKeyFieldHandlers = new NdbOpenJPADomainFieldHandlerImpl[numberOfPartitionKeyColumns];
                if (logger.isDetailEnabled()) {
                    logger.detail("partition key columns for class: "+ typeName
                            + " partition key columns: " + numberOfPartitionKeyColumns
                            + " names: " + Arrays.toString(partitionKeyColumnNames));
                }
            }
        } else {
            // classes without mapped tables are not supported
            message = local.message("ERR_No_Mapped_Table", describedType.getClass().getName());
            logger.info(message);
            setUnsupported(message);
        }
        // set up the fields
        List<FieldMapping> allFieldMappings = new ArrayList<FieldMapping>();
        allFieldMappings.addAll(Arrays.asList(classMapping.getFieldMappings()));
        FieldMapping versionFieldMapping = classMapping.getVersionFieldMapping();
        if (versionFieldMapping != null) {
            allFieldMappings.add(versionFieldMapping);
        }
        for (FieldMapping fm: allFieldMappings) {
            if (logger.isDetailEnabled()) logger.detail("field name: " + fm.getName() + " of type: " + fm.getTypeCode());
            NdbOpenJPADomainFieldHandlerImpl fmd = new NdbOpenJPADomainFieldHandlerImpl(
                    dictionary, this, domainTypeHandlerFactory, fm);
            fields.add(fmd);
            if (!fmd.isSupported()) {
                setUnsupported(fmd.getReason());
            } else {
                // add column names to allColumnNames
                for (com.mysql.clusterj.core.store.Column column: fmd.getStoreColumns()) {
                    allStoreColumns.add(column);
                }
                if (fmd.isPrimaryKey()) {
                    primaryKeyFields.add(fmd);
                }
            }
        }
        primaryKeyFieldNumbers = new int[primaryKeyFields.size()];
        int i = 0;
        for (NdbOpenJPADomainFieldHandlerImpl fmd: primaryKeyFields) {
            primaryKeyFieldNumbers[i++] = fmd.getFieldNumber();
        }
        fieldHandlers = fields.toArray(new NdbOpenJPADomainFieldHandlerImpl[fields.size()]);
        // check to make sure all partition keys have registered
        for (int j = 0; j < numberOfPartitionKeyColumns; ++j) {
            if (partitionKeyFieldHandlers[j] == null) {
                setUnsupported();
                reasons.add("Unmapped partition key " + partitionKeyColumnNames[j]);
            }
        }
    }

    /** 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 NdbOpenJPADomainFieldHandlerImpl 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
     */
    protected void registerPrimaryKeyColumn(NdbOpenJPADomainFieldHandlerImpl fmd,
            String 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;
            } else {
                if (logger.isDetailEnabled()) logger.detail(
                        "NdbOpenJPADomainTypeHandlerImpl.registerPrimaryKeyColumn "
                        + "mismatch between partition key column name: " + partitionKeyColumnNames[j]
                        + " and primary key column name: " + columnName);
            }
        }
        return;
    }

    public String getName() {
        return typeName;
    }

    public Class<?> getOidClass() {
        return oidClass;
    }

    public String getTableName() {
        return tableName;
    }

    public DomainFieldHandler getFieldHandler(String fieldName) {
        if (logger.isDetailEnabled()) logger.detail("In " + getName() + " looking for " + fieldName);
        for (NdbOpenJPADomainFieldHandlerImpl domainFieldHandler: fields) {
            if (fieldName.equals(domainFieldHandler.getName())) {
                return domainFieldHandler;
            }
        }
        throw new ClusterJFatalInternalException(
                local.message("ERR_Unknown_Field_Name", fieldName, this.getName()));
    }

    public Class<T> getProxyClass() {
        return null;
    }

    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 objectSetKeys(Object keys, Object instance) {
        throw new ClusterJFatalInternalException(
                local.message("ERR_Implementation_Should_Not_Occur"));
    }

    public void objectSetValues(ResultData rs, ValueHandler handler) {
        for (NdbOpenJPADomainFieldHandlerImpl fmd: fields) {
            fmd.objectSetValue(rs, handler);
        }
    }

    public void objectSetValuesExcept(ResultData rs, ValueHandler handler, String indexName) {
        throw new ClusterJFatalInternalException(
                local.message("ERR_Implementation_Should_Not_Occur"));
    }

    public void objectSetCacheManager(CacheManager cm, Object instance) {
    }

    public void objectResetModified(ValueHandler handler) {
    }

    public void operationGetValues(Operation op) {
        for (NdbOpenJPADomainFieldHandlerImpl fmd: fields) {
            fmd.operationGetValue(op);
        }
    }

    /** Specify that the key columns are to be returned in the result.
     * @param op the operation
     */
    public void operationGetKeys(Operation op) {
        for (NdbOpenJPADomainFieldHandlerImpl fmd: primaryKeyFields) {
            fmd.operationGetValue(op);
        }
    }

    /** Specify the fields to be returned in the result.
     * @param op the operation
     * @param fields the fields to be returned by the operation
     */
    public void operationGetValues(Operation op, BitSet fields) {
        if (fields == null) {
            operationGetValues(op);
        } else {
            int i = 0;
            for (NdbOpenJPADomainFieldHandlerImpl fmd: this.fields) {
                if (fields.get(i++)) {
                    fmd.operationGetValue(op);
                }
            }
        }
    }

    public void operationSetKeys(ValueHandler handler, Operation op) {
        for (NdbOpenJPADomainFieldHandlerImpl fmd: primaryKeyFields) {
            if (logger.isDetailEnabled()) {
                logger.detail("Class: " + typeName
                        + " Primary Key Field: " + fmd.getName() + handler.pkToString(this));
            }
            fmd.operationSetValue(handler, op);
        }
    }

    public void operationSetModifiedValues(ValueHandler handler, Operation op) {
        for (NdbOpenJPADomainFieldHandlerImpl fmd: fields) {
            fmd.operationSetModifiedValue(handler, op);
        }
    }

    public void operationSetValuesExcept(ValueHandler handler, Operation op, String index) {
        try {
            for (NdbOpenJPADomainFieldHandlerImpl fmd : fields) {
                if (!fmd.includedInIndex(index)) {
                    fmd.operationSetValue(handler, op);
                }
            }
        } catch (Exception exception) {
            exception.printStackTrace();
            throw new RuntimeException("NdbOpenJPADomainTypeHandlerImpl.operationSetValuesExcept caught exception", exception);
        }
    }

    public void operationSetModifiedNonPKValues(ValueHandler handler, Operation op) {
        try {
            for (NdbOpenJPADomainFieldHandlerImpl fmd : fields) {
                if (!fmd.isPrimaryKey()) {
                    fmd.operationSetModifiedValue(handler, op);
                }
            }
        } catch (Exception exception) {
            exception.printStackTrace();
            throw new RuntimeException("NdbOpenJPADomainTypeHandlerImpl.operationSetModifiedValuesExcept caught " + exception);
        }
    }

    @SuppressWarnings("unchecked")
    public T getInstance(ValueHandler handler) {
        OpenJPAStateManager sm = ((NdbOpenJPAValueHandler)handler).getStateManager();
        sm.initialize(describedType, PCState.PNONTRANS);
        Object instance= sm.getManagedInstance();
        // System.out.println("NdbOpenJPADomainTypeHandlerImpl.getInstance returned " + instance.getClass() + " " + instance);
        return (T) instance;
    }

    public ValueHandler createKeyValueHandler(Object keys) {

        FieldMapping[] primaryKeyFieldMappings =
                classMapping.getPrimaryKeyFieldMappings();
        int numberOfFields = classMapping.getFieldMappings().length;
        Object[] keyValues = new Object[numberOfFields];
        boolean nullKeyValue = false;
        if (primaryKeyFieldMappings.length != 1) {
        // for each key field, use the field value accessor to get the
        // value from the Oid and put it into the proper place in keyValues
            for (NdbOpenJPADomainFieldHandlerImpl fmd: primaryKeyFields) {
                // store the key value from the oid into the keyValues array
                // this can be improved with a smarter KeyValueHandlerImpl
                Object keyValue = fmd.getKeyValue(keys);
                keyValues[fmd.getFieldNumber()] = keyValue;
                if (keyValue == null) {
                    nullKeyValue = true;
                }
            }
        } else {
            keyValues[primaryKeyFieldMappings[0].getIndex()] = keys;
        }
        KeyValueHandlerImpl keyHandler = new KeyValueHandlerImpl(keyValues);
        return nullKeyValue?null:keyHandler;
    }

    public ValueHandler getValueHandler(Object instance) {
        if (instance instanceof ValueHandler) {
            return (ValueHandler)instance;
        } else {
            OpenJPAStateManager sm = (OpenJPAStateManager)instance;
            return new NdbOpenJPAValueHandler(sm);
        }
    }

    public ValueHandler getValueHandler(OpenJPAStateManager sm, NdbOpenJPAStoreManager store) {
        return new NdbOpenJPAValueHandler(sm, store);
    }

    public int[] getKeyFieldNumbers() {
        return primaryKeyFieldNumbers;
    }

    public void newInstance(OpenJPAStateManager sm) {
        // TODO: accommodate subclasses based on results of select
        sm.initialize(describedType, PCState.PNONTRANS);
    }

    /** Create a list of candidate indexes to evaluate query terms and
     * decide what type of scan to use.
     * @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;
    }

    /** Load the fields for the persistent instance owned by the sm.
     * @param sm the StateManager
     * @param store the StoreManager
     * @param fields the fields to load
     * @param fetch the FetchConfiguration
     * @return true if any field was loaded
     */
    public boolean load(OpenJPAStateManager sm, NdbOpenJPAStoreManager store, 
            BitSet fields, JDBCFetchConfiguration fetch, Object context) throws SQLException {
        if (logger.isDetailEnabled()) {
            StringBuilder buffer = new StringBuilder("load for ");
            buffer.append(typeName);
            buffer.append(" for fields ");
            buffer.append(NdbOpenJPAUtility.printBitSet(sm, fields));
            logger.detail(buffer.toString());
        }
        boolean loadedAny = false;
        List<NdbOpenJPADomainFieldHandlerImpl> requestedFields = new ArrayList<NdbOpenJPADomainFieldHandlerImpl>();
        for (int i=fields.nextSetBit(0); i>=0; i=fields.nextSetBit(i+1)) {
            if (!sm.getLoaded().get(i)) {
                // this field is not loaded
                NdbOpenJPADomainFieldHandlerImpl fieldHandler = fieldHandlers[i];
                if (logger.isDebugEnabled()) logger.debug(
                        "loading field " + fieldHandler.getName()
                        + " for column " + fieldHandler.getColumnName());
                if (fieldHandler.isRelation()) {
                    // if relation, load each field individually with its own query
                    fieldHandler.load(sm, store, fetch);
                    loadedAny = true;
                } else {
                    // if not relation, get a list of all fields to load with one lookup
                    requestedFields.add(fieldHandler);
                }
            }
        }
        if (requestedFields.size() != 0) {
            // load fields via primary key lookup
            NdbOpenJPAResult result = store.lookup(sm, this, requestedFields);
            for (NdbOpenJPADomainFieldHandlerImpl fieldHandler: fieldHandlers) {
                loadedAny = true;
                fieldHandler.load(sm, store, fetch, result);
            }
        }
        return loadedAny;
    }

    /** Load the fields for the persistent instance owned by the sm.
     * This is the normal way for fields to be loaded into the instance from a result.
     * @param sm the StateManager
     * @param store the StoreManager
     * @param fetch the FetchConfiguration
     */
    public void load(OpenJPAStateManager sm, NdbOpenJPAStoreManager store, 
            JDBCFetchConfiguration fetch, NdbOpenJPAResult result) throws SQLException {
        for (NdbOpenJPADomainFieldHandlerImpl fieldHandler: fieldHandlers) {
            if (logger.isDebugEnabled()) logger.debug("loading field " + fieldHandler.getName()
                    + " for column " + fieldHandler.getColumnName() + " from result data.");
            fieldHandler.load(sm, store, fetch, result);
        }
    }

    /** Get StoreColumns for fields identified by the BitSet
     * 
     */
    public Set<com.mysql.clusterj.core.store.Column> getStoreColumns(BitSet fields) {
        // iterate the fields and save the columns in a Set
        if (fields == null) {
            return allStoreColumns;
        }
        Set<com.mysql.clusterj.core.store.Column> result = 
            new HashSet<com.mysql.clusterj.core.store.Column>();
        for (int i=fields.nextSetBit(0); i>=0; i=fields.nextSetBit(i+1)) {
            NdbOpenJPADomainFieldHandlerImpl fieldHandler = fieldHandlers[i];
            for (com.mysql.clusterj.core.store.Column column: fieldHandler.getStoreColumns()) {
                result.add(column);
            }
        }
        return result;
    }

    /** Get the DomainFieldHandler for the related field.
     * 
     * @param fm the related field mapping
     * @return the DomainFieldHandler
     */
    public NdbOpenJPADomainFieldHandlerImpl getDomainFieldHandler(FieldMapping fm) {
        for (NdbOpenJPADomainFieldHandlerImpl domainFieldHandler: fieldHandlers) {
            if (fm.equals(domainFieldHandler.getFieldMapping())) {
                return domainFieldHandler;
            }
        }
        throw new ClusterJFatalInternalException(
                local.message("ERR_Unknown_Field_Mapping",
                        this.getName(), fm.getName()));
    }

    /** Register an index from a field and return a special int[][] that
     * contains all indexHandlerImpls in which the
     * field participates. One index can be defined in the field annotation
     * and this index is identified here. This method is called by the
     * FieldHandler constructor after the Index annotation is processed
     * and the mapped column name is known.
     * TODO: Currently, only one index is supported per field.
     * @param fieldHandler the FieldHandler
     * @param indexName the index name
     * @param dictionary the dictionary used to validate index metadata
     * @return the array of array identifying the indexes into the IndexHandler
     * list and columns in the IndexHandler corresponding to the field
     */
    public int[][] createIndexHandler( NdbOpenJPADomainFieldHandlerImpl fieldHandler,
            Dictionary dictionary, String indexName) {
        IndexHandlerImpl indexHandler = new IndexHandlerImpl(this, dictionary, indexName, fieldHandler);
        int currentIndex = indexHandlerImpls.size();
        indexHandlerImpls.add(indexHandler);
        int[][] result = new int[1][2];
        result[0][0] = currentIndex;
        result[0][1] = 0;
        return result;
    }

    public com.mysql.clusterj.core.store.Table getTable() {
        return storeTable;
    }

    public Table getStoreTable() {
        return storeTable;
    }

    /** Create a partition key for any operation that knows the 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 = storeTable.createPartitionKey();
        // add partition key part value for each partition key field
        for (NdbOpenJPADomainFieldHandlerImpl fmd: partitionKeyFieldHandlers) {
            if (logger.isDetailEnabled()) logger.detail(
                    "Field number " + fmd.getFieldNumber()
                    + " column name " + fmd.getColumnName() + " field name " + fmd.getName());
            fmd.partitionKeySetPart(result, handler);
        }
        return result;
    }

    /** Register a dependency on another class. If the class is not already known,
     * add it to the list of dependencies.
     */
    public NdbOpenJPADomainTypeHandlerImpl<?> registerDependency(ClassMapping mapping) {
        NdbOpenJPADomainTypeHandlerImpl<?> domainTypeHandler = 
            domainTypeHandlerFactory.getDomainTypeHandler(mapping, dictionary);
        dependencies.add(domainTypeHandler);
        return domainTypeHandler;
    }

    /** Status of this class with regard to usability with clusterjpa.
     * 
     */
    private enum Status {
        IN_PROCESS,
        GOOD,
        BAD;
    }

    private Status supportStatus() {
        return status;
    }

    public void initializeRelations() {
        for (NdbOpenJPADomainFieldHandlerImpl fieldHandler: fieldHandlers) {
            fieldHandler.initializeRelations();
        }

        // iterate the dependencies and see if any are not supported
        boolean supported = status != Status.BAD;
        boolean workToDo = !supported;
        // iterate a copy of dependencies to avoid concurrent modification of dependencies
        List<NdbOpenJPADomainTypeHandlerImpl<?>> copyOfDependencies =
                new ArrayList<NdbOpenJPADomainTypeHandlerImpl<?>>(dependencies);
        for (NdbOpenJPADomainTypeHandlerImpl<?> dependent: copyOfDependencies) {
            // top level class will have recursive list of dependencies including itself
            this.dependencies.addAll(dependent.getDependencies());
            switch (dependent.supportStatus()) {
                case IN_PROCESS:
                    workToDo = true;
                    break;
                case GOOD:
                    break;
                case BAD:
                    setUnsupported();
                    supported = false;
                    workToDo = true;
                    String message = local.message("ERR_Bad_Dependency", dependent.typeName);
                    reasons.add(message);
                    if (logger.isDebugEnabled()) logger.debug(message);
            }
            String message = "Processing class " + typeName + " found dependency " + dependent.typeName
            + " support status is: " + dependent.supportStatus();
            if (logger.isDetailEnabled()) logger.detail(message);
        }
        if (workToDo) {
            if (!supported) {
                // found an unsupported class in the dependency list
                for (NdbOpenJPADomainTypeHandlerImpl<?> dependent: dependencies) {
                    dependent.setUnsupported();
                }
            } else {
                for (NdbOpenJPADomainTypeHandlerImpl<?> dependent: dependencies) {
                    dependent.setSupported();
                }
            }
        } else {
            this.setSupported();
        }
        if (logger.isDetailEnabled()) logger.detail("Processing class " + typeName + " has dependencies " + dependencies);
        if (logger.isDetailEnabled()) logger.detail("Processing class " + typeName + " has dependencies " + dependencies);
    }

    private Set<NdbOpenJPADomainTypeHandlerImpl<?>> getDependencies() {
        return dependencies;
    }

    private void setUnsupported() {
        if (status != Status.BAD) {
            if (logger.isDetailEnabled()) logger.detail("Class " + typeName + " marked as BAD.");
            status = Status.BAD;
        }
    }

    private void setUnsupported(String reason) {
        if (status != Status.BAD) {
            if (logger.isDetailEnabled()) logger.detail("Class " + typeName + " marked as BAD.");
            status = Status.BAD;
        }
        reasons.add(reason);
    }

    private void setSupported() {
        if (status != Status.GOOD) {
            if (logger.isDetailEnabled()) logger.detail("Class " + typeName + " marked as GOOD.");
            status = Status.GOOD;
        }
    }

    public boolean isSupportedType() {
        return Status.GOOD == status;
    }

    public String getReasons() {
        if (reasons.size() == 0) {
            return null;
        }
        StringBuilder result = new StringBuilder(
                local.message("MSG_Unsupported_Class", getName()));
        for (String reason:reasons) {
            result.append('\n');
            result.append(reason);
            result.append(';');
        }
        result.append('\n');
        return result.toString();
    }

    @Override
    public String toString() {
        return "NdbOpenJPADomainTypeHandlerImpl:" + typeName;
    }

    public String[] getFieldNames() {
        throw new ClusterJFatalInternalException(
                local.message("ERR_Implementation_Should_Not_Occur"));
    }

    public void operationSetValues(ValueHandler valueHandler, Operation op) {
        try {
            for (NdbOpenJPADomainFieldHandlerImpl fmd : fields) {
                fmd.operationSetValue(valueHandler, op);
            }
        } catch (Exception exception) {
            exception.printStackTrace();
            throw new RuntimeException("NdbOpenJPADomainTypeHandlerImpl.operationSetValues caught exception", exception);
        }
    }

    public void operationSetNonPKValues(ValueHandler valueHandler, Operation op) {
        try {
            for (NdbOpenJPADomainFieldHandlerImpl fmd : fields) {
                if (!fmd.isPrimaryKey()) {
                    fmd.operationSetValue(valueHandler, op);
                }
            }
        } catch (Exception exception) {
            exception.printStackTrace();
            throw new RuntimeException("NdbOpenJPADomainTypeHandlerImpl.operationSetNonPKValues caught exception", exception);
        }
    }

}
