All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.activemq.plugin.SubQueueSelectorCacheBroker Maven / Gradle / Ivy

The newest version!
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.activemq.plugin;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.management.JMException;
import javax.management.ObjectName;

import org.apache.activemq.advisory.AdvisorySupport;
import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerFilter;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.jmx.AnnotatedMBean;
import org.apache.activemq.broker.jmx.BrokerMBeanSupport;
import org.apache.activemq.broker.jmx.VirtualDestinationSelectorCacheView;
import org.apache.activemq.broker.region.Subscription;
import org.apache.activemq.command.ConsumerInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A plugin which allows the caching of the selector from a subscription queue.
 * 

* This stops the build-up of unwanted messages, especially when consumers may * disconnect from time to time when using virtual destinations. *

* This is influenced by code snippets developed by Maciej Rakowicz * * Refer to: * https://issues.apache.org/activemq/browse/AMQ-3004 * http://mail-archives.apache.org/mod_mbox/activemq-users/201011.mbox/%[email protected]%3E */ public class SubQueueSelectorCacheBroker extends BrokerFilter implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(SubQueueSelectorCacheBroker.class); public static final String MATCH_EVERYTHING = "TRUE"; /** * The subscription's selector cache. We cache compiled expressions keyed * by the target destination. */ private ConcurrentMap> subSelectorCache = new ConcurrentHashMap<>(); private final File persistFile; private boolean singleSelectorPerDestination = false; private boolean ignoreWildcardSelectors = false; private ObjectName objectName; private boolean running = true; private final Thread persistThread; private long persistInterval = MAX_PERSIST_INTERVAL; public static final long MAX_PERSIST_INTERVAL = 600000; private static final String SELECTOR_CACHE_PERSIST_THREAD_NAME = "SelectorCachePersistThread"; /** * Constructor */ public SubQueueSelectorCacheBroker(Broker next, final File persistFile) { super(next); this.persistFile = persistFile; LOG.info("Using persisted selector cache from[{}]", persistFile); readCache(); persistThread = new Thread(this, SELECTOR_CACHE_PERSIST_THREAD_NAME); persistThread.start(); enableJmx(); } private void enableJmx() { BrokerService broker = getBrokerService(); if (broker.isUseJmx()) { VirtualDestinationSelectorCacheView view = new VirtualDestinationSelectorCacheView(this); try { objectName = BrokerMBeanSupport.createVirtualDestinationSelectorCacheName(broker.getBrokerObjectName(), "plugin", "virtualDestinationCache"); LOG.trace("virtualDestinationCacheSelector mbean name; " + objectName.toString()); AnnotatedMBean.registerMBean(broker.getManagementContext(), view, objectName); } catch (Exception e) { LOG.warn("JMX is enabled, but when installing the VirtualDestinationSelectorCache, couldn't install the JMX mbeans. Continuing without installing the mbeans."); } } } @Override public void stop() throws Exception { running = false; if (persistThread != null) { persistThread.interrupt(); persistThread.join(); } unregisterMBeans(); } private void unregisterMBeans() { BrokerService broker = getBrokerService(); if (broker.isUseJmx() && this.objectName != null) { try { broker.getManagementContext().unregisterMBean(objectName); } catch (JMException e) { LOG.warn("Trying uninstall VirtualDestinationSelectorCache; couldn't uninstall mbeans, continuting..."); } } } @Override public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception { // don't track selectors for advisory topics, temp destinations or console // related consumers if (!AdvisorySupport.isAdvisoryTopic(info.getDestination()) && !info.getDestination().isTemporary() && !info.isBrowser()) { String destinationName = info.getDestination().getQualifiedName(); LOG.debug("Caching consumer selector [{}] on '{}'", info.getSelector(), destinationName); String selector = info.getSelector() == null ? MATCH_EVERYTHING : info.getSelector(); if (!(ignoreWildcardSelectors && hasWildcards(selector))) { Set selectors = subSelectorCache.get(destinationName); if (selectors == null) { selectors = Collections.synchronizedSet(new HashSet()); } else if (singleSelectorPerDestination && !MATCH_EVERYTHING.equals(selector)) { // in this case, we allow only ONE selector. But we don't count the catch-all "null/TRUE" selector // here, we always allow that one. But only one true selector. boolean containsMatchEverything = selectors.contains(MATCH_EVERYTHING); selectors.clear(); // put back the MATCH_EVERYTHING selector if (containsMatchEverything) { selectors.add(MATCH_EVERYTHING); } } LOG.debug("adding new selector: into cache " + selector); selectors.add(selector); LOG.debug("current selectors in cache: " + selectors); subSelectorCache.put(destinationName, selectors); } } return super.addConsumer(context, info); } static boolean hasWildcards(String selector) { return WildcardFinder.hasWildcards(selector); } @Override public void removeConsumer(ConnectionContext context, ConsumerInfo info) throws Exception { if (!AdvisorySupport.isAdvisoryTopic(info.getDestination()) && !info.getDestination().isTemporary()) { if (singleSelectorPerDestination) { String destinationName = info.getDestination().getQualifiedName(); Set selectors = subSelectorCache.get(destinationName); if (info.getSelector() == null && (selectors != null && selectors.size() > 1)) { boolean removed = selectors.remove(MATCH_EVERYTHING); LOG.debug("A non-selector consumer has dropped. Removing the catchall matching pattern 'TRUE'. Successful? " + removed); } } } super.removeConsumer(context, info); } @SuppressWarnings("unchecked") private void readCache() { if (persistFile != null && persistFile.exists()) { try { try (FileInputStream fis = new FileInputStream(persistFile);) { ObjectInputStream in = new SubSelectorClassObjectInputStream(fis); try { LOG.debug("Reading selector cache...."); subSelectorCache = (ConcurrentHashMap>) in.readObject(); if (LOG.isDebugEnabled()) { final StringBuilder sb = new StringBuilder(); sb.append("Selector cache data loaded from: ").append(persistFile.getAbsolutePath()).append("\n"); sb.append("The following entries were loaded from the cache file: \n"); subSelectorCache.forEach((k,v) -> { sb.append("\t").append(k).append(": ").append(v).append("\n"); }); LOG.debug(sb.toString()); } } catch (ClassNotFoundException ex) { LOG.error("Invalid selector cache data found. Please remove file.", ex); } finally { in.close(); } } } catch (IOException ex) { LOG.error("Unable to read persisted selector cache...it will be ignored!", ex); } } } /** * Persist the selector cache. */ private void persistCache() { LOG.debug("Persisting selector cache...."); try { FileOutputStream fos = new FileOutputStream(persistFile); try { ObjectOutputStream out = new ObjectOutputStream(fos); try { out.writeObject(subSelectorCache); } finally { out.flush(); out.close(); } } catch (IOException ex) { LOG.error("Unable to persist selector cache", ex); } finally { fos.close(); } } catch (IOException ex) { LOG.error("Unable to access file[{}]", persistFile, ex); } } /** * Persist the selector cache every {@code MAX_PERSIST_INTERVAL}ms. * * @see java.lang.Runnable#run() */ @Override public void run() { while (running) { try { Thread.sleep(persistInterval); } catch (InterruptedException ex) { } persistCache(); } } public boolean isSingleSelectorPerDestination() { return singleSelectorPerDestination; } public void setSingleSelectorPerDestination(boolean singleSelectorPerDestination) { this.singleSelectorPerDestination = singleSelectorPerDestination; } @SuppressWarnings("unchecked") public Set getSelectorsForDestination(String destinationName) { final Set cachedSelectors = subSelectorCache.get(destinationName); if (cachedSelectors != null) { synchronized(cachedSelectors) { return new HashSet<>(cachedSelectors); } } return Collections.EMPTY_SET; } public long getPersistInterval() { return persistInterval; } public void setPersistInterval(long persistInterval) { this.persistInterval = persistInterval; } public boolean deleteSelectorForDestination(String destinationName, String selector) { final Set cachedSelectors = subSelectorCache.get(destinationName); return cachedSelectors != null ? cachedSelectors.remove(selector) : false; } public boolean deleteAllSelectorsForDestination(String destinationName) { final Set cachedSelectors = subSelectorCache.get(destinationName); if (cachedSelectors != null) { cachedSelectors.clear(); } return true; } public boolean isIgnoreWildcardSelectors() { return ignoreWildcardSelectors; } public void setIgnoreWildcardSelectors(boolean ignoreWildcardSelectors) { this.ignoreWildcardSelectors = ignoreWildcardSelectors; } // find wildcards inside like operator arguments static class WildcardFinder { private static final Pattern LIKE_PATTERN=Pattern.compile( "\\bLIKE\\s+'(?([^']|'')+)'(\\s+ESCAPE\\s+'(?.)')?", Pattern.CASE_INSENSITIVE); private static final String REGEX_SPECIAL = ".+?*(){}[]\\-"; private static String getLike(final Matcher matcher) { return matcher.group("like"); } private static boolean hasLikeOperator(final Matcher matcher) { return matcher.find(); } private static String getEscape(final Matcher matcher) { String escapeChar = matcher.group("escape"); if (escapeChar == null) { return null; } else if (REGEX_SPECIAL.contains(escapeChar)) { escapeChar = "\\"+escapeChar; } return escapeChar; } private static boolean hasWildcardInCurrentMatch(final Matcher matcher) { String wildcards = "[_%]"; if (getEscape(matcher) != null) { wildcards = "(^|[^" + getEscape(matcher) + "])" + wildcards; } return Pattern.compile(wildcards).matcher(getLike(matcher)).find(); } public static boolean hasWildcards(String selector) { Matcher matcher = LIKE_PATTERN.matcher(selector); while(hasLikeOperator(matcher)) { if (hasWildcardInCurrentMatch(matcher)) { return true; } } return false; } } private static class SubSelectorClassObjectInputStream extends ObjectInputStream { public SubSelectorClassObjectInputStream(InputStream is) throws IOException { super(is); } @Override protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (!(desc.getName().startsWith("java.lang.") || desc.getName().startsWith("com.thoughtworks.xstream") || desc.getName().startsWith("java.util.") || desc.getName().length() > 2 && desc.getName().substring(2).startsWith("java.util.") // Allow arrays || desc.getName().startsWith("org.apache.activemq."))) { throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName()); } return super.resolveClass(desc); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy