content: /*
   * 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.ArrayList;
  import java.util.Collections;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Set;
  
  import jakarta.persistence.criteria.CriteriaBuilder;
  import jakarta.persistence.criteria.CriteriaQuery;
  import jakarta.persistence.criteria.Order;
  import jakarta.persistence.criteria.Path;
  import jakarta.persistence.criteria.Root;
  import jakarta.persistence.metamodel.SingularAttribute;
  
  import org.apache.ambari.server.controller.spi.SortRequest;
  import org.apache.ambari.server.controller.spi.SortRequestProperty;
  import org.apache.commons.lang.ObjectUtils;
  
  /**
   * The {@link JpaSortBuilder} class is used to convert and Ambari
   * {@link SortRequest} and list of {@link SortRequestProperty} into a JPA
   * {@link Order} list. This can then be given to a {@link CriteriaBuilder} to
   * provide sorting in the JPA layer during a query.
   * <p/>
   * This classes uses the {@link JpaPredicateVisitor} for the entity being
   * queried in order to convert the entity fields into {@link Path} expressions.
   */
  public class JpaSortBuilder<T> {
  
    /**
     * Constructor.
     *
     */
    public JpaSortBuilder() {
    }
  
    /**
     * Builds the list of sort orders based on the supplied request and JPA
     * predicate visitor.
     *
     * @param sortRequest
     *          the Ambari sort request properties to turn into a JPA sort
     *          request. If {@code null} or the {@link SortRequestProperty} list
     *          is null, an empty list is returned.
     * @param visitor
     *          a visitor that knows how to convert the Ambari properties into
     *          {@link SingularAttribute} (not {@code null}).
     * @return a list of sorts or an empty list if none (never {@code null}).
     */
    public List<Order> buildSortOrders(SortRequest sortRequest,
        JpaPredicateVisitor<T> visitor) {
  
      if (null == sortRequest || null == sortRequest.getProperties()) {
        return Collections.emptyList();
      }
  
      CriteriaBuilder builder = visitor.getCriteriaBuilder();
      List<SortRequestProperty> sortProperties = sortRequest.getProperties();
      List<Order> sortOrders = new ArrayList<>(sortProperties.size());
  
      for (SortRequestProperty sort : sortProperties) {
        String propertyId = sort.getPropertyId();
  
        List<? extends SingularAttribute<?, ?>> singularAttributes = visitor.getPredicateMapping(propertyId);
  
        if (null == singularAttributes || singularAttributes.size() == 0) {
          continue;
        }
  
        Path<?> path = null;
        for (SingularAttribute<?, ?> singularAttribute : singularAttributes) {
          if (null == path) {
  
            CriteriaQuery<T> query = visitor.getCriteriaQuery();
            Set<Root<?>> roots = query.getRoots();
  
            // if there are existing roots; use the existing roots to prevent more
            // roots from being added potentially causing a cartesian product
            // where we don't want one
            if (null != roots && !roots.isEmpty()) {
              Iterator<Root<?>> iterator = roots.iterator();
              while (iterator.hasNext()) {
                Root<?> root = iterator.next();
  
                Class<?> visitorEntityClass = visitor.getEntityClass();
                if (ObjectUtils.equals(visitorEntityClass, root.getJavaType())
                    || ObjectUtils.equals(visitorEntityClass, root.getModel().getJavaType())) {
                  path = root.get(singularAttribute.getName());
                  break;
                }
              }
            }
  
            // no roots exist already which match this entity class, create a new
            // path
            if (null == path) {
              path = query.from(visitor.getEntityClass()).get(singularAttribute.getName());
            }
          } else {
            path = path.get(singularAttribute.getName());
          }
        }
  
        Order sortOrder = null;
        if (sort.getOrder() == org.apache.ambari.server.controller.spi.SortRequest.Order.ASC) {
          sortOrder = builder.asc(path);
        } else {
          sortOrder = builder.desc(path);
        }
  
        sortOrders.add(sortOrder);
      }
  
      return sortOrders;
    }
  }
  