| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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; |
| |
| import java.sql.SQLException; |
| import java.util.List; |
| |
| import org.apache.openjpa.enhance.PersistenceCapable; |
| import org.apache.openjpa.enhance.Reflection; |
| import org.apache.openjpa.jdbc.identifier.DBIdentifier; |
| import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration; |
| import org.apache.openjpa.jdbc.kernel.JDBCStore; |
| import org.apache.openjpa.jdbc.meta.strats.NoneFieldStrategy; |
| import org.apache.openjpa.jdbc.meta.strats.PrimitiveFieldStrategy; |
| 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.Index; |
| import org.apache.openjpa.jdbc.schema.Table; |
| import org.apache.openjpa.jdbc.schema.Unique; |
| 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.kernel.FetchConfiguration; |
| import org.apache.openjpa.kernel.OpenJPAStateManager; |
| import org.apache.openjpa.kernel.StateManagerImpl; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.meta.ClassMetaData; |
| import org.apache.openjpa.meta.FieldMetaData; |
| import org.apache.openjpa.meta.JavaTypes; |
| import org.apache.openjpa.util.ApplicationIds; |
| import org.apache.openjpa.util.InternalException; |
| import org.apache.openjpa.util.MetaDataException; |
| import org.apache.openjpa.util.ObjectId; |
| |
| /** |
| * Specialization of metadata for relational databases. |
| * |
| * @author Abe White |
| */ |
| public class FieldMapping |
| extends FieldMetaData |
| implements ValueMapping, FieldStrategy { |
| |
| private static final long serialVersionUID = 142185362294762433L; |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (FieldMapping.class); |
| |
| private final ValueMapping _val; |
| private final ValueMapping _key; |
| private final ValueMapping _elem; |
| private final FieldMappingInfo _info; |
| private final JDBCColumnOrder _orderCol = new JDBCColumnOrder(); |
| private FieldStrategy _strategy = null; |
| |
| private ForeignKey _fk = null; |
| private ColumnIO _io = null; |
| private Unique _unq = null; |
| private Index _idx = null; |
| private boolean _outer = false; |
| private int _fetchMode = Integer.MAX_VALUE; |
| private Unique[] _joinTableUniques; // Unique constraints on JoinTable |
| private Boolean _bidirectionalJoinTableOwner = null; |
| private Boolean _bidirectionalJoinTableNonOwner = null; |
| |
| private Boolean _bi_MTo1_JT = null; |
| private Boolean _uni_1ToM_FK = null; |
| private Boolean _uni_MTo1_JT = null; |
| private Boolean _uni_1To1_JT = null; |
| private Boolean _bi_1To1_JT = null; |
| |
| private FieldMapping _bi_1ToM_JT_Field = null; |
| private FieldMapping _bi_MTo1_JT_Field = null; |
| private ForeignKey _bi_1ToM_Join_FK = null; |
| private ForeignKey _bi_1ToM_Elem_FK = null; |
| |
| private boolean _hasMapsIdCols = false; |
| |
| /** |
| * Constructor. |
| */ |
| public FieldMapping(String name, Class<?> type, ClassMapping owner) { |
| super(name, type, owner); |
| _info = owner.getMappingRepository().newMappingInfo(this); |
| _val = (ValueMapping) getValue(); |
| _key = (ValueMapping) getKey(); |
| _elem = (ValueMapping) getElement(); |
| |
| setUsesIntermediate(false); |
| setUsesImplData(Boolean.FALSE); |
| } |
| |
| /////// |
| // ORM |
| /////// |
| |
| /** |
| * Raw mapping data about field's join to parent table, as well as |
| * miscellaneous specialized columns like order column. |
| */ |
| public FieldMappingInfo getMappingInfo() { |
| return _info; |
| } |
| |
| /** |
| * The strategy used to map this mapping. |
| */ |
| public FieldStrategy getStrategy() { |
| return _strategy; |
| } |
| |
| /** |
| * The strategy used to map this mapping. The <code>adapt</code> |
| * parameter determines whether to adapt when mapping the strategy; |
| * use null if the strategy should not be mapped. |
| */ |
| public void setStrategy(FieldStrategy strategy, Boolean adapt) { |
| // set strategy first so we can access it during mapping |
| FieldStrategy orig = _strategy; |
| _strategy = strategy; |
| if (strategy != null) { |
| try { |
| strategy.setFieldMapping(this); |
| if (adapt != null) |
| strategy.map(adapt); |
| } catch (RuntimeException re) { |
| // reset strategy |
| _strategy = orig; |
| throw re; |
| } |
| |
| // if set to unmapped, clear defined field cache in parent |
| if (!isMapped()) |
| getDefiningMapping().clearDefinedFieldCache(); |
| } |
| } |
| |
| /** |
| * The mapping's primary table. |
| */ |
| public Table getTable() { |
| if (_fk != null) |
| return _fk.getTable(); |
| if (_val.getForeignKey() != null) |
| return _val.getForeignKey().getTable(); |
| |
| // if this is a map of bi-directional relation, |
| // the column of this field should be in the table |
| // of the entity that is the value of the map |
| if (_val.getDeclaredTypeCode() == JavaTypes.MAP) { |
| ClassMapping meta = _elem.getDeclaredTypeMapping(); |
| if (meta != null) |
| return meta.getTable(); |
| } |
| |
| ValueMappingImpl vm = (ValueMappingImpl)getDefiningMapping(). |
| getEmbeddingMetaData(); |
| if (vm != null && vm.getValueMappedBy() != null) { |
| return vm.getFieldMapping().getElementMapping(). |
| getDeclaredTypeMapping().getTable(); |
| } |
| |
| return getDefiningMapping().getTable(); |
| } |
| |
| /** |
| * I/O information on the join columns. |
| */ |
| public ColumnIO getJoinColumnIO() { |
| return (_io == null) ? ColumnIO.UNRESTRICTED : _io; |
| } |
| |
| /** |
| * I/O information on the join columns. |
| */ |
| public void setJoinColumnIO(ColumnIO io) { |
| _io = io; |
| } |
| |
| /** |
| * Foreign key linking the field table to the class' primary table. |
| */ |
| public ForeignKey getJoinForeignKey() { |
| return _fk; |
| } |
| |
| /** |
| * Foreign key linking the field table to the class' primary table. |
| */ |
| public void setJoinForeignKey(ForeignKey fk) { |
| _fk = fk; |
| } |
| |
| /** |
| * Unique constraint on join foreign key columns. |
| */ |
| public Unique getJoinUnique() { |
| return _unq; |
| } |
| |
| /** |
| * Unique constraint on join foreign key columns. |
| */ |
| public void setJoinUnique(Unique unq) { |
| _unq = unq; |
| } |
| |
| public Unique[] getJoinTableUniques() { |
| return _joinTableUniques; |
| } |
| |
| public void setJoinTableUniques(Unique[] unqs) { |
| _joinTableUniques = unqs; |
| } |
| |
| /** |
| * Index on join foreign key columns. |
| */ |
| public Index getJoinIndex() { |
| return _idx; |
| } |
| |
| /** |
| * Index on join foreign key columns. |
| */ |
| public void setJoinIndex(Index idx) { |
| _idx = idx; |
| } |
| |
| /** |
| * Whether to use an outer join from the class' primary table. |
| */ |
| public boolean isJoinOuter() { |
| return _outer; |
| } |
| |
| /** |
| * Whether to use an outer join from the class' primary table. |
| */ |
| public void setJoinOuter(boolean outer) { |
| _outer = outer; |
| } |
| |
| /** |
| * Field order column, if any. |
| */ |
| public Column getOrderColumn() { |
| return _orderCol.getColumn(); |
| } |
| |
| /** |
| * Field order column, if any. |
| */ |
| public void setOrderColumn(Column order) { |
| _orderCol.setColumn(order); |
| } |
| |
| /** |
| * I/O information for order column. |
| */ |
| public ColumnIO getOrderColumnIO() { |
| return _orderCol.getColumnIO(); |
| } |
| |
| /** |
| * I/O information for order column. |
| */ |
| public void setOrderColumnIO(ColumnIO io) { |
| _orderCol.setColumnIO(io); |
| } |
| |
| /** |
| * Increment the reference count of used schema components. |
| */ |
| @Override |
| public void refSchemaComponents() { |
| if (_fk != null) { |
| _fk.ref(); |
| _fk.refColumns(); |
| } |
| if (_orderCol.getColumn() != null) |
| _orderCol.getColumn().ref(); |
| _val.refSchemaComponents(); |
| _key.refSchemaComponents(); |
| _elem.refSchemaComponents(); |
| if (_joinTableUniques != null) { |
| for (Unique joinUnique : _joinTableUniques) { |
| for (Column col : joinUnique.getColumns()) { |
| col.ref(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Clear mapping information, including strategy. |
| */ |
| @Override |
| public void clearMapping() { |
| _strategy = null; |
| _fk = null; |
| _unq = null; |
| _idx = null; |
| _outer = false; |
| _orderCol.setColumn(null); |
| _val.clearMapping(); |
| _key.clearMapping(); |
| _elem.clearMapping(); |
| _info.clear(); |
| setResolve(MODE_MAPPING, false); |
| } |
| |
| /** |
| * Update {@link MappingInfo} with our current mapping information. |
| */ |
| @Override |
| public void syncMappingInfo() { |
| if (isVersion()) { |
| // we rely on the fact that the version will setup our mapping |
| // info correctly when it is synced |
| } else if (getMappedByMapping() != null) { |
| _info.clear(); |
| _val.getValueInfo().clear(); |
| _key.getValueInfo().clear(); |
| _elem.getValueInfo().clear(); |
| |
| FieldMapping mapped = getMappedByMapping(); |
| _info.syncStrategy(this); |
| if (_orderCol.getColumn() != null |
| && mapped.getOrderColumn() == null) |
| _info.syncOrderColumn(this); |
| _val.getValueInfo().setUseClassCriteria |
| (_val.getUseClassCriteria()); |
| _key.getValueInfo().setUseClassCriteria |
| (_key.getUseClassCriteria()); |
| _elem.getValueInfo().setUseClassCriteria |
| (_elem.getUseClassCriteria()); |
| } else { |
| _info.syncWith(this); |
| _val.syncMappingInfo(); |
| _key.syncMappingInfo(); |
| _elem.syncMappingInfo(); |
| } |
| } |
| |
| /** |
| * Returns true if field class does not use the "none" strategy (including |
| * if it has a null strategy, and therefore is probably in the process of |
| * being mapped). |
| */ |
| @Override |
| public boolean isMapped() { |
| return _strategy != NoneFieldStrategy.getInstance(); |
| } |
| |
| ////////////////////// |
| // MetaData interface |
| ////////////////////// |
| |
| /** |
| * The eager fetch mode, as one of the eager constants in |
| * {@link JDBCFetchConfiguration}. |
| */ |
| public int getEagerFetchMode() { |
| if (_fetchMode == Integer.MAX_VALUE) |
| _fetchMode = FetchConfiguration.DEFAULT; |
| return _fetchMode; |
| } |
| |
| /** |
| * The eager fetch mode, as one of the eager constants in |
| * {@link JDBCFetchConfiguration}. |
| */ |
| public void setEagerFetchMode(int mode) { |
| _fetchMode = mode; |
| } |
| |
| /** |
| * Convenience method to perform cast from |
| * {@link FieldMetaData#getRepository} |
| */ |
| @Override |
| public MappingRepository getMappingRepository() { |
| return (MappingRepository) getRepository(); |
| } |
| |
| /** |
| * Convenience method to perform cast from |
| * {@link FieldMetaData#getDefiningMetaData} |
| */ |
| public ClassMapping getDefiningMapping() { |
| return (ClassMapping) getDefiningMetaData(); |
| } |
| |
| /** |
| * Convenience method to perform cast from |
| * {@link FieldMetaData#getDeclaringMetaData} |
| */ |
| public ClassMapping getDeclaringMapping() { |
| return (ClassMapping) getDeclaringMetaData(); |
| } |
| |
| /** |
| * Convenience method to perform cast from {@link FieldMetaData#getKey} |
| */ |
| public ValueMapping getKeyMapping() { |
| return _key; |
| } |
| |
| /** |
| * Convenience method to perform cast from {@link FieldMetaData#getElement} |
| */ |
| public ValueMapping getElementMapping() { |
| return _elem; |
| } |
| |
| /** |
| * Convenience method to perform cast from {@link FieldMetaData#getValue} |
| */ |
| public ValueMapping getValueMapping() { |
| return (ValueMapping) getValue(); |
| } |
| |
| /** |
| * Convenience method to perform cast from |
| * {@link FieldMetaData#getMappedByMetaData} |
| */ |
| public FieldMapping getMappedByMapping() { |
| return (FieldMapping) getMappedByMetaData(); |
| } |
| |
| /** |
| * Convenience method to perform cast from |
| * {@link FieldMetaData#getInverseMetaDatas} |
| */ |
| public FieldMapping[] getInverseMappings() { |
| return (FieldMapping[]) getInverseMetaDatas(); |
| } |
| |
| @Override |
| public boolean resolve(int mode) { |
| int cur = getResolve(); |
| if (super.resolve(mode)) { |
| return true; |
| } |
| if ((mode & MODE_MAPPING) != 0 && (cur & MODE_MAPPING) == 0) { |
| resolveMapping(); |
| } |
| if ((mode & MODE_MAPPING_INIT) != 0 && (cur & MODE_MAPPING_INIT) == 0) { |
| initializeMapping(); |
| } |
| return false; |
| } |
| |
| /** |
| * Resolve the mapping information for this field. |
| */ |
| private void resolveMapping() { |
| MappingRepository repos = getMappingRepository(); |
| if (repos.getMappingDefaults().defaultMissingInfo()) { |
| // copy embedded template mapping info |
| ClassMapping cls = getDefiningMapping(); |
| if (cls.getEmbeddingMapping() != null) { |
| ClassMapping orig = repos.getMapping(cls.getDescribedType(), |
| cls.getEnvClassLoader(), true); |
| FieldMapping tmplate = orig.getFieldMapping(getName()); |
| if (tmplate != null) |
| copyMappingInfo(tmplate); |
| } |
| // copy superclass field info |
| else if (cls.isMapped() && cls.getPCSuperclass() != null |
| && cls.getDescribedType() != getDeclaringType()) { |
| FieldMapping sup = cls.getPCSuperclassMapping(). |
| getFieldMapping(getName()); |
| if (sup != null) |
| copyMappingInfo(sup); |
| } |
| } |
| |
| if (_strategy == null) { |
| if (isVersion()) |
| _strategy = NoneFieldStrategy.getInstance(); |
| else |
| repos.getStrategyInstaller().installStrategy(this); |
| } |
| Log log = getRepository().getLog(); |
| if (log.isTraceEnabled()) |
| log.trace(_loc.get("field-strategy", getName(), |
| _strategy.getAlias())); |
| |
| // mark mapped columns |
| if (_orderCol.getColumn() != null) { |
| if (getOrderColumnIO().isInsertable(0, false)) |
| _orderCol.getColumn().setFlag(Column.FLAG_DIRECT_INSERT, true); |
| if (getOrderColumnIO().isUpdatable(0, false)) |
| _orderCol.getColumn().setFlag(Column.FLAG_DIRECT_UPDATE, true); |
| } |
| if (_fk != null) { |
| Column[] cols = _fk.getColumns(); |
| ColumnIO io = getJoinColumnIO(); |
| for (int i = 0; i < cols.length; i++) { |
| if (io.isInsertable(i, false)) |
| cols[i].setFlag(Column.FLAG_FK_INSERT, true); |
| if (io.isUpdatable(i, false)) |
| cols[i].setFlag(Column.FLAG_FK_UPDATE, true); |
| } |
| } |
| |
| _val.resolve(MODE_MAPPING); |
| _key.resolve(MODE_MAPPING); |
| _elem.resolve(MODE_MAPPING); |
| } |
| |
| /** |
| * Copy mapping info from the given instance to this one. |
| */ |
| public void copyMappingInfo(FieldMapping fm) { |
| setMappedBy(fm.getMappedBy()); |
| _info.copy(fm.getMappingInfo()); |
| _val.copyMappingInfo(fm.getValueMapping()); |
| _key.copyMappingInfo(fm.getKeyMapping()); |
| _elem.copyMappingInfo(fm.getElementMapping()); |
| } |
| |
| /** |
| * Prepare mapping for runtime use. |
| */ |
| private void initializeMapping() { |
| _val.resolve(MODE_MAPPING_INIT); |
| _key.resolve(MODE_MAPPING_INIT); |
| _elem.resolve(MODE_MAPPING_INIT); |
| if (_strategy != null) |
| _strategy.initialize(); |
| } |
| |
| @Override |
| public void copy(FieldMetaData fmd) { |
| super.copy(fmd); |
| if (_fetchMode == Integer.MAX_VALUE) |
| _fetchMode = ((FieldMapping) fmd).getEagerFetchMode(); |
| } |
| |
| @Override |
| protected boolean validateDataStoreExtensionPrefix(String prefix) { |
| return "jdbc-".equals(prefix); |
| } |
| |
| //////////////////////////////// |
| // FieldStrategy implementation |
| //////////////////////////////// |
| |
| @Override |
| public String getAlias() { |
| return assertStrategy().getAlias(); |
| } |
| |
| @Override |
| public void map(boolean adapt) { |
| assertStrategy().map(adapt); |
| } |
| |
| /** |
| * Map this field to its table, optionally requiring that it be |
| * in another table. Utility method for use by mapping strategies. |
| */ |
| public void mapJoin(boolean adapt, boolean joinRequired) { |
| Table table = _info.getTable(this, joinRequired, adapt); |
| |
| if(table != null && table.equals(getDefiningMapping().getTable())) { |
| // Don't create a join if the field's table is the same as the |
| // class's table. |
| table = null; |
| } |
| |
| ForeignKey join = null; |
| if (table != null) |
| join = _info.getJoin(this, table, adapt); |
| if (join == null && joinRequired) |
| throw new MetaDataException(_loc.get("join-required", this)); |
| |
| if (join == null) { |
| _info.assertNoJoin(this, true); |
| _info.assertNoForeignKey(this, !adapt); |
| _info.assertNoUnique(this, !adapt); |
| _info.assertNoIndex(this, !adapt); |
| } else { |
| _fk = join; |
| _io = _info.getColumnIO(); |
| _outer = _info.isJoinOuter(); |
| _unq = _info.getJoinUnique(this, false, adapt); |
| _joinTableUniques = _info.getJoinTableUniques(this, false, adapt); |
| _idx = _info.getJoinIndex(this, adapt); |
| table.setAssociation(); |
| } |
| } |
| |
| /** |
| * Maps the primary key on the secondary table for this field, if the |
| * user's defaults create one. This must be called after |
| * this field is mapped so that it's table has its columns set. |
| */ |
| public void mapPrimaryKey(boolean adapt) { |
| if (adapt && _fk != null && _fk.getTable().getPrimaryKey() == null) |
| getMappingRepository().getMappingDefaults(). |
| installPrimaryKey(this, _fk.getTable()); |
| } |
| |
| @Override |
| public void initialize() { |
| assertStrategy().initialize(); |
| } |
| |
| @Override |
| public void insert(OpenJPAStateManager sm, JDBCStore store, RowManager rm) |
| throws SQLException { |
| setPKValueFromMappedByIdField(sm); |
| assertStrategy().insert(sm, store, rm); |
| } |
| |
| private void setPKValueFromMappedByIdField(OpenJPAStateManager sm) { |
| if (sm instanceof StateManagerImpl) { |
| List<FieldMetaData> mappedByIdFields = ((StateManagerImpl)sm). |
| getMappedByIdFields(); |
| if (mappedByIdFields == null) |
| return; |
| if (!mappedByIdFields.contains(this)) |
| return; |
| if (!isMappedById()) |
| return; |
| PersistenceCapable pc = (PersistenceCapable)sm. |
| fetchObject(getIndex()); |
| if (pc == null) |
| return; |
| StateManagerImpl pkSm = (StateManagerImpl)pc. |
| pcGetStateManager(); |
| Object pkVal = getPKValue(pkSm); |
| if (pkVal == null) |
| return; |
| setPKValue((StateManagerImpl)sm, pkVal); |
| sm.setObjectId( |
| ApplicationIds.create(sm.getPersistenceCapable(), |
| sm.getMetaData())); |
| } |
| } |
| |
| private Object getPKValue(StateManagerImpl pkSm) { |
| ClassMetaData pkMeta = pkSm.getMetaData(); |
| FieldMetaData[] fmds = pkMeta.getPrimaryKeyFields(); |
| // MappedById is for single value primary key or embeddable id |
| if (fmds.length == 0) |
| return null; |
| else |
| return ApplicationIds.getKey(pkSm.getObjectId(), pkMeta); |
| } |
| |
| private void setPKValue(StateManagerImpl sm, Object pkVal) { |
| ClassMetaData meta = sm.getMetaData(); |
| FieldMetaData[] fmds = meta.getPrimaryKeyFields(); |
| if (fmds.length == 0) |
| return; |
| |
| Strategy strat = ((FieldMapping)fmds[0]).getStrategy(); |
| // single value primary key |
| if (strat instanceof PrimitiveFieldStrategy) |
| ((PrimitiveFieldStrategy)strat).setAutoAssignedValue(sm, null, null, |
| pkVal); |
| else { |
| //composite key |
| String mappedByIdFieldName = getMappedByIdValue(); |
| if (mappedByIdFieldName != null && |
| mappedByIdFieldName.length() > 0) { |
| //The name of the attribute within the composite key to which |
| //the relationship attribute corresponds. |
| Object target = ((ObjectId)sm.getObjectId()).getId(); |
| if (target == null) |
| return; |
| setMappedByIdValue(target, pkVal, mappedByIdFieldName); |
| pkVal = target; |
| } |
| sm.storeObjectField(fmds[0].getIndex(), pkVal); |
| } |
| } |
| |
| public void setMappedByIdValue(Object target, |
| Object val, String mappedByIdFieldName) { |
| Reflection.set(target, |
| Reflection.findField(target.getClass(), mappedByIdFieldName, true), |
| val); |
| } |
| |
| @Override |
| public void update(OpenJPAStateManager sm, JDBCStore store, RowManager rm) |
| throws SQLException { |
| assertStrategy().update(sm, store, rm); |
| } |
| |
| @Override |
| public void delete(OpenJPAStateManager sm, JDBCStore store, RowManager rm) |
| throws SQLException { |
| assertStrategy().delete(sm, store, rm); |
| } |
| |
| /** |
| * Delete the row for this object if the reference foreign key exists. |
| * Utility method for use by mapping strategies. |
| */ |
| public void deleteRow(OpenJPAStateManager sm, JDBCStore store, |
| RowManager rm) |
| throws SQLException { |
| if (_fk != null) { |
| Row row = rm.getRow(getTable(), Row.ACTION_DELETE, sm, true); |
| row.whereForeignKey(_fk, sm); |
| } |
| } |
| |
| /** |
| * Return the row to use for this field. This method is meant only for |
| * single-value fields that might reside in a table that is joined to |
| * the primary table through the join foreign key. It is not |
| * meant for multi-valued fields like collections and maps. The method |
| * checks whether we're using an outer join and if so it deletes the |
| * field's previous value, then if the field is non-null returns an insert |
| * row for the new value. The join foreign key will already be set on |
| * the returned row; mapping strategies just need to set their own values. |
| * Utility method for use by mapping strategies. |
| */ |
| public Row getRow(OpenJPAStateManager sm, JDBCStore store, RowManager rm, |
| int action) |
| throws SQLException { |
| Row row = null; |
| boolean newOuterRow = false; |
| if (_fk != null && _outer && action != Row.ACTION_DELETE) { |
| // if updating with outer join, delete old value first, then insert; |
| // we can't just update b/c the row might not exist |
| if (action == Row.ACTION_UPDATE) { |
| // maybe some other field already is updating? |
| row = rm.getRow(getTable(), Row.ACTION_UPDATE, sm, false); |
| if (row == null) { |
| Row del = rm.getRow(getTable(), Row.ACTION_DELETE, sm, |
| true); |
| del.whereForeignKey(_fk, sm); |
| } |
| } else |
| row = rm.getRow(getTable(), Row.ACTION_INSERT, sm, false); |
| |
| // only update/insert if the row exists already or the value is |
| // not null/default |
| if (row == null && !isNullValue(sm)) { |
| row = rm.getRow(getTable(), Row.ACTION_INSERT, sm, true); |
| newOuterRow = true; |
| } |
| } else |
| row = rm.getRow(getTable(), action, sm, true); |
| |
| // setup fk |
| if (row != null && _fk != null) { |
| if (row.getAction() == Row.ACTION_INSERT) |
| row.setForeignKey(_fk, _io, sm); |
| else |
| row.whereForeignKey(_fk, sm); |
| |
| // if this is a new outer joined row, mark it invalid until |
| // some mapping actually sets information on it |
| if (newOuterRow) |
| row.setValid(false); |
| } |
| return row; |
| } |
| |
| /** |
| * Return true if this field is null/default in the given instance. |
| */ |
| private boolean isNullValue(OpenJPAStateManager sm) { |
| switch (getTypeCode()) { |
| case JavaTypes.BOOLEAN: |
| return !sm.fetchBoolean(getIndex()); |
| case JavaTypes.BYTE: |
| return sm.fetchByte(getIndex()) == 0; |
| case JavaTypes.CHAR: |
| return sm.fetchChar(getIndex()) == 0; |
| case JavaTypes.DOUBLE: |
| return sm.fetchDouble(getIndex()) == 0; |
| case JavaTypes.FLOAT: |
| return sm.fetchFloat(getIndex()) == 0; |
| case JavaTypes.INT: |
| return sm.fetchInt(getIndex()) == 0; |
| case JavaTypes.LONG: |
| return sm.fetchLong(getIndex()) == 0; |
| case JavaTypes.SHORT: |
| return sm.fetchShort(getIndex()) == 0; |
| case JavaTypes.STRING: |
| return sm.fetchString(getIndex()) == null; |
| default: |
| return sm.fetchObject(getIndex()) == null; |
| } |
| } |
| |
| @Override |
| public Boolean isCustomInsert(OpenJPAStateManager sm, JDBCStore store) { |
| return assertStrategy().isCustomInsert(sm, store); |
| } |
| |
| @Override |
| public Boolean isCustomUpdate(OpenJPAStateManager sm, JDBCStore store) { |
| return assertStrategy().isCustomUpdate(sm, store); |
| } |
| |
| @Override |
| public Boolean isCustomDelete(OpenJPAStateManager sm, JDBCStore store) { |
| return assertStrategy().isCustomDelete(sm, store); |
| } |
| |
| @Override |
| public void customInsert(OpenJPAStateManager sm, JDBCStore store) |
| throws SQLException { |
| assertStrategy().customInsert(sm, store); |
| } |
| |
| @Override |
| public void customUpdate(OpenJPAStateManager sm, JDBCStore store) |
| throws SQLException { |
| assertStrategy().customUpdate(sm, store); |
| } |
| |
| @Override |
| public void customDelete(OpenJPAStateManager sm, JDBCStore store) |
| throws SQLException { |
| assertStrategy().customDelete(sm, store); |
| } |
| |
| @Override |
| public void setFieldMapping(FieldMapping owner) { |
| assertStrategy().setFieldMapping(owner); |
| } |
| |
| @Override |
| public int supportsSelect(Select sel, int type, OpenJPAStateManager sm, |
| JDBCStore store, JDBCFetchConfiguration fetch) { |
| return assertStrategy().supportsSelect(sel, type, sm, store, fetch); |
| } |
| |
| @Override |
| public void selectEagerParallel(SelectExecutor sel, OpenJPAStateManager sm, |
| JDBCStore store, JDBCFetchConfiguration fetch, int eagerMode) { |
| assertStrategy().selectEagerParallel(sel, sm, store, fetch, eagerMode); |
| } |
| |
| @Override |
| public void selectEagerJoin(Select sel, OpenJPAStateManager sm, |
| JDBCStore store, JDBCFetchConfiguration fetch, int eagerMode) { |
| assertStrategy().selectEagerJoin(sel, sm, store, fetch, eagerMode); |
| } |
| |
| @Override |
| public boolean isEagerSelectToMany() { |
| return assertStrategy().isEagerSelectToMany(); |
| } |
| |
| @Override |
| public int select(Select sel, OpenJPAStateManager sm, JDBCStore store, |
| JDBCFetchConfiguration fetch, int eagerMode) { |
| return assertStrategy().select(sel, sm, store, fetch, eagerMode); |
| } |
| |
| /** |
| * Return any joins needed to get from the primary table to this table. |
| */ |
| public Joins join(Select sel) { |
| if (_fk == null) |
| return null; |
| |
| Joins joins = sel.newJoins(); |
| if (_outer) |
| return joins.outerJoin(_fk, true, false); |
| return joins.join(_fk, true, false); |
| } |
| |
| /** |
| * Add a <code>wherePrimaryKey</code> or <code>whereForeignKey</code> |
| * condition to the given select, depending on whether we have a join |
| * foreign key. |
| */ |
| public void wherePrimaryKey(Select sel, OpenJPAStateManager sm, |
| JDBCStore store) { |
| if (_fk != null) |
| sel.whereForeignKey(_fk, sm.getObjectId(), getDefiningMapping(), |
| store); |
| else |
| sel.wherePrimaryKey(sm.getObjectId(), getDefiningMapping(), |
| store); |
| } |
| |
| /** |
| * Add ordering to the given select for all non-relation order values, |
| * including the synthetic order column, if any. |
| * |
| * @param elem the related type we're fetching, or null |
| * @param joins the joins to this field's table |
| */ |
| public void orderLocal(Select sel, ClassMapping elem, Joins joins) { |
| _orderCol.order(sel, elem, joins); |
| JDBCOrder[] orders = (JDBCOrder[]) getOrders(); |
| for (JDBCOrder order : orders) |
| if (!order.isInRelation()) |
| order.order(sel, elem, joins); |
| } |
| |
| /** |
| * Add ordering to the given select for all relation-based values. |
| * |
| * @param elem the related type we're fetching |
| * @param joins the joins across the relation |
| */ |
| public void orderRelation(Select sel, ClassMapping elem, Joins joins) { |
| JDBCOrder[] orders = (JDBCOrder[]) getOrders(); |
| for (JDBCOrder order : orders) |
| if (order.isInRelation()) |
| order.order(sel, elem, joins); |
| } |
| |
| @Override |
| public Object loadEagerParallel(OpenJPAStateManager sm, JDBCStore store, |
| JDBCFetchConfiguration fetch, Object res) |
| throws SQLException { |
| return assertStrategy().loadEagerParallel(sm, store, fetch, res); |
| } |
| |
| @Override |
| public void loadEagerJoin(OpenJPAStateManager sm, JDBCStore store, |
| JDBCFetchConfiguration fetch, Result res) |
| throws SQLException { |
| assertStrategy().loadEagerJoin(sm, store, fetch, res); |
| } |
| |
| @Override |
| public void load(OpenJPAStateManager sm, JDBCStore store, |
| JDBCFetchConfiguration fetch, Result res) |
| throws SQLException { |
| assertStrategy().load(sm, store, fetch, res); |
| } |
| |
| @Override |
| public void load(OpenJPAStateManager sm, JDBCStore store, |
| JDBCFetchConfiguration fetch) |
| throws SQLException { |
| assertStrategy().load(sm, store, fetch); |
| } |
| |
| @Override |
| public Object toDataStoreValue(Object val, JDBCStore store) { |
| return assertStrategy().toDataStoreValue(val, store); |
| } |
| |
| @Override |
| public Object toKeyDataStoreValue(Object val, JDBCStore store) { |
| return assertStrategy().toKeyDataStoreValue(val, store); |
| } |
| |
| @Override |
| public void appendIsEmpty(SQLBuffer sql, Select sel, Joins joins) { |
| assertStrategy().appendIsEmpty(sql, sel, joins); |
| } |
| |
| @Override |
| public void appendIsNotEmpty(SQLBuffer sql, Select sel, Joins joins) { |
| assertStrategy().appendIsNotEmpty(sql, sel, joins); |
| } |
| |
| @Override |
| public void appendIsNull(SQLBuffer sql, Select sel, Joins joins) { |
| assertStrategy().appendIsNull(sql, sel, joins); |
| } |
| |
| @Override |
| public void appendIsNotNull(SQLBuffer sql, Select sel, Joins joins) { |
| assertStrategy().appendIsNotNull(sql, sel, joins); |
| } |
| |
| @Override |
| public void appendSize(SQLBuffer sql, Select sel, Joins joins) { |
| assertStrategy().appendSize(sql, sel, joins); |
| } |
| |
| @Override |
| public void appendIndex(SQLBuffer sql, Select sel, Joins joins) { |
| assertStrategy().appendIndex(sql, sel, joins); |
| } |
| |
| @Override |
| public void appendType(SQLBuffer sql, Select sel, Joins joins) { |
| assertStrategy().appendType(sql, sel, joins); |
| } |
| |
| @Override |
| public Joins join(Joins joins, boolean forceOuter) { |
| return assertStrategy().join(joins, forceOuter); |
| } |
| |
| @Override |
| public Joins joinKey(Joins joins, boolean forceOuter) { |
| return assertStrategy().joinKey(joins, forceOuter); |
| } |
| |
| @Override |
| public Joins joinRelation(Joins joins, boolean forceOuter, |
| boolean traverse) { |
| return assertStrategy().joinRelation(joins, forceOuter, traverse); |
| } |
| |
| @Override |
| public Joins joinKeyRelation(Joins joins, boolean forceOuter, |
| boolean traverse) { |
| return assertStrategy().joinKeyRelation(joins, forceOuter, traverse); |
| } |
| |
| /** |
| * Joins from the owning class' table to the table where this field lies |
| * using the join foreign key. Utility method for use by mapping strategies. |
| */ |
| public Joins join(Joins joins, boolean forceOuter, boolean toMany) { |
| if (_fk == null) |
| return joins; |
| if (_outer || forceOuter) |
| return joins.outerJoin(_fk, true, toMany); |
| return joins.join(_fk, true, toMany); |
| } |
| |
| @Override |
| public Object loadProjection(JDBCStore store, JDBCFetchConfiguration fetch, |
| Result res, Joins joins) |
| throws SQLException { |
| // OPENJPA-662: Version fields have NoneFieldStrategy -- hence they |
| // need special treatment |
| if (isVersion()) { |
| return getDefiningMapping().getVersion().load(null, store, res, joins); |
| } |
| return assertStrategy().loadProjection(store, fetch, res, joins); |
| } |
| |
| @Override |
| public Object loadKeyProjection(JDBCStore store, |
| JDBCFetchConfiguration fetch, Result res, Joins joins) |
| throws SQLException { |
| return assertStrategy() |
| .loadKeyProjection(store, fetch, res, joins); |
| } |
| |
| @Override |
| public boolean isVersionable() { |
| return assertStrategy().isVersionable(); |
| } |
| |
| @Override |
| public void where(OpenJPAStateManager sm, JDBCStore store, RowManager rm, |
| Object prevValue) |
| throws SQLException { |
| assertStrategy().where(sm, store, rm, prevValue); |
| } |
| |
| private FieldStrategy assertStrategy() { |
| if (_strategy == null) |
| throw new InternalException(); |
| return _strategy; |
| } |
| |
| /////////////////////////////// |
| // ValueMapping implementation |
| /////////////////////////////// |
| |
| @Override |
| public ValueMappingInfo getValueInfo() { |
| return _val.getValueInfo(); |
| } |
| |
| @Override |
| public ValueHandler getHandler() { |
| return _val.getHandler(); |
| } |
| |
| @Override |
| public void setHandler(ValueHandler handler) { |
| _val.setHandler(handler); |
| } |
| |
| @Override |
| public FieldMapping getFieldMapping() { |
| return this; |
| } |
| |
| @Override |
| public ClassMapping getTypeMapping() { |
| return _val.getTypeMapping(); |
| } |
| |
| @Override |
| public ClassMapping getDeclaredTypeMapping() { |
| return _val.getDeclaredTypeMapping(); |
| } |
| |
| @Override |
| public ClassMapping getEmbeddedMapping() { |
| return _val.getEmbeddedMapping(); |
| } |
| |
| @Override |
| public FieldMapping getValueMappedByMapping() { |
| return _val.getValueMappedByMapping(); |
| } |
| |
| @Override |
| public Column[] getColumns() { |
| // pcl: 6 July 2007: this seems a bit hacky, but if the mapping is a |
| // version, it will have a NoneFieldMapping (since the version strategy |
| // for the class takes care of it's mapping), and NoneFieldStrategies |
| // do not have columns. |
| // |
| // rgc : 2 March 2011 : Still hacky. If the version field is in a mapped super class we need to look |
| // at the defining metadata to find the correct Version. Not sure why the version for the declaring metadata |
| // is different than the defining metadata. |
| if (isVersion()){ |
| ClassMapping cm = (ClassMapping)((FieldMetaData)this).getDefiningMetaData(); |
| return cm.getVersion().getColumns(); |
| }else |
| return _val.getColumns(); |
| } |
| |
| @Override |
| public void setColumns(Column[] cols) { |
| _val.setColumns(cols); |
| } |
| |
| @Override |
| public ColumnIO getColumnIO() { |
| return _val.getColumnIO(); |
| } |
| |
| @Override |
| public void setColumnIO(ColumnIO io) { |
| _val.setColumnIO(io); |
| } |
| |
| @Override |
| public ForeignKey getForeignKey() { |
| return _val.getForeignKey(); |
| } |
| |
| @Override |
| public ForeignKey getForeignKey(ClassMapping target) { |
| return _val.getForeignKey(target); |
| } |
| |
| @Override |
| public void setForeignKey(ForeignKey fk) { |
| _val.setForeignKey(fk); |
| } |
| |
| @Override |
| public int getJoinDirection() { |
| return _val.getJoinDirection(); |
| } |
| |
| @Override |
| public void setJoinDirection(int direction) { |
| _val.setJoinDirection(direction); |
| } |
| |
| @Override |
| public void setForeignKey(Row row, OpenJPAStateManager sm) |
| throws SQLException { |
| _val.setForeignKey(row, sm); |
| } |
| |
| @Override |
| public void setForeignKey(Row row, OpenJPAStateManager sm, int targetNumber) |
| throws SQLException { |
| _val.setForeignKey(row, sm, targetNumber); |
| } |
| |
| @Override |
| public void whereForeignKey(Row row, OpenJPAStateManager sm) |
| throws SQLException { |
| _val.whereForeignKey(row, sm); |
| } |
| |
| @Override |
| public ClassMapping[] getIndependentTypeMappings() { |
| return _val.getIndependentTypeMappings(); |
| } |
| |
| @Override |
| public int getSelectSubclasses() { |
| return _val.getSelectSubclasses(); |
| } |
| |
| @Override |
| public Unique getValueUnique() { |
| return _val.getValueUnique(); |
| } |
| |
| @Override |
| public void setValueUnique(Unique unq) { |
| _val.setValueUnique(unq); |
| } |
| |
| @Override |
| public Index getValueIndex() { |
| return _val.getValueIndex(); |
| } |
| |
| @Override |
| public void setValueIndex(Index idx) { |
| _val.setValueIndex(idx); |
| } |
| |
| @Override |
| public boolean getUseClassCriteria() { |
| return _val.getUseClassCriteria(); |
| } |
| |
| @Override |
| public void setUseClassCriteria(boolean criteria) { |
| _val.setUseClassCriteria(criteria); |
| } |
| |
| @Override |
| public int getPolymorphic() { |
| return _val.getPolymorphic(); |
| } |
| |
| @Override |
| public void setPolymorphic(int poly) { |
| _val.setPolymorphic(poly); |
| } |
| |
| /** |
| * @deprecated |
| */ |
| @Deprecated |
| @Override |
| public void mapConstraints(String name, boolean adapt) { |
| _val.mapConstraints(name, adapt); |
| } |
| |
| @Override |
| public void mapConstraints(DBIdentifier name, boolean adapt) { |
| _val.mapConstraints(name, adapt); |
| } |
| |
| @Override |
| public void copyMappingInfo(ValueMapping vm) { |
| _val.copyMappingInfo(vm); |
| } |
| |
| /** |
| * Affirms if this field is the owning side of a bidirectional relation |
| * with a join table. Evaluated only once and the result cached for |
| * subsequent call. Hence must be called after resolution. |
| */ |
| public boolean isBidirectionalJoinTableMappingOwner() { |
| if (_bidirectionalJoinTableOwner != null) |
| return _bidirectionalJoinTableOwner; |
| |
| _bidirectionalJoinTableOwner = false; |
| ForeignKey fk = getForeignKey(); |
| if (fk != null) |
| return false; |
| ForeignKey jfk = getJoinForeignKey(); |
| if (jfk == null) |
| return false; |
| FieldMapping mappedBy = getValueMappedByMapping(); |
| if (mappedBy != null) |
| return false; |
| ValueMapping elem = getElementMapping(); |
| if (elem == null) |
| return false; |
| ClassMapping relType = elem.getDeclaredTypeMapping(); |
| if (relType == null) |
| return false; |
| FieldMapping[] relFmds = relType.getFieldMappings(); |
| for (FieldMapping rfm : relFmds) { |
| if (rfm.getDeclaredTypeMetaData() == getDeclaringMapping()) { |
| ForeignKey rjfk = rfm.getJoinForeignKey(); |
| if (rjfk == null) |
| continue; |
| if (rjfk.getTable() == jfk.getTable() && |
| jfk.getTable().getColumns().length == |
| jfk.getColumns().length + rjfk.getColumns().length) { |
| _bidirectionalJoinTableOwner = true; |
| break; |
| } |
| } |
| } |
| return _bidirectionalJoinTableOwner; |
| } |
| |
| /** |
| * Affirms if this field is the non-owning side of a bidirectional relation |
| * with a join table. Evaluated only once and the result cached for |
| * subsequent call. Hence must be called after resolution. |
| */ |
| public boolean isBidirectionalJoinTableMappingNonOwner() { |
| if (_bidirectionalJoinTableNonOwner != null) |
| return _bidirectionalJoinTableNonOwner; |
| |
| _bidirectionalJoinTableNonOwner = false; |
| ForeignKey fk = getForeignKey(); |
| if (fk == null) |
| return false; |
| ForeignKey jfk = getJoinForeignKey(); |
| if (jfk == null) |
| return false; |
| FieldMapping mappedBy = getValueMappedByMapping(); |
| if (mappedBy != null) |
| return false; |
| ValueMapping elem = getElementMapping(); |
| if (elem == null) |
| return false; |
| ClassMapping relType = getDeclaredTypeMapping(); |
| if (relType == null) |
| return false; |
| FieldMapping[] relFmds = relType.getFieldMappings(); |
| for (FieldMapping rfm : relFmds) { |
| ValueMapping relem = rfm.getElementMapping(); |
| if (relem != null && relem.getDeclaredTypeMapping() == |
| getDeclaringMapping()) { |
| ForeignKey rjfk = rfm.getJoinForeignKey(); |
| if (rjfk == null) |
| continue; |
| if (rjfk.getTable() == jfk.getTable() && |
| jfk.getTable().getColumns().length == |
| jfk.getColumns().length + rjfk.getColumns().length) { |
| _bidirectionalJoinTableNonOwner = true; |
| break; |
| } |
| } |
| } |
| return _bidirectionalJoinTableNonOwner; |
| } |
| |
| public boolean isBiMTo1JT() { |
| if (_bi_MTo1_JT == null) { |
| _bi_MTo1_JT = getMappingRepository().isBiMTo1JT(this); |
| } |
| return _bi_MTo1_JT; |
| } |
| |
| public boolean isUni1ToMFK() { |
| if (_uni_1ToM_FK == null) |
| _uni_1ToM_FK = getMappingRepository().isUni1ToMFK(this); |
| return _uni_1ToM_FK; |
| } |
| |
| public boolean isUniMTo1JT() { |
| if (_uni_MTo1_JT == null) |
| _uni_MTo1_JT = getMappingRepository().isUniMTo1JT(this); |
| return _uni_MTo1_JT; |
| } |
| |
| public boolean isUni1To1JT() { |
| if (_uni_1To1_JT == null) |
| _uni_1To1_JT = getMappingRepository().isUni1To1JT(this); |
| return _uni_1To1_JT; |
| } |
| |
| public boolean isBi1To1JT() { |
| if (_bi_1To1_JT == null) |
| _bi_1To1_JT = getMappingRepository().isBi1To1JT(this); |
| return _bi_1To1_JT; |
| } |
| |
| public FieldMapping getBi_1ToM_JTField() { |
| if (_bi_1ToM_JT_Field == null) { |
| _bi_1ToM_JT_Field = getMappingRepository().getBi_1ToM_JoinTableField(this); |
| } |
| return _bi_1ToM_JT_Field; |
| } |
| |
| public FieldMapping getBi_MTo1_JTField() { |
| if (_bi_MTo1_JT_Field == null) { |
| _bi_MTo1_JT_Field = getMappingRepository().getBi_MTo1_JoinTableField(this); |
| } |
| return _bi_MTo1_JT_Field; |
| } |
| |
| public ForeignKey getBi1ToMJoinFK() { |
| if (_bi_1ToM_Join_FK == null) { |
| getBi_1ToM_JTField(); |
| if (_bi_1ToM_JT_Field != null) |
| _bi_1ToM_Join_FK = _bi_1ToM_JT_Field.getJoinForeignKey(); |
| } |
| return _bi_1ToM_Join_FK; |
| } |
| |
| public ForeignKey getBi1ToMElemFK() { |
| if (_bi_1ToM_Elem_FK == null) { |
| getBi_1ToM_JTField(); |
| if (_bi_1ToM_JT_Field != null) |
| _bi_1ToM_Elem_FK = _bi_1ToM_JT_Field.getElementMapping().getForeignKey(); |
| } |
| return _bi_1ToM_Elem_FK; |
| } |
| |
| public void setBi1MJoinTableInfo() { |
| if (getAssociationType() == FieldMetaData.ONE_TO_MANY) { |
| FieldMapping mapped = getBi_MTo1_JTField(); |
| if (mapped != null) { |
| FieldMappingInfo info = getMappingInfo(); |
| FieldMappingInfo mappedInfo = mapped.getMappingInfo(); |
| info.setTableIdentifier(mappedInfo.getTableIdentifier()); |
| info.setColumns(mapped.getElementMapping().getValueInfo().getColumns()); |
| getElementMapping().getValueInfo().setColumns( |
| mappedInfo.getColumns()); |
| } |
| } |
| } |
| |
| public boolean isNonDefaultMappingUsingJoinTableStrategy() { |
| return isBi1To1JT() || isUni1To1JT() || isUniMTo1JT() || isBiMTo1JT(); |
| } |
| |
| public void setMapsIdCols(boolean hasMapsIdCols) { |
| _hasMapsIdCols = hasMapsIdCols; |
| } |
| |
| public boolean hasMapsIdCols() { |
| return _hasMapsIdCols; |
| } |
| |
| @Override |
| public boolean isDelayCapable() { |
| return (getOrderColumn() == null && !isInDefaultFetchGroup() && super.isDelayCapable()); |
| } |
| } |