blob: 71ec053793b786c6a2363cbf7bfbc2bb3ef7ddb6 [file] [log] [blame]
/*
* 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.kernel;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.Discriminator;
import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.meta.Strategy;
import org.apache.openjpa.jdbc.meta.Version;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.jdbc.sql.Row;
import org.apache.openjpa.jdbc.sql.RowImpl;
import org.apache.openjpa.jdbc.sql.RowManager;
import org.apache.openjpa.jdbc.sql.SQLExceptions;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.PCState;
import org.apache.openjpa.kernel.StateManagerImpl;
import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configuration;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.OpenJPAException;
import org.apache.openjpa.util.OptimisticException;
/**
* Base update manager with common functionality.
*
* @author Abe White
*/
public abstract class AbstractUpdateManager
implements UpdateManager, Configurable {
protected JDBCConfiguration conf = null;
protected DBDictionary dict = null;
@Override
public void setConfiguration(Configuration conf) {
this.conf = (JDBCConfiguration) conf;
dict = this.conf.getDBDictionaryInstance();
}
@Override
public void startConfiguration() {
}
@Override
public void endConfiguration() {
}
@Override
public Collection flush(Collection states, JDBCStore store) {
Connection conn = store.getConnection();
try {
PreparedStatementManager psMgr = newPreparedStatementManager(store,
conn);
return flush(states, store, psMgr);
} finally {
try { conn.close(); } catch (SQLException se) {}
}
}
protected Collection flush(Collection states, JDBCStore store,
PreparedStatementManager psMgr) {
// run through all the states and update them as necessary
RowManager rowMgr = newRowManager();
Collection customs = new LinkedList();
Collection exceps = psMgr.getExceptions();
Collection mappedByIdStates = new ArrayList();
for (Object state : states) {
OpenJPAStateManager obj = (OpenJPAStateManager) state;
if (obj instanceof StateManagerImpl) {
StateManagerImpl sm = (StateManagerImpl) obj;
if (sm.getMappedByIdFields() != null)
mappedByIdStates.add(sm);
else exceps = populateRowManager(sm, rowMgr, store, exceps,
customs);
}
else
exceps = populateRowManager(obj, rowMgr, store, exceps,
customs);
}
// flush rows
exceps = flush(rowMgr, psMgr, exceps);
if (mappedByIdStates.size() != 0) {
for (Object mappedByIdState : mappedByIdStates) {
StateManagerImpl sm = (StateManagerImpl) mappedByIdState;
exceps = populateRowManager(sm, rowMgr, store, exceps, customs);
}
// flush rows
exceps = flush(rowMgr, psMgr, exceps);
}
// now do any custom mappings
for (Iterator itr = customs.iterator(); itr.hasNext();) {
try {
((CustomMapping) itr.next()).execute(store);
} catch (SQLException se) {
exceps = addException(exceps, SQLExceptions.getStore(se, dict));
} catch (OpenJPAException ke) {
exceps = addException(exceps, ke);
}
}
// return all exceptions
return exceps;
}
/**
* Return a new {@link RowManager}.
*/
protected abstract RowManager newRowManager();
/**
* Return a new {@link PreparedStatementManager}.
*/
protected abstract PreparedStatementManager newPreparedStatementManager(
JDBCStore store, Connection conn);
/**
* Flush all rows of the given row manager. Add exceptions to
* <code>exceps</code> (which may start as null) using
* {@link #addException}. Return <code>exceps</code>.
*/
protected abstract Collection flush(RowManager rowMgr,
PreparedStatementManager psMgr, Collection exceps);
/**
* Populate the row manager with rows to be flushed for the given state.
*
* @param exceps exceptions encountered when flushing will be added to
* this list and returned; the list may be null initially
* @param customs buffer custom mappings
* @return the exceptions list
*/
protected Collection populateRowManager(OpenJPAStateManager sm,
RowManager rowMgr, JDBCStore store, Collection exceps,
Collection customs) {
int action = Row.ACTION_UPDATE;
try {
BitSet dirty;
if (sm.getPCState() == PCState.PNEW && !sm.isFlushed()) {
action = Row.ACTION_INSERT;
insert(sm, (ClassMapping) sm.getMetaData(), rowMgr, store,
customs);
} else if (sm.getPCState() == PCState.PNEWFLUSHEDDELETED
|| sm.getPCState() == PCState.PDELETED) {
action = Row.ACTION_DELETE;
delete(sm, (ClassMapping) sm.getMetaData(), rowMgr, store,
customs);
} else if ((dirty = ImplHelper.getUpdateFields(sm)) != null) {
update(sm, dirty, (ClassMapping) sm.getMetaData(), rowMgr,
store, customs, false);
} else if (sm.isVersionUpdateRequired()) {
updateIndicators(sm, (ClassMapping) sm.getMetaData(), rowMgr,
store, customs, true);
} else if (sm.isVersionCheckRequired()) {
if (!((ClassMapping) sm.getMetaData()).getVersion().
checkVersion(sm, store, false))
exceps = addException(exceps, new OptimisticException(sm.
getManagedInstance()));
}
} catch (SQLException se) {
exceps = addException(exceps, SQLExceptions.getStore(se, dict));
} catch (OpenJPAException ke) {
RowImpl row = (RowImpl) rowMgr.getRow(((ClassMapping) sm.getMetaData()).getTable(), action, sm, false);
if (row != null) {
row.setFlushed(true);
}
exceps = addException(exceps, ke);
}
return exceps;
}
/**
* Add the given exception to the given list, which may start out as null.
*/
protected Collection addException(Collection exceps, Exception err) {
if (exceps == null)
exceps = new LinkedList();
exceps.add(err);
return exceps;
}
/**
* Recursive method to insert the given instance, base class first.
*/
protected void insert(OpenJPAStateManager sm, ClassMapping mapping,
RowManager rowMgr, JDBCStore store, Collection customs)
throws SQLException {
Boolean custom = mapping.isCustomInsert(sm, store);
if (!Boolean.FALSE.equals(custom))
mapping.customInsert(sm, store);
if (Boolean.TRUE.equals(custom))
return;
ClassMapping sup = mapping.getJoinablePCSuperclassMapping();
if (sup != null)
insert(sm, sup, rowMgr, store, customs);
mapping.insert(sm, store, rowMgr);
FieldMapping[] fields = mapping.getDefinedFieldMappings();
if (((StateManagerImpl)sm).getMappedByIdFields() != null) {
// when there is mappedByIdFields, the id field is not
// fully populated. We need to insert other fields first
// so that in the process of inserting other fields,
// the values of mappedById fields can be set into
// the id fields. Once the id fields are fully populated,
// we will then insert the id fields.
fields = reorderFields(fields);
}
BitSet dirty = sm.getDirty();
for (FieldMapping field : fields) {
if (dirty.get(field.getIndex())
&& !bufferCustomInsert(field, sm, store, customs)) {
field.insert(sm, store, rowMgr);
}
}
if (sup == null) {
Version vers = mapping.getVersion();
if (!bufferCustomInsert(vers, sm, store, customs))
vers.insert(sm, store, rowMgr);
Discriminator dsc = mapping.getDiscriminator();
if (!bufferCustomInsert(dsc, sm, store, customs))
dsc.insert(sm, store, rowMgr);
}
}
private FieldMapping[] reorderFields(FieldMapping[] fields) {
List<FieldMapping> pkFmds = new ArrayList<>();
FieldMapping[] ret = new FieldMapping[fields.length];
int j = 0;
for (FieldMapping field : fields) {
if (!field.isPrimaryKey())
ret[j++] = field;
else
pkFmds.add(field);
}
for (FieldMapping pkFmd : pkFmds) {
ret[j++] = pkFmd;
}
return ret;
}
/**
* If the given mapping uses a custom insert, places a
* {@link CustomMapping} struct for it in the given collection and
* returns true, else returns false.
*/
private boolean bufferCustomInsert(Strategy strat, OpenJPAStateManager sm,
JDBCStore store, Collection customs) {
Boolean custom = strat.isCustomInsert(sm, store);
if (!Boolean.FALSE.equals(custom))
customs.add(new CustomMapping(CustomMapping.INSERT, sm, strat));
return Boolean.TRUE.equals(custom);
}
/**
* Recursive method to delete the given instance, base class last.
*/
protected void delete(OpenJPAStateManager sm, ClassMapping mapping,
RowManager rowMgr, JDBCStore store, Collection customs)
throws SQLException {
Boolean custom = mapping.isCustomDelete(sm, store);
if (!Boolean.FALSE.equals(custom))
mapping.customDelete(sm, store);
if (Boolean.TRUE.equals(custom))
return;
FieldMapping[] fields = mapping.getDefinedFieldMappings();
for (FieldMapping field : fields)
if (!bufferCustomDelete(field, sm, store, customs))
field.delete(sm, store, rowMgr);
ClassMapping sup = mapping.getJoinablePCSuperclassMapping();
if (sup == null) {
Version vers = mapping.getVersion();
if (!bufferCustomDelete(vers, sm, store, customs))
vers.delete(sm, store, rowMgr);
Discriminator dsc = mapping.getDiscriminator();
if (!bufferCustomDelete(dsc, sm, store, customs))
dsc.delete(sm, store, rowMgr);
}
mapping.delete(sm, store, rowMgr);
if (sup != null)
delete(sm, sup, rowMgr, store, customs);
}
/**
* @see #bufferCustomInsert
*/
private boolean bufferCustomDelete(Strategy strat, OpenJPAStateManager sm,
JDBCStore store, Collection customs) {
Boolean custom = strat.isCustomDelete(sm, store);
if (!Boolean.FALSE.equals(custom))
customs.add(new CustomMapping(CustomMapping.DELETE, sm, strat));
return Boolean.TRUE.equals(custom);
}
/**
* Recursive method to update the given instance.
*/
protected void update(OpenJPAStateManager sm, BitSet dirty,
ClassMapping mapping, RowManager rowMgr, JDBCStore store,
Collection customs, boolean updateIndicators) throws SQLException {
Boolean custom = mapping.isCustomUpdate(sm, store);
if (!Boolean.FALSE.equals(custom))
mapping.customUpdate(sm, store);
if (Boolean.TRUE.equals(custom))
return;
// update all fields before all mappings so that the mappings can
// detect whether any fields in their rows have been modified
FieldMapping[] fields = mapping.getDefinedFieldMappings();
for (FieldMapping field : fields) {
if (dirty.get(field.getIndex())
&& !bufferCustomUpdate(field, sm, store, customs)) {
field.update(sm, store, rowMgr);
if (!updateIndicators) {
FieldMapping[] inverseFieldMappings =
field.getInverseMappings();
if (inverseFieldMappings.length == 0) {
updateIndicators = true;
}
else {
for (FieldMapping inverseFieldMapping :
inverseFieldMappings) {
if (inverseFieldMapping.getMappedBy() != null) {
updateIndicators = true;
break;
}
}
}
}
}
}
ClassMapping sup = mapping.getJoinablePCSuperclassMapping();
if (sup == null) {
if (updateIndicators) {
updateIndicators(sm, mapping, rowMgr, store, customs, false);
}
}
else
update(sm, dirty, sup, rowMgr, store, customs, updateIndicators);
mapping.update(sm, store, rowMgr);
}
/**
* Update version and discriminator indicators.
*/
protected void updateIndicators(OpenJPAStateManager sm,
ClassMapping mapping, RowManager rowMgr, JDBCStore store,
Collection customs,
boolean versionUpdateOnly) throws SQLException {
while (mapping.getJoinablePCSuperclassMapping() != null)
mapping = mapping.getJoinablePCSuperclassMapping();
Version vers = mapping.getVersion();
if (!bufferCustomUpdate(vers, sm, store, customs))
vers.update(sm, store, rowMgr);
if (versionUpdateOnly) {
// if we are only updating the version column, we need to add
// in the primary key select
mapping.update(sm, store, rowMgr);
} else {
// otherwise we need to make sure we update the discriminator too
Discriminator dsc = mapping.getDiscriminator();
if (!bufferCustomUpdate(dsc, sm, store, customs))
dsc.update(sm, store, rowMgr);
}
}
/**
* @see #bufferCustomInsert
*/
private boolean bufferCustomUpdate(Strategy strat, OpenJPAStateManager sm,
JDBCStore store, Collection customs) {
Boolean custom = strat.isCustomUpdate(sm, store);
if (!Boolean.FALSE.equals(custom))
customs.add(new CustomMapping(CustomMapping.UPDATE, sm, strat));
return Boolean.TRUE.equals(custom);
}
/**
* Executes customized mapping updates.
*/
protected static class CustomMapping {
public static final int INSERT = 0;
public static final int UPDATE = 1;
public static final int DELETE = 3;
private final int _action;
private final OpenJPAStateManager _sm;
private final Strategy _strat;
public CustomMapping(int action, OpenJPAStateManager sm, Strategy strat)
{
_action = action;
_sm = sm;
_strat = strat;
}
public void execute(JDBCStore store) throws SQLException {
switch (_action) {
case INSERT:
_strat.customInsert(_sm, store);
break;
case UPDATE:
_strat.customUpdate(_sm, store);
break;
case DELETE:
_strat.customDelete(_sm, store);
break;
}
}
}
}