/** * Copyright 2009-2013 Oy Vaadin Ltd * * Licensed 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 com.vaadin.addon.jpacontainer.provider; import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.LinkedList; import javax.persistence.EntityManager; import javax.persistence.EntityTransaction; import com.vaadin.addon.jpacontainer.EntityProviderChangeEvent; import com.vaadin.addon.jpacontainer.EntityProviderChangeListener; import com.vaadin.addon.jpacontainer.EntityProviderChangeNotifier; import com.vaadin.addon.jpacontainer.MutableEntityProvider; /** * Extended version of {@link LocalEntityProvider} that provides editing * support. Transactions can either be handled internally by the provider, or by * an external container such as Spring or EJB (see the JPAContainer manual for * examples of how to do this). By default, transactions are handled internally * by invoking the transaction methods of the EntityManager. * <p> * This entity provider fires {@link EntityProviderChangeEvent}s every time an * entity is added, updated or deleted. * * @author Petter Holmström (Vaadin Ltd) * @since 1.0 */ public class MutableLocalEntityProvider<T> extends LocalEntityProvider<T> implements MutableEntityProvider<T>, EntityProviderChangeNotifier<T> { private static final long serialVersionUID = -6628293930338167750L; /** * Creates a new <code>MutableLocalEntityProvider</code>. The entity manager * must be set using * {@link #setEntityManager(javax.persistence.EntityManager) }. * * @param entityClass * the entity class (must not be null). */ public MutableLocalEntityProvider(Class<T> entityClass) { super(entityClass); } /** * Creates a new <code>MutableLocalEntityProvider</code>. * * @param entityClass * the entity class (must not be null). * @param entityManager * the entity manager to use (must not be null). */ public MutableLocalEntityProvider(Class<T> entityClass, EntityManager entityManager) { super(entityClass, entityManager); } private boolean transactionsHandled = true; /** * Specifies whether the entity provider should handle transactions itself * or whether they should be handled outside (e.g. if declarative * transactions are used). * * @param transactionsHandled * true to handle the transactions internally, false to rely on * external transaction handling. */ public void setTransactionsHandledByProvider(boolean transactionsHandled) { this.transactionsHandled = transactionsHandled; } /** * Returns whether the entity provider is handling transactions internally * (the default) or relies on external transaction handling. * * @return true if transactions are handled internally, false if not. */ public boolean isTransactionsHandledByProvider() { return transactionsHandled; } /** * If {@link #isTransactionsHandledByProvider() } is true, * <code>operation</code> will be executed inside a transaction that is * commited after the operation is completed. Otherwise, * <code>operation</code> will just be executed. * * @param operation * the operation to run (must not be null). */ protected void runInTransaction(Runnable operation) { assert operation != null : "operation must not be null"; if (isTransactionsHandledByProvider()) { EntityTransaction et = getEntityManager().getTransaction(); if (et.isActive()) { // The transaction has been started outside of this method // and should also be committed/rolled back outside of // this method operation.run(); } else { try { et.begin(); operation.run(); et.commit(); } finally { if (et.isActive()) { et.rollback(); } } } } else { operation.run(); } } @SuppressWarnings("unchecked") public T addEntity(final T entity) { assert entity != null; final Object[] entityA = new Object[1]; runInTransaction(new Runnable() { public void run() { EntityManager em = getEntityManager(); entityA[0] = em.merge(entity); em.flush(); } }); T dEntity = detachEntity((T) entityA[0]); fireEntityProviderChangeEvent(new EntitiesAddedEvent<T>(this, dEntity)); return dEntity; } @SuppressWarnings("unchecked") public void removeEntity(final Object entityId) { assert entityId != null; final Object[] entityA = new Object[1]; runInTransaction(new Runnable() { public void run() { EntityManager em = getEntityManager(); T entity = em.find(getEntityClassMetadata().getMappedClass(), entityId); if (entity != null) { em.remove(em.merge(entity)); em.flush(); entityA[0] = detachEntity(entity); } } }); if (entityA[0] != null) { fireEntityProviderChangeEvent(new EntitiesRemovedEvent<T>(this, (T) entityA[0])); } } @SuppressWarnings("unchecked") public T updateEntity(final T entity) { assert entity != null : "entity must not be null"; final Object[] entityA = new Object[1]; runInTransaction(new Runnable() { public void run() { EntityManager em = getEntityManager(); entityA[0] = em.merge(entity); em.flush(); } }); T dEntity = detachEntity((T) entityA[0]); fireEntityProviderChangeEvent(new EntitiesUpdatedEvent<T>(this, dEntity)); return dEntity; } @SuppressWarnings("unchecked") public void updateEntityProperty(final Object entityId, final String propertyName, final Object propertyValue) throws IllegalArgumentException { assert entityId != null : "entityId must not be null"; assert propertyName != null : "propertyName must not be null"; final Object[] entityA = new Object[1]; runInTransaction(new Runnable() { public void run() { EntityManager em = getEntityManager(); T entity = em.find(getEntityClassMetadata().getMappedClass(), entityId); if (entity != null) { // make sure we are working with the latest versions em.refresh(entity); getEntityClassMetadata().setPropertyValue(entity, propertyName, propertyValue); // re-attach also referenced entities to the persistence // context entity = em.merge(entity); em.flush(); entityA[0] = detachEntity(entity); } } }); if (entityA[0] != null) { fireEntityProviderChangeEvent(new EntityPropertyUpdatedEvent(this, propertyName, entityA)); } } /* * Transient note: Listeners (read: JPAContainers) should re attach themselves when deserialized */ transient private LinkedList<WeakReference<EntityProviderChangeListener<T>>> listeners; private LinkedList<WeakReference<EntityProviderChangeListener<T>>> getListeners() { if(listeners == null) { listeners = new LinkedList<WeakReference<EntityProviderChangeListener<T>>>(); } return listeners; } public void addListener(EntityProviderChangeListener<T> listener) { synchronized (getListeners()) { assert listener != null : "listener must not be null"; getListeners().add(new WeakReference<EntityProviderChangeListener<T>>( listener)); } } public void removeListener(EntityProviderChangeListener<T> listener) { synchronized (getListeners()) { assert listener != null : "listener must not be null"; Iterator<WeakReference<EntityProviderChangeListener<T>>> it = getListeners() .iterator(); while (it.hasNext()) { EntityProviderChangeListener<T> l = it.next().get(); // also clean up old references if (null == l || listener.equals(l)) { it.remove(); } } } } private boolean fireEntityProviderChangeEvent = true; /** * Sets whether {@link EntityProviderChangeEvent}s should be fired by this * entity provider. */ protected void setFireEntityProviderChangeEvents(boolean fireEvents) { this.fireEntityProviderChangeEvent = fireEvents; } /** * Returns whether {@link EntityProviderChangeEvent}s should be fired by * this entity provider. */ protected boolean isFireEntityProviderChangeEvent() { return fireEntityProviderChangeEvent; } /** * Sends <code>event</code> to all registered listeners if * {@link #isFireEntityProviderChangeEvent() } is true. * * @param event * the event to send (must not be null). */ @SuppressWarnings("unchecked") protected void fireEntityProviderChangeEvent( final EntityProviderChangeEvent<T> event) { LinkedList<WeakReference<EntityProviderChangeListener<T>>> list; synchronized (getListeners()) { assert event != null : "event must not be null"; if (getListeners().isEmpty() && !isFireEntityProviderChangeEvent()) { return; } // cleanup Iterator<WeakReference<EntityProviderChangeListener<T>>> it = getListeners() .iterator(); while (it.hasNext()) { if (null == it.next().get()) { it.remove(); } } // copy list to use outside the synchronized block list = (LinkedList<WeakReference<EntityProviderChangeListener<T>>>) getListeners() .clone(); } for (WeakReference<EntityProviderChangeListener<T>> ref : list) { EntityProviderChangeListener<T> listener = ref.get(); if (null != listener) { listener.entityProviderChange(event); } } } }