/** * 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.ambari.server.api.query; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Path; import javax.persistence.criteria.Root; import javax.persistence.metamodel.SingularAttribute; import org.apache.ambari.server.controller.predicate.AlwaysPredicate; import org.apache.ambari.server.controller.predicate.ArrayPredicate; import org.apache.ambari.server.controller.predicate.CategoryPredicate; import org.apache.ambari.server.controller.predicate.ComparisonPredicate; import org.apache.ambari.server.controller.predicate.PredicateVisitor; import org.apache.ambari.server.controller.predicate.UnaryPredicate; import org.apache.ambari.server.controller.spi.Predicate; import org.apache.ambari.server.controller.utilities.PredicateHelper; /** * The {@link JpaPredicateVisitor} is used to convert an Ambari * {@link Predicate} into a JPA {@link javax.persistence.criteria.Predicate}. */ public abstract class JpaPredicateVisitor<T> implements PredicateVisitor { /** * JPA entity manager */ private EntityManager m_entityManager; /** * Builds the {@link CriteriaQuery} from the {@link Predicate}. */ private CriteriaBuilder m_builder; /** * The root that the {@code from} clause requests from. */ final private Root<T> m_root; /** * The query to submit to JPA. */ final private CriteriaQuery<T> m_query; /** * The entity class that the root of the query is built from. */ final private Class<T> m_entityClass; /** * The last calculated predicate. */ private javax.persistence.criteria.Predicate m_lastPredicate = null; /** * A queue of lists of {@link javax.persistence.criteria.Predicate}. Every * time an {@code OR} or {@code AND} is encountered, a new chain (list) is * created and the prior list is enqeued. When the logical statement is * closed, the chain is completed and added to the prior chain's list. */ private ArrayDeque<List<javax.persistence.criteria.Predicate>> m_queue = new ArrayDeque<>(); /** * Constructor. * * @param entityManager * the EM used to get a {@link CriteriaBuilder}. * @param entityClass * the entity class being queried from. */ public JpaPredicateVisitor(EntityManager entityManager, Class<T> entityClass) { m_entityManager = entityManager; m_builder = m_entityManager.getCriteriaBuilder(); m_entityClass = entityClass; m_query = m_builder.createQuery(entityClass); m_root = m_query.from(entityClass); } /** * Gets the entity class that is the root type in the JPA {@code from} clause. * * @return the entity class (not {@code null}). */ public abstract Class<T> getEntityClass(); /** * Gets the {@link SingularAttribute}s mapped to the specified Ambari-style * property. * * @param propertyId * the Ambari-style property (not {@code null}). * @return the {@link SingularAttribute}, or {@code null} if no mapping * exists. */ public abstract List<? extends SingularAttribute<?, ?>> getPredicateMapping( String propertyId); /** * Gets the final JPA {@link javax.persistence.criteria.Predicate} after the * visitor is done traversing the Ambari {@link Predicate}. * * @return the predicate, or {@code null} if none. */ public javax.persistence.criteria.Predicate getJpaPredicate() { return m_lastPredicate; } /** * Gets the query to use along with {@link #getJpaPredicate()}. * * @return the query (not {@code null}). */ public CriteriaQuery<T> getCriteriaQuery() { return m_query; } /** * Gets the criteria builder used to construct the query and predicates. * * @return the builder (never {@code null}). */ public CriteriaBuilder getCriteriaBuilder() { return m_builder; } /** * {@inheritDoc} */ @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public void acceptComparisonPredicate(ComparisonPredicate predicate) { String propertyId = predicate.getPropertyId(); List<? extends SingularAttribute<?, ?>> singularAttributes = getPredicateMapping(propertyId); if (null == singularAttributes || singularAttributes.size() == 0) { return; } SingularAttribute<?, ?> lastSingularAttribute = null; Path<Comparable> path = null; for (SingularAttribute<?, ?> singularAttribute : singularAttributes) { lastSingularAttribute = singularAttribute; if (singularAttribute != null) { if (null == path) { path = m_root.get(singularAttribute.getName()); } else { path = path.get(singularAttribute.getName()); } } } if (null == path) { return; } String operator = predicate.getOperator(); Comparable<?> value = predicate.getValue(); // convert string to enum for proper JPA comparisons if (lastSingularAttribute != null) { Class<?> clazz = lastSingularAttribute.getJavaType(); if (clazz.isEnum()) { Class<? extends Enum> enumClass = (Class<? extends Enum>) clazz; value = Enum.valueOf(enumClass, value.toString()); } } javax.persistence.criteria.Predicate jpaPredicate = null; if ("=".equals(operator)) { jpaPredicate = m_builder.equal(path, value); } else if ("<".equals(operator)) { jpaPredicate = m_builder.lessThan(path, value); } else if ("<=".equals(operator)) { jpaPredicate = m_builder.lessThanOrEqualTo(path, value); } else if (">".equals(operator)) { jpaPredicate = m_builder.greaterThan(path, value); } else if (">=".equals(operator)) { jpaPredicate = m_builder.greaterThanOrEqualTo(path, value); } if (null == jpaPredicate) { return; } if (null == m_queue.peekLast()) { m_lastPredicate = jpaPredicate; } else { m_queue.peekLast().add(jpaPredicate); } } /** * {@inheritDoc} */ @Override public void acceptArrayPredicate(ArrayPredicate predicate) { // no predicates, no work Predicate[] predicates = predicate.getPredicates(); if (predicates.length == 0) { return; } // create a new list for all of the predicates in this chain List<javax.persistence.criteria.Predicate> predicateList = new ArrayList<>(); m_queue.add(predicateList); // visit every child predicate so it can be added to the list String operator = predicate.getOperator(); for (int i = 0; i < predicates.length; i++) { PredicateHelper.visit(predicates[i], this); } javax.persistence.criteria.Predicate jpaPredicate = null; // the list is done; deque and apply logical AND or OR predicateList = m_queue.pollLast(); if (predicateList != null) { javax.persistence.criteria.Predicate[] array = new javax.persistence.criteria.Predicate[predicateList.size()]; array = predicateList.toArray(array); if ("AND".equals(operator)) { jpaPredicate = m_builder.and(array); } else { jpaPredicate = m_builder.or(array); } if (null == m_queue.peekLast()) { m_lastPredicate = jpaPredicate; } else { m_queue.peekLast().add(jpaPredicate); } } } /** * {@inheritDoc} */ @Override public void acceptUnaryPredicate(UnaryPredicate predicate) { } /** * {@inheritDoc} */ @Override public void acceptAlwaysPredicate(AlwaysPredicate predicate) { } /** * {@inheritDoc} */ @Override public void acceptCategoryPredicate(CategoryPredicate predicate) { } }