All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.jdo.spi.persistence.support.sqlstore.model.ClassDesc Maven / Gradle / Ivy

There is a newer version: 6.2024.6
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
// Portion Copyright [2018] Payara Foundation and/or affiliates

/*
 * ClassDesc.java
 *
 * Created on March 3, 2000
 *
 */

package com.sun.jdo.spi.persistence.support.sqlstore.model;

import org.netbeans.modules.dbschema.ColumnElement;
import org.netbeans.modules.dbschema.ColumnPairElement;
import org.netbeans.modules.dbschema.TableElement;
import com.sun.jdo.api.persistence.model.Model;
import com.sun.jdo.api.persistence.model.jdo.ConcurrencyGroupElement;
import com.sun.jdo.api.persistence.model.jdo.PersistenceClassElement;
import com.sun.jdo.api.persistence.model.jdo.PersistenceFieldElement;
import com.sun.jdo.api.persistence.model.jdo.RelationshipElement;
import com.sun.jdo.api.persistence.model.mapping.MappingClassElement;
import com.sun.jdo.api.persistence.model.mapping.MappingFieldElement;
import com.sun.jdo.api.persistence.model.mapping.MappingRelationshipElement;
import com.sun.jdo.api.persistence.model.mapping.impl.*;
import com.sun.jdo.api.persistence.support.*;
import com.sun.jdo.spi.persistence.support.sqlstore.*;
import com.sun.jdo.spi.persistence.support.sqlstore.sql.RetrieveDescImpl;
import com.sun.jdo.spi.persistence.support.sqlstore.sql.UpdateObjectDescImpl;
import com.sun.jdo.spi.persistence.support.sqlstore.sql.concurrency.*;
import com.sun.jdo.spi.persistence.support.sqlstore.sql.generator.UpdateQueryPlan;
import org.glassfish.common.util.StringHelper;
import com.sun.jdo.spi.persistence.utility.logging.Logger;
import org.glassfish.persistence.common.I18NHelper;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;

/**
 *
 */
