blob: 516004f5d0fedf940b52d098c9817f0c5b30091a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openjpa.abstractstore;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.conf.OpenJPAConfigurationImpl;
import org.apache.openjpa.kernel.FetchConfiguration;
import org.apache.openjpa.kernel.FetchConfigurationImpl;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.PCState;
import org.apache.openjpa.kernel.Seq;
import org.apache.openjpa.kernel.StoreContext;
import org.apache.openjpa.kernel.StoreManager;
import org.apache.openjpa.kernel.StoreQuery;
import org.apache.openjpa.lib.rop.ResultObjectProvider;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.ValueStrategies;
import org.apache.openjpa.util.ApplicationIds;
import org.apache.openjpa.util.Id;
import org.apache.openjpa.util.ImplHelper;
/**
* Abstract store manager implementation to ease development of custom
* OpenJPA back-ends. A concrete subclass must define implementations for the
* following methods:
* <ul>
* <li>{@link StoreManager#exists}</li>
* <li>{@link #initialize}</li>
* <li>{@link #load}</li>
* <li>{@link
* #flush(Collection,Collection,Collection,Collection,Collection)}</li>
* <li>{@link #executeExtent}</li>
* </ul> Additionally, subclasses should not attempt to acquire resources
* until {@link #open} has been called. Store manager instances might be
* created to call metadata methods such as {@link #newConfiguration} or
* {@link #getUnsupportedOptions} and never opened. These instances should
* not consume any data store resources.
* Notes:
* <ul>
* <li>The {@link StoreManager#initialize} method is responsible
* for creating new instances of objects freshly loaded from the
* database. The method will be invoked with a {@link OpenJPAStateManager}
* that the newly-loaded object should be associated with. To create the
* new object and set up this association correctly, the implementation
* should use the {@link OpenJPAStateManager#initialize} method.</li>
* <li>If your data store supports some sort of transaction or
* unit of work, you should override the {@link #begin}, {@link #commit},
* and {@link #rollback} methods.</li>
* <li>This class provides no infrastructure support for optimistic
* transactions. To provide optimistic transaction support:
* <ul>
* <li>Override {@link #beginOptimistic}, {@link #rollbackOptimistic},
* and {@link #syncVersion}.</li>
* <li>Override {@link #getUnsupportedOptions} to not include {@link
* OpenJPAConfiguration#OPTION_OPTIMISTIC} in the list of unsupported
* options.</li>
* <li>Ensure that your flush implementation sets the next
* version for each modified object via the {@link
* OpenJPAStateManager#setNextVersion} method.</li>
* <li>If your version object does not implement {@link Comparable},
* override {@link #compareVersion}, which relies on the
* {@link Comparable#compareTo} method.</li>
* </ul></li>
* <li>If your data store supports a mechanism for automatically
* generating and managing identity values (or if you want to
* provide that facility on top of your data store), implement
* the {@link #getDataStoreIdSequence} method if you want to use a
* <code>long</code> as your datastore identity type and are
* happy with OpenJPA's {@link Id} class. To use another datastore identity
* type, override {@link #getManagedType},
* {@link #getDataStoreIdType}, {@link #copyDataStoreId}, and
* {@link #newDataStoreId} instead. In either case, override
* {@link #getUnsupportedOptions} to not include
* {@link OpenJPAConfiguration#OPTION_ID_DATASTORE} in the list of
* unsupported options.</li>
* <li>If your data store does not support queries (or if you do
* not want to convert OpenJPA's query parse tree into a
* datastore-specific query), you still have two options in terms
* of query execution:
* <ul>
* <li><em>In-memory execution</em>: If you
* execute a query against an extent or a class, OpenJPA will
* automatically load the full extent of objects into memory and
* execute the query in memory.</li>
* <li><em>openjpa.MethodQL</em>: MethodQL allows
* you to use the query APIs to execute a method that finds
* data in your back-end and returns that data as a
* {@link org.apache.openjpa.lib.rop.ResultList}. For more details on
* MethodQL, see the OpenJPA Reference Guide.</li>
* </ul></li>
* </ul>
*
* @since 0.3.1
*/
public abstract class AbstractStoreManager
implements StoreManager {
protected StoreContext ctx;
@Override
public final void setContext(StoreContext ctx) {
this.ctx = ctx;
open();
}
/**
* Returns the {@link StoreContext} that this store manager is
* associated with.
*/
public StoreContext getContext() {
return ctx;
}
/**
* No-op implementation. Ready this store manager for persistent operations.
*/
protected void open() {
}
/**
* No-op implementation. Override this method to provide optimistic
* locking semantics for your data store if you need notification of
* the beginning of an optimistic transaction.
*/
@Override
public void beginOptimistic() {
}
/**
* No-op implementation. Override this method to provide optimistic
* locking semantics for your data store if you need notification of
* a rollback of an optimistic transaction before {@link #begin} is invoked.
*/
@Override
public void rollbackOptimistic() {
}
/**
* OpenJPA assumes that after this method is invoked, all data
* accesses through this store manager will be part of a single
* unit of work that can be rolled back.
* This is a no-op implementation. If your data store does not
* support any concept of locking or transactions, you need not
* override this method.
*/
@Override
public void begin() {
}
/**
* This is a no-op implementation. If your data store does not
* have a concept of transactions or a unit of work, you need not
* override this method. If it does, then override this method to
* notify the data store that the current transaction should be committed.
*/
@Override
public void commit() {
}
/**
* This is a no-op implementation. If your data store does not
* have a concept of transactions or a unit of work, you need not
* override this method. If it does, then override this method to
* notify the data store that the current transaction should be rolled back.
*/
@Override
public void rollback() {
}
/**
* Since this store manager does not provide optimistic locking
* support, this method always returns <code>true</code>.
*/
@Override
public boolean syncVersion(OpenJPAStateManager sm, Object edata) {
return true;
}
/**
* This method is invoked when OpenJPA needs to load an object whose
* identity is known but which has not yet been loaded from the data
* store. <code>sm</code> is a partially-set-up state manager for this
* object. The ID and least-derived type information for the instance
* to load can be obtained by invoking
* <code>sm.getObjectId()</code> and <code>sm.getMetaData()</code>.
*
* When implementing this method, load the data for this object from
* the data store, determine the most-derived subclass of the newly-loaded
* data, and then use the {@link OpenJPAStateManager#initialize} method to
* populate <code>sm</code> with a new instance of the appropriate type.
* Once {@link OpenJPAStateManager#initialize} has been invoked, proceed to
* load field data into <code>sm</code> as in the {@link #load} method, by
* using {@link OpenJPAStateManager#store} (or the appropriate
* <code>OpenJPAStateManager.store<em>type</em></code> method) to put the
* data into the object.
*/
@Override
public abstract boolean initialize(OpenJPAStateManager sm, PCState state,
FetchConfiguration fetch, Object edata);
/**
* This method is invoked when OpenJPA needs to load additional data
* into an object that has already been at least partially loaded by
* a previous {@link #initialize} invocation.
* Load data into <code>sm</code> by using {@link
* OpenJPAStateManager#store} (or the appropriate
* <code>OpenJPAStateManager.store<em>type</em></code> method) to put the
* data into the object.
*/
@Override
public abstract boolean load(OpenJPAStateManager sm, BitSet fields,
FetchConfiguration fetch, int lockLevel, Object edata);
/**
* This implementation just delegates to the proper singular
* method ({@link StoreManager#initialize} or {@link StoreManager#load})
* depending on each state manager's state. If your data store provides
* bulk loading APIs, overriding this method to be more clever may be
* advantageous.
*/
@Override
public Collection<Object> loadAll(Collection<OpenJPAStateManager> sms, PCState state, int load,
FetchConfiguration fetch, Object edata) {
return ImplHelper.loadAll(sms, this, state, load, fetch, edata);
}
/**
* Breaks down <code>states</code> based on the objects' current
* states, and delegates to
* {@link #flush(Collection,Collection,Collection,Collection,Collection)}.
*/
@Override
public Collection<Exception> flush(Collection<OpenJPAStateManager> sms) {
// break down state managers by state; initialize as empty lists;
// use constants for efficiency
Collection<OpenJPAStateManager> pNew = new LinkedList<>();
Collection<OpenJPAStateManager> pNewUpdated = new LinkedList<>();
Collection<OpenJPAStateManager> pNewFlushedDeleted = new LinkedList<>();
Collection<OpenJPAStateManager> pDirty = new LinkedList<>();
Collection<OpenJPAStateManager> pDeleted = new LinkedList<>();
for (OpenJPAStateManager sm : sms) {
if (sm.getPCState() == PCState.PNEW && !sm.isFlushed())
pNew.add(sm);
else if (sm.getPCState() == PCState.PNEW && sm.isFlushed())
pNewUpdated.add(sm);
else if (sm.getPCState() == PCState.PNEWFLUSHEDDELETED)
pNewFlushedDeleted.add(sm);
else if (sm.getPCState() == PCState.PDIRTY)
pDirty.add(sm);
else if (sm.getPCState() == PCState.PDELETED)
pDeleted.add(sm);
}
// no dirty instances to flush?
if (pNew.isEmpty() && pNewUpdated.isEmpty()
&& pNewFlushedDeleted.isEmpty() && pDirty.isEmpty()
&& pDeleted.isEmpty())
return Collections.EMPTY_LIST;
return flush(pNew, pNewUpdated, pNewFlushedDeleted, pDirty, pDeleted);
}
@Override
public void beforeStateChange(OpenJPAStateManager sm, PCState fromState,
PCState toState) {
}
@Override
public boolean assignObjectId(OpenJPAStateManager sm, boolean preFlush) {
ClassMetaData meta = sm.getMetaData();
if (meta.getIdentityType() == ClassMetaData.ID_APPLICATION)
return ApplicationIds.assign(sm, this, preFlush);
// datastore identity
Object val = ImplHelper.generateIdentityValue(ctx, meta,
JavaTypes.LONG);
return assignDataStoreId(sm, val);
}
/**
* Assign a new datastore identity to the given instance. This given
* value may be null.
*/
protected boolean assignDataStoreId(OpenJPAStateManager sm, Object val) {
ClassMetaData meta = sm.getMetaData();
if (val == null && meta.getIdentityStrategy() != ValueStrategies.NATIVE)
return false;
if (val == null)
val = getDataStoreIdSequence(meta).next(ctx, meta);
sm.setObjectId(newDataStoreId(val, meta));
return true;
}
@Override
public boolean assignField(OpenJPAStateManager sm, int field,
boolean preFlush) {
FieldMetaData fmd = sm.getMetaData().getField(field);
Object val = ImplHelper.generateFieldValue(ctx, fmd);
if (val == null)
return false;
sm.store(field, val);
return true;
}
@Override
public Class<?> getManagedType(Object oid) {
if (oid instanceof Id)
return ((Id) oid).getType();
return null;
}
@Override
public Class<?> getDataStoreIdType(ClassMetaData meta) {
return Id.class;
}
@Override
public Object copyDataStoreId(Object oid, ClassMetaData meta) {
Id id = (Id) oid;
return new Id(meta.getDescribedType(), id.getId(),
id.hasSubclasses());
}
@Override
public Object newDataStoreId(Object val, ClassMetaData meta) {
// we use base types for all oids
while (meta.getPCSuperclass() != null)
meta = meta.getPCSuperclassMetaData();
return Id.newInstance(meta.getDescribedType(), val);
}
/**
* Override to retain a dedicated connection.
*/
@Override
public void retainConnection() {
}
/**
* Override to release previously-retained connection.
*/
@Override
public void releaseConnection() {
}
/**
* Returns <code>null</code>. If your data store can provide a
* distinct connection object, return it here.
*/
@Override
public Object getClientConnection() {
return null;
}
/**
* Create a {@link ResultObjectProvider} that can return all instances
* of <code>type</code>, optionally including subclasses as defined
* by <code>subclasses</code>.
* The implementation of the result provider will typically execute
* some sort of data store query to find all the applicable objects, loop
* through the results, extracting object IDs from the data, and invoke
* {@link StoreContext#find(Object,FetchConfiguration,BitSet,Object,int)}
* on each OID. When invoking this method, the first argument is the OID.
* The second is the given fetch configuration. The
* third argument is a mask of fields to exclude from loading; it will
* typically be null. The fourth argument is an object that will be passed
* through to {@link #initialize} or {@link #load}, and typically will
* contain the actual data to load. For example, for a JDBC-based store
* manager, this might be the result set that is being iterated over. If
* this argument is <code>null</code>, then the {@link #initialize} or
* {@link #load} method will have to issue another command to the data
* store in order to fetch the data to be loaded.
*/
@Override
public abstract ResultObjectProvider executeExtent(ClassMetaData meta,
boolean subs, FetchConfiguration fetch);
@Override
public StoreQuery newQuery(String language) {
return null;
}
@Override
public FetchConfiguration newFetchConfiguration() {
return new FetchConfigurationImpl();
}
/**
* Casts <code>v1</code> and <code>v2</code> to {@link Comparable}, and
* invokes <code>v1.compareTo (v2)</code>. If <code>v1</code> is less
* than <code>v2</code>, returns {@link #VERSION_EARLIER}. If the same,
* returns {@link #VERSION_SAME}. Otherwise, returns {@link
* #VERSION_LATER}. If either <code>v1</code> or <code>v2</code> are
* <code>null</code>, returns {@link #VERSION_DIFFERENT}.
*/
@Override
public int compareVersion(OpenJPAStateManager state, Object v1, Object v2) {
if (v1 == null || v2 == null)
return VERSION_DIFFERENT;
int compare = ((Comparable) v1).compareTo((Comparable) v2);
if (compare < 0)
return VERSION_EARLIER;
if (compare == 0)
return VERSION_SAME;
return VERSION_LATER;
}
/**
* Returns the system-configured sequence. To use some other sort
* of datastore identifier (a GUID, string, or someting of that nature),
* override {@link #getManagedType},
* {@link #getDataStoreIdType}, {@link #copyDataStoreId},
* {@link #newDataStoreId}.
*/
@Override
public Seq getDataStoreIdSequence(ClassMetaData forClass) {
return ctx.getConfiguration().getSequenceInstance();
}
/**
* Returns null.
*/
@Override
public Seq getValueSequence(FieldMetaData forField) {
return null;
}
/**
* Returns <code>false</code>. If your data store supports
* cancelling queries, this method should cancel any
* currently-running queries and return <code>true</code> if any
* were cancelled.
*/
@Override
public boolean cancelAll() {
return false;
}
@Override
public void close() {
}
/**
* Responsible for writing modifications happened back to the data
* store. If you do not remove the
* {@link OpenJPAConfiguration#OPTION_INC_FLUSH} option in
* {@link #getUnsupportedOptions}, this will be called only once at the
* end of a transaction. Otherwise, it may be called periodically
* throughout the course of a transaction.
* If this store manager supports optimistic transactions, datastore
* version information should be updated during flush, and the state
* manager's version indicator should be updated through the
* {@link OpenJPAStateManager#setNextVersion} method.
* This method will only be invoked if there are meaningful changes
* to store. This differs from the behavior of {@link StoreManager#flush},
* which may be invoked with a collection of objects in states that
* do not require any datastore action (for example, objects in the
* transient-transactional state).
*
* @param pNew Objects that should be added to the store,
* and that have not previously been flushed.
* @param pNewUpdated New objects that have been modified since
* they were initially flushed. These were
* in <code>persistentNew</code> in an earlier flush invocation.
* @param pNewFlushedDeleted New objects that have been deleted since
* they were initially flushed. These were
* in <code>persistentNew</code> in an earlier flush invocation.
* @param pDirty Objects that were loaded from the data
* store and have since been modified.
* @param pDeleted Objects that were loaded from the data
* store and have since been deleted. These
* may have been in a previous flush invocation's persistentDirty list.
* @return a collection of exceptions encountered during flushing.
*/
protected abstract Collection<Exception> flush(Collection<OpenJPAStateManager> pNew,
Collection<OpenJPAStateManager> pNewUpdated, Collection<OpenJPAStateManager> pNewFlushedDeleted,
Collection<OpenJPAStateManager> pDirty, Collection<OpenJPAStateManager> pDeleted);
/**
* Return a new configuration instance for this runtime. Configuration
* data is maintained at the factory level and is available to all OpenJPA
* components; therefore it is a good place to maintain shared resources
* such as connection pools, etc.
*/
protected OpenJPAConfiguration newConfiguration() {
return new OpenJPAConfigurationImpl();
}
/**
* Returns a set of option names that this store manager does
* not support. By default, returns the following:
* <ul>
* <li>{@link OpenJPAConfiguration#OPTION_OPTIMISTIC}</li>
* <li>{@link OpenJPAConfiguration#OPTION_ID_DATASTORE}</li>
* <li>{@link OpenJPAConfiguration#OPTION_INC_FLUSH}</li>
* <li>{@link OpenJPAConfiguration#OPTION_VALUE_AUTOASSIGN}</li>
* <li>{@link OpenJPAConfiguration#OPTION_VALUE_INCREMENT}</li>
* <li>{@link OpenJPAConfiguration#OPTION_DATASTORE_CONNECTION}</li>
* </ul>
*/
protected Collection<String> getUnsupportedOptions() {
Collection<String> c = new HashSet<>();
c.add(OpenJPAConfiguration.OPTION_OPTIMISTIC);
c.add(OpenJPAConfiguration.OPTION_ID_DATASTORE);
c.add(OpenJPAConfiguration.OPTION_INC_FLUSH);
c.add(OpenJPAConfiguration.OPTION_VALUE_AUTOASSIGN);
c.add(OpenJPAConfiguration.OPTION_VALUE_INCREMENT);
c.add(OpenJPAConfiguration.OPTION_DATASTORE_CONNECTION);
return c;
}
/**
* Returns a string name to identify the platform of this
* store manager. Returns the class name of this store manager by default.
*/
protected String getPlatform ()
{
return getClass ().getName ();
}
}