/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* UpdateObjectDescImpl.java
*
* Created on March 3, 2000
*
*/
package com.sun.jdo.spi.persistence.support.sqlstore.sql;
import com.sun.jdo.api.persistence.support.JDOFatalInternalException;
import com.sun.jdo.spi.persistence.support.sqlstore.*;
import com.sun.jdo.spi.persistence.support.sqlstore.model.ClassDesc;
import com.sun.jdo.spi.persistence.support.sqlstore.model.FieldDesc;
import com.sun.jdo.spi.persistence.support.sqlstore.model.ForeignFieldDesc;
import com.sun.jdo.spi.persistence.support.sqlstore.model.LocalFieldDesc;
import com.sun.jdo.spi.persistence.support.sqlstore.sql.concurrency.Concurrency;
import com.sun.jdo.spi.persistence.utility.I18NHelper;
import com.sun.jdo.spi.persistence.utility.logging.Logger;
import java.util.*;
/**
* Stores the update information for the associated state manager.
*/
public class UpdateObjectDescImpl implements UpdateObjectDesc {
/** Array of Object. */
private List afterHiddenValues;
private SQLStateManager afterImage;
/** Array of Object. */
private List beforeHiddenValues;
private SQLStateManager beforeImage;
private Concurrency concurrency;
private Class pcClass;
private int updateAction;
/**
* Array of LocalFieldDesc.
* Fields contained in this array are written to the database.
*/
private List updatedFields;
private Map updatedJoinTableRelationships;
/** Marker for fast relationship update check. */
private boolean relationshipChanged = false;
/** The logger. */
private static Logger logger = LogHelperSQLStore.getLogger();
/** I18N message handler. */
private final static ResourceBundle messages = I18NHelper.loadBundle(
"com.sun.jdo.spi.persistence.support.sqlstore.Bundle", // NOI18N
UpdateObjectDescImpl.class.getClassLoader());
public UpdateObjectDescImpl(Class pcClass) {
this.pcClass = pcClass;
updatedFields = new ArrayList();
}
public Class getPersistenceCapableClass() {
return pcClass;
}
public void reset() {
updatedFields.clear();
if (updatedJoinTableRelationships != null) {
updatedJoinTableRelationships.clear();
}
relationshipChanged = false;
concurrency = null;
}
public boolean hasUpdatedFields() {
return (updatedFields.size() > 0);
}
public Collection getUpdatedJoinTableFields() {
if (updatedJoinTableRelationships == null) {
return null;
}
return updatedJoinTableRelationships.keySet();
}
// RESOLVE: Should return _all_ join table descs, not separatly by field.
public Collection getUpdateJoinTableDescs(FieldDesc fieldDesc) {
HashMap updateJoinTableDescs = (HashMap) updatedJoinTableRelationships.get(fieldDesc);
if (updateJoinTableDescs != null) {
return updateJoinTableDescs.values();
}
return null;
}
public boolean hasUpdatedJoinTableRelationships() {
return (updatedJoinTableRelationships != null &&
updatedJoinTableRelationships.size() > 0);
}
/**
* Returns <code>true</code> if any of the changed fields is byte[].
*/
public boolean hasModifiedLobField() {
if (updatedFields != null) {
for (Iterator i = updatedFields.iterator(); i.hasNext(); ) {
// The list updatedFields only contains LocalFieldDesc.
// Thus it's safe to cast to LocalFieldDesc below.
LocalFieldDesc field = (LocalFieldDesc)i.next();
if (field.isMappedToLob()) {
return true;
}
}
}
return false;
}
/**
* Marks the relationship change property for this instance, if the
* updated field is a relationship field or a hidden field tracing a
* foreign key column in the database.
*
* @param fieldDesc Updated field.
*/
public void markRelationshipChange(FieldDesc fieldDesc) {
if (fieldDesc.isRelationshipField() || fieldDesc.isForeignKeyField()) {
if (logger.isLoggable(Logger.FINEST)) {
logger.finest("sqlstore.sql.updateobjdescimpl.markrelationshipchange"); // NOI18N
}
// MARK THE RELATIONSHIP CHANGE for this instance.
relationshipChanged = true;
}
}
/**
* Returns <code>true</code>, if this state manager has a changed
* relationship field.
* @return True, if this state manager has a changed relationship field.
*/
public boolean hasChangedRelationships() {
// If the relationship is set before the makePersistent call,
// this condition might be false for INSERTs.
if (relationshipChanged) {
return true;
}
// Check for updated join table relationships.
if (hasUpdatedJoinTableRelationships()) {
return true;
}
// Check for updated foreign key relationships.
if (updatedFields != null) {
for (Iterator iter = updatedFields.iterator(); iter.hasNext(); ) {
LocalFieldDesc field = (LocalFieldDesc) iter.next();
if (field.isForeignKeyField()) {
return true;
}
}
}
return false;
}
/**
* Removes a previously scheduled jointable entry for relationship
* field <code>fieldDesc</code>. The <code>action</code>
* parameter specifies, if the entry to be removed is
* scheduled for creation or removal.
*
* @param fieldDesc Updated relationship field.
* @param foreignSM Associated state manager on the opposite side.
* @param action The action is either CREATE or REMOVE.
* @return True, if the specified jointable entry was found and removed, false otherwise.
* @see #recordUpdatedJoinTableRelationship
*/
public boolean removeUpdatedJoinTableRelationship(ForeignFieldDesc fieldDesc,
SQLStateManager foreignSM,
int action) {
HashMap updateJoinTableDescs = null;
if ((updatedJoinTableRelationships == null) ||
((updateJoinTableDescs = (HashMap) updatedJoinTableRelationships.get(fieldDesc)) == null)) {
return false;
}
UpdateJoinTableDesc desc = (UpdateJoinTableDesc) updateJoinTableDescs.get(foreignSM);
if (desc != null && desc.getAction() == action) {
return (updateJoinTableDescs.remove(foreignSM) != null);
}
return false;
}
/**
* Schedules a jointable entry for relationship field
* <code>fieldDesc</code>. The scheduled jointable entry is
* uniquely identified by the relationship field and the two
* associated state managers. The <code>action</code> parameter
* specifies, if the jointable entry should be created or removed.
*
* @param fieldDesc Updated relationship field.
* @param parentSM State manager responsible for <code>fieldDesc</code>'s defining class.
* @param foreignSM State manager responsible for the other side.
* @param action The action is either CREATE or REMOVE.
* @see #removeUpdatedJoinTableRelationship
*/
public void recordUpdatedJoinTableRelationship(ForeignFieldDesc fieldDesc,
SQLStateManager parentSM,
SQLStateManager foreignSM,
int action) {
if (updatedJoinTableRelationships == null) {
updatedJoinTableRelationships = new HashMap();
}
HashMap updateJoinTableDescs = null;
if ((updateJoinTableDescs = (HashMap) updatedJoinTableRelationships.get(fieldDesc)) == null) {
updateJoinTableDescs = new HashMap();
updatedJoinTableRelationships.put(fieldDesc, updateJoinTableDescs);
}
UpdateJoinTableDesc desc = null;
if ((desc = (UpdateJoinTableDesc) updateJoinTableDescs.get(foreignSM)) == null) {
desc = new UpdateJoinTableDesc(parentSM, foreignSM, action);
updateJoinTableDescs.put(foreignSM, desc);
}
}
public void clearUpdatedJoinTableRelationships() {
updatedJoinTableRelationships = null;
}
public void recordUpdatedField(LocalFieldDesc fieldDesc) {
if (!updatedFields.contains(fieldDesc))
updatedFields.add(fieldDesc);
}
public List getUpdatedFields() {
return updatedFields;
}
public Object getAfterValue(FieldDesc f) {
if (afterImage == null) {
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"sqlstore.sql.updateobjdescimpl.afterimagenull")); //NOI18N
}
if (f.absoluteID < 0) {
return afterHiddenValues.get(-(f.absoluteID + 1));
} else {
return f.getValue(afterImage);
}
}
public Object getBeforeValue(FieldDesc f) {
if (beforeImage == null) {
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"sqlstore.sql.updateobjdescimpl.beforeimagenull")); //NOI18N
}
if (f.absoluteID < 0) {
return beforeHiddenValues.get(-(f.absoluteID + 1));
} else {
return f.getValue(beforeImage);
}
}
public int getUpdateAction() {
return updateAction;
}
public ClassDesc getConfig() {
return (ClassDesc) afterImage.getPersistenceConfig();
}
public SQLStateManager getAfterImage() {
return afterImage;
}
public boolean isBeforeImageRequired() {
return afterImage.isBeforeImageRequired();
}
public Concurrency getConcurrency() {
return concurrency;
}
public void setConcurrency(Concurrency concurrency) {
this.concurrency = concurrency;
}
/**
* We send the AfterImage for updates and inserts
* but for updates it will only hold values for updated attributes (unless
* the class is configured to send the whole AfterImage, also we'll let the
* concurrency interface affect the sent AfterImage (and the sent
* BeforeImage)). For deletes the AfterImage will be NIL, for inserts the
* BeforeImage will be NIL. For deletes the BeforeImage will contain values
* for all key attributes. Also for deletes and updates we'll send the
* HiddenValues array from the paladin (although we can set to NIL any
* values in the array not needed by this particular update).
*
* UpdatedAttributes will contain indexes into the PersistentDesc.Attributes
* array for new or updated values.
*
* Initially we'll probably just send the whole BeforeImage and AfterImage
* (except that we won't have an AfterImage for Deletes and we won't have
* a BeforeImage for updates).
*/
public void setObjectInfo(StateManager biStateManager,
StateManager aiStateManager,
int action) {
this.beforeImage = (SQLStateManager) biStateManager;
this.afterImage = (SQLStateManager) aiStateManager;
ClassDesc config = (ClassDesc) afterImage.getPersistenceConfig();
updateAction = action;
this.afterHiddenValues = afterImage.hiddenValues;
if (beforeImage != null) {
this.beforeHiddenValues = beforeImage.hiddenValues;
}
// This pass through attributes we are only going to look at local attributes.
// These are attributes that are stored in this object and are not references
// to other persistent objects.
boolean debug = logger.isLoggable(Logger.FINER);
for (int i = 0; i < config.fields.size(); i++) {
FieldDesc f = (FieldDesc) config.fields.get(i);
LocalFieldDesc lf = null;
boolean updated = false;
if (f instanceof LocalFieldDesc) {
lf = (LocalFieldDesc) f;
} else {
continue;
}
if ((updateAction == LOG_DESTROY) ||
((lf.sqlProperties & FieldDesc.PROP_RECORD_ON_UPDATE) > 0)) {
continue;
} else if (lf.absoluteID < 0) {
if ((beforeImage == null) ||
(beforeImage.getHiddenValue(lf.absoluteID) !=
afterImage.getHiddenValue(lf.absoluteID))) {
updated = true;
}
} else if (lf.getType().isPrimitive() ||
String.class == lf.getType() ||
java.util.Date.class == lf.getType()) {
Object afterVal = lf.getValue(afterImage);
Object beforeVal = null;
if (beforeImage != null) {
beforeVal = lf.getValue(beforeImage);
}
if ((beforeVal != null) && (afterVal != null)) {
if (!beforeVal.equals(afterVal)) {
updated = true;
}
} else {
updated = true;
}
} else {
// What else??
}
if (updated) {
if (debug) {
logger.finer("sqlstore.sql.updateobjdescimpl.updated", f.getName()); // NOI18N
}
updatedFields.add(lf);
}
}
if (concurrency != null) {
concurrency.commit(this, beforeImage, afterImage, updateAction);
}
}
/**
* Triggers the version update if the associated state manager is
* registered for version consistency and database fields have been
* modified. The version is incremented, if
* <ul>
* <li>The associated instance is version consistent.</li>
* <li>The associated instance has updated database fields.</li>
* </ul>
* Note: The version is <b>not</b> incremented, if a relationship
* mapped to a join table was updated.
*/
public void incrementVersion() {
if (afterImage.hasVersionConsistency()
&& updateAction == ActionDesc.LOG_UPDATE
&& hasUpdatedFields()) {
afterImage.incrementVersion();
}
}
/**
* Marks the associated state manager as failed.
*/
public void setVerificationFailed() {
afterImage.setVerificationFailed();
}
public boolean hasVersionConsistency() {
return afterImage.hasVersionConsistency();
}
}