/* * 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 org.tynamo.model.jpa.services; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.LockModeType; import javax.persistence.NoResultException; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Expression; import javax.persistence.criteria.Root; import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.Type; import org.apache.tapestry5.grid.GridDataSource; import org.apache.tapestry5.ioc.annotations.Autobuild; import org.apache.tapestry5.ioc.services.PropertyAccess; import org.apache.tapestry5.jpa.JpaGridDataSource; import org.slf4j.Logger; import org.tynamo.descriptor.CollectionDescriptor; import org.tynamo.descriptor.TynamoClassDescriptor; import org.tynamo.descriptor.TynamoPropertyDescriptor; import org.tynamo.internal.services.GenericPersistenceService; import org.tynamo.model.jpa.internal.ConfigurableEntityManagerProvider; import org.tynamo.services.DescriptorService; @SuppressWarnings("unchecked") public class JpaPersistenceServiceImpl extends GenericPersistenceService implements JpaPersistenceService { private Logger logger; private DescriptorService descriptorService; private EntityManager em; public JpaPersistenceServiceImpl(Logger logger, DescriptorService descriptorService, ConfigurableEntityManagerProvider entityManagerProvider, PropertyAccess propertyAccess) { super(propertyAccess); this.logger = logger; this.descriptorService = descriptorService; this.em = entityManagerProvider.getEntityManager(); } /** * https://trails.dev.java.net/servlets/ReadMsg?listName=users&msgNo=1226 * <p/> * Very often I find myself writing: * <code> * Object example = new Object(); example.setProperty(uniqueValue); * List objects = ((TynamoPage)getPage()).getPersistenceService().getInstances(example); * (MyObject)objects.get(0); * </code> * when, in fact, I know that the single property I populated my example object with should be unique, and thus only * one object should be returned * * @param type The type to use to check for security restrictions. * @param detachedQuery * @return */ public <T> T getInstance(final Class<T> type, CriteriaQuery detachedQuery) { final CriteriaQuery<T> query = alterCriteria(type, detachedQuery); return em.createQuery(query).getSingleResult(); } /** * (non-Javadoc) * * @see org.tynamo.services.PersistenceService#getInstance(Class,Serializable) */ public <T> T getInstance(final Class<T> type, final Serializable id) { return em.find(type, id); } public <T> T loadInstance(final Class<T> type, Serializable id) { T entity = (T) em.find(type, id); if (entity == null) { throw new NoResultException(); } return entity; } /** * Execute an HQL query. * * @param queryString a query expressed in Hibernate's query language * @return a List of entities containing the results of the query execution */ public List findByQuery(String queryString) { return findByQuery(queryString, new QueryParameter[0]); } /** * Execute an HQL query. * * @param queryString a query expressed in Hibernate's query language * @param parameters the (optional) parameters for the query. * @return a List of entities containing the results of the query execution */ public List findByQuery(String queryString, QueryParameter... parameters) { return findByQuery(queryString, 0, 0, parameters); } /** * Execute an HQL query. * * @param queryString a query expressed in Hibernate's query language * @param startIndex the index of the first item to be retrieved * @param maxResults the number of items to be retrieved, if <code>0</code> it retrieves ALL the items * @param parameters the (optional) parameters for the query. * @return a List of entities containing the results of the query execution */ public List findByQuery(String queryString, int startIndex, int maxResults, QueryParameter... parameters) { Query query = em.createQuery(queryString); for (QueryParameter parameter : parameters) { parameter.applyNamedParameterToQuery(query); } if (maxResults > 0) query.setMaxResults(maxResults); if (startIndex > 0) query.setFirstResult(startIndex); if (logger.isDebugEnabled()) logger.debug(query.toString()); return query.getResultList(); } /** * (non-Javadoc) * * @see org.tynamo.services.PersistenceService#getInstances(java.lang.Class) */ public <T> List<T> getInstances(final Class<T> type) { CriteriaBuilder qb = em.getCriteriaBuilder(); CriteriaQuery<T> query = qb.createQuery(type); //Root<T> entity = query.from(type); return em.createQuery(query).getResultList(); } public <T> List<T> getInstances(final Class<T> type, int startIndex, int maxResults) { CriteriaBuilder qb = em.getCriteriaBuilder(); CriteriaQuery<T> query = qb.createQuery(type); Root<T> entity = query.from(type); return getInstances(type, query, startIndex, maxResults); } /** * (non-Javadoc) * * @see org.tynamo.services.PersistenceService#save(java.lang.Object) */ public <T> T save(T instance) // throws ValidationException { /* try { */ TynamoClassDescriptor TynamoClassDescriptor = descriptorService.getClassDescriptor(instance.getClass()); /* check isTransient to avoid merging on entities not persisted yet. TRAILS-33 */ if (!TynamoClassDescriptor.getHasCyclicRelationships() || isTransient(instance, TynamoClassDescriptor)) { em.persist(instance); } else { instance = (T) em.merge(instance); } return instance; // } /* catch (DataAccessException dex) { throw new PersistenceException(dex); } */ } public void removeAll(Collection collection) { // em.deleteAll(collection); } public void remove(Object instance) { em.remove(instance); } public <T> List<T> getInstances(Class<T> type, CriteriaQuery query) { query = alterCriteria(type, query); query.distinct(true); //query.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY); return em.createQuery(query).getResultList(); //return query.getExecutableCriteria(entityManagerSource.getSession()).list(); } /** (non-Javadoc) */ /* public List<Class> getAllTypes() { ArrayList<Class> allTypes = new ArrayList<Class>(); for (Object classMetadata : getSessionFactory().getAllClassMetadata().values()) { allTypes.add(((ClassMetadata) classMetadata).getMappedClass(EntityMode.POJO)); } return allTypes; } */ public void reattach(Object model) { em.lock(model, LockModeType.NONE); } /** * (non-Javadoc) */ public <T> T getInstance(final Class<T> type) { CriteriaBuilder qb = em.getCriteriaBuilder(); CriteriaQuery<T> query = qb.createQuery(type); return (T) getInstance(type, query); } /** * Returns an entity's pk. * * @param data * @param classDescriptor * @return * @note (ascandroli): I tried to implement it using something like: */ public Serializable getIdentifier(final Object data, final TynamoClassDescriptor classDescriptor) { // Ignore classdescriptor for now return getIdentifier(data); } public List getOrphanInstances(CollectionDescriptor descriptor, Object owner) { //FIXME Not implemented yet return Collections.EMPTY_LIST; // TYNAMO-228 } public int count(Class type) { CriteriaBuilder qb = em.getCriteriaBuilder(); CriteriaQuery<Long> query = qb.createQuery(Long.class); Root entity = query.from(type); Expression<Long> count = qb.count(entity); query.select(count); Long size = em.createQuery(query).getSingleResult(); return size.intValue(); } public Serializable getIdentifier(final Object data) { return (Serializable) em.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(data); } public boolean isTransient(Object data, TynamoClassDescriptor classDescriptor) { return getIdentifier(data, classDescriptor) == null; } public List getInstances(final Object example, final TynamoClassDescriptor classDescriptor) { //create Criteria instance CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery searchQuery = cb.createQuery(example.getClass()); searchQuery = alterCriteria(example.getClass(), searchQuery); Root entity = searchQuery.from(example.getClass()); //loop over the example object's PropertyDescriptors for (TynamoPropertyDescriptor propertyDescriptor : classDescriptor.getPropertyDescriptors()) { //only add a Criterion to the Criteria instance if this property is searchable if (propertyDescriptor.isSearchable()) { String propertyName = propertyDescriptor.getName(); Class propertyClass = propertyDescriptor.getPropertyType(); Object value = null; //PropertyUtils.read(example, propertyName); //only add a Criterion to the Criteria instance if the value for this property is non-null if (value != null) { if (String.class.isAssignableFrom(propertyClass) && ((String) value).length() > 0) { searchQuery.where(cb.like(entity.get(propertyName), value.toString())); //.add(Restrictions.like(propertyName, value.toString(), MatchMode.ANYWHERE)); } /** * 'one'-end of many-to-one, one-to-one * * Just match the identifier */ else if (propertyDescriptor.isObjectReference()) { Serializable identifierValue = getIdentifier(value, descriptorService.getClassDescriptor(propertyDescriptor.getBeanType())); Type t = entity.getModel().getIdType(); SingularAttribute idAttribute = entity.getModel().getId(t.getJavaType()); searchQuery.where(cb.equal(entity.get(idAttribute), identifierValue)); } else if (propertyClass.isPrimitive()) { //primitive types: ignore zeroes in case of numeric types, ignore booleans anyway (TODO come up with something...) if (!propertyClass.equals(boolean.class) && ((Number) value).longValue() != 0) { searchQuery.where(cb.equal(entity.get(propertyName), value)); //searchQuery.add(Restrictions.eq(propertyName, value)); } } else if (propertyDescriptor.isCollection()) { //one-to-many or many-to-many CollectionDescriptor collectionDescriptor = (CollectionDescriptor) propertyDescriptor; TynamoClassDescriptor collectionClassDescriptor = descriptorService.getClassDescriptor(collectionDescriptor.getElementType()); if (collectionClassDescriptor != null) { String identifierName = collectionClassDescriptor.getIdentifierDescriptor().getName(); Collection<Serializable> identifierValues = new ArrayList<Serializable>(); Collection associatedItems = (Collection) value; if (associatedItems != null && associatedItems.size() > 0) { for (Object o : associatedItems) { identifierValues.add(getIdentifier(o, collectionClassDescriptor)); } //add a 'value IN collection' restriction searchQuery.where(cb.in(entity.get(identifierName)).value(identifierValues)); /*searchQuery.createCriteria(propertyName) .add(Restrictions.in(identifierName, identifierValues));*/ } } } } } } //searchQuery.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY); searchQuery.distinct(true); // FIXME This won't work because the shadow proxy doesn't implement SessionImplementor // that em is casted to. Maybe we should inject SessionManager instead // and obtain the Session from it return em.createQuery(searchQuery).getResultList(); //return searchQuery.getExecutableCriteria(entityManagerSource.getSession()).list(); } public int count(Class type, CriteriaQuery detachedCriteria) { //detachedCriteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY); detachedCriteria.distinct(true); final CriteriaQuery criteria = alterCriteria(type, detachedCriteria); //Criteria executableCriteria = criteria.getExecutableCriteria(entityManagerSource.getSession()).setProjection(Projections.rowCount()); return em.createQuery(criteria).getMaxResults(); //criteria. //return (Integer) executableCriteria.uniqueResult(); } public <T> List<T> getInstances(Class<T> type, final CriteriaQuery detachedCriteria, final int startIndex, final int maxResults) { return getInstances(alterCriteria(type, detachedCriteria), startIndex, maxResults); } public List getInstances(final CriteriaQuery detachedQuery, final int startIndex, final int maxResults) { detachedQuery.distinct(true);//setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY); //Criteria executableCriteria = detachedQuery.getExecutableCriteria(entityManagerSource.getSession()); Query q = em.createQuery(detachedQuery); if (startIndex >= 0) { q.setFirstResult(startIndex); } if (maxResults > 0) { q.setMaxResults(maxResults); } return q.getResultList(); } /** * This hook allows subclasses to modify the query criteria, such as for security * * @param detachedCriteria The original Criteria query * @return The modified Criteria query for execution */ protected CriteriaQuery alterCriteria(Class type, CriteriaQuery detachedCriteria) { return detachedCriteria; } @Override public <T> T merge(T instance) { return (T) em.merge(instance); } @Override public <T> T saveOrUpdate(T instance) { em.persist(instance); return instance; } @Override public <T> GridDataSource getGridDataSource(Class<T> type) { return new JpaGridDataSource<T>(em, type); } // public <T> T saveCollectionElement(String addExpression, T member, Object parent) { // T instance = save(member); // Utils.executeOgnlExpression(addExpression, member, parent); // save(parent); // return instance; // } // // // public void removeCollectionElement(String removeExpression, Object member, Object parent) { // Utils.executeOgnlExpression(removeExpression, member, parent); // save(parent); // remove(member); // } }