001: /**********************************************************************
002: Copyright (c) 2005 Andy Jefferson 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: ...
018: **********************************************************************/package org.jpox.store.mapping;
019:
020: import org.jpox.ClassLoaderResolver;
021: import org.jpox.ObjectManager;
022: import org.jpox.ObjectManagerHelper;
023: import org.jpox.StateManager;
024: import org.jpox.api.ApiAdapter;
025: import org.jpox.exceptions.JPOXException;
026: import org.jpox.exceptions.JPOXObjectNotFoundException;
027: import org.jpox.exceptions.JPOXUserException;
028: import org.jpox.identity.OID;
029: import org.jpox.identity.OIDFactory;
030: import org.jpox.metadata.AbstractMemberMetaData;
031: import org.jpox.metadata.MetaDataManager;
032: import org.jpox.metadata.Relation;
033: import org.jpox.store.exceptions.NotYetFlushedException;
034: import org.jpox.store.exceptions.NullValueException;
035: import org.jpox.util.JPOXLogger;
036:
037: /**
038: * Mapping to represent multiple mappings.
039: * This mapping can be used to represent, for example,
040: * <UL>
041: * <LI>a reference field (where there are multiple implementations)</LI>
042: * <LI>an element in a collection and the element has no table of its own, but multiple subclasses</LI>
043: * <LI>a FK of a PC field (where there may be multiple fields in the PK of the PC object)</LI>
044: * </UL>
045: * @version $Revision: 1.37 $
046: **/
047: public abstract class MultiMapping extends JavaTypeMapping {
048: /** The Java mappings represented by this mapping. */
049: protected JavaTypeMapping[] javaTypeMappings = new JavaTypeMapping[0];
050:
051: /** Number of DatastoreField - convenience field to improve performance **/
052: private int numberOfDatastoreFields = 0;
053:
054: /**
055: * Method to add a Java type mapping for a field
056: * @param mapping The mapping to add
057: */
058: public void addJavaTypeMapping(JavaTypeMapping mapping) {
059: JavaTypeMapping[] jtm = javaTypeMappings;
060: javaTypeMappings = new JavaTypeMapping[jtm.length + 1];
061: System.arraycopy(jtm, 0, javaTypeMappings, 0, jtm.length);
062: javaTypeMappings[jtm.length] = mapping;
063: }
064:
065: /**
066: * Accessor for the Java type mappings
067: * @return The Java type mappings
068: */
069: public JavaTypeMapping[] getJavaTypeMapping() {
070: return javaTypeMappings;
071: }
072:
073: /**
074: * Accessor for the number of datastore fields.
075: * @return The number of datastore fields.
076: */
077: public int getNumberOfDatastoreFields() {
078: if (numberOfDatastoreFields == 0) {
079: for (int i = 0; i < javaTypeMappings.length; i++) {
080: numberOfDatastoreFields += javaTypeMappings[i]
081: .getNumberOfDatastoreFields();
082: }
083: }
084: return numberOfDatastoreFields;
085: }
086:
087: /**
088: * Accessor for a datastore mapping.
089: * @param index The position of the mapping to return
090: * @return The datastore mapping
091: */
092: public DatastoreMapping getDataStoreMapping(int index) {
093: int currentIndex = 0;
094: int numberJavaMappings = javaTypeMappings.length;
095: for (int i = 0; i < numberJavaMappings; i++) {
096: int numberDatastoreMappings = javaTypeMappings[i]
097: .getNumberOfDatastoreFields();
098: for (int j = 0; j < numberDatastoreMappings; j++) {
099: if (currentIndex == index) {
100: return javaTypeMappings[i].getDataStoreMapping(j);
101: }
102: currentIndex++;
103: }
104: }
105:
106: // TODO Localise this message
107: throw new JPOXException("Invalid index " + index
108: + " for DataStoreMapping.").setFatal();
109: }
110:
111: /**
112: * Method to set the parameters in the PreparedStatement with the fields of
113: * this object.
114: * @param om Object Manager
115: * @param ps The PreparedStatement
116: * @param pos The parameter positions
117: * @param value The object to populate the statement with
118: * @throws NotYetFlushedException Thrown if the object is not yet flushed to the datastore
119: */
120: public void setObject(ObjectManager om, Object ps, int[] pos,
121: Object value) {
122: setObject(om, ps, pos, value, null, -1);
123: }
124:
125: /**
126: * Sets a <code>value</code> into <code>preparedStatement</code>
127: * at position specified by <code>exprIndex</code>.
128: * @param om the ObjectManager
129: * @param ps a datastore object that executes statements in the database
130: * @param pos the position of the value in the statement
131: * @param value the value
132: * @param ownerSM the owner StateManager
133: * @param ownerFieldNumber the owner absolute field number
134: */
135: public void setObject(ObjectManager om, Object ps, int[] pos,
136: Object value, StateManager ownerSM, int ownerFieldNumber) {
137: ClassLoaderResolver clr = om.getClassLoaderResolver();
138: ApiAdapter api = om.getApiAdapter();
139:
140: int n = 0;
141: boolean foundClassAssignableFromValue = false;
142: NotYetFlushedException notYetFlushed = null;
143:
144: if (value != null) {
145: if (!om.isInserting(value)) {
146: // Object either already exists, or is not yet being inserted.
147: Object id = api.getIdForObject(value);
148:
149: // Check if the PersistenceCapable exists in this datastore
150: boolean requiresPersisting = false;
151: if (om.getApiAdapter().isDetached(value)
152: && ownerSM != null) {
153: // Detached object that needs attaching (or persisting if detached from a different datastore)
154: requiresPersisting = true;
155: } else if (id == null) {
156: // Transient object, so we need to persist it
157: requiresPersisting = true;
158: } else {
159: ObjectManager valueOM = ObjectManagerHelper
160: .getObjectManager(value);
161: if (valueOM != null && om != valueOM) {
162: throw new JPOXUserException(LOCALISER
163: .msg("041015"), id);
164: }
165: }
166:
167: if (requiresPersisting) {
168: // The object is either not yet persistent or is detached and so needs attaching
169: Object pcNew = om.persistObjectInternal(value,
170: null, null, -1, StateManager.PC);
171: om.flushInternal(false);
172: id = api.getIdForObject(pcNew);
173: if (om.getApiAdapter().isDetached(value)
174: && ownerSM != null) {
175: // Update any detached reference to refer to the attached variant
176: ownerSM.replaceField(ownerFieldNumber, pcNew,
177: true);
178: int relationType = fmd.getRelationType(clr);
179: if (relationType == Relation.ONE_TO_ONE_BI) {
180: StateManager relatedSM = om
181: .findStateManager(pcNew);
182: AbstractMemberMetaData[] relatedMmds = fmd
183: .getRelatedMemberMetaData(clr);
184: // TODO Allow for multiple related fields
185: relatedSM.replaceField(relatedMmds[0]
186: .getAbsoluteFieldNumber(), ownerSM
187: .getObject(), true);
188: } else if (relationType == Relation.MANY_TO_ONE_BI) {
189: // TODO Update the container element with the attached variant
190: if (JPOXLogger.PERSISTENCE.isInfoEnabled()) {
191: JPOXLogger.PERSISTENCE
192: .info("PCMapping.setObject : object "
193: + ownerSM
194: .getInternalObjectId()
195: + " has field "
196: + ownerFieldNumber
197: + " that is 1-N bidirectional - should really update the reference in the relation. Not yet supported");
198: }
199: }
200: }
201: }
202: if (getNumberOfDatastoreFields() <= 0) {
203: // If the field doesnt map to any datastore fields, omit the set process
204: return;
205: }
206: }
207: }
208:
209: Class type = null;
210: StateManager sm = null;
211: if (value != null) {
212: sm = om.findStateManager(value);
213: }
214: try {
215: if (pos == null) {
216: return;
217: }
218: if (sm != null) {
219: sm.setStoringPC();
220: }
221:
222: MetaDataManager mmgr = om.getStoreManager()
223: .getMetaDataManager();
224: boolean isPersistentInterface = mmgr
225: .getMetaDataForInterface(clr
226: .classForName(getType()), clr) != null;
227: if (isPersistentInterface) {
228: // Field is declared as a "persistent-interface" type so all impls of that type should match
229: type = clr.classForName(getType());
230: } else if (fmd != null && fmd.getFieldTypes() != null
231: && fmd.getFieldTypes().length == 1) {
232: isPersistentInterface = mmgr.getMetaDataForInterface(
233: clr.classForName(fmd.getFieldTypes()[0]), clr) != null;
234: if (isPersistentInterface) {
235: // Field is declared as interface and accepts "persistent-interface" value, so all impls should match
236: type = clr.classForName(fmd.getFieldTypes()[0]);
237: }
238: }
239:
240: for (int i = 0; i < javaTypeMappings.length; i++) {
241: if (n >= pos.length) {
242: //this means we store all implementations to the same columns, so we reset the index
243: n = 0;
244: }
245: int[] posMapping;
246: if (javaTypeMappings[i].getReferenceMapping() != null) {
247: posMapping = new int[javaTypeMappings[i]
248: .getReferenceMapping()
249: .getNumberOfDatastoreFields()];
250: } else {
251: posMapping = new int[javaTypeMappings[i]
252: .getNumberOfDatastoreFields()];
253: }
254: for (int j = 0; j < posMapping.length; j++) {
255: posMapping[j] = pos[n++];
256: }
257: try {
258: if (!isPersistentInterface) {
259: // Normal interface
260: type = clr.classForName(javaTypeMappings[i]
261: .getType());
262: }
263: if (value != null
264: && type.isAssignableFrom(value.getClass())) {
265: foundClassAssignableFromValue = true;
266: javaTypeMappings[i].setObject(om, ps,
267: posMapping, value);
268: } else {
269: javaTypeMappings[i].setObject(om, ps,
270: posMapping, null);
271: }
272: } catch (NotYetFlushedException e) {
273: notYetFlushed = e;
274: }
275: }
276: if (notYetFlushed != null) {
277: throw notYetFlushed;
278: }
279: } finally {
280: if (sm != null) {
281: sm.unsetStoringPC();
282: }
283: }
284: if (value != null && !foundClassAssignableFromValue) {
285: // as required by the JDO spec, a ClassCastException is thrown
286: // TODO Change this to a multiple field mapping localised message
287: throw new ClassCastException(LOCALISER.msg("041044",
288: fmd != null ? fmd.getFullFieldName() : "", type
289: .getName(), value.getClass().getName()));
290: }
291: }
292:
293: /**
294: * Method to retrieve an object of this type from the ResultSet.
295: * @param om Object Manager
296: * @param rs The ResultSet
297: * @param pos The parameter positions
298: * @return The object
299: */
300: public Object getObject(ObjectManager om, final Object rs, int[] pos) {
301: // Go through the possible types for this field and find a non-null value (if there is one)
302: int n = 0;
303: for (int i = 0; i < javaTypeMappings.length; i++) {
304: int[] posMapping;
305: if (n >= pos.length) {
306: //this means we store all implementations to the same columns, so we reset the index
307: n = 0;
308: }
309:
310: if (javaTypeMappings[i].getReferenceMapping() != null) {
311: posMapping = new int[javaTypeMappings[i]
312: .getReferenceMapping()
313: .getNumberOfDatastoreFields()];
314: } else {
315: posMapping = new int[javaTypeMappings[i]
316: .getNumberOfDatastoreFields()];
317: }
318: for (int j = 0; j < posMapping.length; j++) {
319: posMapping[j] = pos[n++];
320: }
321:
322: Object value = null;
323: try {
324: // Retrieve the value (PC object) for this mappings' object
325: value = javaTypeMappings[i].getObject(om, rs,
326: posMapping);
327: } catch (NullValueException e) {
328: // expected if implementation object is null and has primitive
329: // fields in the primary key
330: } catch (JPOXObjectNotFoundException onfe) {
331: // expected, will try next implementation
332: }
333: if (value != null) {
334: if (value instanceof OID) {
335: // What situation is this catering for exactly ?
336: String className;
337: if (javaTypeMappings[i].getReferenceMapping() != null) {
338: className = javaTypeMappings[i]
339: .getReferenceMapping()
340: .getDataStoreMapping(0)
341: .getDatastoreField()
342: .getStoredJavaType();
343: } else {
344: className = javaTypeMappings[i]
345: .getDataStoreMapping(0)
346: .getDatastoreField()
347: .getStoredJavaType();
348: }
349: value = OIDFactory.getInstance(om, className,
350: ((OID) value).getKeyValue());
351: return om.findObject(value, false, true, null);
352: } else if (om.getClassLoaderResolver().classForName(
353: getType()).isAssignableFrom(value.getClass())) {
354: return value;
355: }
356: }
357: }
358: return null;
359: }
360: }
|