/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * 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 net.ontopia.persistence.proxy; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import net.ontopia.utils.OntopiaRuntimeException; /** * INTERNAL: An abstract PersistentIF implementation that handles most * of the machinery needed to implement persistent objects. Note that * other persistent classes should extend this class. */ public abstract class AbstractRWPersistent implements PersistentIF { protected IdentityIF id; protected TransactionIF txn; public AbstractRWPersistent() { } public AbstractRWPersistent(TransactionIF txn) { _p_setTransaction(txn); } // ----------------------------------------------------------------------------- // PersistentIF implementation // ----------------------------------------------------------------------------- public IdentityIF _p_getIdentity() { return id; } public Class<?> _p_getType() { return getClass(); } public void _p_setIdentity(IdentityIF identity) { this.id = identity; } public TransactionIF _p_getTransaction() { return txn; } public void _p_setTransaction(TransactionIF txn) { if (this.txn != null) throw new OntopiaRuntimeException("Cannot change the transaction of a persistent object."); this.txn = txn; } public abstract void syncAfterMerge(IdentityIF source, IdentityIF target); protected void syncFieldsAfterMerge(IdentityIF source, IdentityIF target, int... fields) { for (int i = 0; i < fields.length; i++) { int field = fields[i]; // only process loaded fields if (isLoaded(field)) { Object o = getValue(field); if (o instanceof AbstractRWPersistent) { // only process objects of same type if (((Class<?>)source.getType()).isAssignableFrom(o.getClass())) { ((AbstractRWPersistent) o).syncAfterMerge(source, target); } } if (o instanceof TrackableSet) { TrackableSet ts = (TrackableSet) o; if (ts.getAdded() != null) { for (Object sub : ts.getAdded()) { if (sub instanceof AbstractRWPersistent) { if (((Class<?>)source.getType()).isAssignableFrom(sub.getClass())) { ((AbstractRWPersistent) sub).syncAfterMerge(source, target); } } } } if (ts.getRemoved() != null) { for (Object sub : ts.getRemoved()) { if (sub instanceof AbstractRWPersistent) { if (((Class<?>)source.getType()).isAssignableFrom(sub.getClass())) { ((AbstractRWPersistent) sub).syncAfterMerge(source, target); } } } } } } } if (_p_getIdentity().equals(source)) { _p_setIdentity(target); } } // ----------------------------------------------------------------------------- // PersistentIF machinery // ----------------------------------------------------------------------------- /** * INTERNAL: Called when the instance requests the initialization of * the specified field value. This call will lead to the value being * retrieved from the data repository if the instance is managed by * a transaction, otherwise a default value will be set. * * @return FIXME: only loaded values will be returned. if the * default value is being set, the return value is null. */ protected <F> F loadField(int field) { // load field from storage if (isLoaded(field)) { return this.<F>getValue(field); } else if (isNewObject() || !isInDatabase()) { return null; } else { // get identity IdentityIF identity = _p_getIdentity(); if (identity == null) return null; // load from storage F value = null; try { value = txn.<F>loadField(identity, field); } catch (IdentityNotFoundException e) { // let value be null } // set value and mark field as loaded setValue(field, value); return value; } } // NOTE: method will throw IdentityNotFoundException if value object // not found protected <F> F loadFieldNoCheck(int field) throws IdentityNotFoundException { // load field from storage if (isLoaded(field)) { return this.<F>getValue(field); } else if (isNewObject() || !isInDatabase()) { return null; } else { // get identity IdentityIF identity = _p_getIdentity(); if (identity == null) return null; // load from storage F value = txn.<F>loadField(identity, field); // set value and mark field as loaded setValue(field, value); return value; } } protected <F> Collection<F> loadCollectionField(int field) { // load field from storage if (isLoaded(field)) { Collection<F> o = this.<Collection<F>>getValue(field); return (o == null ? Collections.<F>emptySet() : o); } else if (isNewObject() || !isInDatabase()) { return Collections.EMPTY_SET; } else { // get identity IdentityIF identity = _p_getIdentity(); if (identity == null) return Collections.EMPTY_SET; // load from storage Collection<F> coll = null; try { coll = txn.<Collection<F>>loadField(identity, field); } catch (IdentityNotFoundException e) { // let coll be null } if (coll == null) { setValue(field, coll); return Collections.EMPTY_SET; } else { // set value and mark field as loaded coll = new TrackableSet<F>(txn, coll); setValue(field, coll); return coll; } } } public abstract void detach(); protected void detachField(int field) { if (!isLoaded(field)) loadField(field); } protected void detachCollectionField(int field) { if (!isLoaded(field)) loadCollectionField(field); } /** * INTERNAL: Called when a field value has been changed. The * managing transaction will be notified. */ protected void valueChanged(int field, Object value, boolean dchange) { // initialize state if (pstate == STATE_HOLLOW) { IdentityIF identity = _p_getIdentity(); if (identity != null) txn._getObject(identity); } //! System.out.println(">> " + field + " " + _p_getIdentity() + " " + value); setValue(field, value); // set new value / replace value setDirty(field, true); if (isPersistent()) txn.objectDirty(this); // if value is ContentReader then flush transaction immediately if (value instanceof OnDemandValue) txn.flush(); } protected void valueAdded(int field, Object value, boolean dchange) { TrackableCollectionIF coll; if (isLoaded(field)) { coll = (TrackableCollectionIF)getValue(field); if (coll == null) { coll = new TrackableSet(txn, null); // null == empty set setValue(field, coll); } } else if (isNewObject() || !isInDatabase()) { coll = new TrackableSet(txn, null); // null == empty set setValue(field, coll); } else { if (dchange) { Collection _coll = null; try { _coll = (Collection)txn.loadField(_p_getIdentity(), field); } catch (IdentityNotFoundException e) { // let coll be null } coll = new TrackableSet(txn, _coll); } else { coll = new TrackableLazySet(txn, _p_getIdentity(), field); } setValue(field, coll); } if (coll.addWithTracking(value)) { //! System.out.println(">>+ " + field + " " + _p_getIdentity() + " " + value); setDirty(field, true); if (isPersistent()) txn.objectDirty(this); } } protected void valueRemoved(int field, Object value, boolean dchange) { TrackableCollectionIF coll = null; if (isLoaded(field)) { coll = (TrackableCollectionIF)getValue(field); } else if (isInDatabase()) { if (dchange) { Collection _coll = null; try { _coll = (Collection)txn.loadField(_p_getIdentity(), field); } catch (IdentityNotFoundException e) { // let coll be null } coll = new TrackableSet(txn, _coll); } else { coll = new TrackableLazySet(txn, _p_getIdentity(), field); } setValue(field, coll); } if (coll != null && coll.removeWithTracking(value)) { //! System.out.println(">>- " + field + " " + _p_getIdentity() + " " + value); setDirty(field, true); if (isPersistent()) txn.objectDirty(this); } } // ----------------------------------------------------------------------------- // Queries // ----------------------------------------------------------------------------- protected Object executeQuery(String name, Object[] params) { return txn.executeQuery(name, params); } // ----------------------------------------------------------------------------- // Object data and metadata // ----------------------------------------------------------------------------- protected static final int[] MASKS; static { int[] masks = new int[32]; for (int i=0; i < 32; i++) { masks[i] = (int)Math.pow(2, i); } MASKS = masks; } private static final byte STATE_NEW = 1; // object exists in database private static final byte STATE_IN_DATABASE = 2; // object exists in database private static final byte STATE_PERSISTENT = 4; // object persistent/created in transaction private static final byte STATE_DELETED = 8; // object deleted in transaction private static final byte STATE_HOLLOW = 16; // object retrieved in previous transaction(s) // bit-masks private int lflags; // is field specified (loaded) private int dflags; // is field dirty (not flushed) private int fflags; // is field dirty (flushed) private byte pstate; // new OR persistent/deleted OR in-database public Object[] values = new Object[_p_getFieldCount()]; // field values // -- persistent state public boolean isTransient() { return !(isPersistent() || isDeleted()); } public boolean isNewObject() { return ((pstate & STATE_NEW) == STATE_NEW); } public void setNewObject(boolean newObject) { if (newObject) { pstate = STATE_NEW; // new (note == NEW) } else { pstate &= ~(STATE_NEW); // -new } } public boolean isInDatabase() { return ((pstate & STATE_IN_DATABASE) == STATE_IN_DATABASE); } public void setInDatabase(boolean inDatabase) { if (inDatabase) { pstate |= STATE_IN_DATABASE; // +new } else { pstate &= ~(STATE_IN_DATABASE); // -new } } public boolean isPersistent() { return ((pstate & STATE_PERSISTENT) == STATE_PERSISTENT); } public void setPersistent(boolean persistent) { if (persistent) { pstate |= STATE_PERSISTENT; // +persistent pstate &= ~(STATE_DELETED); // -deleted } else { pstate &= ~(STATE_PERSISTENT); // -persistent } } public boolean isDeleted() { return ((pstate & STATE_DELETED) == STATE_DELETED); } public void setDeleted(boolean deleted) { if (deleted) { pstate |= STATE_DELETED; // +deleted pstate &= ~(STATE_PERSISTENT); //a -persistent } else { pstate &= ~(STATE_DELETED); // -deleted } } // -- loaded public boolean isLoaded(int field) { // initialize state if (pstate == STATE_HOLLOW) { IdentityIF identity = _p_getIdentity(); if (identity != null) txn._getObject(identity); } return ((lflags & MASKS[field]) == MASKS[field]); } // -- values public Object loadValue(FieldInfoIF finfo) { if (finfo.isCollectionField()) return loadCollectionField(finfo.getIndex()); else return loadField(finfo.getIndex()); } protected <F> F getValue(int field) { return (F) values[field]; } protected void setValue(int field, Object value) { lflags |= MASKS[field]; // set flag values[field] = value; } protected void unsetValue(int field, Object value) { lflags &= ~(MASKS[field]); // unset flags dflags &= ~(MASKS[field]); fflags &= ~(MASKS[field]); values[field] = null; } // -- dirty (unflushed) public boolean isDirty() { return (dflags != 0); } public boolean isDirty(int field) { return ((dflags & MASKS[field]) == MASKS[field]); } public int nextDirty(int start) { for (int i=start; i < values.length; i++) { if ((dflags & MASKS[i]) == MASKS[i]) return i; } return -1; } public int nextDirty(int start, int end) { for (int i=start; i < end; i++) { if ((dflags & MASKS[i]) == MASKS[i]) return i; } return -1; } public void setDirty(int field, boolean dirty) { if (dirty) dflags |= MASKS[field]; // set flag else dflags &= ~(MASKS[field]); // unset flag } // -- dirty (flushed) public boolean isDirtyFlushed() { return (fflags != 0); } public boolean isDirtyFlushed(int field) { return ((fflags & MASKS[field]) == MASKS[field]); } public int nextDirtyFlushed(int start) { for (int i=start; i < values.length; i++) { if ((fflags & MASKS[i]) == MASKS[i]) return i; } return -1; } public int nextDirtyFlushed(int start, int end) { for (int i=start; i < end; i++) { if ((fflags & MASKS[i]) == MASKS[i]) return i; } return -1; } public void setDirtyFlushed(int field, boolean dirty) { if (values[field] instanceof OnDemandValue) { OnDemandValue odv = (OnDemandValue)values[field]; IdentityIF identity = _p_getIdentity(); RDBMSMapping mapping = txn.getStorageAccess().getStorage().getMapping(); ClassInfoIF cinfo = mapping.getClassInfo(identity.getType()); FieldInfoIF finfo = cinfo.getValueFieldInfos()[field]; odv.setContext(identity, finfo); } if (dirty) { fflags |= MASKS[field]; // set flag dflags &= ~(MASKS[field]); // unset flag } else { fflags &= ~(MASKS[field]); // unset flag } } // -- misc public void clearAll() { // reset flags lflags = 0; dflags = 0; fflags = 0; // reset state pstate = STATE_HOLLOW; // hollow / transient // clear field values for (int i=0; i < values.length; i++) { values[i] = null; } } public String _p_toString() { return states() + "\n l:" + list(lflags) + "\n d:" + list(dflags) + "\n f:" + list(fflags) + "\n " + (values == null ? null : Arrays.asList(values).toString()); } private String states() { return isNewObject() + "-" + isPersistent() + "-" + isDeleted() + "-" + isInDatabase(); } private String list(int bitmask) { StringBuilder sb = new StringBuilder(); for (int i=0; i < values.length; i++) { if (i > 0) sb.append(", "); sb.append((bitmask & MASKS[i]) == MASKS[i]); } return sb.toString(); } }