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 java.util.Collection;
021: import java.util.HashSet;
022: import java.util.Iterator;
023: import java.util.Map;
024:
025: import org.jpox.ClassLoaderResolver;
026: import org.jpox.ClassNameConstants;
027: import org.jpox.ObjectManager;
028: import org.jpox.StateManager;
029: import org.jpox.api.ApiAdapter;
030: import org.jpox.exceptions.JPOXException;
031: import org.jpox.exceptions.JPOXUserException;
032: import org.jpox.metadata.AbstractMemberMetaData;
033: import org.jpox.metadata.ContainerMetaData;
034: import org.jpox.sco.SCO;
035: import org.jpox.sco.SCOUtils;
036: import org.jpox.store.DatastoreAdapter;
037: import org.jpox.store.DatastoreContainerObject;
038: import org.jpox.store.MappedStoreManager;
039: import org.jpox.store.StoreManager;
040: import org.jpox.store.exceptions.NoDatastoreMappingException;
041:
042: /**
043: * Mapping for a field that represents a container of objects, such as a List,
044: * a Set, a Collection, a Map, or an array. Has an owner table.
045: * Can be represented in the following ways :-
046: * <ul>
047: * <li>Using a Join-Table, where the linkage between owner and elements/keys/values is stored in this table</li>
048: * <li>Using a Foreign-Key in the element/key/value</li>
049: * <li>Serialised into a single column in the owner table.</li>
050: * <li>Embedded into the Join-Table</li>
051: * <li>Serialised into a single-column in the Join-Table</li>
052: * </ul>
053: * The contents of the container are backed by a store, handling interfacing with the datastore.
054: *
055: * @version $Revision: 1.50 $
056: */
057: public abstract class AbstractContainerMapping extends
058: SingleFieldMapping {
059: /** Name of the field that is the container. */
060: protected String fieldName;
061:
062: /** Store Manager being used. */
063: protected StoreManager storeMgr;
064:
065: /**
066: * Initialize this JavaTypeMapping with the given DatastoreAdapter for
067: * the given FieldMetaData.
068: *
069: * @param dba The Datastore Adapter that this Mapping should use.
070: * @param fmd FieldMetaData for the field to be mapped (if any)
071: * @param container The datastore container storing this mapping (if any)
072: * @param clr the ClassLoaderResolver
073: */
074: public void initialize(DatastoreAdapter dba,
075: AbstractMemberMetaData fmd,
076: DatastoreContainerObject container, ClassLoaderResolver clr) {
077: super .initialize(dba, fmd, container, clr);
078: this .fieldName = fmd.getName();
079: this .storeMgr = datastoreContainer.getStoreManager();
080:
081: ContainerMetaData conmd = fmd.getContainer();
082: if (conmd == null) {
083: throw new JPOXUserException(LOCALISER.msg("041023", fmd
084: .getFullFieldName()));
085: }
086:
087: if (!containerIsStoredInSingleColumn()) {
088: // Not serialised so we use JoinTable or ForeignKey
089: ((MappedStoreManager) storeMgr)
090: .newJoinDatastoreContainerObject(fmd, clr);
091: }
092: }
093:
094: /**
095: * Method to prepare a field mapping for use in the datastore.
096: * This creates the column in the table.
097: */
098: protected void prepareDatastoreMapping() {
099: if (containerIsStoredInSingleColumn()) {
100: // Serialised collections/maps/arrays should just create a (typically BLOB) column as normal in the owning table
101: super .prepareDatastoreMapping();
102: }
103: }
104:
105: /**
106: * Accessor for the name of the java-type actually used when mapping the particular datastore
107: * field. This java-type must have an entry in the datastore mappings.
108: * @param index requested datastore field index.
109: * @return the name of java-type for the requested datastore field.
110: */
111: public String getJavaTypeForDatastoreMapping(int index) {
112: if (containerIsStoredInSingleColumn()) {
113: // Serialised container so just return serialised
114: return ClassNameConstants.JAVA_IO_SERIALIZABLE;
115: }
116: return super .getJavaTypeForDatastoreMapping(index);
117: }
118:
119: /**
120: * Method to set a field in the passed JDBC PreparedStatement using this mapping.
121: * Only valid when the collection is serialised.
122: * @param om The Object Manager
123: * @param preparedStatement The JDBC Prepared Statement to be populated
124: * @param exprIndex The parameter positions in the JDBC statement to populate.
125: * @param value The value to populate into it
126: */
127: public void setObject(ObjectManager om, Object preparedStatement,
128: int[] exprIndex, Object value) {
129: if (fmd == null || !containerIsStoredInSingleColumn()) {
130: throw new JPOXException(failureMessage("setObject"))
131: .setFatal();
132: }
133:
134: StateManager[] sms = null;
135: ApiAdapter api = om.getApiAdapter();
136: if (value != null) {
137: Collection smsColl = null;
138: if (value instanceof java.util.Collection) {
139: Iterator elementsIter = ((java.util.Collection) value)
140: .iterator();
141: while (elementsIter.hasNext()) {
142: Object elem = elementsIter.next();
143: if (api.isPersistable(elem)) {
144: StateManager sm = om.findStateManager(elem);
145: if (sm != null) {
146: if (smsColl == null) {
147: smsColl = new HashSet();
148: }
149: smsColl.add(sm);
150: }
151: }
152: }
153: } else if (value instanceof java.util.Map) {
154: Iterator entriesIter = ((java.util.Map) value)
155: .entrySet().iterator();
156: while (entriesIter.hasNext()) {
157: Map.Entry entry = (Map.Entry) entriesIter.next();
158: Object key = entry.getKey();
159: Object val = entry.getValue();
160: if (api.isPersistable(key)) {
161: StateManager sm = om.findStateManager(key);
162: if (sm != null) {
163: if (smsColl == null) {
164: smsColl = new HashSet();
165: }
166: smsColl.add(sm);
167: }
168: }
169: if (api.isPersistable(val)) {
170: StateManager sm = om.findStateManager(val);
171: if (sm != null) {
172: if (smsColl == null) {
173: smsColl = new HashSet();
174: }
175: smsColl.add(sm);
176: }
177: }
178: }
179: }
180: if (smsColl != null) {
181: sms = (StateManager[]) smsColl
182: .toArray(new StateManager[smsColl.size()]);
183: }
184: }
185:
186: if (sms != null) {
187: // Set all PC objects as being stored (so we dont detach them in any serialisation process)
188: for (int i = 0; i < sms.length; i++) {
189: sms[i].setStoringPC();
190: }
191: }
192: getDataStoreMapping(0).setObject(preparedStatement,
193: exprIndex[0], value);
194: if (sms != null) {
195: // Unset all PC objects now they are stored
196: for (int i = 0; i < sms.length; i++) {
197: sms[i].unsetStoringPC();
198: }
199: }
200: }
201:
202: /**
203: * Method to retrieve an object from the passed JDBC ResultSet.
204: * Only valid when the collection is serialised.
205: * @param om Object Manager
206: * @param resultSet The JDBC ResultSet
207: * @param exprIndex The JDBC parameter position(s) to extract the object from
208: * @return The collection object
209: */
210: public Object getObject(ObjectManager om, Object resultSet,
211: int[] exprIndex) {
212: if (fmd == null || !containerIsStoredInSingleColumn()) {
213: throw new JPOXException(failureMessage("getObject"))
214: .setFatal();
215: }
216: return getDataStoreMapping(0)
217: .getObject(resultSet, exprIndex[0]);
218: }
219:
220: /**
221: * Accessor for the datastore class.
222: * @return The datastore class
223: */
224: public DatastoreContainerObject getDatastoreContainer() {
225: if (containerIsStoredInSingleColumn()) {
226: // Serialised into owner table
227: return datastoreContainer;
228: } else {
229: return null;
230: }
231: }
232:
233: /**
234: * Accessor for the number of datastore fields
235: * @return The number of datastore fields
236: */
237: public int getNumberOfDatastoreFields() {
238: if (containerIsStoredInSingleColumn()) {
239: // Serialised into owner table
240: return super .getNumberOfDatastoreFields();
241: } else {
242: // By default, we have no columns as such for the container
243: return 0;
244: }
245: }
246:
247: /**
248: * Accessor for a datastore mapping
249: * @param index The id of the mapping
250: * @return The datastore mapping
251: */
252: public DatastoreMapping getDataStoreMapping(int index) {
253: if (containerIsStoredInSingleColumn()) {
254: // Serialised into owner table
255: return super .getDataStoreMapping(index);
256: } else {
257: throw new NoDatastoreMappingException(fieldName);
258: }
259: }
260:
261: /**
262: * Accessor for the datastore mappings for this java type
263: * @return The datastore mapping(s)
264: */
265: public DatastoreMapping[] getDataStoreMappings() {
266: if (containerIsStoredInSingleColumn()) {
267: // Serialised into owner table
268: return super .getDataStoreMappings();
269: } else {
270: throw new NoDatastoreMappingException(fieldName);
271: }
272: }
273:
274: /**
275: * Convenience method to return if the container (collection or map) is stored
276: * in the owning table as a column. The container is stored in a single column in the following situations :-
277: * <UL>
278: * <LI>The FieldMetaData has 'serialized="true"'</LI>
279: * <LI>The collection has embedded-element="true" but no join table (and so serialised)</LI>
280: * <LI>The map has embedded-key/value="true" but no join table (and so serialised)</LI>
281: * <LI>The array has embedded-element="true" but no join table (and so serialised)</LI>
282: * <LI>The array has no join table and non-PC elements (and so serialised)</LI>
283: * </UL>
284: * @return Whether it is stored in a single column in the main table.
285: */
286: protected boolean containerIsStoredInSingleColumn() {
287: return (fmd != null && fmd.isSerialized());
288: }
289:
290: /**
291: * This mapping is included in the select statement.
292: * @return Whether to include in select statement
293: */
294: public boolean includeInFetchStatement() {
295: // Only include in a fetch when it is serialised into 1 column
296: return containerIsStoredInSingleColumn();
297: }
298:
299: /**
300: * This mapping is included in the update statement.
301: * @return Whether to include in update statement
302: */
303: public boolean includeInUpdateStatement() {
304: // Only include in an update when it is serialised into 1 column
305: return containerIsStoredInSingleColumn();
306: }
307:
308: /**
309: * This mapping is included in the insert statement.
310: * @return Whether to include in insert statement
311: */
312: public boolean includeInInsertStatement() {
313: // Only include in an insert when it is serialised into 1 column
314: return containerIsStoredInSingleColumn();
315: }
316:
317: /**
318: * Hash code function.
319: * @return The hash code for this object
320: */
321: public int hashCode() {
322: return fmd == null || storeMgr == null ? super .hashCode()
323: : (fmd.hashCode() ^ storeMgr.hashCode());
324: }
325:
326: /**
327: * Accessor for a sample value for this mapping.
328: * @return The sample value
329: */
330: public Object getSampleValue(ClassLoaderResolver clr) {
331: return null;
332: }
333:
334: /**
335: * Method to replace the field that this mapping represents with a SCO wrapper.
336: * The wrapper will be suitable for the passed instantiated type and if it is null will be for
337: * the declared type of the field.
338: * @param ownerSM State Manager for the owning object
339: * @param value The value to create the wrapper with
340: * @param forInsert Whether to insert the SCO with this value
341: * @param forUpdate Whether to update the SCO with this value
342: * @return The SCO wrapper object that the field was replaced with
343: */
344: protected SCO replaceFieldWithWrapper(StateManager ownerSM,
345: Object value, boolean forInsert, boolean forUpdate) {
346: Class type = fmd.getType();
347: if (value != null) {
348: type = value.getClass();
349: } else if (fmd.getOrderMetaData() != null
350: && type.isAssignableFrom(java.util.List.class)) {
351: type = java.util.List.class;
352: }
353: SCO sco = SCOUtils.newSCOInstance(ownerSM, fmd, fmd.getType(),
354: type, value, forInsert, forUpdate, true);
355: return sco;
356: }
357:
358: // ---------------- Implementation of MappingCallbacks --------------------
359:
360: /**
361: * Method to be called after any fetch of the owner class element.
362: * @param sm StateManager of the owner
363: */
364: public void postFetch(StateManager sm) {
365: if (containerIsStoredInSingleColumn()) {
366: // Do nothing when serialised since we are handled in the main request
367: return;
368: }
369:
370: replaceFieldWithWrapper(sm, null, false, false);
371: }
372: }
|