001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * <p>
009 * http://www.apache.org/licenses/LICENSE-2.0
010 * <p>
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.broker.jmx;
018
019import static org.apache.activemq.util.IntrospectionSupport.findGetterMethod;
020
021import java.io.IOException;
022import java.io.Serializable;
023import java.io.StringWriter;
024import java.lang.reflect.Method;
025import java.util.ArrayList;
026import java.util.Comparator;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.function.Predicate;
031import java.util.stream.Collectors;
032
033import javax.management.ObjectName;
034
035import org.apache.activemq.advisory.AdvisorySupport;
036import org.apache.activemq.command.ActiveMQTopic;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040import com.fasterxml.jackson.databind.ObjectMapper;
041
042/**
043 * Defines a query API for destinations MBeans
044 *
045 * Typical usage
046 *
047 *         return DestinationsViewFilter.create(filter)
048 *                .setDestinations(broker.getQueueViews())
049 *                .filter(page, pageSize);
050 *
051 * where 'filter' is JSON representation of the query, like
052 *
053 * {name: '77', filter:'nonEmpty', sortColumn:'queueSize', sortOrder:'desc'}
054 *
055 * 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.
056 * The result will be properly paged, according to 'page' and 'pageSize' parameters.
057 *
058 */
059public class DestinationsViewFilter implements Serializable {
060    private static final Logger LOG = LoggerFactory.getLogger(DestinationsViewFilter.class);
061
062    private static final long serialVersionUID = 1L;
063
064    /**
065     * Name pattern used to filter destinations
066     */
067    String name;
068
069    /**
070     * Arbitrary filter key to be applied to the destinations. Currently only simple predefined filters has been implemented:
071     *
072     * empty - return only empty queues (queueSize = 0)
073     * nonEmpty - return only non-empty queues queueSize != 0)
074     * noConsumer - return only destinations that doesn't have consumers
075     * nonAdvisory - return only non-Advisory topics
076     *
077     * For more implementation details see {@link DestinationsViewFilter.getPredicate}
078     *
079     */
080    String filter;
081
082    /**
083     * Sort destinations by this {@link DestinationView} property
084     */
085    String sortColumn = "name";
086
087    /**
088     * Order of sorting - 'asc' or 'desc'
089     */
090    String sortOrder = "asc";
091
092    Map<ObjectName, DestinationView> destinations;
093
094
095    public DestinationsViewFilter() {
096    }
097
098    /**
099     * Creates an object from the JSON string
100     *
101     */
102    public static DestinationsViewFilter create(String json) throws IOException {
103        ObjectMapper mapper = new ObjectMapper();
104        if (json == null) {
105            return new DestinationsViewFilter();
106        }
107        json = json.trim();
108        if (json.length() == 0 || json.equals("{}")) {
109            return new DestinationsViewFilter();
110        }
111        return mapper.readerFor(DestinationsViewFilter.class).readValue(json);
112    }
113
114    /**
115     * Destination MBeans to be queried
116     */
117    public DestinationsViewFilter setDestinations(Map<ObjectName, DestinationView> destinations) {
118        this.destinations = destinations;
119        return this;
120    }
121
122    /**
123     * Filter, sort and page results.
124     *
125     * Returns JSON map with resulting destination views and total number of matched destinations
126     *
127     * @param page - defines result page to be returned
128     * @param pageSize - defines page size to be used
129     * @throws IOException
130     */
131    String filter(int page, int pageSize) throws IOException {
132        final ObjectMapper mapper = new ObjectMapper();
133        final Predicate<DestinationView> predicate = getPredicate();
134        final Map<ObjectName, DestinationView> filteredDestinations = new HashMap<>(destinations);
135        destinations = filteredDestinations.entrySet().stream().filter(d -> predicate.test(d.getValue())).collect(
136                Collectors.toMap(d -> d.getKey(), d -> d.getValue()));
137
138        Map<ObjectName, DestinationView> pagedDestinations = getPagedDestinations(page, pageSize);
139        Map<String, Object> result = new HashMap<String, Object>();
140        result.put("data", pagedDestinations);
141        result.put("count", destinations.size());
142        StringWriter writer = new StringWriter();
143        mapper.writeValue(writer, result);
144        return writer.toString();
145    }
146
147    Map<ObjectName, DestinationView> getPagedDestinations(int page, int pageSize) {
148        int start = (page - 1) * pageSize;
149        int end = Math.min(page * pageSize, destinations.size());
150
151        final List<Map.Entry<ObjectName, DestinationView>> sortedCopy = new ArrayList<>(destinations.entrySet());
152        sortedCopy.sort(getComparator());
153        return sortedCopy.subList(start, end).stream().collect(
154                Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()));
155    }
156
157    Predicate<DestinationView> getPredicate() {
158        return new Predicate<DestinationView>() {
159            @Override
160            public boolean test(DestinationView input) {
161                boolean match = true;
162                if (getName() != null && !getName().isEmpty()) {
163                    match = input.getName().contains(getName());
164                }
165
166                if (match) {
167                    if (getFilter().equals("empty")) {
168                        match = input.getQueueSize() == 0;
169                    }
170                    if (getFilter().equals("nonEmpty")) {
171                        match = input.getQueueSize() != 0;
172                    }
173                    if (getFilter().equals("noConsumer")) {
174                        match = input.getConsumerCount() == 0;
175                    }
176                    if (getFilter().equals("nonAdvisory")) {
177                        return !(input instanceof TopicView && AdvisorySupport.isAdvisoryTopic(new ActiveMQTopic(input.getName())));
178                    }
179                }
180
181                return match;
182            }
183        };
184    }
185
186    Comparator<Map.Entry<ObjectName, DestinationView>> getComparator() {
187        return new Comparator<Map.Entry<ObjectName, DestinationView>>() {
188
189            Method getter = findGetterMethod(DestinationView.class, getSortColumn());
190
191            @SuppressWarnings({ "unchecked", "rawtypes" })
192            @Override
193            public int compare(Map.Entry<ObjectName, DestinationView> left, Map.Entry<ObjectName, DestinationView> right) {
194                try {
195                    if (getter != null) {
196                        Object leftValue = getter.invoke(left.getValue());
197                        Object rightValue = getter.invoke(right.getValue());
198                        if (leftValue instanceof Comparable && rightValue instanceof Comparable) {
199                            if (getSortOrder().equalsIgnoreCase("desc")) {
200                                return ((Comparable) rightValue).compareTo(leftValue);
201                            } else {
202                                return ((Comparable) leftValue).compareTo(rightValue);
203                            }
204                        }
205                    }
206                    return 0;
207                } catch (Exception e) {
208                    LOG.info("Exception sorting destinations", e);
209                    return 0;
210                }
211            }
212        };
213    }
214
215    public Map<ObjectName, DestinationView> getDestinations() {
216        return destinations;
217    }
218
219    public String getName() {
220        return name;
221    }
222
223    public void setName(String name) {
224        this.name = name;
225    }
226
227    public String getFilter() {
228        return filter;
229    }
230
231    public void setFilter(String filter) {
232        this.filter = filter;
233    }
234
235    public String getSortOrder() {
236        return sortOrder;
237    }
238
239    public void setSortOrder(String sortOrder) {
240        this.sortOrder = sortOrder;
241    }
242
243    public String getSortColumn() {
244        return sortColumn;
245    }
246
247    public void setSortColumn(String sortColumn) {
248        this.sortColumn = sortColumn;
249    }
250}