public class ClassDesc
        implements com.sun.jdo.spi.persistence.support.sqlstore.PersistenceConfig {

    private ArrayList fetchGroups;

    private int maxHierarchicalGroupID;

    /**
     * Used for batched update check. Set to true if this class has at least one
     * local field that does not belong to DFG
     */
    private boolean hasLocalNonDFGField;

    /** Contains all local and foreign fields. */
    public ArrayList fields;

    /** Contains all hidden fields. */
    public ArrayList hiddenFields;

    /** Contains all relationship fields. */
    public ArrayList foreignFields;

    /** Contains the fields used for version consistency validation. */
    private LocalFieldDesc[] versionFields;

    private ArrayList tables;

    private Class pcClass;

    private Class oidClass;

    public int maxFields;

    public int maxVisibleFields;

    public int maxHiddenFields;

    private Concurrency optimisticConcurrency;

    private Concurrency checkDirtyConcurrency;

    private Concurrency databaseConcurrency;

    private Concurrency explicitConcurrency;

    private MappingClassElementImpl mdConfig;

    private ClassLoader classLoader;

    private Constructor constructor;

    private PersistenceClassElement pcElement;

    private PersistenceFieldElement[] persistentFields;

    private Field keyFields[];
    private String keyFieldNames[];
    private LocalFieldDesc[] keyFieldDescs;

    /** The logger. */
    private static Logger logger = LogHelperSQLStore.getLogger();

    /** RetrieveDescriptor cache for navigation and reloading. */
    private final Map retrieveDescCache = new HashMap();

    /**
     * RetrieveDescriptor cache for navigation queries. This cache
     * holds foreign RetrieveDescriptors constrained by the relationship
     * key values.
     */
    private final Map foreignRetrieveDescCache = new HashMap();

    /** Retrieve descriptor for version consistency verification. */
    private RetrieveDesc retrieveDescForVerification;
    private Object retrieveDescForVerificationSynchObj = new Object();

    /** UpdateQueryPlan cache. */
    private final Map updateQueryPlanCache = new HashMap();

    /** UpdateQueryPlan for insert. */
    private UpdateQueryPlan updateQueryPlanForInsert;
    private Object updateQueryPlanForInsertSynchObj = new Object();

    /** UpdateQueryPlan for delete. */
    private UpdateQueryPlan updateQueryPlanForDelete;
    private Object updateQueryPlanForDeleteSynchObj = new Object();

    /** I18N message handler. */
    private final static ResourceBundle messages = I18NHelper.loadBundle(
            "com.sun.jdo.spi.persistence.support.sqlstore.Bundle", // NOI18N
            ClassDesc.class.getClassLoader());

    /** PC Constructor signature. */
    private static final Class[] sigSM = new Class[]{StateManager.class};

    public ClassDesc(MappingClassElement mdConfig, Class pcClass) {

        this.mdConfig = (MappingClassElementImpl) mdConfig;
        pcElement = this.mdConfig.getPersistenceElement();
        this.pcClass = pcClass;
        classLoader = pcClass.getClassLoader();

        try {
            constructor = pcClass.getConstructor(sigSM);
        } catch (Exception e) {
            // Persistence capable classes must provide this constructor
            throw new JDOFatalUserException(I18NHelper.getMessage(messages,
                    "jdo.persistencemanagerimpl.assertpersistencecapable.error", // NOI18N
                    pcClass.getName()), e);
        }

        fields = new ArrayList();
        foreignFields = new ArrayList();
        tables = new ArrayList();
        fetchGroups = new ArrayList();
    }

    public String toString() {
        return getName();
    }

    /**
     * Creates a new instance of ClassDesc for the given
     * pcClass.
     * @param pcClass The persistence capable class.
     * @return A new instance of ClassDesc.
     */
    static ClassDesc newInstance(Class pcClass) {
        Model model = Model.RUNTIME;
        String className = pcClass.getName();
        ClassLoader classLoader = pcClass.getClassLoader();
        ClassDesc rc = null;

        try {
            MappingClassElement mdConfig =
               model.getMappingClass(className, classLoader);

            // Validate the model information for this class.
            validateModel(model, className, classLoader);

            rc = new ClassDesc(mdConfig, pcClass);

        } catch (JDOException e) {
            throw e;
        } catch (IllegalArgumentException e) {
            throw new JDOFatalUserException(I18NHelper.getMessage(messages,
                "core.configuration.loadfailed.class", className), e); // NOI18N
        } catch (Exception e) {
            throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
                "core.configuration.loadfailed.class", className), e); // NOI18N
        }

        return rc;
    }

    /**
     * Validate the mapping for the given class. If the result
     * collection returned by validate() is not empty, it means
     * it failed the test. After logging all the exceptions,
     * throw a JDOUserException and inform the user to go back
     * to the mapping tool to fix up the mapping file. The
     * JDOUserException contains the validation messages aswell.
     *
     * @param model Runtime model.
     * @param className Persistence capable class' name.
     * @param classLoader Persistence capable class' loader.
     */
    static private void validateModel(Model model,
                                      String className,
                                      ClassLoader classLoader) {
        Collection c = null;

        if (!(c = model.validate(className, classLoader, null)).isEmpty()) {
            Iterator iter = c.iterator();
            StringBuilder validationMsgs = new StringBuilder();

            while (iter.hasNext()) {
                Exception ex = (Exception) iter.next();
                String validationMsg = ex.getLocalizedMessage();

                logger.fine(I18NHelper.getMessage(messages,
                    "core.configuration.validationproblem", // NOI18N
                    className, validationMsg));
                validationMsgs.append(validationMsg).append('\n'); // NOI18N
            }
            throw new JDOFatalUserException(I18NHelper.getMessage(messages,
                "core.configuration.validationfailed", // NOI18N
                className, validationMsgs.toString()));
        }
    }

    public void initialize(ConfigCache cache) {
        boolean debug = logger.isLoggable();
        if (debug) {
            logger.fine("sqlstore.model.classdesc.persistconfiginit", mdConfig); // NOI18N
        }

        loadOidClass();
        initializeFields();
        computeTrackedPrimitiveFields();

        initializeTables();
        initializeJoinTables();
        initializeVersionFields();
        initializeConcurrency();

        initializeKeyFields();
        initializeFetchGroups();

        fixupForeignReferences(cache);
        fixupFieldProperties();
        computeTrackedRelationshipFields();
        cleanupTrackedFields();

        // All fields should be initialized at this point. Now calculate
        // the total number of hidden fields and fields.
        if (hiddenFields != null) {
            maxHiddenFields = hiddenFields.size();
        }
        maxFields = maxVisibleFields + maxHiddenFields;

        if (debug) {
            logger.fine("sqlstore.model.classdesc.persistconfiginit.exit"); // NOI18N
        }
    }

    private void loadOidClass() {

        if (oidClass != null) return;

        String keyClassName = pcElement.getKeyClass();
        String suffix = keyClassName.substring(keyClassName.length() - 4);

        // First check whether the key class name ends with ".oid".
        // If so, we need to convert it '.' to '$' because is it
        // an inner class.
        if (suffix.compareToIgnoreCase(".oid") == 0) { // NOI18N
            StringBuilder buf = new StringBuilder(keyClassName);

            buf.setCharAt(buf.length() - 4, '$');
            keyClassName = buf.toString();
        }

        try {
            oidClass = Class.forName(keyClassName, true, classLoader);
        } catch (Throwable e) {
            throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
                    "core.configuration.cantloadclass", keyClassName)); // NOI18N
        }

        if (logger.isLoggable()) {
            logger.fine("sqlstore.model.classdesc.loadedclass", oidClass); // NOI18N
        }
    }

    /**
     * This method maps all the visible fields. It has the side-effect of computing
     * the value for maxVisibleFields.
     */
    private void initializeFields() {
        ArrayList concurrencyGroups = new ArrayList();
        persistentFields = pcElement.getFields();

        for (int i = 0; i < persistentFields.length; i++) {
            PersistenceFieldElement pcf = persistentFields[i];
            MappingFieldElementImpl mdf = (MappingFieldElementImpl) mdConfig.getField(pcf.getName());

            if (mdf == null) {
                throw new JDOFatalUserException(I18NHelper.getMessage(messages,
                        "core.configuration.fieldnotmapped", // NOI18N
                        pcf.getName(), pcElement.getName()));
            }

            FieldDesc f;

            if (!(mdf instanceof MappingRelationshipElement)) {
                f = createLocalField(mdf);
            } else {
                f = createForeignField((RelationshipElement) pcf, (MappingRelationshipElementImpl) mdf);
            }

            initializeFetchAndConcurrencyGroup(f, pcf, mdf, concurrencyGroups);

            if (mdf.isReadOnly()) {
                f.sqlProperties |= FieldDesc.PROP_READ_ONLY;
            }

            try {
                f.setupDesc(pcClass, pcf.getName());
            } catch (JDOException e) {
                throw e;
            } catch (Exception e) {
                throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
                "core.configuration.loadfailed.field", // NOI18N
                pcf.getName(), pcElement.getName()), e);
            }

            f.absoluteID = pcf.getFieldNumber();

            addField(f);

            if (logger.isLoggable(Logger.FINEST)) {
                Object[] items = new Object[] {f.getName(),new Integer(f.absoluteID)};
                logger.finest("sqlstore.model.classdesc.fieldinfo", items); // NOI18N
            }
        }

        this.maxVisibleFields = fields.size();
    }

    /**
     * Creates an instance of LocalFieldDesc.

     * @param mdf Input MappingFieldElementImpl
     */
    private LocalFieldDesc createLocalField(MappingFieldElementImpl mdf) {
        ArrayList columnDesc = mdf.getColumnObjects();

        // Make sure this field is properly mapped.
        if ((columnDesc == null) || (columnDesc.size() == 0)) {
            throw new JDOFatalUserException(I18NHelper.getMessage(messages,
                    "core.configuration.fieldnotmapped", // NOI18N
                    mdf.getName(), pcElement.getName()));
        }
        return new LocalFieldDesc(this, columnDesc);
    }

    /**
     * Creates an instance of LocalFieldDesc for column
     * that corresponds to a hidden field. Adds the newly created field to
     * hiddenFields and declaredHiddenFields.
     *
     * @param column The input column.
     * @return New instance of LocalFieldDesc
     */
    private LocalFieldDesc createLocalHiddenField(ColumnElement column) {
        ArrayList columnDesc = new ArrayList();
        columnDesc.add(column);
        LocalFieldDesc lf = new LocalFieldDesc(this, columnDesc);

        if (hiddenFields == null) {
            hiddenFields = new ArrayList();
        }
        hiddenFields.add(lf);

        // AbsouluteID for hidden fields must be < 0.
        lf.absoluteID = -hiddenFields.size();

        if (logger.isLoggable(Logger.FINEST)) {
            Object[] items = new Object[] {pcClass,lf.getName(),column.getName().getFullName()};
            logger.finest("sqlstore.model.classdesc.getlocalfielddesc", items); // NOI18N
        }

        return lf;
    }

    private FieldDesc createForeignField(RelationshipElement fpcf,
                                         MappingRelationshipElementImpl fmdf) {

        ForeignFieldDesc ff = new ForeignFieldDesc(this);

        addForeignField(ff);

        ff.cardinalityLWB = fpcf.getLowerBound();
        ff.cardinalityUPB = fpcf.getUpperBound();
        ff.deleteAction = fpcf.getDeleteAction();

        initializeColumnLists(ff, fmdf);

        // This is a workaround to make sure that that cardinalityUPB is not <= 1
        // if the field is of type collection.
        if (Model.RUNTIME.isCollection(fpcf.getCollectionClass()) &&
                (ff.cardinalityUPB <= 1)) {
            ff.cardinalityUPB = Integer.MAX_VALUE;
        }

        if (ff.cardinalityUPB > 1) {
            try {
                ff.setComponentType(Class.forName(fpcf.getElementClass(), true, classLoader));
            } catch (Throwable e) {
              logger.log(Logger.WARNING, "sqlstore.exception.log", e);
            }
        }

        return ff;
    }

    private void initializeColumnLists(ForeignFieldDesc ff, MappingRelationshipElementImpl fmdf) {
        ArrayList assocPairs = fmdf.getAssociatedColumnObjects();
        ArrayList pairs = fmdf.getColumnObjects();
        ArrayList localColumns = new ArrayList();
        ArrayList foreignColumns = new ArrayList();

        // We need to go through each local column and extract the foreign column.
        if ((assocPairs == null) || (assocPairs.size() == 0)) {
            for (int i = 0; i < pairs.size(); i++) {
                ColumnPairElement fce = (ColumnPairElement) pairs.get(i);
                localColumns.add(fce.getLocalColumn());
                foreignColumns.add(fce.getReferencedColumn());
            }

            ff.localColumns = localColumns;
            ff.foreignColumns = foreignColumns;
        } else {
            ArrayList assocLocalColumns = new ArrayList();
            ArrayList assocForeignColumns = new ArrayList();

            for (int i = 0; i < pairs.size(); i++) {
                ColumnPairElement alc = (ColumnPairElement) pairs.get(i);
                localColumns.add(alc.getLocalColumn());
                assocLocalColumns.add(alc.getReferencedColumn());
            }

            for (int i = 0; i < assocPairs.size(); i++) {
                ColumnPairElement afc = (ColumnPairElement) assocPairs.get(i);
                assocForeignColumns.add(afc.getLocalColumn());
                foreignColumns.add(afc.getReferencedColumn());
            }

            ff.localColumns = localColumns;
            ff.assocLocalColumns = assocLocalColumns;
            ff.assocForeignColumns = assocForeignColumns;
            ff.foreignColumns = foreignColumns;
        }
    }

    private void initializeFetchAndConcurrencyGroup(FieldDesc f,
                                                    PersistenceFieldElement pcf,
                                                    MappingFieldElementImpl mdf,
                                                    ArrayList concurrencyGroups) {
        f.fetchGroup = mdf.getFetchGroup();

        // RESOLVE: For now, we can only handle one concurrency group per field
        // MBO:
        // I can call method getConcurrencyGroups w/o exception using the latest mapping files.
        // Please note the mapping files do not include any concurrency group info,
        // thus getConcurrencyGroups returns an empty array.
        ConcurrencyGroupElement cgroups[] = pcf.getConcurrencyGroups();
        //ConcurrencyGroupElement cgroups[] = null;

        if ((cgroups == null) || (cgroups.length == 0)) {
            if (f.fetchGroup == FieldDesc.GROUP_DEFAULT) {
                f.concurrencyGroup = f.fetchGroup;
            }
        } else {
            ConcurrencyGroupElement cge = cgroups[0];
            int index = 0;

            if ((index = concurrencyGroups.indexOf(cge)) == -1) {
                index = concurrencyGroups.size();
                concurrencyGroups.add(cge);
            }

            f.concurrencyGroup = index;
        }
    }

    private void createSecondaryTableKey(TableDesc table, MappingReferenceKeyElementImpl mappingSecondaryKey) {

        ColumnPairElement pairs[] = mappingSecondaryKey.getColumnPairs();
        KeyDesc referencingKey = new KeyDesc();
        KeyDesc referencedKey = new KeyDesc();
        TableDesc secondaryTable = findTableDesc(((MappingTableElementImpl) mappingSecondaryKey.getTable()).getTableObject());

        for (int i = 0; i < pairs.length; i++) {
            ColumnPairElement pair = pairs[i];

            ColumnElement lc = pair.getLocalColumn();
            ColumnElement fc = pair.getReferencedColumn();

            referencingKey.addColumn(lc);

            FieldDesc lf = getLocalFieldDesc(lc);
            referencingKey.addField(lf);

            // We need to force field for the referencing key to be in the DFG
            // so it will always be loaded. This is to facilitate updating
            // secondary tables that requires this field to be loaded
            // for constraint purposes.
            lf.fetchGroup = FieldDesc.GROUP_DEFAULT;

            referencedKey.addColumn(fc);
            referencedKey.addField(getLocalFieldDesc(fc));
        }

        table.addSecondaryTableKey(new ReferenceKeyDesc(secondaryTable, referencingKey, referencedKey));
        secondaryTable.setPrimaryTableKey(new ReferenceKeyDesc(table, referencedKey, referencingKey));
    }

    /**
     * This method maps all the tables.
     */
    private void initializeTables() {
        ArrayList mdTables = mdConfig.getTables();

        createTables(mdTables);
        processSecondaryTables(mdTables);
    }

    private void createTables(ArrayList mdTables) {
        for (int i = 0; i < mdTables.size(); i++) {
            MappingTableElementImpl mdt = (MappingTableElementImpl) mdTables.get(i);
            TableDesc t = new TableDesc(mdt.getTableObject());

            ArrayList keys = mdt.getKeyObjects();
            KeyDesc key = new KeyDesc();
            t.setKey(key);
            key.addColumns(keys);

            for (int j = 0; j < keys.size(); j++) {
                ColumnElement c = (ColumnElement) keys.get(j);

                if (c != null) {
                    key.addField(getLocalFieldDesc(c));
                }
            }

            addTableDesc(t);
        }
    }

    /**
     * Validity checks on secondary tables:
     *	1) Ensure that every secondary table has a TableDesc
     *	2) Every referencing key is the same length as the table's key
     * Build the referencing keys.
     * NOTE: This method assumes that the entries of mdTables
     * and tables are sorted in the same order.
     */
    private void processSecondaryTables(ArrayList mdTables) {

        for (int i = 0; i < tables.size(); i++) {
            MappingTableElementImpl mdt = (MappingTableElementImpl) mdTables.get(i);
            TableDesc t = (TableDesc) tables.get(i);
            ArrayList secondaryKeys = mdt.getReferencingKeys();

            for (int j = 0; j < secondaryKeys.size(); j++) {
                MappingReferenceKeyElementImpl mappingSecondaryKey = (MappingReferenceKeyElementImpl) secondaryKeys.get(j);
                createSecondaryTableKey(t, mappingSecondaryKey);
            }
        }
    }

    private void initializeJoinTables() {
        Iterator iter = foreignFields.iterator();

        while (iter.hasNext()) {
            ForeignFieldDesc ff = (ForeignFieldDesc) iter.next();

            if (ff.useJoinTable()) {
                TableElement joinTable = ((ColumnElement) ff.assocLocalColumns.get(0)).getDeclaringTable();
                TableDesc joinTableDesc = findTableDesc(joinTable);

                if (joinTableDesc == null) {
                    joinTableDesc = new TableDesc(joinTable);

                    // Mark this table as a join table
                    joinTableDesc.setJoinTable(true);
                    addTableDesc(joinTableDesc);
                }
              }
        }
    }

    /**
     * Returns a list of fields in fetchGroup groupID.
     *
     * @param groupID Fetch group id.
     * @return List of fields in fetchGroup groupID. The list
     *  for FieldDesc.GROUP_NONE is empty.
     * @see #initializeFetchGroups
     */
    public ArrayList getFetchGroup(int groupID) {
        int index = 0;

        if (groupID >= FieldDesc.GROUP_NONE) {
            index = groupID;
        } else if (groupID < FieldDesc.GROUP_NONE) {
            index = -groupID + maxHierarchicalGroupID;
        }

        for (int i = fetchGroups.size(); i <= index; i++) {
            fetchGroups.add(null);
        }

        ArrayList group = (ArrayList) fetchGroups.get(index);

        if (group == null) {
            group = new ArrayList();
            fetchGroups.set(index, group);
        }

        return group;
    }

    private void addField(FieldDesc f) {
        fields.add(f);
    }

    private void addForeignField(ForeignFieldDesc f) {
        foreignFields.add(f);
    }

    /**
     * Compute the fetch group lists for the declared fields.
     * A fetch group lists the fields sharing the same value for
     * FieldDesc.fetchGroup.
     * Note: Fields that aren't in any fetch group must not be added
     * to fetchGroups[FieldDesc.GROUP_NONE].
     *
     * @see #getFetchGroup
     */
    private void initializeFetchGroups() {

        for (int i = 0; i < 2; i++) {
            ArrayList theFields = null;

            if (i == 0) {
                theFields = fields;
            } else {
                // It is possible to have hidden fields in the DFG.
                if ((theFields = hiddenFields) == null)
                    continue;
            }

            for (int j = 0; j < theFields.size(); j++) {
                FieldDesc f = (FieldDesc) theFields.get(j);

                // Do not add the field to the fetch group for GROUP_NONE.
                if (f.fetchGroup > FieldDesc.GROUP_NONE) {
                    getFetchGroup(f.fetchGroup).add(f);
                }

                // Check declared visible fields only.
                if (i == 0 && !f.isRelationshipField()
                        && f.fetchGroup != FieldDesc.GROUP_DEFAULT) {
                    hasLocalNonDFGField = true;
                }
            }
        }

        this.maxHierarchicalGroupID = fetchGroups.size() - 1;

        for (int i = 0; i < fields.size(); i++) {
            FieldDesc f = (FieldDesc) fields.get(i);

            // Do not add the field to the fetch group for GROUP_NONE.
            if (f.fetchGroup < FieldDesc.GROUP_NONE) {
                getFetchGroup(f.fetchGroup).add(f);
            }
        }
    }

    private void initializeConcurrency() {
        optimisticConcurrency = new ConcurrencyOptVerify();
        optimisticConcurrency.configPersistence(this);
        checkDirtyConcurrency = new ConcurrencyCheckDirty();
        checkDirtyConcurrency.configPersistence(this);
        databaseConcurrency = new ConcurrencyDBNative();
        databaseConcurrency.configPersistence(this);
        explicitConcurrency = new ConcurrencyDBExplicit();
        explicitConcurrency.configPersistence(this);
    }

    private void initializeKeyFields() {
        boolean debug = logger.isLoggable(Logger.FINEST);
        if (oidClass == null)
            return;

        keyFields = oidClass.getFields();
        keyFieldNames = new String[keyFields.length];
        keyFieldDescs = new LocalFieldDesc[keyFields.length];

        if (debug) {
            logger.finest("sqlstore.model.classdesc.createsqldesc", oidClass); // NOI18N
        }

        for (int i = 0; i < keyFields.length; i++) {
            Field kf = keyFields[i];
            String name = kf.getName();
            keyFieldNames[i] = name;

            if (name.equals("serialVersionUID")) { // NOI18N
                continue;
            }

            LocalFieldDesc f = getLocalFieldDesc(name);

            if (f != null) {
                if (debug) {
                    logger.finest("sqlstore.model.classdesc.pkfield", f.getName()); // NOI18N
                }

                // The fetch group for pk fields should always be DFG.
                f.fetchGroup = FieldDesc.GROUP_DEFAULT;
                f.sqlProperties &= ~(FieldDesc.PROP_REF_INTEGRITY_UPDATES);
                f.sqlProperties &= ~(FieldDesc.PROP_IN_CONCURRENCY_CHECK);
                f.sqlProperties |= FieldDesc.PROP_PRIMARY_KEY_FIELD;

                keyFieldDescs[i] = f;
            } else {
                throw new JDOFatalUserException(I18NHelper.getMessage(messages,
                        "core.configuration.noneexistentpkfield", // NOI18N
                        name, oidClass.getName(), pcClass.getName()));
            }
        }
    }

    /**
     * Initialize the list of field descriptors of version consistency fields.
     * The names of the version fields are obtained from
     * {@link MappingClassElement#getVersionFields}.
     */
    private void initializeVersionFields() {
        int size = mdConfig.getVersionFields().size();
        Iterator versionFieldIterator = mdConfig.getVersionFields().iterator();
        versionFields = new LocalFieldDesc[size];

        for (int i = 0; i < size; i++) {
            MappingFieldElement mdField = (MappingFieldElement) versionFieldIterator.next();
            LocalFieldDesc f = (LocalFieldDesc) getField(mdField.getName());

            if (f != null) {
                if (logger.isLoggable()) {
                    logger.finest("sqlstore.model.classdesc.vcfield", f.getName()); // NOI18N
                }

                versionFields[i] = f;
                registerVersionFieldWithTable(f);

                // The fetch group for version fields should always be DFG.
                f.fetchGroup = FieldDesc.GROUP_DEFAULT;
                f.sqlProperties &= ~(FieldDesc.PROP_REF_INTEGRITY_UPDATES);
                f.sqlProperties |= FieldDesc.PROP_VERSION_FIELD;
            } else {
                throw new JDOFatalUserException(I18NHelper.getMessage(messages,
                        "core.configuration.noneexistentvcfield", // NOI18N
                        mdField.getName(), pcClass.getName()));
            }
        }
    }

    /**
     * Registers the version field versionField with the
     * corresponding table.
     *
     * @param versionField Field used in version consistency check.
     */
    private void registerVersionFieldWithTable(LocalFieldDesc versionField) {
        // Version field must be mapped to exactly one column.
        ColumnElement ce =  (ColumnElement) versionField.getColumnElements().next();
        Iterator iter = tables.iterator();

        while (iter.hasNext()) {
            TableDesc table = (TableDesc) iter.next();

            if (!table.isJoinTable()) {
                if (ce.getDeclaringTable() == table.getTableElement()) {
                    table.setVersionField(versionField);
                    break;
                }
            }
        }
    }

    /**
     * Computes all the primitive tracked fields.
     * Primitive fields track each other if they are mapped to same columns.
     * One of them is made the primary tracked field as per precedence rules
     * on the field types. This field is used to bind values to columns while
     * updating the database.
     */
    private void computeTrackedPrimitiveFields() {
        // Compute the list of primitive fields to track for each primitive field.
        for (int i = 0; i < fields.size(); i++) {
            FieldDesc f = (FieldDesc) fields.get(i);

            if (!f.isRelationshipField()) {
                LocalFieldDesc lf = (LocalFieldDesc) f;

                lf.computeTrackedPrimitiveFields();
                lf.computePrimaryTrackedPrimitiveField();
            }
        }
    }

    /**
     * Computes the tracked relationship fields. Relationships are tracked in
     * the following way:
     * 
    *
  • A relationship field tracks a local field, if it's mappped * to the same columns. The relationship will be updated when the * local field is set.
  • *
  • Relationship fields track each other if they are mapped to * the same columns. The first field in the field order is marked * as primary tracked field. If the foreign key columns are * explicitly mapped, this field also tracks the local foreign key * field. The foreign key columns are explicitly mapped in * overlapping pk/fk situations. The other relationship fields * mapped to the same columns are updated when the primary tracked * relationship field is set.
  • *
* @see FieldDesc#PROP_PRIMARY_TRACKED_FIELD * @see FieldDesc#PROP_SECONDARY_TRACKED_FIELD */ private void computeTrackedRelationshipFields() { // Compute the list of fields to track for each field. for (int i = 0; i < 2; i++) { ArrayList theFields = null; // We first check all the visible fields and then the hidden fields. if (i == 0) { theFields = this.fields; } else { if ((theFields = this.hiddenFields) == null) { continue; } } for (int j = 0; j < theFields.size(); j++) { FieldDesc f = (FieldDesc) theFields.get(j); f.computeTrackedRelationshipFields(); } } } /** * Remove redundant ForeignFieldDescs from each LocalFieldDesc's tracked field list. */ private void cleanupTrackedFields() { for (int i = 0; i < fields.size(); i++) { FieldDesc f = (FieldDesc) fields.get(i); if (f instanceof LocalFieldDesc) { ((LocalFieldDesc) f).cleanupTrackedFields(); } } } /** * The fixupForeignReferences method finds all the references to foreign * classes in this configuration. It then builds the appropriate run-time * information (ForeignFieldDesc) for the foreign reference from the * meta-data. */ private void fixupForeignReferences(ConfigCache cache) { for (int i = 0; i < foreignFields.size(); i++) { ForeignFieldDesc ff = (ForeignFieldDesc) foreignFields.get(i); Class classType = null; if ((classType = ff.getComponentType()) == null) { classType = ff.getType(); } ClassDesc foreignConfig = (ClassDesc) cache.getPersistenceConfig(classType); if (foreignConfig == null) continue; // Look up the inverse relationship field name if there is any. String irName = pcElement.getRelationship(ff.getName()).getInverseRelationshipName(); ForeignFieldDesc inverseField = null; if (irName != null) { inverseField = (ForeignFieldDesc) foreignConfig.getField(irName); } ff.fixupForeignReference(foreignConfig, inverseField); } } private void fixupFieldProperties() { for (int i = 0; i < foreignFields.size(); i++) { ForeignFieldDesc ff = (ForeignFieldDesc) foreignFields.get(i); ff.fixupFieldProperties(); } } LocalFieldDesc getLocalFieldDesc(ColumnElement column) { LocalFieldDesc result; for (int i = 0; i < 2; i++) { ArrayList theFields = null; if (i == 0) { theFields = fields; } else { theFields = hiddenFields; } if (theFields != null) { for (int j = 0; j < theFields.size(); j++) { FieldDesc f = (FieldDesc) theFields.get(j); if (f instanceof LocalFieldDesc) { result = (LocalFieldDesc) f; for (int k = 0; k < result.columnDescs.size(); k++) { ColumnElement c = (ColumnElement) result.columnDescs.get(k); // if (c.equals(column)) if (c.getName().getFullName().compareTo(column.getName().getFullName()) == 0) { // If f is a tracked field and it is not the primary, we continue // searching. if ((f.getTrackedFields() != null) && ((f.sqlProperties & FieldDesc.PROP_PRIMARY_TRACKED_FIELD) == 0)) { continue; } return result; } } } } } } // If we didn't find the field associated with the column, we need to // create a hidden field and add it to the hiddenFields list. result = createLocalHiddenField(column); return result; } /** * Returns the local field descriptor for the field name. * * @param name Field name. * @return Local field descriptor for the field name. * @throws JDOFatalInternalException if the field is not defined for this class. */ public LocalFieldDesc getLocalFieldDesc(String name) { FieldDesc desc = getField(name); if (desc == null) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.generic.unknownfield", // NOI18N name, getName())); } if (!(desc instanceof LocalFieldDesc)) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.generic.notinstanceof", // NOI18N desc.getClass().getName(), "LocalFieldDesc")); // NOI18N } return ((LocalFieldDesc) desc); } public TableDesc findTableDesc(TableElement mdTable) { for (int i = 0; i < tables.size(); i++) { TableDesc t = (TableDesc) tables.get(i); if (t.getTableElement().equals(mdTable)) { return t; } } return null; } private void addTableDesc(TableDesc t) { // setConsistencyLevel of this table before adding it to our list if (!t.isJoinTable()) { // JoinTables represent relationships instead of "real" objects, // they should never have a special consistencyLevel. t.setConsistencyLevel(mdConfig.getConsistencyLevel()); } tables.add(t); } public int getTableIndex(TableDesc tableDesc) { return tables.indexOf(tableDesc); } public FieldDesc getField(String name) { for (int i = 0; i < fields.size(); i++) { FieldDesc f = (FieldDesc) fields.get(i); if ((f != null) && (f.getName().compareTo(name) == 0)) { return f; } } if (hiddenFields != null) { for (int i = 0; i < hiddenFields.size(); i++) { FieldDesc f = (FieldDesc) hiddenFields.get(i); if (f.getName().compareTo(name) == 0) { return f; } } } return null; } public FieldDesc getField(int index) { if (index >= 0) { return (FieldDesc) fields.get(index); } else { return (FieldDesc) hiddenFields.get(-(index + 1)); } } public Constructor getConstructor() { return constructor; } public Class getPersistenceCapableClass() { return pcClass; } public Class getOidClass() { return oidClass; } public String getName() { return pcClass.getName(); } public Iterator getTables() { return tables.iterator(); } public TableDesc getPrimaryTable() { return (TableDesc) tables.get(0); } public boolean isNavigable() { return mdConfig.isNavigable(); } public boolean hasVersionConsistency() { return mdConfig.getConsistencyLevel() == MappingClassElement.VERSION_CONSISTENCY; } public LocalFieldDesc[] getVersionFields() { return versionFields; } public Concurrency getConcurrency(boolean optimistic) { // Following algo is used to determine which concurrency to return: // consistency level specified in model(represented by the variable consistencyLevel), // takes precedence over // concurrency specified by transaction (represented by parameter 'optimistic' to this method) Concurrency concurrency = null; int consistencyLevel = mdConfig.getConsistencyLevel(); if (consistencyLevel == MappingClassElement.NONE_CONSISTENCY) { // No consistency level specified in model if (optimistic) { concurrency = (Concurrency) optimisticConcurrency.clone(); } else { concurrency = (Concurrency) databaseConcurrency.clone(); } } else { // currently, we would not try to interprete consistencyLevel as bitmap // When we implment consistencyLevel like 2+, 3+, we would have to interprete // consistencyLevel as bitmap and implement some changes in class hierarchy // starting with interface Concurrency switch(consistencyLevel) { // We would never reach this code because we are in the else part // case MappingClassElement.NONE_CONSISTENCY : // concurrency = (Concurrency) databaseConcurrency.clone(); // break; case MappingClassElement.CHECK_MODIFIED_AT_COMMIT_CONSISTENCY : concurrency = (Concurrency) checkDirtyConcurrency.clone(); break; case MappingClassElement.LOCK_WHEN_LOADED_CONSISTENCY : // This consistency level is implemeted inside SelectStatement and // not by any object implementing Concurrency concurrency = (Concurrency) explicitConcurrency.clone(); break; case MappingClassElement.VERSION_CONSISTENCY : // This consistency level is implemented inside ClassDesc and // UpdateQueryPlan and not by any object implementing Concurrency. // Selects are handled in ClassDesc, updates/deletes in // UpdateQueryPlan. concurrency = (Concurrency) databaseConcurrency.clone(); break; // **Note** // Plese change text for the exception thrown in default below when // we start supporting new consistency levels. The text lists currently supported // consistency level default : throw new JDOUnsupportedOptionException(I18NHelper.getMessage(messages, "core.configuration.unsupportedconsistencylevel", pcClass));// NOI18N } } return concurrency; } /** * Determines whether this classDesc has the * CHECK_MODIFIED_AT_COMMIT_CONSISTENCY level. * @return true if this has the * CHECK_MODIFIED_AT_COMMIT_CONSISTENCY level; * false otherwise. */ public boolean hasModifiedCheckAtCommitConsistency() { return(mdConfig.getConsistencyLevel() == MappingClassElement.CHECK_MODIFIED_AT_COMMIT_CONSISTENCY); } public boolean isPKField(int index) { return persistentFields[index].isKey(); } /** * Return the key fields as array of java.lang.reflect.Field instances. * @return The key fields as array of java.lang.reflect.Field instances. */ public Field[] getKeyFields() { return keyFields; } /** * Returns the key field names as array of String. * @return The key field names as array of String. */ public String[] getKeyFieldNames() { return keyFieldNames; } /** * Returns the descriptors for key fields as array of LocalFieldDesc. * @return The descriptors for key fields as array of LocalFieldDesc. */ public LocalFieldDesc[] getKeyFieldDescs() { return keyFieldDescs; } /** * Returns a new SQLStateManager instance, * initialized with passed store manager and this instance of * the runtime class model. * * @param store Store manager, an instance of SQLStoreManager. * @return A new SQLStateManager instance. */ public SQLStateManager newStateManagerInstance(PersistenceStore store) { return new SQLStateManager(store, this); } /** * Returns a RetrieveDescriptor which represent a SQL query selecting a pc * instance by pk-fields. Please note that the RDs are cached, so the method * first checks the cache. If there is no corresponding RetrieveDescriptor * in the cache, it creates a new one and stores it in the cache. If the * additionalField is not null, the method retrieves the field indicated by * it along with the query. Fetch group fields will be added when the query * plan is build. * * Note, the reason to introduce the RetrieveDesc cache in ClassDesc and not * in the store manager is, that we can have the cache per class, where * the store manager could only provide one big cache for all pc classes. * * @param additionalField The field to be retrieved in addition to the * DFG fields. * @param store The store manager. * @return A RetrieveDescriptor selecting a pc instance by pk-fields. * @see #getRetrieveDescForFKQuery */ public RetrieveDesc getRetrieveDescForPKQuery(FieldDesc additionalField, PersistenceStore store) { RetrieveDescImpl rd = null; String cacheKey = generateRDCacheKey(additionalField); synchronized (retrieveDescCache) { // Cache lookup. rd = (RetrieveDescImpl) retrieveDescCache.get(cacheKey); // Generate a new RD if there isn't one be found in the cache. if (rd == null) { rd = (RetrieveDescImpl) store.getRetrieveDesc(pcClass); if (additionalField != null) { RetrieveDesc frd = null; String name = additionalField.getName(); // If the additionalField is not null, we will retrieve // the field indicated by it along with the query. if (additionalField instanceof ForeignFieldDesc) { Class additionalClass = ((ForeignFieldDesc) additionalField). foreignConfig.getPersistenceCapableClass(); frd = store.getRetrieveDesc(additionalClass); } rd.addPrefetchedField(name, frd); } addPKConstraints(rd); // Cache fillup. retrieveDescCache.put(cacheKey, rd); } } return rd; } /** * Returns a RetrieveDescriptor which represent a SQL query selecting pc * instances by the relationship key. The relationship key is taken from * the foreign field foreignField and used as query constraint. * Please note that the RDs are cached, so the method first checks the cache. * If there is no corresponding RetrieveDescriptor in the cache, it creates * a new one and stores it in the cache. FetchGroup fields will be added * when the query plan is build, see SelectQueryPlan#processFetchGroups. * * Note, the reason to introduce the RetrieveDesc cache in ClassDesc and not * in the store manager is, that we can have the cache per class, where * the store manager could only provide one big cache for all pc classes. * * @param foreignField The relationship field to be retrieved. * Following is true for this field. *
    *
  • It is part of an independent fetch group with only one field in the fetch group. *

    Or *

    Not part of any fetch group. *

  • *
  • It is not mapped to a join table.
  • *
* @param store The store manager. * @return A RetrieveDescriptor selecting pc instance(s) corresponding to * the foreign field * @see #getRetrieveDescForPKQuery */ public RetrieveDesc getRetrieveDescForFKQuery(ForeignFieldDesc foreignField, PersistenceStore store) { RetrieveDescImpl rd = null; String cacheKey = generateRDCacheKey(foreignField); synchronized (foreignRetrieveDescCache) { // Cache lookup. rd = (RetrieveDescImpl) foreignRetrieveDescCache.get(cacheKey); // Generate a new RD if there isn't one be found in the cache. if (rd == null) { rd = (RetrieveDescImpl) store.getRetrieveDesc(foreignField.foreignConfig.getPersistenceCapableClass()); addFKConstraints(rd, foreignField); // Cache fillup. foreignRetrieveDescCache.put(cacheKey, rd); } } return rd; } /** * Gets RetrieveDescriptor(rd) for verifying a VC instance. The returned rd * is set up to expect constraints for pk followed by constraints for version * fields. * @param store * @return Instance of retrieve Descriptor for verifying a VC instance. */ public RetrieveDesc getRetrieveDescForVerificationQuery(PersistenceStore store) { assert hasVersionConsistency(); synchronized(retrieveDescForVerificationSynchObj) { if (retrieveDescForVerification == null) { RetrieveDescImpl rd = (RetrieveDescImpl) store.getRetrieveDesc(pcClass); int index = addPKConstraints(rd); rd.addParameterConstraints(versionFields, index); rd.setOption(RetrieveDescImpl.OPT_VERIFY); retrieveDescForVerification = rd; } } return retrieveDescForVerification; } /** * Generate for each keyField 'keyfield = ?' in the where clause * with InputParamValues for later binding of the actual query parameters. */ private int addPKConstraints(RetrieveDescImpl rd) { // PK Constraints are always added first hence startIndex == 0 rd.addParameterConstraints(keyFieldDescs, 0); return keyFieldDescs.length; } /** * Generate the condition on the relationship key in the where clause * with InputParamValues for later binding of the actual query parameters. */ private void addFKConstraints(RetrieveDescImpl rd, ForeignFieldDesc foreignField) { for (int i = 0; i < foreignField.foreignFields.size(); i++) { LocalFieldDesc fff = (LocalFieldDesc) foreignField.foreignFields.get(i); rd.addParameterConstraint(fff, i); } } /** * Generates the key for a RetrieveDescriptor for the cache lookup. * The key has one of two forms: *
  • * Fully qualified classname of the pcclass, if additionalField is null. *
  • *
  • * Fully qualified classname of the pcclass + '/' + additionalFieldName + * '/' + additionalFieldID, otherwise. *
  • * @param additionalField The field to be retrieved in addition to the * DFG fields. * @return The generated cache key as a String. */ private String generateRDCacheKey(FieldDesc additionalField) { StringBuilder key = new StringBuilder(); key.append(pcClass.getName()); if (additionalField != null) { // using '/' as separator between class and fieldname key.append('/'); key.append(additionalField.getName()); key.append('/'); key.append(additionalField.absoluteID); } return key.toString(); } /** * Returns true, if this class has got local fields not in the default * fetch group. Because UpdateQueryPlans for updates are cached depending * on the set of updated fields {@link #getUpdateQueryPlanForUpdate}, we * might need to compare the updated fields btw. two instances when * batching is enabled. * * @return True, if this class has got local fields not in the default * fetch group. * @see SQLStateManager#requiresImmediateFlush */ public boolean hasLocalNonDFGFields() { // All instances with modified DFG fields can be batched with // the same statement. If there's a primitive field outside // the DFG, we need to compare modified fields to check if the // same statement can be used. return hasLocalNonDFGField; } /** * Retrieves the update query plan for the specified descriptor. * @param desc the descriptor * @param store the store manager * @return Instance of update query plan for the specified descriptor. */ public UpdateQueryPlan getUpdateQueryPlan( UpdateObjectDescImpl desc, SQLStoreManager store) { switch (desc.getUpdateAction()) { case ActionDesc.LOG_CREATE: return getUpdateQueryPlanForInsert(desc, store); case ActionDesc.LOG_DESTROY: return getUpdateQueryPlanForDelete(desc, store); case ActionDesc.LOG_UPDATE: return getUpdateQueryPlanForUpdate(desc, store); default: // TBD: error message + I18N // error unknown action return null; } } /** */ private UpdateQueryPlan getUpdateQueryPlanForInsert( UpdateObjectDescImpl desc, SQLStoreManager store) { synchronized(updateQueryPlanForInsertSynchObj) { if (updateQueryPlanForInsert == null) { updateQueryPlanForInsert = buildQueryPlan(store, desc); } } return updateQueryPlanForInsert; } /** */ private UpdateQueryPlan getUpdateQueryPlanForDelete( UpdateObjectDescImpl desc, SQLStoreManager store) { synchronized(updateQueryPlanForDeleteSynchObj) { if (updateQueryPlanForDelete == null) { updateQueryPlanForDelete = buildQueryPlan(store, desc); } } return updateQueryPlanForDelete; } /** */ private UpdateQueryPlan getUpdateQueryPlanForUpdate( UpdateObjectDescImpl desc, SQLStoreManager store) { String key = getSortedFieldNumbers(desc.getUpdatedFields()); UpdateQueryPlan plan; synchronized(updateQueryPlanCache) { plan = (UpdateQueryPlan)updateQueryPlanCache.get(key); if (plan == null) { plan = buildQueryPlan(store, desc); updateQueryPlanCache.put(key, plan); } } return plan; } /** * Builds and initializes a new query plan based on the information * passed with the desc parameter. The returned plan * and its related data structures will be readonly. * * @param store Store manager * @param desc Update information, including the update action and * modified fields as appropriate. * * @return A new query plan. The returned plan will be readonly. */ private UpdateQueryPlan buildQueryPlan(SQLStoreManager store, UpdateObjectDescImpl desc) { UpdateQueryPlan plan; plan = new UpdateQueryPlan(desc, store); plan.build(true); // Initialize the text for all statements. After this point, // the plan and its related data structures will be readonly. plan.getStatements(); return plan; } /** * The methods returns the string representation of the sorted field numbers * of the FieldDescs. * The key is the string representation of the sorted field number list * of updated fields. * @param fields the list of FieldDescs * @return the sorted field number string */ private String getSortedFieldNumbers(List fields) { // Use the array of field numbers of the updated fields as the key int size = fields.size(); int [] fieldNos = new int[size]; for (int i = 0; i < size; i++) { FieldDesc f = (FieldDesc)fields.get(i); fieldNos[i] = f.absoluteID; } Arrays.sort(fieldNos); return StringHelper.intArrayToSeparatedList(fieldNos, ","); //NOI18N } }




    © 2015 - 2024 Weber Informatics LLC | Privacy Policy