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}