/* * 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.deltaspike.data.impl.handler; import org.apache.deltaspike.core.util.OptionalUtil; import org.apache.deltaspike.data.api.EntityRepository; import org.apache.deltaspike.data.api.Query; import org.apache.deltaspike.data.impl.builder.QueryBuilder; import org.apache.deltaspike.data.impl.meta.RequiresTransaction; import org.apache.deltaspike.data.impl.property.Property; import org.apache.deltaspike.data.impl.property.query.NamedPropertyCriteria; import org.apache.deltaspike.data.impl.property.query.PropertyQueries; import org.apache.deltaspike.data.impl.util.EntityUtils; import org.apache.deltaspike.data.impl.util.jpa.PersistenceUnitUtilDelegateFactory; import org.apache.deltaspike.data.spi.DelegateQueryHandler; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.PersistenceUnitUtil; import javax.persistence.QueryHint; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.metamodel.SingularAttribute; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import static org.apache.deltaspike.core.util.ArraysUtils.isEmpty; import static org.apache.deltaspike.data.impl.util.QueryUtils.isString; /** * Implement basic functionality from the {@link EntityRepository}. * * @param <E> Entity type. * @param <PK> Primary key type, must be a serializable. */ public class EntityRepositoryHandler<E, PK extends Serializable> implements EntityRepository<E, PK>, DelegateQueryHandler { private static final Logger log = Logger.getLogger(EntityRepositoryHandler.class.getName()); @Inject private CdiQueryInvocationContext context; @Override @RequiresTransaction public E save(E entity) { if (context.isNew(entity)) { entityManager().persist(entity); return entity; } return entityManager().merge(entity); } @Override @RequiresTransaction public E saveAndFlush(E entity) { E result = save(entity); flush(); return result; } @Override @RequiresTransaction public E saveAndFlushAndRefresh(E entity) { E result = saveAndFlush(entity); entityManager().refresh(result); return result; } @Override @RequiresTransaction public void refresh(E entity) { entityManager().refresh(entity); } @Override public E findBy(PK primaryKey) { Query query = context.getMethod().getAnnotation(Query.class); if (query != null && query.hints().length > 0) { Map<String, Object> hints = new HashMap<String, Object>(); for (QueryHint hint : query.hints()) { hints.put(hint.name(), hint.value()); } return entityManager().find(entityClass(), primaryKey, hints); } else { return entityManager().find(entityClass(), primaryKey); } } public Object findOptional(PK primaryKey) { Object found = null; try { found = findBy(primaryKey); } catch (Exception e) { } return OptionalUtil.wrap(found); } @Override public List<E> findBy(E example, SingularAttribute<E, ?>... attributes) { return findBy(example, -1, -1, attributes); } @Override public List<E> findBy(E example, int start, int max, SingularAttribute<E, ?>... attributes) { return executeExampleQuery(example, start, max, false, attributes); } @Override public List<E> findByLike(E example, SingularAttribute<E, ?>... attributes) { return findByLike(example, -1, -1, attributes); } @Override public List<E> findByLike(E example, int start, int max, SingularAttribute<E, ?>... attributes) { return executeExampleQuery(example, start, max, true, attributes); } @SuppressWarnings("unchecked") @Override public List<E> findAll() { return context.applyRestrictions(entityManager().createQuery(allQuery(), entityClass())).getResultList(); } @SuppressWarnings("unchecked") @Override public List<E> findAll(int start, int max) { TypedQuery<E> query = entityManager().createQuery(allQuery(), entityClass()); if (start > 0) { query.setFirstResult(start); } if (max > 0) { query.setMaxResults(max); } return context.applyRestrictions(query).getResultList(); } @Override public Long count() { return (Long) context.applyRestrictions(entityManager().createQuery(countQuery(), Long.class)) .getSingleResult(); } @Override public Long count(E example, SingularAttribute<E, ?>... attributes) { return executeCountQuery(example, false, attributes); } @Override public Long countLike(E example, SingularAttribute<E, ?>... attributes) { return executeCountQuery(example, true, attributes); } @SuppressWarnings("unchecked") @Override public PK getPrimaryKey(E entity) { return (PK) persistenceUnitUtil().getIdentifier(entity); } @Override @RequiresTransaction public void remove(E entity) { entityManager().remove(entity); } @Override @RequiresTransaction public void removeAndFlush(E entity) { entityManager().remove(entity); flush(); } @Override @RequiresTransaction public void attachAndRemove(E entity) { if (!entityManager().contains(entity)) { entity = entityManager().merge(entity); } remove(entity); } @Override @RequiresTransaction public void flush() { entityManager().flush(); } public EntityManager entityManager() { return context.getEntityManager(); } public CriteriaQuery<E> criteriaQuery() { return entityManager().getCriteriaBuilder().createQuery(entityClass()); } public TypedQuery<E> typedQuery(String qlString) { return entityManager().createQuery(qlString, entityClass()); } @SuppressWarnings("unchecked") public Class<E> entityClass() { return (Class<E>) context.getEntityClass(); } public String tableName() { return EntityUtils.tableName(context.getEntityClass(), entityManager()); } public String entityName() { return context.getRepositoryMethod().getRepository().getRepositoryEntity().getEntityName(); } // ---------------------------------------------------------------------------- // PRIVATE // ---------------------------------------------------------------------------- private String allQuery() { return QueryBuilder.selectQuery(entityName()); } private String countQuery() { return QueryBuilder.countQuery(entityName()); } private String exampleQuery(String queryBase, List<Property<Object>> properties, boolean useLikeOperator) { StringBuilder jpqlQuery = new StringBuilder(queryBase).append(" where "); jpqlQuery.append(prepareWhere(properties, useLikeOperator)); return jpqlQuery.toString(); } private void addParameters(TypedQuery<?> query, E example, List<Property<Object>> properties, boolean useLikeOperator) { for (Property<Object> property : properties) { property.setAccessible(); query.setParameter(property.getName(), transform(property.getValue(example), useLikeOperator)); } } private Object transform(Object value, final boolean useLikeOperator) { if (value != null && useLikeOperator && isString(value)) { // seems to be an OpenJPA bug: // parameters in querys fail validation, e.g. UPPER(e.name) like UPPER(:name) String result = ((String) value).toUpperCase(); return "%" + result + "%"; } return value; } private String prepareWhere(List<Property<Object>> properties, boolean useLikeOperator) { Iterator<Property<Object>> iterator = properties.iterator(); StringBuilder result = new StringBuilder(); while (iterator.hasNext()) { Property<Object> property = iterator.next(); String name = property.getName(); if (useLikeOperator && property.getJavaClass().getName().equals(String.class.getName())) { result.append("UPPER(e.").append(name).append(") like :").append(name) .append(iterator.hasNext() ? " and " : ""); } else { result.append("e.").append(name).append(" = :").append(name).append(iterator.hasNext() ? " and " : ""); } } return result.toString(); } private List<String> extractPropertyNames(SingularAttribute<E, ?>... attributes) { List<String> result = new ArrayList<String>(attributes.length); for (SingularAttribute<E, ?> attribute : attributes) { result.add(attribute.getName()); } return result; } private List<Property<Object>> extractProperties(SingularAttribute<E, ?>... attributes) { List<String> names = extractPropertyNames(attributes); List<Property<Object>> properties = PropertyQueries.createQuery(entityClass()) .addCriteria(new NamedPropertyCriteria(names.toArray(new String[]{}))).getResultList(); return properties; } private List<E> executeExampleQuery(E example, int start, int max, boolean useLikeOperator, SingularAttribute<E, ?>... attributes) { // Not sure if this should be the intended behaviour // when we don't get any attributes maybe we should // return a empty list instead of all results if (isEmpty(attributes)) { return findAll(start, max); } List<Property<Object>> properties = extractProperties(attributes); String jpqlQuery = exampleQuery(allQuery(), properties, useLikeOperator); log.log(Level.FINER, "findBy|findByLike: Created query {0}", jpqlQuery); TypedQuery<E> query = entityManager().createQuery(jpqlQuery, entityClass()); // set starting position if (start > 0) { query.setFirstResult(start); } // set maximum results if (max > 0) { query.setMaxResults(max); } context.applyRestrictions(query); addParameters(query, example, properties, useLikeOperator); return query.getResultList(); } private Long executeCountQuery(E example, boolean useLikeOperator, SingularAttribute<E, ?>... attributes) { if (isEmpty(attributes)) { return count(); } List<Property<Object>> properties = extractProperties(attributes); String jpqlQuery = exampleQuery(countQuery(), properties, useLikeOperator); log.log(Level.FINER, "count: Created query {0}", jpqlQuery); TypedQuery<Long> query = entityManager().createQuery(jpqlQuery, Long.class); addParameters(query, example, properties, useLikeOperator); context.applyRestrictions(query); return query.getSingleResult(); } private PersistenceUnitUtil persistenceUnitUtil() { return PersistenceUnitUtilDelegateFactory.get(entityManager()); } }