/* * #%L * Wisdom-Framework * %% * Copyright (C) 2013 - 2014 Wisdom Framework * %% * 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. * #L% */ package org.wisdom.framework.jpa.crud; import com.google.common.collect.Iterables; import org.wisdom.api.model.*; import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; /** * Abstract implementation of the Crud service for JPA. * Most methods are very naive implementation and should probably be optimized. * * @param <T> the type of the entity * @param <I> the type of the entity primary key */ public abstract class AbstractJTACrud<T, I extends Serializable> implements Crud<T, I> { /** * The entity manager. */ protected final EntityManager entityManager; /** * The class of the entity. */ protected final Class<T> entity; /** * The class of the primary key. */ protected final Class<I> idClass; /** * The name of the persistent unit. */ protected final String pu; /** * The repository in which the Crud is registered. */ protected final Repository repository; /** * Super constructor, that implementation must call. * * @param pu the name of the persistence unit * @param em the entity manager * @param entity the class of the entity * @param id the class of the primary key * @param repository the repository */ public AbstractJTACrud(String pu, EntityManager em, Class<T> entity, Class<I> id, Repository repository) { this.entityManager = em; this.entity = entity; this.idClass = id; this.pu = pu; this.repository = repository; } /** * Gets the class of the represented entity. * * @return the entity's class. */ public Class<T> getEntityClass() { return entity; } /** * Gets the class of the Id used by the persistent layer. * * @return the type of the id. */ public Class<I> getIdClass() { return idClass; } /** * Gets the repository storing the instances of this entity. * * @return the repository object, may be {@literal null} if there are no repository. */ public Repository getRepository() { return repository; } /** * A method implemented when the Crud service is stopped. */ public void dispose() { // Do nothing by default. } /** * Create a FluentTransaction with this Crud service, * * @param <R> the return type of the transaction block. * @return a new FluentTransaction. * @throws java.lang.UnsupportedOperationException if transactions are not supported. * @throws org.wisdom.api.model.InitTransactionException if an exception occurred before running the transaction block. * @throws RollBackHasCauseAnException if an exception occurred when the transaction is rollback. */ @Override public <R> FluentTransaction<R> transaction() { return FluentTransaction.transaction(getTransactionManager()); } /** * Create a FluentTransaction, with the given transaction block. * * @param <R> the return type of the transaction block * @param callable, The transaction block to be executed by the returned FluentTransaction * @return a new FluentTransaction with a transaction block already defined. * @throws java.lang.UnsupportedOperationException if transactions are not supported. */ @Override public <R> FluentTransaction<R>.Intermediate transaction(Callable<R> callable) { return FluentTransaction.transaction(getTransactionManager()).with(callable); } /** * Executes the given runnable in a transaction. If the block throws an exception, the transaction is rolled back. * This method may not be supported by all persistent technologies, as they are not necessary supporting * transactions. In that case, this method throw a {@link java.lang.UnsupportedOperationException}. * * @param runnable the runnable to execute in a transaction * @throws HasBeenRollBackException if the transaction has been rollback. * @throws java.lang.UnsupportedOperationException if transactions are not supported. * @throws org.wisdom.api.model.InitTransactionException if an exception occurred before running the transaction block. * @throws RollBackHasCauseAnException if an exception occurred when the transaction is rollback. */ @Override public void executeTransactionalBlock(final Runnable runnable) throws HasBeenRollBackException { inTransaction(new Callable<Void>() { /** * The block to execute within a transaction. * @return {@code null} * @throws Exception the exception thrown by the given runnable */ @Override public Void call() throws Exception { // We call the runnable directly. runnable.run(); //NOSONAR return null; } }); } /** * Executes the given runnable in a transaction. If the block throws an exception, the transaction is rolled back. * This method may not be supported by all persistent technologies, as they are not necessary supporting * transactions. In that case, this method throw a {@link java.lang.UnsupportedOperationException}. * * @param callable the block to execute in a transaction * @return A the result * @throws HasBeenRollBackException if the transaction has been rollback. * @throws java.lang.UnsupportedOperationException if transactions are not supported. * @throws org.wisdom.api.model.InitTransactionException if an exception occurred before running the transaction block. * @throws RollBackHasCauseAnException if an exception occurred when the transaction is rollback. */ @Override public <A> A executeTransactionalBlock(Callable<A> callable) throws HasBeenRollBackException { return inTransaction(callable); } /** * Retrieves an entity by its id. * * @param id the id, must not be null * @return the entity instance, {@literal null} if there are no entities matching the given id. */ @Override public T findOne(final I id) { return inTransaction(new Callable<T>() { @Override public T call() throws Exception { return entityManager.find(entity, id); } }); } /** * Returns all instances of the entity. * * @return the instances, empty if none. */ @Override public Iterable<T> findAll() { return inTransaction(new Callable<Iterable<T>>() { @Override public Iterable<T> call() throws Exception { CriteriaQuery<T> cq = entityManager.getCriteriaBuilder().createQuery(entity); Root<T> pet = cq.from(entity); cq.select(pet); return entityManager.createQuery(cq).getResultList(); } }); } /** * Retrieves the entity matching the given filter. If several entities matches, the first is returned. * * @param filter the filter * @return the first matching instance, {@literal null} if none */ @Override public T findOne(EntityFilter<T> filter) { for (T object : findAll()) { if (filter.accept(object)) { return object; } } return null; } /** * Returns all instances of the type with the given IDs. * * @param ids the ids. * @return the instances, empty if none. */ @Override public Iterable<T> findAll(Iterable<I> ids) { List<T> results = new ArrayList<>(); for (I id : ids) { T t = findOne(id); if (t != null) { results.add(t); } } return results; } /** * Retrieves the entities matching the given filter. * Be aware that the implementation may load all stored entities in memory to retrieve the right set of entities. * * @param filter the filter * @return the matching instances, empty if none. */ @Override public Iterable<T> findAll(EntityFilter<T> filter) { List<T> results = new ArrayList<>(); for (T object : findAll()) { if (filter.accept(object)) { results.add(object); } } return results; } /** * Deletes the given entity instance. The instance is removed from the persistent layer. * * @param t the instance * @return the entity instance, may be the same as the parameter t but can also be different */ @Override public T delete(final T t) { return inTransaction( new Callable<T>() { @Override public T call() throws Exception { T attached = getAttached(t); if (attached != null) { entityManager.remove(attached); } return t; } } ); } /** * Deletes the given entity instance (specified by its id). The instance is removed from the persistent layer. * * @param id the id */ @Override public void delete(final I id) { if (entity != null) { inTransaction( new Callable<Void>() { @Override public Void call() throws Exception { final T entity = findOne(id); T attached = getAttached(entity); if (attached != null) { entityManager.remove(attached); } return null; } } ); } } /** * Gets the 'managed' version of the given entity instance. * * @param object the entity * @return the managed version, it can be the given object is the instance is not detached. If we can't find the * 'managed' version of the object, return {@code null}. */ @SuppressWarnings("unchecked") private T getAttached(T object) { if (entityManager.contains(object)) { return object; } else { return findOne((I) entityManager.getEntityManagerFactory() .getPersistenceUnitUtil().getIdentifier(object)); } } /** * Deletes the given entity instances. The instances are removed from the persistent layer. * * @param entities the entities to remove from the persistent layer * @return the set of entity instances */ @Override public Iterable<T> delete(final Iterable<T> entities) { return inTransaction(new Callable<Iterable<T>>() { @Override public Iterable<T> call() throws Exception { for (T object : entities) { T attached = getAttached(object); if (attached != null) { entityManager.remove(attached); } } return entities; } }); } /** * Saves a given entity. Use the returned instance for further operations as the operation might have * changed the entity instance completely. * <p> * This method is used to save a new entity or to update it. * * @param t the instance to save * @return the saved entity */ @Override public T save(final T t) { return inTransaction(new Callable<T>() { @Override public T call() throws Exception { T attached = getAttached(t); if (attached != null) { // Update entityManager.merge(t); } else { // New object entityManager.persist(t); } return t; } }); } /** * Checks whether an entity instance with the given id exists, i.e. has been saved and is persisted. * * @param id the id, must not be null * @return {@literal true} if an entity with the given id exists, {@literal false} otherwise. */ @Override public boolean exists(I id) { return findOne(id) != null; } /** * Gets the number of stored instances. * * @return the number of stored instances, 0 if none. */ @Override public long count() { return Iterables.size(findAll()); } /** * Saves all given entities. Use the returned instances for further operations as the operation might have * changed the entity instances completely. * * @param entities the entities to save, must not contains {@literal null} values * @return the saved entities */ @Override public Iterable<T> save(final Iterable<T> entities) { return inTransaction(new Callable<Iterable<T>>() { @Override public Iterable<T> call() throws Exception { for (T object : entities) { entityManager.persist(object); } return entities; } }); } /** * Runs the given block in a transaction. * * @param task the block * @param <X> the return type, can be {@code Void} * @return the result of the operation. */ protected abstract <X> X inTransaction(Callable<X> task); }