001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.openjpa.abstractstore;
020:
021: import java.util.BitSet;
022: import java.util.Collection;
023: import java.util.Collections;
024: import java.util.HashSet;
025: import java.util.Iterator;
026: import java.util.LinkedList;
027:
028: import org.apache.openjpa.conf.OpenJPAConfiguration;
029: import org.apache.openjpa.conf.OpenJPAConfigurationImpl;
030: import org.apache.openjpa.kernel.FetchConfiguration;
031: import org.apache.openjpa.kernel.FetchConfigurationImpl;
032: import org.apache.openjpa.kernel.OpenJPAStateManager;
033: import org.apache.openjpa.kernel.PCState;
034: import org.apache.openjpa.kernel.Seq;
035: import org.apache.openjpa.kernel.StoreContext;
036: import org.apache.openjpa.kernel.StoreManager;
037: import org.apache.openjpa.kernel.StoreQuery;
038: import org.apache.openjpa.lib.rop.ResultObjectProvider;
039: import org.apache.openjpa.meta.ClassMetaData;
040: import org.apache.openjpa.meta.FieldMetaData;
041: import org.apache.openjpa.meta.JavaTypes;
042: import org.apache.openjpa.meta.ValueStrategies;
043: import org.apache.openjpa.util.ApplicationIds;
044: import org.apache.openjpa.util.Id;
045: import org.apache.openjpa.util.ImplHelper;
046:
047: /**
048: * Abstract store manager implementation to ease development of custom
049: * OpenJPA back-ends. A concrete subclass must define implementations for the
050: * following methods:
051: * <ul>
052: * <li>{@link StoreManager#exists}</li>
053: * <li>{@link #initialize}</li>
054: * <li>{@link #load}</li>
055: * <li>{@link
056: * #flush(Collection,Collection,Collection,Collection,Collection)}</li>
057: * <li>{@link #executeExtent}</li>
058: * </ul> Additionally, subclasses should not attempt to acquire resources
059: * until {@link #open} has been called. Store manager instances might be
060: * created to call metadata methods such as {@link #newConfiguration} or
061: * {@link #getUnsupportedOptions} and never opened. These instances should
062: * not consume any data store resources.
063: * Notes:
064: * <ul>
065: * <li>The {@link StoreManager#initialize} method is responsible
066: * for creating new instances of objects freshly loaded from the
067: * database. The method will be invoked with a {@link OpenJPAStateManager}
068: * that the newly-loaded object should be associated with. To create the
069: * new object and set up this association correctly, the implementation
070: * should use the {@link OpenJPAStateManager#initialize} method.</li>
071: * <li>If your data store supports some sort of transaction or
072: * unit of work, you should override the {@link #begin}, {@link #commit},
073: * and {@link #rollback} methods.</li>
074: * <li>This class provides no infrastructure support for optimistic
075: * transactions. To provide optimistic transaction support:
076: * <ul>
077: * <li>Override {@link #beginOptimistic}, {@link #rollbackOptimistic},
078: * and {@link #syncVersion}.</li>
079: * <li>Override {@link #getUnsupportedOptions} to not include {@link
080: * OpenJPAConfiguration#OPTION_OPTIMISTIC} in the list of unsupported
081: * options.</li>
082: * <li>Ensure that your flush implementation sets the next
083: * version for each modified object via the {@link
084: * OpenJPAStateManager#setNextVersion} method.</li>
085: * <li>If your version object does not implement {@link Comparable},
086: * override {@link #compareVersion}, which relies on the
087: * {@link Comparable#compareTo} method.</li>
088: * </ul></li>
089: * <li>If your data store supports a mechanism for automatically
090: * generating and managing identity values (or if you want to
091: * provide that facility on top of your data store), implement
092: * the {@link #getDataStoreIdSequence} method if you want to use a
093: * <code>long</code> as your datastore identity type and are
094: * happy with OpenJPA's {@link Id} class. To use another datastore identity
095: * type, override {@link #getManagedType},
096: * {@link #getDataStoreIdType}, {@link #copyDataStoreId}, and
097: * {@link #newDataStoreId} instead. In either case, override
098: * {@link #getUnsupportedOptions} to not include
099: * {@link OpenJPAConfiguration#OPTION_ID_DATASTORE} in the list of
100: * unsupported options.</li>
101: * <li>If your data store does not support queries (or if you do
102: * not want to convert OpenJPA's query parse tree into a
103: * datastore-specific query), you still have two options in terms
104: * of query execution:
105: * <ul>
106: * <li><em>In-memory execution</em>: If you
107: * execute a query against an extent or a class, OpenJPA will
108: * automatically load the full extent of objects into memory and
109: * execute the query in memory.</li>
110: * <li><em>openjpa.MethodQL</em>: MethodQL allows
111: * you to use the query APIs to execute a method that finds
112: * data in your back-end and returns that data as a
113: * {@link org.apache.openjpa.lib.rop.ResultList}. For more details on
114: * MethodQL, see the OpenJPA Reference Guide.</li>
115: * </ul></li>
116: * </ul>
117: *
118: * @since 0.3.1
119: */
120: public abstract class AbstractStoreManager implements StoreManager {
121:
122: protected StoreContext ctx;
123:
124: public final void setContext(StoreContext ctx) {
125: this .ctx = ctx;
126: open();
127: }
128:
129: /**
130: * Returns the {@link StoreContext} that this store manager is
131: * associated with.
132: */
133: public StoreContext getContext() {
134: return ctx;
135: }
136:
137: /**
138: * No-op implementation. Ready this store manager for persistent operations.
139: */
140: protected void open() {
141: }
142:
143: /**
144: * No-op implementation. Override this method to provide optimistic
145: * locking semantics for your data store if you need notification of
146: * the beginning of an optimistic transaction.
147: */
148: public void beginOptimistic() {
149: }
150:
151: /**
152: * No-op implementation. Override this method to provide optimistic
153: * locking semantics for your data store if you need notification of
154: * a rollback of an optimistic transaction before {@link #begin} is invoked.
155: */
156: public void rollbackOptimistic() {
157: }
158:
159: /**
160: * OpenJPA assumes that after this method is invoked, all data
161: * accesses through this store manager will be part of a single
162: * unit of work that can be rolled back.
163: * This is a no-op implementation. If your data store does not
164: * support any concept of locking or transactions, you need not
165: * override this method.
166: */
167: public void begin() {
168: }
169:
170: /**
171: * This is a no-op implementation. If your data store does not
172: * have a concept of transactions or a unit of work, you need not
173: * override this method. If it does, then override this method to
174: * notify the data store that the current transaction should be committed.
175: */
176: public void commit() {
177: }
178:
179: /**
180: * This is a no-op implementation. If your data store does not
181: * have a concept of transactions or a unit of work, you need not
182: * override this method. If it does, then override this method to
183: * notify the data store that the current transaction should be rolled back.
184: */
185: public void rollback() {
186: }
187:
188: /**
189: * Since this store manager does not provide optimistic locking
190: * support, this method always returns <code>true</code>.
191: */
192: public boolean syncVersion(OpenJPAStateManager sm, Object edata) {
193: return true;
194: }
195:
196: /**
197: * This method is invoked when OpenJPA needs to load an object whose
198: * identity is known but which has not yet been loaded from the data
199: * store. <code>sm</code> is a partially-set-up state manager for this
200: * object. The ID and least-derived type information for the instance
201: * to load can be obtained by invoking
202: * <code>sm.getObjectId()</code> and <code>sm.getMetaData()</code>.
203: *
204: * When implementing this method, load the data for this object from
205: * the data store, determine the most-derived subclass of the newly-loaded
206: * data, and then use the {@link OpenJPAStateManager#initialize} method to
207: * populate <code>sm</code> with a new instance of the appropriate type.
208: * Once {@link OpenJPAStateManager#initialize} has been invoked, proceed to
209: * load field data into <code>sm</code> as in the {@link #load} method, by
210: * using {@link OpenJPAStateManager#store} (or the appropriate
211: * <code>OpenJPAStateManager.store<em>type</em></code> method) to put the
212: * data into the object.
213: */
214: public abstract boolean initialize(OpenJPAStateManager sm,
215: PCState state, FetchConfiguration fetch, Object edata);
216:
217: /**
218: * This method is invoked when OpenJPA needs to load additional data
219: * into an object that has already been at least partially loaded by
220: * a previous {@link #initialize} invocation.
221: * Load data into <code>sm</code> by using {@link
222: * OpenJPAStateManager#store} (or the appropriate
223: * <code>OpenJPAStateManager.store<em>type</em></code> method) to put the
224: * data into the object.
225: */
226: public abstract boolean load(OpenJPAStateManager sm, BitSet fields,
227: FetchConfiguration fetch, int lockLevel, Object edata);
228:
229: /**
230: * This implementation just delegates to the proper singular
231: * method ({@link StoreManager#initialize} or {@link StoreManager#load})
232: * depending on each state manager's state. If your data store provides
233: * bulk loading APIs, overriding this method to be more clever may be
234: * advantageous.
235: */
236: public Collection loadAll(Collection sms, PCState state, int load,
237: FetchConfiguration fetch, Object edata) {
238: return ImplHelper.loadAll(sms, this , state, load, fetch, edata);
239: }
240:
241: /**
242: * Breaks down <code>states</code> based on the objects' current
243: * states, and delegates to
244: * {@link #flush(Collection,Collection,Collection,Collection,Collection)}.
245: */
246: public Collection flush(Collection sms) {
247: // break down state managers by state; initialize as empty lists;
248: // use constants for efficiency
249: Collection pNew = new LinkedList();
250: Collection pNewUpdated = new LinkedList();
251: Collection pNewFlushedDeleted = new LinkedList();
252: Collection pDirty = new LinkedList();
253: Collection pDeleted = new LinkedList();
254:
255: OpenJPAStateManager sm;
256: for (Iterator itr = sms.iterator(); itr.hasNext();) {
257: sm = (OpenJPAStateManager) itr.next();
258: if (sm.getPCState() == PCState.PNEW && !sm.isFlushed())
259: pNew.add(sm);
260: else if (sm.getPCState() == PCState.PNEW && sm.isFlushed())
261: pNewUpdated.add(sm);
262: else if (sm.getPCState() == PCState.PNEWFLUSHEDDELETED)
263: pNewFlushedDeleted.add(sm);
264: else if (sm.getPCState() == PCState.PDIRTY)
265: pDirty.add(sm);
266: else if (sm.getPCState() == PCState.PDELETED)
267: pDeleted.add(sm);
268: }
269:
270: // no dirty instances to flush?
271: if (pNew.isEmpty() && pNewUpdated.isEmpty()
272: && pNewFlushedDeleted.isEmpty() && pDirty.isEmpty()
273: && pDeleted.isEmpty())
274: return Collections.EMPTY_LIST;
275:
276: return flush(pNew, pNewUpdated, pNewFlushedDeleted, pDirty,
277: pDeleted);
278: }
279:
280: public void beforeStateChange(OpenJPAStateManager sm,
281: PCState fromState, PCState toState) {
282: }
283:
284: public boolean assignObjectId(OpenJPAStateManager sm,
285: boolean preFlush) {
286: ClassMetaData meta = sm.getMetaData();
287: if (meta.getIdentityType() == ClassMetaData.ID_APPLICATION)
288: return ApplicationIds.assign(sm, this , preFlush);
289:
290: // datastore identity
291: Object val = ImplHelper.generateIdentityValue(ctx, meta,
292: JavaTypes.LONG);
293: return assignDataStoreId(sm, val);
294: }
295:
296: /**
297: * Assign a new datastore identity to the given instance. This given
298: * value may be null.
299: */
300: protected boolean assignDataStoreId(OpenJPAStateManager sm,
301: Object val) {
302: ClassMetaData meta = sm.getMetaData();
303: if (val == null
304: && meta.getIdentityStrategy() != ValueStrategies.NATIVE)
305: return false;
306: if (val == null)
307: val = getDataStoreIdSequence(meta).next(ctx, meta);
308: sm.setObjectId(newDataStoreId(val, meta));
309: return true;
310: }
311:
312: public boolean assignField(OpenJPAStateManager sm, int field,
313: boolean preFlush) {
314: FieldMetaData fmd = sm.getMetaData().getField(field);
315: Object val = ImplHelper.generateFieldValue(ctx, fmd);
316: if (val == null)
317: return false;
318: sm.store(field, val);
319: return true;
320: }
321:
322: public Class getManagedType(Object oid) {
323: if (oid instanceof Id)
324: return ((Id) oid).getType();
325: return null;
326: }
327:
328: public Class getDataStoreIdType(ClassMetaData meta) {
329: return Id.class;
330: }
331:
332: public Object copyDataStoreId(Object oid, ClassMetaData meta) {
333: Id id = (Id) oid;
334: return new Id(meta.getDescribedType(), id.getId(), id
335: .hasSubclasses());
336: }
337:
338: public Object newDataStoreId(Object val, ClassMetaData meta) {
339: // we use base types for all oids
340: while (meta.getPCSuperclass() != null)
341: meta = meta.getPCSuperclassMetaData();
342: return Id.newInstance(meta.getDescribedType(), val);
343: }
344:
345: /**
346: * Override to retain a dedicated connection.
347: */
348: public void retainConnection() {
349: }
350:
351: /**
352: * Override to release previously-retained connection.
353: */
354: public void releaseConnection() {
355: }
356:
357: /**
358: * Returns <code>null</code>. If your data store can provide a
359: * distinct connection object, return it here.
360: */
361: public Object getClientConnection() {
362: return null;
363: }
364:
365: /**
366: * Create a {@link ResultObjectProvider} that can return all instances
367: * of <code>type</code>, optionally including subclasses as defined
368: * by <code>subclasses</code>.
369: * The implementation of the result provider will typically execute
370: * some sort of data store query to find all the applicable objects, loop
371: * through the results, extracting object IDs from the data, and invoke
372: * {@link StoreContext#find(Object,FetchConfiguration,BitSet,Object,int)}
373: * on each OID. When invoking this method, the first argument is the OID.
374: * The second is the given fetch configuration. The
375: * third argument is a mask of fields to exclude from loading; it will
376: * typically be null. The fourth argument is an object that will be passed
377: * through to {@link #initialize} or {@link #load}, and typically will
378: * contain the actual data to load. For example, for a JDBC-based store
379: * manager, this might be the result set that is being iterated over. If
380: * this argument is <code>null</code>, then the {@link #initialize} or
381: * {@link #load} method will have to issue another command to the data
382: * store in order to fetch the data to be loaded.
383: */
384: public abstract ResultObjectProvider executeExtent(
385: ClassMetaData meta, boolean subs, FetchConfiguration fetch);
386:
387: public StoreQuery newQuery(String language) {
388: return null;
389: }
390:
391: public FetchConfiguration newFetchConfiguration() {
392: return new FetchConfigurationImpl();
393: }
394:
395: /**
396: * Casts <code>v1</code> and <code>v2</code> to {@link Comparable}, and
397: * invokes <code>v1.compareTo (v2)</code>. If <code>v1</code> is less
398: * than <code>v2</code>, returns {@link #VERSION_EARLIER}. If the same,
399: * returns {@link #VERSION_SAME}. Otherwise, returns {@link
400: * #VERSION_LATER}. If either <code>v1</code> or <code>v2</code> are
401: * <code>null</code>, returns {@link #VERSION_DIFFERENT}.
402: */
403: public int compareVersion(OpenJPAStateManager state, Object v1,
404: Object v2) {
405: if (v1 == null || v2 == null)
406: return VERSION_DIFFERENT;
407:
408: int compare = ((Comparable) v1).compareTo((Comparable) v2);
409: if (compare < 0)
410: return VERSION_EARLIER;
411: if (compare == 0)
412: return VERSION_SAME;
413: return VERSION_LATER;
414: }
415:
416: /**
417: * Returns the system-configured sequence. To use some other sort
418: * of datastore identifier (a GUID, string, or someting of that nature),
419: * override {@link #getManagedType},
420: * {@link #getDataStoreIdType}, {@link #copyDataStoreId},
421: * {@link #newDataStoreId}.
422: */
423: public Seq getDataStoreIdSequence(ClassMetaData forClass) {
424: return ctx.getConfiguration().getSequenceInstance();
425: }
426:
427: /**
428: * Returns null.
429: */
430: public Seq getValueSequence(FieldMetaData forField) {
431: return null;
432: }
433:
434: /**
435: * Returns <code>false</code>. If your data store supports
436: * cancelling queries, this method should cancel any
437: * currently-running queries and return <code>true</code> if any
438: * were cancelled.
439: */
440: public boolean cancelAll() {
441: return false;
442: }
443:
444: public void close() {
445: }
446:
447: /**
448: * Responsible for writing modifications happened back to the data
449: * store. If you do not remove the
450: * {@link OpenJPAConfiguration#OPTION_INC_FLUSH} option in
451: * {@link #getUnsupportedOptions}, this will be called only once at the
452: * end of a transaction. Otherwise, it may be called periodically
453: * throughout the course of a transaction.
454: * If this store manager supports optimistic transactions, datastore
455: * version information should be updated during flush, and the state
456: * manager's version indicator should be updated through the
457: * {@link OpenJPAStateManager#setNextVersion} method.
458: * This method will only be invoked if there are meaningful changes
459: * to store. This differs from the behavior of {@link StoreManager#flush},
460: * which may be invoked with a collection of objects in states that
461: * do not require any datastore action (for example, objects in the
462: * transient-transactional state).
463: *
464: * @param pNew Objects that should be added to the store,
465: * and that have not previously been flushed.
466: * @param pNewUpdated New objects that have been modified since
467: * they were initially flushed. These were
468: * in <code>persistentNew</code> in an earlier flush invocation.
469: * @param pNewFlushedDeleted New objects that have been deleted since
470: * they were initially flushed. These were
471: * in <code>persistentNew</code> in an earlier flush invocation.
472: * @param pDirty Objects that were loaded from the data
473: * store and have since been modified.
474: * @param pDeleted Objects that were loaded from the data
475: * store and have since been deleted. These
476: * may have been in a previous flush invocation's persistentDirty list.
477: * @return a collection of exceptions encountered during flushing.
478: */
479: protected abstract Collection flush(Collection pNew,
480: Collection pNewUpdated, Collection pNewFlushedDeleted,
481: Collection pDirty, Collection pDeleted);
482:
483: /**
484: * Return a new configuration instance for this runtime. Configuration
485: * data is maintained at the factory level and is available to all OpenJPA
486: * components; therefore it is a good place to maintain shared resources
487: * such as connection pools, etc.
488: */
489: protected OpenJPAConfiguration newConfiguration() {
490: return new OpenJPAConfigurationImpl();
491: }
492:
493: /**
494: * Returns a set of option names that this store manager does
495: * not support. By default, returns the following:
496: * <ul>
497: * <li>{@link OpenJPAConfiguration#OPTION_OPTIMISTIC}</li>
498: * <li>{@link OpenJPAConfiguration#OPTION_ID_DATASTORE}</li>
499: * <li>{@link OpenJPAConfiguration#OPTION_INC_FLUSH}</li>
500: * <li>{@link OpenJPAConfiguration#OPTION_VALUE_AUTOASSIGN}</li>
501: * <li>{@link OpenJPAConfiguration#OPTION_VALUE_INCREMENT}</li>
502: * <li>{@link OpenJPAConfiguration#OPTION_DATASTORE_CONNECTION}</li>
503: * </ul>
504: */
505: protected Collection getUnsupportedOptions() {
506: Collection c = new HashSet();
507: c.add(OpenJPAConfiguration.OPTION_OPTIMISTIC);
508: c.add(OpenJPAConfiguration.OPTION_ID_DATASTORE);
509: c.add(OpenJPAConfiguration.OPTION_INC_FLUSH);
510: c.add(OpenJPAConfiguration.OPTION_VALUE_AUTOASSIGN);
511: c.add(OpenJPAConfiguration.OPTION_VALUE_INCREMENT);
512: c.add(OpenJPAConfiguration.OPTION_DATASTORE_CONNECTION);
513: return c;
514: }
515:
516: /**
517: * Returns a string name to identify the platform of this
518: * store manager. Returns the class name of this store manager by default.
519: */
520: protected String getPlatform() {
521: return getClass().getName();
522: }
523: }
|