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 com.fasterxml.jackson.databind.ObjectMapper;
020import com.google.common.base.Predicate;
021import com.google.common.collect.ImmutableMap;
022import com.google.common.collect.Maps;
023import com.google.common.collect.Ordering;
024import org.apache.activemq.advisory.AdvisorySupport;
025import org.apache.activemq.command.ActiveMQTopic;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029import javax.management.ObjectName;
030import java.io.IOException;
031import java.io.Serializable;
032import java.io.StringWriter;
033import java.lang.reflect.Method;
034import java.util.HashMap;
035import java.util.Map;
036
037import static org.apache.activemq.util.IntrospectionSupport.*;
038
039/**
040 * Defines a query API for destinations MBeans
041 *
042 * Typical usage
043 *
044 *         return DestinationsViewFilter.create(filter)
045 *                .setDestinations(broker.getQueueViews())
046 *                .filter(page, pageSize);
047 *
048 * where 'filter' is JSON representation of the query, like
049 *
050 * {name: '77', filter:'nonEmpty', sortColumn:'queueSize', sortOrder:'desc'}
051 *
052 * 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.
053 * The result will be properly paged, according to 'page' and 'pageSize' parameters.
054 *
055 */
056public class DestinationsViewFilter implements Serializable {
057    private static final Logger LOG = LoggerFactory.getLogger(DestinationsViewFilter.class);
058
059    private static final long serialVersionUID = 1L;
060
061    /**
062     * Name pattern used to filter destinations
063     */
064    String name;
065
066    /**
067     * Arbitrary filter key to be applied to the destinations. Currently only simple predefined filters has been implemented:
068     *
069     * empty - return only empty queues (queueSize = 0)
070     * nonEmpty - return only non-empty queues queueSize != 0)
071     * noConsumer - return only destinations that doesn't have consumers
072     * nonAdvisory - return only non-Advisory topics
073     *
074     * For more implementation details see {@link DestinationsViewFilter.getPredicate}
075     *
076     */
077    String filter;
078
079    /**
080     * Sort destinations by this {@link DestinationView} property
081     */
082    String sortColumn = "name";
083
084    /**
085     * Order of sorting - 'asc' or 'desc'
086     */
087    String sortOrder = "asc";
088
089    Map<ObjectName, DestinationView> destinations;
090
091
092    public DestinationsViewFilter() {
093    }
094
095    /**
096     * Creates an object from the JSON string
097     *
098     */
099    public static DestinationsViewFilter create(String json) throws IOException {
100        ObjectMapper mapper = new ObjectMapper();
101        if (json == null) {
102            return new DestinationsViewFilter();
103        }
104        json = json.trim();
105        if (json.length() == 0 || json.equals("{}")) {
106            return new DestinationsViewFilter();
107        }
108        return mapper.readerFor(DestinationsViewFilter.class).readValue(json);
109    }
110
111    /**
112     * Destination MBeans to be queried
113     */
114    public DestinationsViewFilter setDestinations(Map<ObjectName, DestinationView> destinations) {
115        this.destinations = destinations;
116        return this;
117    }
118
119    /**
120     * Filter, sort and page results.
121     *
122     * Returns JSON map with resulting destination views and total number of matched destinations
123     *
124     * @param page - defines result page to be returned
125     * @param pageSize - defines page size to be used
126     * @throws IOException
127     */
128    String filter(int page, int pageSize) throws IOException {
129        ObjectMapper mapper = new ObjectMapper();
130        destinations = Maps.filterValues(destinations, getPredicate());
131        Map<ObjectName, DestinationView> pagedDestinations = getPagedDestinations(page, pageSize);
132        Map<String, Object> result = new HashMap<String, Object>();
133        result.put("data", pagedDestinations);
134        result.put("count", destinations.size());
135        StringWriter writer = new StringWriter();
136        mapper.writeValue(writer, result);
137        return writer.toString();
138    }
139
140    Map<ObjectName, DestinationView> getPagedDestinations(int page, int pageSize) {
141        ImmutableMap.Builder<ObjectName, DestinationView> builder = ImmutableMap.builder();
142        int start = (page - 1) * pageSize;
143        int end = Math.min(page * pageSize, destinations.size());
144        int i = 0;
145        for (Map.Entry<ObjectName, DestinationView> entry :
146                getOrdering().sortedCopy(destinations.entrySet())) {
147            if (i >= start && i < end) {
148                builder.put(entry.getKey(), entry.getValue());
149            }
150            i++;
151        }
152        return builder.build();
153    }
154
155    Predicate<DestinationView> getPredicate() {
156        return new Predicate<DestinationView>() {
157            @Override
158            public boolean apply(DestinationView input) {
159                boolean match = true;
160                if (getName() != null && !getName().isEmpty()) {
161                    match = input.getName().contains(getName());
162                }
163
164                if (match) {
165                    if (getFilter().equals("empty")) {
166                        match = input.getQueueSize() == 0;
167                    }
168                    if (getFilter().equals("nonEmpty")) {
169                        match = input.getQueueSize() != 0;
170                    }
171                    if (getFilter().equals("noConsumer")) {
172                        match = input.getConsumerCount() == 0;
173                    }
174                    if (getFilter().equals("nonAdvisory")) {
175                        return !(input instanceof TopicView && AdvisorySupport.isAdvisoryTopic(new ActiveMQTopic(input.getName())));
176                    }
177                }
178
179                return match;
180            }
181        };
182    }
183
184    Ordering<Map.Entry<ObjectName, DestinationView>> getOrdering() {
185        return new Ordering<Map.Entry<ObjectName, DestinationView>>() {
186
187            Method getter = findGetterMethod(DestinationView.class, getSortColumn());
188
189            @Override
190            public int compare(Map.Entry<ObjectName, DestinationView> left, Map.Entry<ObjectName, DestinationView> right) {
191                try {
192                    if (getter != null) {
193                        Object leftValue = getter.invoke(left.getValue());
194                        Object rightValue = getter.invoke(right.getValue());
195                        if (leftValue instanceof Comparable && rightValue instanceof Comparable) {
196                            if (getSortOrder().toLowerCase().equals("desc")) {
197                                return ((Comparable) rightValue).compareTo(leftValue);
198                            } else {
199                                return ((Comparable) leftValue).compareTo(rightValue);
200                            }
201                        }
202                    }
203                    return 0;
204                } catch (Exception e) {
205                    LOG.info("Exception sorting destinations", e);
206                    return 0;
207                }
208            }
209        };
210    }
211
212    public Map<ObjectName, DestinationView> getDestinations() {
213        return destinations;
214    }
215
216    public String getName() {
217        return name;
218    }
219
220    public void setName(String name) {
221        this.name = name;
222    }
223
224    public String getFilter() {
225        return filter;
226    }
227
228    public void setFilter(String filter) {
229        this.filter = filter;
230    }
231
232    public String getSortOrder() {
233        return sortOrder;
234    }
235
236    public void setSortOrder(String sortOrder) {
237        this.sortOrder = sortOrder;
238    }
239
240    public String getSortColumn() {
241        return sortColumn;
242    }
243
244    public void setSortColumn(String sortColumn) {
245        this.sortColumn = sortColumn;
246    }
247}