package org.jboss.seam.persistence; import static org.jboss.seam.annotations.Install.BUILT_IN; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Date; import java.util.HashSet; import java.util.Set; import javax.persistence.EntityManager; import javax.persistence.OptimisticLockException; import javax.transaction.Synchronization; import org.jboss.seam.Component; import org.jboss.seam.Entity; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.annotations.intercept.PostConstruct; /** * Abstraction layer for persistence providers (JPA implementations). This class * provides a working base implementation that can be optimized for performance * and non-standardized features by extending and overriding the methods. * * The methods on this class are a great todo list for the next rev of the JPA * spec ;-) * * @author Gavin King * @author Pete Muir * */ @Name("org.jboss.seam.persistence.persistenceProvider") @Scope(ScopeType.STATELESS) @BypassInterceptors @Install(precedence = BUILT_IN, classDependencies = "javax.persistence.EntityManager") public class PersistenceProvider { public enum Feature { /** * Identifies whether this JPA provider supports using a wildcard as the * subject of a count query. * * <p> * Here's a count query that uses a wildcard as the subject. * </p> * * <pre> * select count(*) from Vehicle v * </pre> * <p> * Per the JPA 1.0 spec, using a wildcard as a subject of a count query is * not permitted. Instead, the subject must be the entity or the alias, as * in this count query: * </p> * * <pre> * select count(v) from Vehicle v * </pre> * <p> * Hibernate supports the wildcard syntax as an vendor extension. * Furthermore, Hibernate produces an invalid SQL query when using the * compliant subject if the entity has a composite primary key. Therefore, * we prefer to use the wildcard syntax if it is supported. * </p> */ WILDCARD_AS_COUNT_QUERY_SUBJECT } protected Set<Feature> featureSet = new HashSet<Feature>(); @PostConstruct // @Create method not called on stateless components public void init() { } /** * Indicate whether this JPA provider supports the feature defined by the * provided Feature enum value. */ public boolean supportsFeature(Feature feature) { return featureSet.contains(feature); } /** * Set the flush mode to manual-only flushing. Called when an atomic * persistence context is required. */ public void setFlushModeManual(EntityManager entityManager) { throw new UnsupportedOperationException("Use of FlushMode.MANUAL requires Hibernate as the persistence provider. Please use Hibernate, a custom persistenceProvider, or remove the MANUAL flush mode setting."); } /** * <p> * Set the FlushMode the persistence contexts should use during rendering by * calling {@link PersistenceContexts#changeFlushMode(FlushModeType, true)}. * The actual changing of the flush mode is handled by the * {@link PersistenceContexts} instance. The boolean argument should be true * to indicate that this is a temporary change and that the old flush mode * should be restored after render. * </p> * <p> * Ideally, this should be MANUAL since changes should never flush to the * database while in render response and the cost of a dirty check can be * avoided. However, since the MANUAL mode is not officially part of the JPA * specification, the default implementation will perform no operation. * </p> */ public void setRenderFlushMode() { // no-op in default implementation } /** * Does the persistence context have unflushed changes? If it does not, * persistence context replication can be optimized. * * @return true to indicate that there are unflushed changes */ public boolean isDirty(EntityManager entityManager) { return true; // best we can do! } /** * Get the value of the entity identifier attribute. * * @param bean * a managed entity instance */ public Object getId(Object bean, EntityManager entityManager) { return Entity.forBean(bean).getIdentifier(bean); } /** * Get the name of the entity * * @param bean * @param entityManager * * @throws IllegalArgumentException * if the passed object is not an entity */ public String getName(Object bean, EntityManager entityManager) throws IllegalArgumentException { return Entity.forBean(bean).getName(); } /** * Get the value of the entity version attribute. * * @param bean * a managed entity instance */ public Object getVersion(Object bean, EntityManager entityManager) { return Entity.forBean(bean).getVersion(bean); } public void checkVersion(Object bean, EntityManager entityManager, Object oldVersion, Object version) { boolean equal; if (oldVersion instanceof Date) { equal = ((Date) oldVersion).getTime() == ((Date) version).getTime(); } else { equal = oldVersion.equals(version); } if (!equal) { throw new OptimisticLockException("Current database version number does not match passivated version number"); } } /** * Enable a Filter. This is here just especially for Hibernate, since we well * know that other products don't have such cool features. */ public void enableFilter(Filter filter, EntityManager entityManager) { throw new UnsupportedOperationException("Use of filters requires Hibernate as the persistence provider. Please use Hibernate or remove the filters configuration."); } /** * Register a Synchronization with the current transaction. */ public boolean registerSynchronization(Synchronization sync, EntityManager entityManager) { return false; // best we can do! } public static PersistenceProvider instance() { return (PersistenceProvider) Component.getInstance(PersistenceProvider.class, ScopeType.STATELESS); } /** * Wrap the delegate before returning it to the application */ public Object proxyDelegate(Object delegate) { return delegate; } /** * Wrap the entityManager before returning it to the application */ public EntityManager proxyEntityManager(EntityManager entityManager) { return (EntityManager) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { EntityManagerProxy.class }, new EntityManagerInvocationHandler(entityManager)); } /** * Returns the class of an entity bean instance * * @param bean * The entity bean instance * @return The class of the entity bean */ public Class getBeanClass(Object bean) { return Entity.forBean(bean).getBeanClass(); } public Method getPostLoadMethod(Object bean, EntityManager entityManager) { return Entity.forBean(bean).getPostLoadMethod(); } public Method getPrePersistMethod(Object bean, EntityManager entityManager) { return Entity.forBean(bean).getPrePersistMethod(); } public Method getPreUpdateMethod(Object bean, EntityManager entityManager) { return Entity.forBean(bean).getPreUpdateMethod(); } public Method getPreRemoveMethod(Object bean, EntityManager entityManager) { return Entity.forBean(bean).getPreRemoveMethod(); } @Deprecated public Method getPreRemoveMethod(Class beanClass) { return Entity.forClass(beanClass).getPreRemoveMethod(); } @Deprecated public Method getPostLoadMethod(Class beanClass) { return Entity.forClass(beanClass).getPostLoadMethod(); } @Deprecated public Method getPrePersistMethod(Class beanClass) { return Entity.forClass(beanClass).getPrePersistMethod(); } @Deprecated public Method getPreUpdateMethod(Class beanClass) { return Entity.forClass(beanClass).getPreUpdateMethod(); } }