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.broker.region.virtual;
018
019import java.util.regex.Matcher;
020import java.util.regex.Pattern;
021
022import org.apache.activemq.broker.Broker;
023import org.apache.activemq.broker.ConnectionContext;
024import org.apache.activemq.broker.region.Destination;
025import org.apache.activemq.command.ActiveMQDestination;
026import org.apache.activemq.command.ActiveMQQueue;
027import org.apache.activemq.command.ActiveMQTopic;
028import org.apache.activemq.filter.DestinationFilter;
029
030/**
031 * Creates <a href="http://activemq.org/site/virtual-destinations.html">Virtual
032 * Topics</a> using a prefix and postfix. The virtual destination creates a
033 * wildcard that is then used to look up all active queue subscriptions which
034 * match.
035 *
036 * @org.apache.xbean.XBean
037 */
038public class VirtualTopic implements VirtualDestination {
039
040    private String prefix = "Consumer.*.";
041    private String postfix = "";
042    private String name = ">";
043    private boolean selectorAware = false;
044    private boolean local = false;
045    private boolean concurrentSend = false;
046    private boolean transactedSend = false;
047    private boolean dropOnResourceLimit = false;
048    private boolean setOriginalDestination = true;
049
050    @Override
051    public ActiveMQDestination getVirtualDestination() {
052        return new ActiveMQTopic(getName());
053    }
054
055    @Override
056    public Destination intercept(Destination destination) {
057        return selectorAware ? new SelectorAwareVirtualTopicInterceptor(destination, this) :
058                new VirtualTopicInterceptor(destination, this);
059    }
060
061    @Override
062    public ActiveMQDestination getMappedDestinations() {
063        return new ActiveMQQueue(prefix + name + postfix);
064    }
065
066    @Override
067    public Destination interceptMappedDestination(Destination destination) {
068        // do a reverse map from destination to get actual virtual destination
069        final String physicalName = destination.getActiveMQDestination().getPhysicalName();
070        final Pattern pattern = Pattern.compile(getRegex(prefix) + "(.*)" + getRegex(postfix));
071        final Matcher matcher = pattern.matcher(physicalName);
072        if (matcher.matches()) {
073            final String virtualName = matcher.group(1);
074            return new MappedQueueFilter(new ActiveMQTopic(virtualName), destination);
075        }
076        return destination;
077    }
078
079    private String getRegex(String part) {
080        StringBuilder builder = new StringBuilder();
081        for (char c : part.toCharArray()) {
082            switch (c) {
083                case '.':
084                    builder.append("\\.");
085                    break;
086                case '*':
087                    builder.append("[^\\.]*");
088                    break;
089                default:
090                    builder.append(c);
091            }
092        }
093        return builder.toString();
094    }
095
096    @Override
097    public void create(Broker broker, ConnectionContext context, ActiveMQDestination destination) throws Exception {
098        if (destination.isQueue() && destination.isPattern()) {
099            DestinationFilter filter = DestinationFilter.parseFilter(new ActiveMQQueue(prefix + DestinationFilter.ANY_DESCENDENT));
100            if (filter.matches(destination)) {
101                broker.addDestination(context, destination, false);
102
103            }
104        }
105    }
106
107    @Override
108    public void remove(Destination destination) {
109    }
110
111    // Properties
112    // -------------------------------------------------------------------------
113
114    public String getPostfix() {
115        return postfix;
116    }
117
118    /**
119     * Sets any postix used to identify the queue consumers
120     */
121    public void setPostfix(String postfix) {
122        this.postfix = postfix;
123    }
124
125    public String getPrefix() {
126        return prefix;
127    }
128
129    /**
130     * Sets the prefix wildcard used to identify the queue consumers for a given
131     * topic
132     */
133    public void setPrefix(String prefix) {
134        this.prefix = prefix;
135    }
136
137    public String getName() {
138        return name;
139    }
140
141    public void setName(String name) {
142        this.name = name;
143    }
144
145    /**
146     * Indicates whether the selectors of consumers are used to determine
147     * dispatch to a virtual destination, when true only messages matching an
148     * existing consumer will be dispatched.
149     *
150     * @param selectorAware
151     *            when true take consumer selectors into consideration
152     */
153    public void setSelectorAware(boolean selectorAware) {
154        this.selectorAware = selectorAware;
155    }
156
157    public boolean isSelectorAware() {
158        return selectorAware;
159    }
160
161    public boolean isLocal() {
162        return local;
163    }
164
165    public void setLocal(boolean local) {
166        this.local = local;
167    }
168
169    @Override
170    public String toString() {
171        return new StringBuilder("VirtualTopic:").append(prefix).append(',').append(name).append(',').
172                                                  append(postfix).append(',').append(selectorAware).
173                                                  append(',').append(local).toString();
174    }
175
176    public boolean isConcurrentSend() {
177        return concurrentSend;
178    }
179
180    /**
181     * When true, dispatch to matching destinations in parallel (in multiple threads)
182     * @param concurrentSend
183     */
184    public void setConcurrentSend(boolean concurrentSend) {
185        this.concurrentSend = concurrentSend;
186    }
187
188    public boolean isTransactedSend() {
189        return transactedSend;
190    }
191
192    /**
193     * When true, dispatch to matching destinations always uses a transaction.
194     * @param transactedSend
195     */
196    public void setTransactedSend(boolean transactedSend) {
197        this.transactedSend = transactedSend;
198    }
199
200    @Override
201    public int hashCode() {
202        final int prime = 31;
203        int result = 1;
204        result = prime * result + (concurrentSend ? 1231 : 1237);
205        result = prime * result + (local ? 1231 : 1237);
206        result = prime * result + ((name == null) ? 0 : name.hashCode());
207        result = prime * result + ((postfix == null) ? 0 : postfix.hashCode());
208        result = prime * result + ((prefix == null) ? 0 : prefix.hashCode());
209        result = prime * result + (selectorAware ? 1231 : 1237);
210        result = prime * result + (transactedSend ? 1231 : 1237);
211        return result;
212    }
213
214    @Override
215    public boolean equals(Object obj) {
216        if (this == obj)
217            return true;
218        if (obj == null)
219            return false;
220        if (getClass() != obj.getClass())
221            return false;
222        VirtualTopic other = (VirtualTopic) obj;
223        if (concurrentSend != other.concurrentSend)
224            return false;
225        if (local != other.local)
226            return false;
227        if (name == null) {
228            if (other.name != null)
229                return false;
230        } else if (!name.equals(other.name))
231            return false;
232        if (postfix == null) {
233            if (other.postfix != null)
234                return false;
235        } else if (!postfix.equals(other.postfix))
236            return false;
237        if (prefix == null) {
238            if (other.prefix != null)
239                return false;
240        } else if (!prefix.equals(other.prefix))
241            return false;
242        if (selectorAware != other.selectorAware)
243            return false;
244        if (transactedSend != other.transactedSend)
245            return false;
246        return true;
247    }
248
249    public boolean isDropOnResourceLimit() {
250        return dropOnResourceLimit;
251    }
252
253    public void setDropOnResourceLimit(boolean dropOnResourceLimit) {
254        this.dropOnResourceLimit = dropOnResourceLimit;
255    }
256
257    public boolean isSetOriginalDestination() {
258        return setOriginalDestination;
259    }
260
261    public void setSetOriginalDestination(boolean setOriginalDestination) {
262        this.setOriginalDestination = setOriginalDestination;
263    }
264}