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 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 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.plugin; 018 019import org.apache.activemq.broker.Broker; 020import org.apache.activemq.broker.BrokerFilter; 021import org.apache.activemq.broker.ConnectionContext; 022import org.apache.activemq.broker.region.Subscription; 023import org.apache.activemq.command.ConsumerInfo; 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027import java.io.File; 028import java.io.FileInputStream; 029import java.io.FileOutputStream; 030import java.io.IOException; 031import java.io.ObjectInputStream; 032import java.io.ObjectOutputStream; 033import java.util.Collections; 034import java.util.HashSet; 035import java.util.Set; 036import java.util.concurrent.ConcurrentHashMap; 037 038/** 039 * A plugin which allows the caching of the selector from a subscription queue. 040 * <p/> 041 * This stops the build-up of unwanted messages, especially when consumers may 042 * disconnect from time to time when using virtual destinations. 043 * <p/> 044 * This is influenced by code snippets developed by Maciej Rakowicz 045 * 046 * @author Roelof Naude roelof(dot)naude(at)gmail.com 047 * @see https://issues.apache.org/activemq/browse/AMQ-3004 048 * @see http://mail-archives.apache.org/mod_mbox/activemq-users/201011.mbox/%3C8A013711-2613-450A-A487-379E784AF1D6@homeaway.co.uk%3E 049 */ 050public class SubQueueSelectorCacheBroker extends BrokerFilter implements Runnable { 051 private static final Logger LOG = LoggerFactory.getLogger(SubQueueSelectorCacheBroker.class); 052 053 /** 054 * The subscription's selector cache. We cache compiled expressions keyed 055 * by the target destination. 056 */ 057 private ConcurrentHashMap<String, Set<String>> subSelectorCache = new ConcurrentHashMap<String, Set<String>>(); 058 059 private final File persistFile; 060 061 private boolean running = true; 062 private Thread persistThread; 063 private static final long MAX_PERSIST_INTERVAL = 600000; 064 private static final String SELECTOR_CACHE_PERSIST_THREAD_NAME = "SelectorCachePersistThread"; 065 066 /** 067 * Constructor 068 */ 069 public SubQueueSelectorCacheBroker(Broker next, final File persistFile) { 070 super(next); 071 this.persistFile = persistFile; 072 LOG.info("Using persisted selector cache from[{}]", persistFile); 073 074 readCache(); 075 076 persistThread = new Thread(this, SELECTOR_CACHE_PERSIST_THREAD_NAME); 077 persistThread.start(); 078 } 079 080 @Override 081 public void stop() throws Exception { 082 running = false; 083 if (persistThread != null) { 084 persistThread.interrupt(); 085 persistThread.join(); 086 } //if 087 } 088 089 @Override 090 public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception { 091 String destinationName = info.getDestination().getQualifiedName(); 092 LOG.debug("Caching consumer selector [{}] on a {}", info.getSelector(), destinationName); 093 String selector = info.getSelector(); 094 095 // As ConcurrentHashMap doesn't support null values, use always true expression 096 if (selector == null) { 097 selector = "TRUE"; 098 } 099 100 Set<String> selectors = subSelectorCache.get(destinationName); 101 if (selectors == null) { 102 selectors = Collections.synchronizedSet(new HashSet<String>()); 103 } 104 selectors.add(selector); 105 subSelectorCache.put(destinationName, selectors); 106 107 return super.addConsumer(context, info); 108 } 109 110 private void readCache() { 111 if (persistFile != null && persistFile.exists()) { 112 try { 113 FileInputStream fis = new FileInputStream(persistFile); 114 try { 115 ObjectInputStream in = new ObjectInputStream(fis); 116 try { 117 subSelectorCache = (ConcurrentHashMap<String, Set<String>>) in.readObject(); 118 } catch (ClassNotFoundException ex) { 119 LOG.error("Invalid selector cache data found. Please remove file.", ex); 120 } finally { 121 in.close(); 122 } //try 123 } finally { 124 fis.close(); 125 } //try 126 } catch (IOException ex) { 127 LOG.error("Unable to read persisted selector cache...it will be ignored!", ex); 128 } //try 129 } //if 130 } 131 132 /** 133 * Persist the selector cache. 134 */ 135 private void persistCache() { 136 LOG.debug("Persisting selector cache...."); 137 try { 138 FileOutputStream fos = new FileOutputStream(persistFile); 139 try { 140 ObjectOutputStream out = new ObjectOutputStream(fos); 141 try { 142 out.writeObject(subSelectorCache); 143 } finally { 144 out.flush(); 145 out.close(); 146 } //try 147 } catch (IOException ex) { 148 LOG.error("Unable to persist selector cache", ex); 149 } finally { 150 fos.close(); 151 } //try 152 } catch (IOException ex) { 153 LOG.error("Unable to access file[{}]", persistFile, ex); 154 } //try 155 } 156 157 /** 158 * @return The JMS selector for the specified {@code destination} 159 */ 160 public Set<String> getSelector(final String destination) { 161 return subSelectorCache.get(destination); 162 } 163 164 /** 165 * Persist the selector cache every {@code MAX_PERSIST_INTERVAL}ms. 166 * 167 * @see java.lang.Runnable#run() 168 */ 169 public void run() { 170 while (running) { 171 try { 172 Thread.sleep(MAX_PERSIST_INTERVAL); 173 } catch (InterruptedException ex) { 174 } //try 175 176 persistCache(); 177 } 178 } 179} 180