001: /**********************************************************************
002: Copyright (c) 2002 Kelly Grizzle (TJDO) and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015:
016: Contributors:
017: 2003 Andy Jefferson - coding standards
018: 2005 Andy Jefferson - basic serialisation support
019: 2005 Andy Jefferson - updated serialisation using SCOUtils methods
020: 2005 Andy Jefferson - changed to allow the store to be set or list
021: ...
022: **********************************************************************/package org.jpox.store.mapping;
023:
024: import java.util.Collection;
025:
026: import org.jpox.StateManager;
027: import org.jpox.exceptions.JPOXException;
028: import org.jpox.exceptions.JPOXUserException;
029: import org.jpox.metadata.AbstractMemberMetaData;
030: import org.jpox.sco.SCO;
031: import org.jpox.sco.SCOCollection;
032: import org.jpox.sco.SCOContainer;
033: import org.jpox.sco.SCOUtils;
034: import org.jpox.store.exceptions.ReachableObjectNotCascadedException;
035: import org.jpox.store.expression.CollectionExpression;
036: import org.jpox.store.expression.CollectionLiteral;
037: import org.jpox.store.expression.CollectionSubqueryExpression;
038: import org.jpox.store.expression.LogicSetExpression;
039: import org.jpox.store.expression.QueryExpression;
040: import org.jpox.store.expression.ScalarExpression;
041: import org.jpox.store.query.Queryable;
042: import org.jpox.store.scostore.CollectionStore;
043: import org.jpox.util.JPOXLogger;
044:
045: /**
046: * Mapping for Collection (Set/List) types.
047: *
048: * @version $Revision: 1.67 $
049: **/
050: public class CollectionMapping extends AbstractContainerMapping
051: implements MappingCallbacks {
052: /**
053: * Equality operator.
054: * @param obj Object to compare against
055: * @return Whether they are equal
056: */
057: public boolean equals(Object obj) {
058: if (obj == this ) {
059: return true;
060: }
061:
062: if (!obj.getClass().equals(getClass())) {
063: return false;
064: }
065:
066: CollectionMapping sm = (CollectionMapping) obj;
067:
068: return fmd.equals(sm.fmd) && storeMgr.equals(sm.storeMgr);
069: }
070:
071: /**
072: * Accessor for the Java type represented here.
073: * @return The java type
074: */
075: public Class getJavaType() {
076: return Collection.class;
077: }
078:
079: // --------------- Implementation of MappingCallbacks -------------------
080:
081: /**
082: * Method to be called after the insert of the owner class element.
083: * @param sm StateManager of the owner
084: */
085: public void postInsert(StateManager sm) {
086: Collection value = (Collection) sm.provideField(fmd
087: .getAbsoluteFieldNumber());
088: if (containerIsStoredInSingleColumn()) {
089: // Make sure the elements are ok for proceeding
090: SCOUtils.validateObjectsForWriting(sm.getObjectManager(),
091: value);
092: return;
093: }
094:
095: if (value == null) {
096: // replace null collections with an empty SCO wrapper
097: replaceFieldWithWrapper(sm, null, false, false);
098: return;
099: }
100:
101: Object[] collElements = value.toArray();
102: if (!fmd.isCascadePersist()) {
103: // Field doesnt support cascade-persist so no reachability
104: if (JPOXLogger.REACHABILITY.isDebugEnabled()) {
105: JPOXLogger.REACHABILITY.debug(LOCALISER.msg("007006",
106: fmd.getFullFieldName()));
107: }
108:
109: // Check for any persistable elements that arent persistent
110: for (int i = 0; i < collElements.length; i++) {
111: if (!sm.getObjectManager().getApiAdapter().isDetached(
112: collElements[i])
113: && !sm.getObjectManager().getApiAdapter()
114: .isPersistent(collElements[i])) {
115: // Element is not persistent so throw exception
116: throw new ReachableObjectNotCascadedException(fmd
117: .getFullFieldName(), collElements[i]);
118: }
119: }
120: } else {
121: // Reachability
122: if (JPOXLogger.REACHABILITY.isDebugEnabled()) {
123: JPOXLogger.REACHABILITY.debug(LOCALISER.msg("007007",
124: fmd.getFullFieldName()));
125: }
126:
127: // Check if some elements need attaching
128: // TODO Investigate if we can just use the attachCopy route below and skip off this check
129: boolean needsAttaching = false;
130: for (int i = 0; i < collElements.length; i++) {
131: if (sm.getObjectManager().getApiAdapter().isDetached(
132: collElements[i])) {
133: needsAttaching = true;
134: break;
135: }
136: }
137:
138: if (needsAttaching) {
139: // Create a wrapper and attach the elements (and add the others)
140: SCO collWrapper = replaceFieldWithWrapper(sm, null,
141: false, false);
142: collWrapper.attachCopy(value);
143: } else {
144: if (value.size() > 0) {
145: // Add the elements direct to the datastore
146: ((CollectionStore) storeMgr.getStore(sm
147: .getObjectManager()
148: .getClassLoaderResolver(), fmd, value
149: .getClass())).addAll(sm, value, 0);
150:
151: // Create a SCO wrapper with the elements loaded
152: replaceFieldWithWrapper(sm, value, false, false);
153: } else {
154: // Create a SCO wrapper
155: replaceFieldWithWrapper(sm, null, false, false);
156: }
157: }
158: }
159: }
160:
161: /**
162: * Method to be called after any update of the owner class element.
163: * @param sm StateManager of the owner
164: */
165: public void postUpdate(StateManager sm) {
166: Collection value = (Collection) sm.provideField(fmd
167: .getAbsoluteFieldNumber());
168: if (containerIsStoredInSingleColumn()) {
169: // Make sure the elements are ok for proceeding
170: SCOUtils.validateObjectsForWriting(sm.getObjectManager(),
171: value);
172: return;
173: }
174:
175: if (value == null) {
176: // remove any elements in the collection and replace it with an empty SCO wrapper
177: ((CollectionStore) storeMgr.getStore(sm.getObjectManager()
178: .getClassLoaderResolver(), fmd, null)).clear(sm);
179:
180: replaceFieldWithWrapper(sm, null, false, false);
181: return;
182: }
183:
184: if (value instanceof SCOContainer) {
185: // Already have a SCO value
186: SCOContainer sco = (SCOContainer) value;
187: if (sm.getObject() == sco.getOwner()
188: && fieldName.equals(sco.getFieldName())) {
189: // Flush any outstanding updates
190: sco.flush();
191:
192: return;
193: }
194:
195: if (sco.getOwner() != null) {
196: throw new JPOXException(LOCALISER
197: .msg("CollectionMapping.WrongOwnerError"))
198: .setFatal();
199: }
200: }
201:
202: if (!fmd.isCascadeUpdate()) {
203: // TODO Should this throw exception if the element doesnt exist?
204: // User doesnt want to update by reachability
205: if (JPOXLogger.REACHABILITY.isDebugEnabled()) {
206: JPOXLogger.REACHABILITY.debug(LOCALISER.msg("007008",
207: fmd.getFullFieldName()));
208: }
209: return;
210: }
211: if (JPOXLogger.REACHABILITY.isDebugEnabled()) {
212: JPOXLogger.REACHABILITY.debug(LOCALISER.msg("007009", fmd
213: .getFullFieldName()));
214: }
215:
216: // Update the datastore with this value of collection (clear old elements and add new ones)
217: // TODO Consider making this more efficient picking the ones to remove/add
218: CollectionStore backingStore = ((CollectionStore) storeMgr
219: .getStore(sm.getObjectManager()
220: .getClassLoaderResolver(), fmd, value
221: .getClass()));
222:
223: backingStore.clear(sm);
224: backingStore.addAll(sm, value, 0);
225:
226: // Replace the field with a wrapper containing these elements
227: replaceFieldWithWrapper(sm, value, false, false);
228: }
229:
230: /**
231: * Method to be called before any delete of the owner class element.
232: * @param sm StateManager of the owner
233: */
234: public void preDelete(StateManager sm) {
235: if (containerIsStoredInSingleColumn()) {
236: // Field is stored with the main object so nothing to clean up
237: return;
238: }
239:
240: // makes sure field is loaded
241: sm.getObjectManager().getApiAdapter().isLoaded(sm,
242: fmd.getAbsoluteFieldNumber());
243: Collection value = (Collection) sm.provideField(fmd
244: .getAbsoluteFieldNumber());
245: if (value == null) {
246: return;
247: }
248:
249: boolean isDependentElement = fmd.getCollection()
250: .isDependentElement();
251: boolean hasJoin = (fmd.getJoinMetaData() != null);
252: boolean hasFK = false;
253: if (!hasJoin) {
254: if (fmd.getElementMetaData() != null
255: && fmd.getElementMetaData().getForeignKeyMetaData() != null) {
256: // FK collection, using <element> FK spec
257: hasFK = true;
258: } else if (fmd.getForeignKeyMetaData() != null) {
259: // FK collection, using <field> FK spec
260: hasFK = true;
261: }
262: AbstractMemberMetaData[] relatedMmds = fmd
263: .getRelatedMemberMetaData(sm.getObjectManager()
264: .getClassLoaderResolver());
265: if (relatedMmds != null
266: && relatedMmds[0].getForeignKeyMetaData() != null) {
267: // FK collection (bidir), using <field> FK spec at other end
268: hasFK = true;
269: }
270: }
271: if (sm.getObjectManager().getOMFContext()
272: .getPersistenceConfiguration().getDeletionPolicy()
273: .equals("JDO2")) {
274: // JDO2 doesnt currently (2.0 spec) take note of foreign-key
275: hasFK = false;
276: }
277:
278: // TODO Why dont we just do clear here always ? THe backing store should take care of if nulling or deleting etc
279: if (isDependentElement || hasJoin || !hasFK) {
280: // Elements are either dependent (in which case we need to delete them) or theres a join (in which case
281: // we need to remove the join entries), or there are no FKs specified (in which case we need to clean up)
282: if (!(value instanceof SCO)) {
283: value = (Collection) sm.wrapSCOField(fmd
284: .getAbsoluteFieldNumber(), value, false, false,
285: true);
286: }
287: value.clear();
288: ((SCOCollection) value).flush();
289: }
290: }
291:
292: // ------------------------------- JDOQL Query Methods --------------------------------------
293:
294: /**
295: * Accessor for a literal representing this type.
296: * @param qs The Query
297: * @param value the value of this object in the literal
298: * @return The literal
299: */
300: public ScalarExpression newLiteral(QueryExpression qs, Object value) {
301: if (containerIsStoredInSingleColumn()) {
302: throw new JPOXUserException(LOCALISER.msg("041025", fmd
303: .getFullFieldName())).setFatal();
304: }
305: if (value instanceof Queryable) {
306: return new CollectionSubqueryExpression(qs,
307: ((Queryable) value).newQueryStatement());
308: } else {
309: return new CollectionLiteral(qs, this , (Collection) value);
310: }
311: }
312:
313: /**
314: * Accessor for a scalar expression involving this object.
315: * @param qs The Query
316: * @param te The table holding this object.
317: * @return The expression
318: */
319: public ScalarExpression newScalarExpression(QueryExpression qs,
320: LogicSetExpression te) {
321: if (containerIsStoredInSingleColumn()) {
322: throw new JPOXUserException(LOCALISER.msg("041025", fmd
323: .getFullFieldName())).setFatal();
324: }
325: return new CollectionExpression(qs, datastoreContainer
326: .getIDMapping(), te, ((CollectionStore) storeMgr
327: .getStore(qs.getClassLoaderResolver(), fmd, null)),
328: fieldName);
329: }
330: }
|