You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by pc...@apache.org on 2006/07/19 23:35:07 UTC

svn commit: r423615 [23/44] - in /incubator/openjpa/trunk: ./ openjpa-jdbc-5/ openjpa-jdbc-5/src/ openjpa-jdbc-5/src/main/ openjpa-jdbc-5/src/main/java/ openjpa-jdbc-5/src/main/java/org/ openjpa-jdbc-5/src/main/java/org/apache/ openjpa-jdbc-5/src/main/...

Added: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationFieldStrategy.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationFieldStrategy.java?rev=423615&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationFieldStrategy.java (added)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationFieldStrategy.java Wed Jul 19 14:34:44 2006
@@ -0,0 +1,731 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.jdbc.meta.strats;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
+import org.apache.openjpa.jdbc.kernel.JDBCFetchState;
+import org.apache.openjpa.jdbc.kernel.JDBCStore;
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.jdbc.meta.Embeddable;
+import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.jdbc.meta.MappingInfo;
+import org.apache.openjpa.jdbc.meta.ValueMapping;
+import org.apache.openjpa.jdbc.meta.ValueMappingInfo;
+import org.apache.openjpa.jdbc.schema.Column;
+import org.apache.openjpa.jdbc.schema.ColumnIO;
+import org.apache.openjpa.jdbc.schema.ForeignKey;
+import org.apache.openjpa.jdbc.schema.Table;
+import org.apache.openjpa.jdbc.sql.DBDictionary;
+import org.apache.openjpa.jdbc.sql.Joins;
+import org.apache.openjpa.jdbc.sql.Result;
+import org.apache.openjpa.jdbc.sql.Row;
+import org.apache.openjpa.jdbc.sql.RowManager;
+import org.apache.openjpa.jdbc.sql.SQLBuffer;
+import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.jdbc.sql.SelectExecutor;
+import org.apache.openjpa.jdbc.sql.Union;
+import org.apache.openjpa.kernel.OpenJPAStateManager;
+import org.apache.openjpa.lib.log.Log;
+import org.apache.openjpa.lib.util.Localizer;
+import org.apache.openjpa.meta.JavaTypes;
+import org.apache.openjpa.util.ApplicationIds;
+import org.apache.openjpa.util.MetaDataException;
+import org.apache.openjpa.util.OpenJPAId;
+
+/**
+ * Mapping for a single-valued relation to another entity.
+ *
+ * @author Abe White
+ * @since 4.0
+ */
+public class RelationFieldStrategy
+    extends AbstractFieldStrategy
+    implements Embeddable {
+
+    private static final Localizer _loc = Localizer.forPackage
+        (RelationFieldStrategy.class);
+
+    private Boolean _fkOid = null;
+
+    public void map(boolean adapt) {
+        if (field.getTypeCode() != JavaTypes.PC || field.isEmbeddedPC())
+            throw new MetaDataException(_loc.get("not-relation", field));
+
+        field.getKeyMapping().getValueInfo().assertNoSchemaComponents
+            (field.getKey(), !adapt);
+        field.getElementMapping().getValueInfo().assertNoSchemaComponents
+            (field.getElement(), !adapt);
+        boolean criteria = field.getValueInfo().getUseClassCriteria();
+
+        // check for named inverse
+        FieldMapping mapped = field.getMappedByMapping();
+        if (mapped != null) {
+            field.getMappingInfo().assertNoSchemaComponents(field, !adapt);
+            field.getValueInfo().assertNoSchemaComponents(field, !adapt);
+            mapped.resolve(mapped.MODE_META | mapped.MODE_MAPPING);
+
+            if (!mapped.getDefiningMapping().isMapped())
+                throw new MetaDataException(_loc.get("mapped-by-unmapped",
+                    field, mapped));
+
+            if (mapped.getTypeCode() == JavaTypes.PC) {
+                if (mapped.getJoinDirection() == mapped.JOIN_FORWARD) {
+                    field.setJoinDirection(field.JOIN_INVERSE);
+                    field.setColumns(mapped.getDefiningMapping().
+                        getPrimaryKeyColumns());
+                } else if (isTypeUnjoinedSubclass(mapped))
+                    throw new MetaDataException(_loc.get
+                        ("mapped-inverse-unjoined", field.getName(),
+                            field.getDefiningMapping(), mapped));
+
+                field.setForeignKey(mapped.getForeignKey
+                    (field.getDefiningMapping()));
+            } else if (mapped.getElement().getTypeCode() == JavaTypes.PC) {
+                if (isTypeUnjoinedSubclass(mapped.getElementMapping()))
+                    throw new MetaDataException(_loc.get
+                        ("mapped-inverse-unjoined", field.getName(),
+                            field.getDefiningMapping(), mapped));
+
+                // warn the user about making the collection side the owner
+                Log log = field.getRepository().getLog();
+                if (log.isInfoEnabled())
+                    log.info(_loc.get("coll-owner", field, mapped));
+                field.setForeignKey(mapped.getElementMapping().
+                    getForeignKey());
+            } else
+                throw new MetaDataException(_loc.get("not-inv-relation",
+                    field, mapped));
+
+            field.setUseClassCriteria(criteria);
+            return;
+        }
+
+        // this is necessary to support openjpa 3 mappings, which didn't
+        // differentiate between secondary table joins and relations built
+        // around an inverse key: check to see if we're mapped as a secondary
+        // table join but we're in the table of the related type, and if so
+        // switch our join mapping info to our value mapping info
+        String tableName = field.getMappingInfo().getTableName();
+        Table table = field.getTypeMapping().getTable();
+        ValueMappingInfo vinfo = field.getValueInfo();
+        if (tableName != null && table != null
+            && (tableName.equalsIgnoreCase(table.getName())
+            || tableName.equalsIgnoreCase(table.getFullName()))) {
+            vinfo.setJoinDirection(MappingInfo.JOIN_INVERSE);
+            vinfo.setColumns(field.getMappingInfo().getColumns());
+            field.getMappingInfo().setTableName(null);
+            field.getMappingInfo().setColumns(null);
+        }
+
+        field.mapJoin(adapt, false);
+        if (field.getTypeMapping().isMapped()) {
+            ForeignKey fk = vinfo.getTypeJoin(field, field.getName(), true,
+                adapt);
+            field.setForeignKey(fk);
+            field.setColumnIO(vinfo.getColumnIO());
+            if (vinfo.getJoinDirection() == vinfo.JOIN_INVERSE)
+                field.setJoinDirection(field.JOIN_INVERSE);
+        } else
+            RelationStrategies.mapRelationToUnmappedPC(field, field.getName(),
+                adapt);
+
+        field.setUseClassCriteria(criteria);
+        field.mapConstraints(field.getName(), adapt);
+        field.mapPrimaryKey(adapt);
+    }
+
+    /**
+     * Return whether our defining mapping is an unjoined subclass of
+     * the type of the given value.
+     */
+    private boolean isTypeUnjoinedSubclass(ValueMapping mapped) {
+        ClassMapping def = field.getDefiningMapping();
+        for (; def != null; def = def.getJoinablePCSuperclassMapping())
+            if (def == mapped.getTypeMapping())
+                return false;
+        return true;
+    }
+
+    public void initialize() {
+        field.setUsesIntermediate(true);
+
+        ForeignKey fk = field.getForeignKey();
+        if (fk == null)
+            _fkOid = Boolean.TRUE;
+        else if (field.getJoinDirection() != FieldMapping.JOIN_INVERSE)
+            _fkOid = field.getTypeMapping().isForeignKeyObjectId(fk);
+    }
+
+    public void insert(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
+        throws SQLException {
+        if (field.getMappedBy() != null)
+            return;
+
+        OpenJPAStateManager rel = RelationStrategies.getStateManager
+            (sm.fetchObjectField(field.getIndex()), store.getContext());
+        if (field.getJoinDirection() == field.JOIN_INVERSE)
+            updateInverse(sm, rel, store, rm, sm);
+        else {
+            Row row = field.getRow(sm, store, rm, Row.ACTION_INSERT);
+            if (row != null)
+                field.setForeignKey(row, rel);
+        }
+    }
+
+    public void update(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
+        throws SQLException {
+        if (field.getMappedBy() != null)
+            return;
+
+        Object relObj = sm.fetchObjectField(field.getIndex());
+        OpenJPAStateManager rel = RelationStrategies.getStateManager
+            (sm.fetchObjectField(field.getIndex()), store.getContext());
+
+        if (field.getJoinDirection() == field.JOIN_INVERSE) {
+            nullInverse(sm, rm);
+            updateInverse(sm, rel, store, rm, sm);
+        } else {
+            Row row = field.getRow(sm, store, rm, Row.ACTION_UPDATE);
+            if (row != null)
+                field.setForeignKey(row, rel);
+        }
+    }
+
+    public void delete(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
+        throws SQLException {
+        if (field.getMappedBy() != null)
+            return;
+
+        if (field.getJoinDirection() == field.JOIN_INVERSE) {
+            if (sm.getLoaded().get(field.getIndex())) {
+                OpenJPAStateManager rel = RelationStrategies.getStateManager(sm.
+                    fetchObjectField(field.getIndex()), store.getContext());
+                updateInverse(sm, rel, store, rm, null);
+            } else
+                nullInverse(sm, rm);
+        } else {
+            field.deleteRow(sm, store, rm);
+
+            // if our foreign key has a delete action, we need to set the
+            // related object so constraints can be evaluated
+            OpenJPAStateManager rel = RelationStrategies.getStateManager
+                (sm.fetchObjectField(field.getIndex()), store.getContext());
+            if (rel != null) {
+                ForeignKey fk = field.getForeignKey((ClassMapping)
+                    rel.getMetaData());
+                if (fk.getDeleteAction() == ForeignKey.ACTION_RESTRICT) {
+                    Row row = field.getRow(sm, store, rm, Row.ACTION_DELETE);
+                    row.setForeignKey(fk, null, rel);
+                }
+            }
+        }
+    }
+
+    /**
+     * Null inverse relations that reference the given object.
+     */
+    private void nullInverse(OpenJPAStateManager sm, RowManager rm)
+        throws SQLException {
+        ForeignKey fk = field.getForeignKey();
+        ColumnIO io = field.getColumnIO();
+        if (!io.isAnyUpdatable(fk, true))
+            return;
+
+        if (field.getIndependentTypeMappings().length != 1)
+            throw RelationStrategies.uninversable(field);
+
+        Row row = rm.getAllRows(fk.getTable(), Row.ACTION_UPDATE);
+        row.setForeignKey(fk, io, null);
+        row.whereForeignKey(fk, sm);
+        rm.flushAllRows(row);
+    }
+
+    /**
+     * This method updates the inverse columns of our relation
+     * with the given object.
+     */
+    private void updateInverse(OpenJPAStateManager sm, OpenJPAStateManager rel,
+        JDBCStore store, RowManager rm, OpenJPAStateManager sm2)
+        throws SQLException {
+        // nothing to do if inverse is null or about to be deleted
+        //### should we throw an exception if the inverse is null?
+        if (rel == null || rel.isDeleted())
+            return;
+
+        ForeignKey fk = field.getForeignKey();
+        ColumnIO io = field.getColumnIO();
+
+        int action;
+        if (rel.isNew() && !rel.isFlushed()) {
+            if (sm2 == null || !io.isAnyInsertable(fk, false))
+                return;
+            action = Row.ACTION_INSERT;
+        } else {
+            if (!io.isAnyUpdatable(fk, sm2 == null))
+                return;
+            action = Row.ACTION_UPDATE;
+        }
+
+        if (field.getIndependentTypeMappings().length != 1)
+            throw RelationStrategies.uninversable(field);
+
+        // get the row for the inverse object; the row might be in a secondary
+        // table if there is a field controlling the foreign key
+        Row row = null;
+        FieldMapping[] invs = field.getInverseMappings();
+        for (int i = 0; i < invs.length; i++) {
+            if (invs[i].getMappedByMetaData() == field
+                && invs[i].getTypeCode() == JavaTypes.PC) {
+                row = invs[i].getRow(rel, store, rm, action);
+                break;
+            }
+        }
+        ClassMapping relMapping = field.getTypeMapping();
+        if (row == null)
+            row = rm.getRow(relMapping.getTable(), action, rel, true);
+
+        // if this is an update, this might be the only mod to the row, so
+        // make sure the where condition is set
+        if (action == Row.ACTION_UPDATE
+            && row.getTable() == relMapping.getTable())
+            row.wherePrimaryKey(rel);
+
+        // update the inverse pointer with our oid value
+        row.setForeignKey(fk, io, sm2);
+    }
+
+    public int supportsSelect(Select sel, int type, OpenJPAStateManager sm,
+        JDBCStore store, JDBCFetchConfiguration fetch) {
+        if (type == Select.TYPE_JOINLESS)
+            return (field.getJoinDirection() != field.JOIN_INVERSE
+                && sel.isSelected(field.getTable())) ? 1 : 0;
+        if (type == Select.TYPE_TWO_PART)
+            return 1;
+
+        // already cached?
+        if (sm != null) {
+            Object oid = sm.getIntermediate(field.getIndex());
+            if (store.getContext().findCached(oid, null) != null)
+                return 0;
+        }
+
+        ClassMapping[] clss = field.getIndependentTypeMappings();
+        switch (type) {
+            case Select.EAGER_PARALLEL:
+                return clss.length;
+            case Select.EAGER_OUTER:
+                return (clss.length == 1 && store.getDBDictionary().canOuterJoin
+                    (sel.getJoinSyntax(), field.getForeignKey(clss[0]))) ? 1 :
+                    0;
+            case Select.EAGER_INNER:
+                return (clss.length == 1) ? 1 : 0;
+            default:
+                return 0;
+        }
+    }
+
+    public void selectEagerParallel(SelectExecutor sel,
+        final OpenJPAStateManager sm, final JDBCStore store,
+        final JDBCFetchState fetchState, final int eagerMode) {
+        final ClassMapping[] clss = field.getIndependentTypeMappings();
+        if (!(sel instanceof Union))
+            selectEagerParallel((Select) sel, clss[0], store, fetchState,
+                eagerMode);
+        else {
+            Union union = (Union) sel;
+            if (fetchState.getJDBCFetchConfiguration().getSubclassFetchMode
+                (field.getTypeMapping()) != JDBCFetchConfiguration.EAGER_JOIN)
+                union.abortUnion();
+            union.select(new Union.Selector() {
+                public void select(Select sel, int idx) {
+                    selectEagerParallel(sel, clss[idx], store, fetchState,
+                        eagerMode);
+                }
+            });
+        }
+    }
+
+    /**
+     * Perform an eager parallel select.
+     */
+    private void selectEagerParallel(Select sel, ClassMapping cls,
+        JDBCStore store, JDBCFetchState fetchState, int eagerMode) {
+        sel.selectPrimaryKey(field.getDefiningMapping());
+        // set a variable name that does not conflict with any in the query;
+        // using a variable guarantees that the selected data will use different
+        // aliases and joins than any existing WHERE conditions on this field
+        // that might otherwise limit the relations that match
+        Joins joins = sel.newJoins().setVariable("*");
+        eagerJoin(joins, cls, true);
+        sel.select(cls, field.getSelectSubclasses(), store, fetchState,
+            eagerMode, joins);
+    }
+
+    public void selectEagerJoin(Select sel, OpenJPAStateManager sm,
+        JDBCStore store, JDBCFetchState fetchState, int eagerMode) {
+        // limit the eager mode to single on recursive eager fetching b/c
+        // at this point the select has been modified and an attempt to
+        // clone it for a to-many eager select can result in a clone that
+        // produces invalid SQL
+        ClassMapping cls = field.getIndependentTypeMappings()[0];
+        sel.select(cls, field.getSelectSubclasses(), store, fetchState,
+            JDBCFetchConfiguration.EAGER_JOIN,
+            eagerJoin(sel.newJoins(), cls, false));
+    }
+
+    /**
+     * Add the joins needed to select/load eager data.
+     */
+    private Joins eagerJoin(Joins joins, ClassMapping cls, boolean forceInner) {
+        boolean inverse = field.getJoinDirection() == field.JOIN_INVERSE;
+        if (!inverse)
+            joins = join(joins, false);
+
+        // and join into relation
+        ForeignKey fk = field.getForeignKey(cls);
+        if (!forceInner && field.getNullValue() != FieldMapping.NULL_EXCEPTION)
+            return joins.outerJoinRelation(field.getName(), fk, inverse, false);
+        return joins.joinRelation(field.getName(), fk, inverse, false);
+    }
+
+    public int select(Select sel, OpenJPAStateManager sm, JDBCStore store,
+        JDBCFetchState fetchState, int eagerMode) {
+        if (field.getJoinDirection() == field.JOIN_INVERSE)
+            return -1;
+        // already cached oid?
+        if (sm != null && sm.getIntermediate(field.getIndex()) != null)
+            return -1;
+        if (!Boolean.TRUE.equals(_fkOid))
+            return -1;
+        sel.select(field.getColumns(), field.join(sel));
+        return 0;
+    }
+
+    public Object loadEagerParallel(OpenJPAStateManager sm, JDBCStore store,
+        JDBCFetchState fetchState, Object res)
+        throws SQLException {
+        // process batched results if we haven't already
+        Map rels;
+        if (res instanceof Result)
+            rels = processEagerParallelResult(sm, store, fetchState,
+                (Result) res);
+        else
+            rels = (Map) res;
+
+        // store object for this oid in instance
+        sm.storeObject(field.getIndex(), rels.remove(sm.getObjectId()));
+        return rels;
+    }
+
+    /**
+     * Process the given batched result.
+     */
+    private Map processEagerParallelResult(OpenJPAStateManager sm,
+        JDBCStore store, JDBCFetchState fetchState, Result res)
+        throws SQLException {
+        // do same joins as for load
+        //### cheat: we know typical result joins only care about the relation
+        //### path; thus we can ignore different mappings
+        ClassMapping[] clss = field.getIndependentTypeMappings();
+        Joins joins = res.newJoins().setVariable("*");
+        eagerJoin(joins, clss[0], true);
+
+        Map rels = new HashMap();
+        ClassMapping owner = field.getDefiningMapping();
+        ClassMapping cls;
+        Object oid;
+        while (res.next()) {
+            cls = res.getBaseMapping();
+            if (cls == null)
+                cls = clss[0];
+            oid = owner.getObjectId(store, res, null, true, null);
+            rels.put(oid, res.load(cls, store, fetchState, joins));
+        }
+        res.close();
+
+        return rels;
+    }
+
+    public void loadEagerJoin(OpenJPAStateManager sm, JDBCStore store,
+        JDBCFetchState fetchState, Result res)
+        throws SQLException {
+        ClassMapping cls = field.getIndependentTypeMappings()[0];
+        sm.storeObject(field.getIndex(), res.load(cls, store, fetchState,
+            eagerJoin(res.newJoins(), cls, false)));
+    }
+
+    public void load(OpenJPAStateManager sm, JDBCStore store,
+        JDBCFetchState fetchState, Result res)
+        throws SQLException {
+        if (field.getJoinDirection() == field.JOIN_INVERSE)
+            return;
+        // cached oid?
+        if (sm != null && sm.getIntermediate(field.getIndex()) != null)
+            return;
+        if (!Boolean.TRUE.equals(_fkOid))
+            return;
+        if (!res.containsAll(field.getColumns()))
+            return;
+
+        // get the related object's oid
+        ClassMapping relMapping = field.getTypeMapping();
+        Object oid = null;
+        if (relMapping.isMapped()) {
+            oid = relMapping.getObjectId(store, res, field.getForeignKey(),
+                field.getPolymorphic() != ValueMapping.POLY_FALSE, null);
+        } else {
+            Column[] cols = field.getColumns();
+            if (relMapping.getIdentityType() == ClassMapping.ID_DATASTORE) {
+                long id = res.getLong(cols[0]);
+                if (!res.wasNull())
+                    oid = store.newDataStoreId(id, relMapping, true);
+            } else // application id
+            {
+                if (cols.length == 1) {
+                    Object val = res.getObject(cols[0], null, null);
+                    if (val != null)
+                        oid = ApplicationIds.fromPKValues(new Object[]{ val },
+                            relMapping);
+                } else {
+                    Object[] vals = new Object[cols.length];
+                    for (int i = 0; i < cols.length; i++) {
+                        vals[i] = res.getObject(cols[i], null, null);
+                        if (vals[i] == null)
+                            break;
+                        if (i == cols.length - 1)
+                            oid = ApplicationIds.fromPKValues(vals, relMapping);
+                    }
+                }
+            }
+        }
+
+        if (oid == null)
+            sm.storeObject(field.getIndex(), null);
+        else
+            sm.setIntermediate(field.getIndex(), oid);
+    }
+
+    public void load(final OpenJPAStateManager sm, final JDBCStore store,
+        final JDBCFetchState fetchState)
+        throws SQLException {
+        final JDBCFetchConfiguration fetch =
+            fetchState.getJDBCFetchConfiguration();
+        // check for cached oid value, or load oid if no way to join
+        if (Boolean.TRUE.equals(_fkOid)) {
+            Object oid = sm.getIntermediate(field.getIndex());
+            if (oid != null) {
+                Object val = store.find(oid, field, fetchState);
+                sm.storeObject(field.getIndex(), val);
+                return;
+            }
+        }
+
+        final ClassMapping[] rels = field.getIndependentTypeMappings();
+        final int subs = field.getSelectSubclasses();
+        final Joins[] resJoins = new Joins[rels.length];
+
+        // select related mapping columns; joining from the related type
+        // back to our fk table if not an inverse mapping (in which case we
+        // can just make sure the inverse cols == our pk values)
+        Union union = store.getSQLFactory().newUnion(rels.length);
+        union.setSingleResult(true);
+        if (fetch.getSubclassFetchMode(field.getTypeMapping())
+            != JDBCFetchConfiguration.EAGER_JOIN)
+            union.abortUnion();
+        union.select(new Union.Selector() {
+            public void select(Select sel, int idx) {
+                if (field.getJoinDirection() == field.JOIN_INVERSE)
+                    sel.whereForeignKey(field.getForeignKey(rels[idx]),
+                        sm.getObjectId(), field.getDefiningMapping(), store);
+                else {
+                    resJoins[idx] = sel.newJoins().joinRelation
+                        (field.getName(), field.getForeignKey(rels[idx]),
+                            false, false);
+                    field.wherePrimaryKey(sel, sm, store);
+                }
+                sel.select(rels[idx], subs, store, fetchState,
+                    fetch.EAGER_JOIN, resJoins[idx]);
+            }
+        });
+
+        Result res = union.execute(store, fetch);
+        try {
+            Object val = null;
+            if (res.next())
+                val = res.load(rels[res.indexOf()], store, fetchState,
+                    resJoins[res.indexOf()]);
+            sm.storeObject(field.getIndex(), val);
+        } finally {
+            res.close();
+        }
+    }
+
+    public Object toDataStoreValue(Object val, JDBCStore store) {
+        return RelationStrategies.toDataStoreValue(field, val, store);
+    }
+
+    public void appendIsNull(SQLBuffer sql, Select sel, Joins joins) {
+        // if no inverse, just join to mapping's table (usually a no-op
+        // because it'll be in the primary table) and see if fk cols are null;
+        // if inverse, then we have to do a sub-select to see if any inverse
+        // objects point back to this field's owner
+        if (field.getJoinDirection() != field.JOIN_INVERSE) {
+            //### probably need some sort of subselect here on fk constants
+            joins = join(joins, false);
+            Column[] cols = field.getColumns();
+            if (cols.length == 0)
+                sql.append("1 <> 1");
+            else
+                sql.append(sel.getColumnAlias(cols[0], joins)).
+                    append(" IS ").appendValue(null, cols[0]);
+        } else
+            testInverseNull(sql, sel, joins, true);
+    }
+
+    public void appendIsNotNull(SQLBuffer sql, Select sel, Joins joins) {
+        // if no inverse, just join to mapping's table (usually a no-op
+        // because it'll be in the primary table) and see if fk cols aren't
+        // null; if inverse, then we have to do a sub-select to see if any
+        // inverse objects point back to this field's owner
+        if (field.getJoinDirection() != field.JOIN_INVERSE) {
+            //### probably need some sort of subselect here on fk constants
+            joins = join(joins, false);
+            Column[] cols = field.getColumns();
+            if (cols.length == 0)
+                sql.append("1 = 1");
+            else
+                sql.append(sel.getColumnAlias(cols[0], joins)).
+                    append(" IS NOT ").appendValue(null, cols[0]);
+        } else
+            testInverseNull(sql, sel, joins, false);
+    }
+
+    /**
+     * Append SQL for a sub-select testing whether an inverse object exists
+     * for this relation.
+     */
+    private void testInverseNull(SQLBuffer sql, Select sel, Joins joins,
+        boolean empty) {
+        DBDictionary dict = field.getMappingRepository().getDBDictionary();
+        dict.assertSupport(dict.supportsSubselect, "SupportsSubselect");
+
+        if (field.getIndependentTypeMappings().length != 1)
+            throw RelationStrategies.uninversable(field);
+
+        if (empty)
+            sql.append("0 = ");
+        else
+            sql.append("0 < ");
+
+        ForeignKey fk = field.getForeignKey();
+        ContainerFieldStrategy.appendJoinCount(sql, sel, joins, dict, field,
+            fk);
+    }
+
+    public Joins join(Joins joins, boolean forceOuter) {
+        // if we're not in an inverse object table join normally, otherwise
+        // already traversed the relation; just join back to owner table
+        if (field.getJoinDirection() != field.JOIN_INVERSE)
+            return field.join(joins, forceOuter, false);
+        if (field.getIndependentTypeMappings().length != 1)
+            throw RelationStrategies.uninversable(field);
+        if (forceOuter)
+            return joins.outerJoinRelation(field.getName(),
+                field.getForeignKey(), true, false);
+        return joins.joinRelation(field.getName(), field.getForeignKey(),
+            true, false);
+    }
+
+    public Joins joinRelation(Joins joins, boolean forceOuter,
+        boolean traverse) {
+        // if this is an inverse mapping it's already joined to the relation
+        if (field.getJoinDirection() == field.JOIN_INVERSE)
+            return joins;
+        ClassMapping[] clss = field.getIndependentTypeMappings();
+        if (clss.length != 1) {
+            if (traverse)
+                throw RelationStrategies.unjoinable(field);
+            return joins;
+        }
+        if (forceOuter)
+            return joins.outerJoinRelation(field.getName(),
+                field.getForeignKey(clss[0]), false, false);
+        return joins.joinRelation(field.getName(),
+            field.getForeignKey(clss[0]), false, false);
+    }
+
+    /////////////////////////////
+    // Embeddable implementation
+    /////////////////////////////
+
+    public Column[] getColumns() {
+        return field.getColumns();
+    }
+
+    public ColumnIO getColumnIO() {
+        return field.getColumnIO();
+    }
+
+    public Object[] getResultArguments() {
+        return null;
+    }
+
+    public Object toEmbeddedDataStoreValue(Object val, JDBCStore store) {
+        return toDataStoreValue(val, store);
+    }
+
+    public Object toEmbeddedObjectValue(Object val) {
+        return UNSUPPORTED;
+    }
+
+    public void loadEmbedded(OpenJPAStateManager sm, JDBCStore store,
+        JDBCFetchState fetchState, Object val)
+        throws SQLException {
+        ClassMapping relMapping = field.getTypeMapping();
+        Object oid;
+        if (val == null)
+            oid = null;
+        else if (relMapping.getIdentityType() == ClassMapping.ID_DATASTORE)
+            oid = store.newDataStoreId(((Number) val).longValue(), relMapping,
+                field.getPolymorphic() != ValueMapping.POLY_FALSE);
+        else {
+            Object[] pks = (getColumns().length == 1) ? new Object[]{ val }
+                : (Object[]) val;
+            boolean nulls = true;
+            for (int i = 0; nulls && i < pks.length; i++)
+                nulls = pks[i] == null;
+            if (nulls)
+                oid = null;
+            else {
+                oid = ApplicationIds.fromPKValues(pks, relMapping);
+                if (field.getPolymorphic() == ValueMapping.POLY_FALSE
+                    && oid instanceof OpenJPAId) {
+                    ((OpenJPAId) oid).setManagedInstanceType(relMapping.
+                        getDescribedType());
+                }
+            }
+        }
+
+        if (oid == null)
+            sm.storeObject(field.getIndex(), null);
+        else
+            sm.setIntermediate(field.getIndex(), oid);
+    }
+}

Propchange: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationFieldStrategy.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationMapInverseKeyFieldStrategy.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationMapInverseKeyFieldStrategy.java?rev=423615&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationMapInverseKeyFieldStrategy.java (added)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationMapInverseKeyFieldStrategy.java Wed Jul 19 14:34:44 2006
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.jdbc.meta.strats;
+
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.openjpa.conf.OpenJPAConfiguration;
+import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
+import org.apache.openjpa.jdbc.kernel.JDBCFetchState;
+import org.apache.openjpa.jdbc.kernel.JDBCStore;
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.jdbc.meta.ValueMapping;
+import org.apache.openjpa.jdbc.schema.Column;
+import org.apache.openjpa.jdbc.schema.ForeignKey;
+import org.apache.openjpa.jdbc.sql.Joins;
+import org.apache.openjpa.jdbc.sql.Result;
+import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.jdbc.sql.Union;
+import org.apache.openjpa.kernel.OpenJPAStateManager;
+import org.apache.openjpa.lib.util.Localizer;
+import org.apache.openjpa.meta.JavaTypes;
+import org.apache.openjpa.util.InternalException;
+import org.apache.openjpa.util.MetaDataException;
+import org.apache.openjpa.util.Proxy;
+
+/**
+ * Uses an inverse foreign key in the table of the map value to determine
+ * map values. Derives map keys from a field in each value.
+ *
+ * @author Abe White
+ */
+public class RelationMapInverseKeyFieldStrategy
+    extends RelationToManyInverseKeyFieldStrategy
+    implements LRSMapFieldStrategy {
+
+    private static final Localizer _loc = Localizer.forPackage
+        (RelationMapInverseKeyFieldStrategy.class);
+
+    public FieldMapping getFieldMapping() {
+        return field;
+    }
+
+    public ClassMapping[] getIndependentKeyMappings(boolean traverse) {
+        return ClassMapping.EMPTY_MAPPINGS;
+    }
+
+    public ClassMapping[] getIndependentValueMappings(boolean traverse) {
+        return getIndependentElementMappings(traverse);
+    }
+
+    public Column[] getKeyColumns(ClassMapping cls) {
+        return cls.getFieldMapping(field.getKey().
+            getValueMappedByMetaData().getIndex()).getColumns();
+    }
+
+    public Column[] getValueColumns(ClassMapping cls) {
+        return cls.getPrimaryKeyColumns();
+    }
+
+    public ForeignKey getJoinForeignKey(ClassMapping cls) {
+        return super.getJoinForeignKey(cls);
+    }
+
+    public void selectKey(Select sel, ClassMapping key, OpenJPAStateManager sm,
+        JDBCStore store, JDBCFetchState fetchState, Joins joins) {
+        throw new InternalException();
+    }
+
+    public Object loadKey(OpenJPAStateManager sm, JDBCStore store,
+        JDBCFetchState fetchState, Result res, Joins joins)
+        throws SQLException {
+        throw new InternalException();
+    }
+
+    public Object deriveKey(JDBCStore store, Object value) {
+        OpenJPAStateManager sm = RelationStrategies.getStateManager(value,
+            store.getContext());
+        return (sm == null) ? null : sm.fetchField(field.getKey().
+            getValueMappedByMetaData().getIndex(), false);
+    }
+
+    public Object deriveValue(JDBCStore store, Object key) {
+        return null;
+    }
+
+    public void selectValue(Select sel, ClassMapping val,
+        OpenJPAStateManager sm,
+        JDBCStore store, JDBCFetchState fetchState, Joins joins) {
+        selectElement(sel, val, store, fetchState,
+            JDBCFetchConfiguration.EAGER_NONE, joins);
+    }
+
+    public Object loadValue(OpenJPAStateManager sm, JDBCStore store,
+        JDBCFetchState fetchState, Result res, Joins joins)
+        throws SQLException {
+        return loadElement(sm, store, fetchState, res, joins);
+    }
+
+    public Result[] getResults(final OpenJPAStateManager sm,
+        final JDBCStore store, final JDBCFetchState fetchState,
+        final int eagerMode, final Joins[] joins, boolean lrs)
+        throws SQLException {
+        ValueMapping val = field.getElementMapping();
+        final ClassMapping[] vals = val.getIndependentTypeMappings();
+        Union union = store.getSQLFactory().newUnion(vals.length);
+        JDBCFetchConfiguration fetch = fetchState.getJDBCFetchConfiguration();
+        if (fetch.getSubclassFetchMode(val.getTypeMapping())
+            != JDBCFetchConfiguration.EAGER_JOIN)
+            union.abortUnion();
+        union.setLRS(lrs);
+        union.select(new Union.Selector() {
+            public void select(Select sel, int idx) {
+                joins[1] = selectAll(sel, vals[idx], sm, store, fetchState,
+                    eagerMode);
+            }
+        });
+        Result res = union.execute(store,
+            fetchState.getJDBCFetchConfiguration());
+        return new Result[]{ res, res };
+    }
+
+    public Joins joinKeyRelation(Joins joins, ClassMapping key) {
+        return joins;
+    }
+
+    public Joins joinValueRelation(Joins joins, ClassMapping val) {
+        return joinElementRelation(joins, val);
+    }
+
+    protected Proxy newLRSProxy(OpenJPAConfiguration conf) {
+        return new LRSProxyMap(this, conf);
+    }
+
+    protected void add(JDBCStore store, Object coll, Object obj) {
+        if (obj != null)
+            ((Map) coll).put(deriveKey(store, obj), obj);
+    }
+
+    protected Collection toCollection(Object val) {
+        return (val == null) ? null : ((Map) val).values();
+    }
+
+    public void map(boolean adapt) {
+        if (field.getTypeCode() != JavaTypes.MAP)
+            throw new MetaDataException(_loc.get("not-map", field));
+        if (field.getKey().getValueMappedBy() == null)
+            throw new MetaDataException(_loc.get("not-mapped-by-key", field));
+        super.map(adapt);
+    }
+
+    public Joins joinKey(Joins joins, boolean forceOuter) {
+        return joinRelation(join(joins, forceOuter), forceOuter, false);
+    }
+}

Propchange: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationMapInverseKeyFieldStrategy.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationMapTableFieldStrategy.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationMapTableFieldStrategy.java?rev=423615&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationMapTableFieldStrategy.java (added)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationMapTableFieldStrategy.java Wed Jul 19 14:34:44 2006
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.jdbc.meta.strats;
+
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.openjpa.conf.OpenJPAConfiguration;
+import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
+import org.apache.openjpa.jdbc.kernel.JDBCFetchState;
+import org.apache.openjpa.jdbc.kernel.JDBCStore;
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.jdbc.meta.ValueMapping;
+import org.apache.openjpa.jdbc.schema.Column;
+import org.apache.openjpa.jdbc.schema.ForeignKey;
+import org.apache.openjpa.jdbc.sql.Joins;
+import org.apache.openjpa.jdbc.sql.Result;
+import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.jdbc.sql.Union;
+import org.apache.openjpa.kernel.OpenJPAStateManager;
+import org.apache.openjpa.lib.util.Localizer;
+import org.apache.openjpa.meta.JavaTypes;
+import org.apache.openjpa.util.InternalException;
+import org.apache.openjpa.util.MetaDataException;
+import org.apache.openjpa.util.Proxy;
+
+/**
+ * Uses an association table to hold map values. Derives map keys from
+ * a field in each value.
+ *
+ * @author Abe White
+ */
+public class RelationMapTableFieldStrategy
+    extends RelationToManyTableFieldStrategy
+    implements LRSMapFieldStrategy {
+
+    private static final Localizer _loc = Localizer.forPackage
+        (RelationMapTableFieldStrategy.class);
+
+    public FieldMapping getFieldMapping() {
+        return field;
+    }
+
+    public ClassMapping[] getIndependentKeyMappings(boolean traverse) {
+        return ClassMapping.EMPTY_MAPPINGS;
+    }
+
+    public ClassMapping[] getIndependentValueMappings(boolean traverse) {
+        return getIndependentElementMappings(traverse);
+    }
+
+    public ForeignKey getJoinForeignKey(ClassMapping cls) {
+        return super.getJoinForeignKey(cls);
+    }
+
+    public Column[] getKeyColumns(ClassMapping cls) {
+        return cls.getFieldMapping(field.getKey().
+            getValueMappedByMetaData().getIndex()).getColumns();
+    }
+
+    public Column[] getValueColumns(ClassMapping cls) {
+        return field.getElementMapping().getColumns();
+    }
+
+    public void selectKey(Select sel, ClassMapping key, OpenJPAStateManager sm,
+        JDBCStore store, JDBCFetchState fetchState, Joins joins) {
+        throw new InternalException();
+    }
+
+    public Object loadKey(OpenJPAStateManager sm, JDBCStore store,
+        JDBCFetchState fetchState, Result res, Joins joins)
+        throws SQLException {
+        throw new InternalException();
+    }
+
+    public Object deriveKey(JDBCStore store, Object value) {
+        OpenJPAStateManager sm = RelationStrategies.getStateManager(value,
+            store.getContext());
+        return (sm == null) ? null : sm.fetchField(field.getKey().
+            getValueMappedByMetaData().getIndex(), false);
+    }
+
+    public Object deriveValue(JDBCStore store, Object key) {
+        return null;
+    }
+
+    public void selectValue(Select sel, ClassMapping val,
+        OpenJPAStateManager sm,
+        JDBCStore store, JDBCFetchState fetchState, Joins joins) {
+        selectElement(sel, val, store, fetchState,
+            JDBCFetchConfiguration.EAGER_NONE, joins);
+    }
+
+    public Object loadValue(OpenJPAStateManager sm, JDBCStore store,
+        JDBCFetchState fetchState, Result res, Joins joins)
+        throws SQLException {
+        return loadElement(sm, store, fetchState, res, joins);
+    }
+
+    public Result[] getResults(final OpenJPAStateManager sm,
+        final JDBCStore store, final JDBCFetchState fetchState,
+        final int eagerMode, final Joins[] joins, boolean lrs)
+        throws SQLException {
+        ValueMapping val = field.getElementMapping();
+        final ClassMapping[] vals = val.getIndependentTypeMappings();
+        Union union = store.getSQLFactory().newUnion(vals.length);
+        JDBCFetchConfiguration fetch = fetchState.getJDBCFetchConfiguration();
+        if (fetch.getSubclassFetchMode(val.getTypeMapping())
+            != JDBCFetchConfiguration.EAGER_JOIN)
+            union.abortUnion();
+        union.setLRS(lrs);
+        union.select(new Union.Selector() {
+            public void select(Select sel, int idx) {
+                joins[1] = selectAll(sel, vals[idx], sm, store, fetchState,
+                    eagerMode);
+            }
+        });
+        Result res = union.execute(store,
+            fetchState.getJDBCFetchConfiguration());
+        return new Result[]{ res, res };
+    }
+
+    public Joins joinKeyRelation(Joins joins, ClassMapping key) {
+        return joins;
+    }
+
+    public Joins joinValueRelation(Joins joins, ClassMapping val) {
+        return joinElementRelation(joins, val);
+    }
+
+    protected Proxy newLRSProxy(OpenJPAConfiguration conf) {
+        return new LRSProxyMap(this, conf);
+    }
+
+    protected void add(JDBCStore store, Object coll, Object obj) {
+        if (obj != null)
+            ((Map) coll).put(deriveKey(store, obj), obj);
+    }
+
+    protected Collection toCollection(Object val) {
+        return (val == null) ? null : ((Map) val).values();
+    }
+
+    public void map(boolean adapt) {
+        if (field.getTypeCode() != JavaTypes.MAP)
+            throw new MetaDataException(_loc.get("not-map", field));
+        if (field.getKey().getValueMappedBy() == null)
+            throw new MetaDataException(_loc.get("not-mapped-by-key", field));
+        super.map(adapt);
+    }
+
+    public Joins joinKey(Joins joins, boolean forceOuter) {
+        return joinRelation(join(joins, forceOuter), forceOuter, false);
+    }
+}

Propchange: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationMapTableFieldStrategy.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationStrategies.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationStrategies.java?rev=423615&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationStrategies.java (added)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationStrategies.java Wed Jul 19 14:34:44 2006
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.jdbc.meta.strats;
+
+import java.util.List;
+
+import org.apache.openjpa.jdbc.kernel.JDBCStore;
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.jdbc.meta.FieldStrategy;
+import org.apache.openjpa.jdbc.meta.JavaSQLTypes;
+import org.apache.openjpa.jdbc.meta.ValueMapping;
+import org.apache.openjpa.jdbc.meta.ValueMappingInfo;
+import org.apache.openjpa.jdbc.schema.Column;
+import org.apache.openjpa.kernel.DetachedValueStateManager;
+import org.apache.openjpa.kernel.OpenJPAStateManager;
+import org.apache.openjpa.kernel.StoreContext;
+import org.apache.openjpa.lib.util.Localizer;
+import org.apache.openjpa.meta.JavaTypes;
+import org.apache.openjpa.util.MetaDataException;
+
+/**
+ * Helper methods for relation mappings.
+ *
+ * @author Abe White
+ */
+public class RelationStrategies {
+
+    private static final Localizer _loc = Localizer.forPackage
+        (RelationStrategies.class);
+
+    /**
+     * Return an exception indicating that we cannot join to the given relation.
+     */
+    public static MetaDataException unjoinable(ValueMapping vm) {
+        return new MetaDataException(_loc.get("cant-join", vm));
+    }
+
+    /**
+     * Return an exception indicating that the relation cannot be loaded
+     * because it has independent subclasses and does not represent a full oid.
+     */
+    public static MetaDataException unloadable(ValueMapping vm) {
+        return new MetaDataException(_loc.get("cant-load", vm));
+    }
+
+    /**
+     * Return an exception indicating that the relation is invalid
+     * because it has is based on an inverse foreign key and has independent
+     * subclasses.
+     */
+    public static MetaDataException uninversable(ValueMapping vm) {
+        return new MetaDataException(_loc.get("cant-inverse", vm));
+    }
+
+    /**
+     * Return the given object as its foreign key values.
+     *
+     * @see FieldStrategy#toDataStoreValue
+     */
+    public static Object toDataStoreValue(ValueMapping vm, Object val,
+        JDBCStore store) {
+        ClassMapping rel;
+        if (val == null || val.getClass() == vm.getType())
+            rel = vm.getTypeMapping(); // common case
+        else
+            rel = vm.getMappingRepository().getMapping(val.getClass(),
+                store.getContext().getClassLoader(), true);
+
+        Column[] cols;
+        if (vm.getJoinDirection() == ValueMapping.JOIN_INVERSE)
+            cols = rel.getPrimaryKeyColumns();
+        else
+            cols = vm.getForeignKey(rel).getPrimaryKeyColumns();
+        return rel.toDataStoreValue(val, cols, store);
+    }
+
+    /**
+     * Map a logical foreign key to an unmapped base class relation.
+     */
+    public static void mapRelationToUnmappedPC(ValueMapping vm,
+        String name, boolean adapt) {
+        if (vm.getTypeMapping().getIdentityType() == ClassMapping.ID_UNKNOWN)
+            throw new MetaDataException(_loc.get("rel-to-unknownid", vm));
+
+        ValueMappingInfo vinfo = vm.getValueInfo();
+        Column[] tmplates = newUnmappedPCTemplateColumns(vm, name);
+        vm.setColumns(vinfo.getColumns(vm, name, tmplates,
+            vm.getFieldMapping().getTable(), adapt));
+        vm.setColumnIO(vinfo.getColumnIO());
+    }
+
+    /**
+     * Create template columns for a logical foreign key to an unmapped base
+     * class relation.
+     */
+    private static Column[] newUnmappedPCTemplateColumns(ValueMapping vm,
+        String name) {
+        ClassMapping rel = vm.getTypeMapping();
+        if (rel.getIdentityType() == ClassMapping.ID_DATASTORE) {
+            Column col = new Column();
+            col.setName(name);
+            col.setJavaType(JavaTypes.LONG);
+            col.setRelationId(true);
+            return new Column[]{ col };
+        }
+
+        FieldMapping[] pks = rel.getPrimaryKeyFieldMappings();
+        Column[] cols = new Column[pks.length];
+        for (int i = 0; i < pks.length; i++) {
+            cols[i] = mapPrimaryKey(vm, pks[i]);
+            if (cols.length == 1)
+                cols[i].setName(name);
+            else if (cols[i].getName() == null)
+                cols[i].setName(name + "_" + pks[i].getName());
+            else
+                cols[i].setName(name + "_" + cols[i].getName());
+            cols[i].setTargetField(pks[i].getName());
+            cols[i].setRelationId(true);
+        }
+        return cols;
+    }
+
+    /**
+     * Create a default column for the given primary key field. Uses the
+     * user's raw mapping info if given. Only supports simple field types.
+     * The column name will be set to the name of the related primary key
+     * column, if any.
+     */
+    private static Column mapPrimaryKey(ValueMapping vm, FieldMapping pk) {
+        List cols = pk.getValueInfo().getColumns();
+        if (cols.size() > 1)
+            throw new MetaDataException(_loc.get("bad-unmapped-rel", vm, pk));
+
+        Column tmplate = null;
+        if (cols.size() == 1)
+            tmplate = (Column) cols.get(0);
+
+        Column col = new Column();
+        switch (pk.getTypeCode()) {
+            case JavaTypes.BOOLEAN:
+            case JavaTypes.BOOLEAN_OBJ:
+            case JavaTypes.BYTE:
+            case JavaTypes.BYTE_OBJ:
+            case JavaTypes.CHAR:
+            case JavaTypes.CHAR_OBJ:
+            case JavaTypes.DOUBLE:
+            case JavaTypes.DOUBLE_OBJ:
+            case JavaTypes.FLOAT:
+            case JavaTypes.FLOAT_OBJ:
+            case JavaTypes.INT:
+            case JavaTypes.INT_OBJ:
+            case JavaTypes.LONG:
+            case JavaTypes.LONG_OBJ:
+            case JavaTypes.NUMBER:
+            case JavaTypes.SHORT:
+            case JavaTypes.SHORT_OBJ:
+            case JavaTypes.STRING:
+            case JavaTypes.BIGINTEGER:
+            case JavaTypes.BIGDECIMAL:
+                col.setJavaType(pk.getTypeCode());
+                break;
+            case JavaTypes.DATE:
+                col.setJavaType(JavaSQLTypes.getDateTypeCode(pk.getType()));
+                break;
+            default:
+                throw new MetaDataException(
+                    _loc.get("bad-unmapped-rel", vm, pk));
+        }
+
+        if (tmplate != null) {
+            col.setName(tmplate.getName());
+            col.setType(tmplate.getType());
+            col.setTypeName(tmplate.getTypeName());
+            col.setSize(tmplate.getSize());
+            col.setDecimalDigits(tmplate.getDecimalDigits());
+        }
+        return col;
+    }
+
+    /**
+     * Return the state manager for the given instance, using a detached
+     * state manager if the instnace is not managed.
+     */
+    public static OpenJPAStateManager getStateManager(Object obj,
+        StoreContext ctx) {
+        if (obj == null)
+            return null;
+        OpenJPAStateManager sm = ctx.getStateManager(obj);
+        if (sm == null) // must be detached
+            return new DetachedValueStateManager(obj, ctx);
+        return sm;
+    }
+}

Propchange: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationStrategies.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyInverseKeyFieldStrategy.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyInverseKeyFieldStrategy.java?rev=423615&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyInverseKeyFieldStrategy.java (added)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyInverseKeyFieldStrategy.java Wed Jul 19 14:34:44 2006
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.jdbc.meta.strats;
+
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.openjpa.jdbc.kernel.JDBCFetchState;
+import org.apache.openjpa.jdbc.kernel.JDBCStore;
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.jdbc.meta.FieldMappingInfo;
+import org.apache.openjpa.jdbc.meta.ValueMapping;
+import org.apache.openjpa.jdbc.meta.ValueMappingInfo;
+import org.apache.openjpa.jdbc.schema.Column;
+import org.apache.openjpa.jdbc.schema.ColumnIO;
+import org.apache.openjpa.jdbc.schema.ForeignKey;
+import org.apache.openjpa.jdbc.sql.Joins;
+import org.apache.openjpa.jdbc.sql.Result;
+import org.apache.openjpa.jdbc.sql.Row;
+import org.apache.openjpa.jdbc.sql.RowManager;
+import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.kernel.OpenJPAStateManager;
+import org.apache.openjpa.kernel.StoreContext;
+import org.apache.openjpa.lib.log.Log;
+import org.apache.openjpa.lib.util.Localizer;
+import org.apache.openjpa.meta.JavaTypes;
+import org.apache.openjpa.util.ChangeTracker;
+import org.apache.openjpa.util.MetaDataException;
+import org.apache.openjpa.util.Proxies;
+import org.apache.openjpa.util.Proxy;
+
+/**
+ * Maps a relation to a set of other objects using an inverse
+ * foreign key in the related object table.
+ *
+ * @author Abe White
+ */
+public abstract class RelationToManyInverseKeyFieldStrategy
+    extends StoreCollectionFieldStrategy {
+
+    private static final Localizer _loc = Localizer.forPackage
+        (RelationToManyInverseKeyFieldStrategy.class);
+
+    private boolean _orderInsert = false;
+    private boolean _orderUpdate = false;
+
+    protected ClassMapping[] getIndependentElementMappings(boolean traverse) {
+        return field.getElementMapping().getIndependentTypeMappings();
+    }
+
+    protected ForeignKey getJoinForeignKey(ClassMapping elem) {
+        return field.getElementMapping().getForeignKey(elem);
+    }
+
+    protected void selectElement(Select sel, ClassMapping elem,
+        JDBCStore store, JDBCFetchState fetchState, int eagerMode,
+        Joins joins) {
+        sel.select(elem, field.getElementMapping().getSelectSubclasses(),
+            store, fetchState, eagerMode, joins);
+    }
+
+    protected Object loadElement(OpenJPAStateManager sm, JDBCStore store,
+        JDBCFetchState fetchState, Result res, Joins joins)
+        throws SQLException {
+        ClassMapping elem = res.getBaseMapping();
+        if (elem == null)
+            elem = field.getElementMapping().getIndependentTypeMappings()[0];
+        return res.load(elem, store, fetchState, joins);
+    }
+
+    protected Joins join(Joins joins, ClassMapping elem) {
+        return joins.joinRelation(field.getName(),
+            field.getElementMapping().getForeignKey(elem), true, true);
+    }
+
+    protected Joins joinElementRelation(Joins joins, ClassMapping elem) {
+        return joinRelation(joins, false, false);
+    }
+
+    public void map(boolean adapt) {
+        field.getValueInfo().assertNoSchemaComponents(field, !adapt);
+        field.getKeyMapping().getValueInfo().assertNoSchemaComponents
+            (field.getKey(), !adapt);
+
+        ValueMapping elem = field.getElementMapping();
+        if (elem.getTypeCode() != JavaTypes.PC || elem.isEmbeddedPC()
+            || !elem.getTypeMapping().isMapped())
+            throw new MetaDataException(_loc.get("not-elem-relation", field));
+
+        // check for named inverse
+        FieldMapping mapped = field.getMappedByMapping();
+        FieldMappingInfo finfo = field.getMappingInfo();
+        ValueMappingInfo vinfo = elem.getValueInfo();
+        boolean criteria = vinfo.getUseClassCriteria();
+        if (mapped != null) {
+            mapped.resolve(mapped.MODE_META | mapped.MODE_MAPPING);
+            if (!(mapped.getStrategy()instanceof RelationFieldStrategy))
+                throw new MetaDataException(_loc.get("not-inv-relation",
+                    field, mapped));
+            vinfo.assertNoSchemaComponents(elem, !adapt);
+            elem.setForeignKey(mapped.getForeignKey
+                (field.getDefiningMapping()));
+            elem.setColumns(mapped.getDefiningMapping().
+                getPrimaryKeyColumns());
+            elem.setJoinDirection(ValueMapping.JOIN_EXPECTED_INVERSE);
+            elem.setUseClassCriteria(criteria);
+
+            field.setOrderColumn(finfo.getOrderColumn(field,
+                mapped.getForeignKey().getTable(), adapt));
+            field.setOrderColumnIO(finfo.getColumnIO());
+            return;
+        }
+
+        // map inverse foreign key in related table
+        ForeignKey fk = vinfo.getInverseTypeJoin(elem, field.getName(), adapt);
+        elem.setForeignKey(fk);
+        elem.setColumnIO(vinfo.getColumnIO());
+        elem.setColumns(elem.getTypeMapping().getPrimaryKeyColumns());
+        elem.setJoinDirection(ValueMapping.JOIN_EXPECTED_INVERSE);
+        elem.setUseClassCriteria(criteria);
+        elem.mapConstraints(field.getName(), adapt);
+
+        field.setOrderColumn(finfo.getOrderColumn(field, fk.getTable(),
+            adapt));
+        field.setOrderColumnIO(finfo.getColumnIO());
+    }
+
+    public void initialize() {
+        Column order = field.getOrderColumn();
+        _orderInsert = field.getOrderColumnIO().isInsertable(order, false);
+        _orderUpdate = field.getOrderColumnIO().isUpdatable(order, false);
+
+        ValueMapping elem = field.getElementMapping();
+        Log log = field.getRepository().getLog();
+        if (field.getMappedBy() == null
+            && elem.getUseClassCriteria() && log.isWarnEnabled()) {
+            ForeignKey fk = elem.getForeignKey();
+            if (elem.getColumnIO().isAnyUpdatable(fk, false))
+                log.warn(_loc.get("class-crit-owner", field));
+        }
+    }
+
+    public void insert(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
+        throws SQLException {
+        if (field.getMappedBy() == null || _orderInsert || _orderUpdate)
+            insert(sm, rm, sm.fetchObject(field.getIndex()));
+    }
+
+    private void insert(OpenJPAStateManager sm, RowManager rm, Object vals)
+        throws SQLException {
+        if (field.getMappedBy() != null && !_orderInsert && !_orderUpdate)
+            return;
+        Collection coll = toCollection(vals);
+        if (coll == null || coll.isEmpty())
+            return;
+
+        ClassMapping rel = field.getElementMapping().getTypeMapping();
+        int idx = 0;
+        for (Iterator itr = coll.iterator(); itr.hasNext(); idx++)
+            updateInverse(sm.getContext(), itr.next(), rel, rm, sm, idx);
+    }
+
+    public void update(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
+        throws SQLException {
+        if (field.getMappedBy() != null && !_orderInsert && !_orderUpdate)
+            return;
+
+        Object obj = sm.fetchObject(field.getIndex());
+        ChangeTracker ct = null;
+        if (obj instanceof Proxy) {
+            Proxy proxy = (Proxy) obj;
+            if (Proxies.isOwner(proxy, sm, field.getIndex()))
+                ct = proxy.getChangeTracker();
+        }
+
+        // if no fine-grained change tracking then just delete and reinsert
+        if (ct == null || !ct.isTracking()) {
+            delete(sm, store, rm);
+            insert(sm, rm, obj);
+            return;
+        }
+
+        // null inverse columns for deletes and update them with our oid for
+        // inserts
+        ClassMapping rel = field.getElementMapping().getTypeMapping();
+        StoreContext ctx = store.getContext();
+        if (field.getMappedBy() == null) {
+            Collection rem = ct.getRemoved();
+            for (Iterator itr = rem.iterator(); itr.hasNext();)
+                updateInverse(ctx, itr.next(), rel, rm, null, 0);
+        }
+
+        Collection add = ct.getAdded();
+        int seq = ct.getNextSequence();
+        for (Iterator itr = add.iterator(); itr.hasNext(); seq++)
+            updateInverse(ctx, itr.next(), rel, rm, sm, seq);
+        if (field.getOrderColumn() != null)
+            ct.setNextSequence(seq);
+    }
+
+    public void delete(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
+        throws SQLException {
+        if (field.getMappedBy() != null
+            || field.getElementMapping().getUseClassCriteria())
+            return;
+
+        ValueMapping elem = field.getElementMapping();
+        ColumnIO io = elem.getColumnIO();
+        ForeignKey fk = elem.getForeignKey();
+        if (!io.isAnyUpdatable(fk, true))
+            return;
+
+        // null any existing inverse columns that refer to this obj
+        assertInversable();
+        Row row = rm.getAllRows(fk.getTable(), Row.ACTION_UPDATE);
+        row.setForeignKey(fk, io, null);
+        row.whereForeignKey(fk, sm);
+        rm.flushAllRows(row);
+    }
+
+    /**
+     * This method updates the inverse columns of a 1-M related object
+     * with the given oid.
+     */
+    private void updateInverse(StoreContext ctx, Object inverse,
+        ClassMapping rel, RowManager rm, OpenJPAStateManager sm, int idx)
+        throws SQLException {
+        OpenJPAStateManager invsm = RelationStrategies.getStateManager(inverse,
+            ctx);
+        if (invsm == null || invsm.isDeleted())
+            return;
+
+        ValueMapping elem = field.getElementMapping();
+        ForeignKey fk = elem.getForeignKey();
+        ColumnIO io = elem.getColumnIO();
+        Column order = field.getOrderColumn();
+
+        int action;
+        boolean writeable;
+        boolean orderWriteable;
+        if (invsm.isNew() && !invsm.isFlushed()) {
+            // no need to null inverse columns of new instance
+            if (sm == null)
+                return;
+            writeable = io.isAnyInsertable(fk, false);
+            orderWriteable = _orderInsert;
+            action = Row.ACTION_INSERT;
+        } else {
+            writeable = io.isAnyUpdatable(fk, sm == null);
+            orderWriteable = field.getOrderColumnIO().isUpdatable
+                (order, sm == null);
+            action = Row.ACTION_UPDATE;
+        }
+        if (!writeable && !orderWriteable)
+            return;
+
+        assertInversable();
+
+        // if this is an update, this might be the only mod to the row, so
+        // make sure the where condition is set
+        Row row = rm.getRow(fk.getTable(), action, invsm, true);
+        if (action == Row.ACTION_UPDATE)
+            row.wherePrimaryKey(invsm);
+
+        // update the inverse pointer with our oid value
+        if (writeable)
+            row.setForeignKey(fk, io, sm);
+        if (orderWriteable)
+            row.setInt(order, idx);
+    }
+
+    public Object toDataStoreValue(Object val, JDBCStore store) {
+        ClassMapping cm = field.getElementMapping().getTypeMapping();
+        return cm.toDataStoreValue(val, cm.getPrimaryKeyColumns(), store);
+    }
+
+    public Joins join(Joins joins, boolean forceOuter) {
+        ValueMapping elem = field.getElementMapping();
+        ClassMapping[] clss = elem.getIndependentTypeMappings();
+        if (clss.length != 1)
+            throw RelationStrategies.unjoinable(elem);
+        if (forceOuter)
+            return joins.outerJoinRelation(field.getName(),
+                elem.getForeignKey(clss[0]), true, true);
+        return joins.joinRelation(field.getName(),
+            elem.getForeignKey(clss[0]), true, true);
+    }
+
+    private void assertInversable() {
+        ValueMapping elem = field.getElementMapping();
+        if (elem.getIndependentTypeMappings().length != 1)
+            throw RelationStrategies.uninversable(elem);
+    }
+}

Propchange: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyInverseKeyFieldStrategy.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyTableFieldStrategy.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyTableFieldStrategy.java?rev=423615&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyTableFieldStrategy.java (added)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyTableFieldStrategy.java Wed Jul 19 14:34:44 2006
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.jdbc.meta.strats;
+
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.openjpa.jdbc.kernel.JDBCFetchState;
+import org.apache.openjpa.jdbc.kernel.JDBCStore;
+import org.apache.openjpa.jdbc.meta.ClassMapping;
+import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.jdbc.meta.FieldMappingInfo;
+import org.apache.openjpa.jdbc.meta.ValueMapping;
+import org.apache.openjpa.jdbc.meta.ValueMappingInfo;
+import org.apache.openjpa.jdbc.schema.Column;
+import org.apache.openjpa.jdbc.schema.ForeignKey;
+import org.apache.openjpa.jdbc.sql.Joins;
+import org.apache.openjpa.jdbc.sql.Result;
+import org.apache.openjpa.jdbc.sql.Row;
+import org.apache.openjpa.jdbc.sql.RowManager;
+import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.kernel.OpenJPAStateManager;
+import org.apache.openjpa.kernel.StoreContext;
+import org.apache.openjpa.lib.util.Localizer;
+import org.apache.openjpa.meta.JavaTypes;
+import org.apache.openjpa.util.ChangeTracker;
+import org.apache.openjpa.util.MetaDataException;
+import org.apache.openjpa.util.Proxies;
+import org.apache.openjpa.util.Proxy;
+
+/**
+ * Maps a set of related objects through an association table.
+ *
+ * @author Abe White
+ */
+public abstract class RelationToManyTableFieldStrategy
+    extends StoreCollectionFieldStrategy {
+
+    private static final Localizer _loc = Localizer.forPackage
+        (RelationToManyTableFieldStrategy.class);
+
+    private Boolean _fkOid = null;
+
+    protected ClassMapping[] getIndependentElementMappings(boolean traverse) {
+        return (traverse)
+            ? field.getElementMapping().getIndependentTypeMappings()
+            : ClassMapping.EMPTY_MAPPINGS;
+    }
+
+    protected ForeignKey getJoinForeignKey(ClassMapping elem) {
+        return field.getJoinForeignKey();
+    }
+
+    protected void selectElement(Select sel, ClassMapping elem,
+        JDBCStore store, JDBCFetchState fetchState, int eagerMode,
+        Joins joins) {
+        sel.select(elem, field.getElementMapping().getSelectSubclasses(),
+            store, fetchState, eagerMode, joins);
+    }
+
+    protected Object loadElement(OpenJPAStateManager sm, JDBCStore store,
+        JDBCFetchState fetchState, Result res, Joins joins)
+        throws SQLException {
+        ClassMapping elem = res.getBaseMapping();
+        if (elem == null)
+            elem = field.getElementMapping().getIndependentTypeMappings()[0];
+        return res.load(elem, store, fetchState, joins);
+    }
+
+    protected Joins join(Joins joins, ClassMapping elem) {
+        return join(joins, false);
+    }
+
+    protected Joins joinElementRelation(Joins joins, ClassMapping elem) {
+        return joins.joinRelation(field.getName(), field.getElementMapping().
+            getForeignKey(elem), false, false);
+    }
+
+    public void map(boolean adapt) {
+        field.getValueInfo().assertNoSchemaComponents(field, !adapt);
+        field.getKeyMapping().getValueInfo().assertNoSchemaComponents
+            (field.getKey(), !adapt);
+
+        ValueMapping elem = field.getElementMapping();
+        if (elem.getTypeCode() != JavaTypes.PC || elem.isEmbeddedPC())
+            throw new MetaDataException(_loc.get("not-elem-relation", field));
+
+        // check for named inverse
+        FieldMapping mapped = field.getMappedByMapping();
+        ValueMappingInfo vinfo = elem.getValueInfo();
+        boolean criteria = vinfo.getUseClassCriteria();
+        if (mapped != null) {
+            if (mapped.getElement().getTypeCode() != JavaTypes.PC)
+                throw new MetaDataException(_loc.get("not-inv-relation-coll",
+                    field, mapped));
+            field.getMappingInfo().assertNoSchemaComponents(field, !adapt);
+            vinfo.assertNoSchemaComponents(elem, !adapt);
+            mapped.resolve(mapped.MODE_META | mapped.MODE_MAPPING);
+
+            if (!mapped.getDefiningMapping().isMapped())
+                throw new MetaDataException(_loc.get("mapped-by-unmapped",
+                    field, mapped));
+
+            field.setJoinForeignKey(mapped.getElementMapping().
+                getForeignKey(field.getDefiningMapping()));
+            elem.setForeignKey(mapped.getJoinForeignKey());
+            elem.setUseClassCriteria(criteria);
+            field.setOrderColumn(mapped.getOrderColumn());
+            return;
+        }
+
+        field.mapJoin(adapt, true);
+        if (elem.getTypeMapping().isMapped()) {
+            ForeignKey fk = vinfo.getTypeJoin(elem, "element", false, adapt);
+            elem.setForeignKey(fk);
+            elem.setColumnIO(vinfo.getColumnIO());
+        } else
+            RelationStrategies.mapRelationToUnmappedPC(elem, "element", adapt);
+        elem.setUseClassCriteria(criteria);
+        elem.mapConstraints("element", adapt);
+
+        FieldMappingInfo finfo = field.getMappingInfo();
+        Column orderCol = finfo.getOrderColumn(field, field.getTable(), adapt);
+        field.setOrderColumn(orderCol);
+        field.setOrderColumnIO(finfo.getColumnIO());
+        field.mapPrimaryKey(adapt);
+    }
+
+    public void initialize() {
+        ValueMapping elem = field.getElementMapping();
+        ForeignKey fk = elem.getForeignKey();
+        if (fk == null)
+            _fkOid = Boolean.TRUE;
+        else
+            _fkOid = elem.getTypeMapping().isForeignKeyObjectId(fk);
+    }
+
+    public void insert(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
+        throws SQLException {
+        if (field.getMappedBy() == null)
+            insert(sm, rm, sm.fetchObject(field.getIndex()));
+    }
+
+    private void insert(OpenJPAStateManager sm, RowManager rm, Object vals)
+        throws SQLException {
+        Collection coll = toCollection(vals);
+        if (coll == null || coll.isEmpty())
+            return;
+
+        Row row = rm.getSecondaryRow(field.getTable(), Row.ACTION_INSERT);
+        row.setForeignKey(field.getJoinForeignKey(), field.getJoinColumnIO(),
+            sm);
+
+        ValueMapping elem = field.getElementMapping();
+        StoreContext ctx = sm.getContext();
+        Column order = field.getOrderColumn();
+        boolean setOrder = field.getOrderColumnIO().isInsertable(order, false);
+        int idx = 0;
+        OpenJPAStateManager esm;
+        for (Iterator itr = coll.iterator(); itr.hasNext(); idx++) {
+            esm = RelationStrategies.getStateManager(itr.next(), ctx);
+            elem.setForeignKey(row, esm);
+            if (setOrder)
+                row.setInt(order, idx);
+            rm.flushSecondaryRow(row);
+        }
+    }
+
+    public void update(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
+        throws SQLException {
+        if (field.getMappedBy() != null)
+            return;
+
+        Object obj = sm.fetchObject(field.getIndex());
+        ChangeTracker ct = null;
+        if (obj instanceof Proxy) {
+            Proxy proxy = (Proxy) obj;
+            if (Proxies.isOwner(proxy, sm, field.getIndex()))
+                ct = proxy.getChangeTracker();
+        }
+
+        // if no fine-grained change tracking then just delete and reinsert
+        if (ct == null || !ct.isTracking()) {
+            delete(sm, store, rm);
+            insert(sm, rm, obj);
+            return;
+        }
+
+        StoreContext ctx = store.getContext();
+        ValueMapping elem = field.getElementMapping();
+        OpenJPAStateManager esm;
+
+        // delete the removes
+        Collection rem = ct.getRemoved();
+        if (!rem.isEmpty()) {
+            Row delRow = rm.getSecondaryRow(field.getTable(),
+                Row.ACTION_DELETE);
+            delRow.whereForeignKey(field.getJoinForeignKey(), sm);
+
+            for (Iterator itr = rem.iterator(); itr.hasNext();) {
+                esm = RelationStrategies.getStateManager(itr.next(), ctx);
+                elem.whereForeignKey(delRow, esm);
+                rm.flushSecondaryRow(delRow);
+            }
+        }
+
+        // insert the adds
+        Collection add = ct.getAdded();
+        if (!add.isEmpty()) {
+            Row addRow = rm.getSecondaryRow(field.getTable(),
+                Row.ACTION_INSERT);
+            addRow.setForeignKey(field.getJoinForeignKey(),
+                field.getJoinColumnIO(), sm);
+
+            int seq = ct.getNextSequence();
+            Column order = field.getOrderColumn();
+            boolean setOrder = field.getOrderColumnIO().isInsertable(order,
+                false);
+            for (Iterator itr = add.iterator(); itr.hasNext(); seq++) {
+                esm = RelationStrategies.getStateManager(itr.next(), ctx);
+                elem.setForeignKey(addRow, esm);
+                if (setOrder)
+                    addRow.setInt(order, seq);
+                rm.flushSecondaryRow(addRow);
+            }
+            if (order != null)
+                ct.setNextSequence(seq);
+        }
+    }
+
+    public void delete(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
+        throws SQLException {
+        Row row = rm.getAllRows(field.getTable(), Row.ACTION_DELETE);
+        row.whereForeignKey(field.getJoinForeignKey(), sm);
+        rm.flushAllRows(row);
+    }
+
+    public Object toDataStoreValue(Object val, JDBCStore store) {
+        return RelationStrategies.toDataStoreValue(field.getElementMapping(),
+            val, store);
+    }
+
+    public Joins join(Joins joins, boolean forceOuter) {
+        return field.join(joins, forceOuter, true);
+    }
+
+    public Joins joinRelation(Joins joins, boolean forceOuter,
+        boolean traverse) {
+        ValueMapping elem = field.getElementMapping();
+        ClassMapping[] clss = elem.getIndependentTypeMappings();
+        if (clss.length != 1) {
+            if (traverse)
+                throw RelationStrategies.unjoinable(elem);
+            return joins;
+        }
+        if (forceOuter)
+            return joins.outerJoinRelation(field.getName(),
+                elem.getForeignKey(clss[0]), false, false);
+        return joins.joinRelation(field.getName(),
+            elem.getForeignKey(clss[0]), false, false);
+    }
+}

Propchange: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyTableFieldStrategy.java
------------------------------------------------------------------------------
    svn:executable = *