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
   * <p>
   * http://www.apache.org/licenses/LICENSE-2.0
   * <p>
   * 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.activemq.broker.jmx;
  
  import static org.apache.activemq.util.IntrospectionSupport.findGetterMethod;
  
  import java.io.IOException;
  import java.io.Serializable;
  import java.io.StringWriter;
  import java.lang.reflect.Method;
  import java.util.ArrayList;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.function.Predicate;
  import java.util.stream.Collectors;
  
  import javax.management.ObjectName;
  
  import org.apache.activemq.advisory.AdvisorySupport;
  import org.apache.activemq.command.ActiveMQTopic;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import com.fasterxml.jackson.databind.ObjectMapper;
  
  /**
   * Defines a query API for destinations MBeans
   *
   * Typical usage
   *
   *         return DestinationsViewFilter.create(filter)
   *                .setDestinations(broker.getQueueViews())
   *                .filter(page, pageSize);
   *
   * where 'filter' is JSON representation of the query, like
   *
   * {name: '77', filter:'nonEmpty', sortColumn:'queueSize', sortOrder:'desc'}
   *
   * This returns a JSON map, containing filtered map of MBeans in the "data" field and total number of destinations that match criteria in the "count" field.
   * The result will be properly paged, according to 'page' and 'pageSize' parameters.
   *
   */
  public class DestinationsViewFilter implements Serializable {
      private static final Logger LOG = LoggerFactory.getLogger(DestinationsViewFilter.class);
  
      private static final long serialVersionUID = 1L;
  
      /**
       * Name pattern used to filter destinations
       */
      String name;
  
      /**
       * Arbitrary filter key to be applied to the destinations. Currently only simple predefined filters has been implemented:
       *
       * empty - return only empty queues (queueSize = 0)
       * nonEmpty - return only non-empty queues queueSize != 0)
       * noConsumer - return only destinations that doesn't have consumers
       * nonAdvisory - return only non-Advisory topics
       *
       * For more implementation details see {@link DestinationsViewFilter.getPredicate}
       *
       */
      String filter;
  
      /**
       * Sort destinations by this {@link DestinationView} property
       */
      String sortColumn = "name";
  
      /**
       * Order of sorting - 'asc' or 'desc'
       */
      String sortOrder = "asc";
  
      Map<ObjectName, DestinationView> destinations;
  
  
      public DestinationsViewFilter() {
      }
  
      /**
       * Creates an object from the JSON string
       *
       */
      public static DestinationsViewFilter create(String json) throws IOException {
          ObjectMapper mapper = new ObjectMapper();
          if (json == null) {
              return new DestinationsViewFilter();
          }
          json = json.trim();
          if (json.length() == 0 || json.equals("{}")) {
              return new DestinationsViewFilter();
          }
          return mapper.readerFor(DestinationsViewFilter.class).readValue(json);
      }
  
      /**
       * Destination MBeans to be queried
       */
      public DestinationsViewFilter setDestinations(Map<ObjectName, DestinationView> destinations) {
          this.destinations = destinations;
          return this;
      }
  
      /**
       * Filter, sort and page results.
       *
       * Returns JSON map with resulting destination views and total number of matched destinations
       *
       * @param page - defines result page to be returned
       * @param pageSize - defines page size to be used
       * @throws IOException
       */
      String filter(int page, int pageSize) throws IOException {
          final ObjectMapper mapper = new ObjectMapper();
          final Predicate<DestinationView> predicate = getPredicate();
          final Map<ObjectName, DestinationView> filteredDestinations = new HashMap<>(destinations);
          destinations = filteredDestinations.entrySet().stream().filter(d -> predicate.test(d.getValue())).collect(
                  Collectors.toMap(d -> d.getKey(), d -> d.getValue()));
  
          Map<ObjectName, DestinationView> pagedDestinations = getPagedDestinations(page, pageSize);
          Map<String, Object> result = new HashMap<String, Object>();
          result.put("data", pagedDestinations);
          result.put("count", destinations.size());
          StringWriter writer = new StringWriter();
          mapper.writeValue(writer, result);
          return writer.toString();
      }
  
      Map<ObjectName, DestinationView> getPagedDestinations(int page, int pageSize) {
          int start = (page - 1) * pageSize;
          int end = Math.min(page * pageSize, destinations.size());
  
          final List<Map.Entry<ObjectName, DestinationView>> sortedCopy = new ArrayList<>(destinations.entrySet());
          sortedCopy.sort(getComparator());
          return sortedCopy.subList(start, end).stream().collect(
                  Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()));
      }
  
      Predicate<DestinationView> getPredicate() {
          return new Predicate<DestinationView>() {
              @Override
              public boolean test(DestinationView input) {
                  boolean match = true;
                  if (getName() != null && !getName().isEmpty()) {
                      match = input.getName().contains(getName());
                  }
  
                  if (match) {
                      if (getFilter().equals("empty")) {
                          match = input.getQueueSize() == 0;
                      }
                      if (getFilter().equals("nonEmpty")) {
                          match = input.getQueueSize() != 0;
                      }
                      if (getFilter().equals("noConsumer")) {
                          match = input.getConsumerCount() == 0;
                      }
                      if (getFilter().equals("nonAdvisory")) {
                          return !(input instanceof TopicView && AdvisorySupport.isAdvisoryTopic(new ActiveMQTopic(input.getName())));
                      }
                  }
  
                  return match;
              }
          };
      }
  
      Comparator<Map.Entry<ObjectName, DestinationView>> getComparator() {
          return new Comparator<Map.Entry<ObjectName, DestinationView>>() {
  
              Method getter = findGetterMethod(DestinationView.class, getSortColumn());
  
              @SuppressWarnings({ "unchecked", "rawtypes" })
              @Override
              public int compare(Map.Entry<ObjectName, DestinationView> left, Map.Entry<ObjectName, DestinationView> right) {
                  try {
                      if (getter != null) {
                          Object leftValue = getter.invoke(left.getValue());
                          Object rightValue = getter.invoke(right.getValue());
                          if (leftValue instanceof Comparable && rightValue instanceof Comparable) {
                              if (getSortOrder().equalsIgnoreCase("desc")) {
                                  return ((Comparable) rightValue).compareTo(leftValue);
                              } else {
                                  return ((Comparable) leftValue).compareTo(rightValue);
                              }
                          }
                      }
                      return 0;
                  } catch (Exception e) {
                      LOG.info("Exception sorting destinations", e);
                      return 0;
                  }
              }
          };
      }
  
      public Map<ObjectName, DestinationView> getDestinations() {
          return destinations;
      }
  
      public String getName() {
          return name;
      }
  
      public void setName(String name) {
          this.name = name;
      }
  
      public String getFilter() {
          return filter;
      }
  
      public void setFilter(String filter) {
          this.filter = filter;
      }
  
      public String getSortOrder() {
          return sortOrder;
      }
  
      public void setSortOrder(String sortOrder) {
          this.sortOrder = sortOrder;
      }
  
      public String getSortColumn() {
          return sortColumn;
      }
  
      public void setSortColumn(String sortColumn) {
          this.sortColumn = sortColumn;
      }
  }
  