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}