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;
018
019import java.io.IOException;
020import java.util.Collections;
021import java.util.List;
022import java.util.concurrent.CopyOnWriteArrayList;
023import java.util.concurrent.atomic.AtomicInteger;
024
025import javax.jms.InvalidSelectorException;
026import javax.jms.JMSException;
027import javax.management.ObjectName;
028
029import org.apache.activemq.broker.Broker;
030import org.apache.activemq.broker.ConnectionContext;
031import org.apache.activemq.command.ActiveMQDestination;
032import org.apache.activemq.command.ConsumerId;
033import org.apache.activemq.command.ConsumerInfo;
034import org.apache.activemq.command.MessageAck;
035import org.apache.activemq.filter.BooleanExpression;
036import org.apache.activemq.filter.DestinationFilter;
037import org.apache.activemq.filter.LogicExpression;
038import org.apache.activemq.filter.MessageEvaluationContext;
039import org.apache.activemq.filter.NoLocalExpression;
040import org.apache.activemq.selector.SelectorParser;
041import org.slf4j.Logger;
042import org.slf4j.LoggerFactory;
043
044public abstract class AbstractSubscription implements Subscription {
045
046    private static final Logger LOG = LoggerFactory.getLogger(AbstractSubscription.class);
047    protected Broker broker;
048    protected ConnectionContext context;
049    protected ConsumerInfo info;
050    protected final DestinationFilter destinationFilter;
051    protected final CopyOnWriteArrayList<Destination> destinations = new CopyOnWriteArrayList<Destination>();
052    protected final AtomicInteger prefetchExtension = new AtomicInteger(0);
053
054    private BooleanExpression selectorExpression;
055    private ObjectName objectName;
056    private int cursorMemoryHighWaterMark = 70;
057    private boolean slowConsumer;
058    private long lastAckTime;
059    private final SubscriptionStatistics subscriptionStatistics = new SubscriptionStatistics();
060
061    public AbstractSubscription(Broker broker,ConnectionContext context, ConsumerInfo info) throws InvalidSelectorException {
062        this.broker = broker;
063        this.context = context;
064        this.info = info;
065        this.destinationFilter = DestinationFilter.parseFilter(info.getDestination());
066        this.selectorExpression = parseSelector(info);
067        this.lastAckTime = System.currentTimeMillis();
068    }
069
070    private static BooleanExpression parseSelector(ConsumerInfo info) throws InvalidSelectorException {
071        BooleanExpression rc = null;
072        if (info.getSelector() != null) {
073            rc = SelectorParser.parse(info.getSelector());
074        }
075        if (info.isNoLocal()) {
076            if (rc == null) {
077                rc = new NoLocalExpression(info.getConsumerId().getConnectionId());
078            } else {
079                rc = LogicExpression.createAND(new NoLocalExpression(info.getConsumerId().getConnectionId()), rc);
080            }
081        }
082        if (info.getAdditionalPredicate() != null) {
083            if (rc == null) {
084                rc = info.getAdditionalPredicate();
085            } else {
086                rc = LogicExpression.createAND(info.getAdditionalPredicate(), rc);
087            }
088        }
089        return rc;
090    }
091
092    @Override
093    public synchronized void acknowledge(final ConnectionContext context, final MessageAck ack) throws Exception {
094        this.lastAckTime = System.currentTimeMillis();
095        subscriptionStatistics.getConsumedCount().increment();
096    }
097
098    @Override
099    public boolean matches(MessageReference node, MessageEvaluationContext context) throws IOException {
100        ConsumerId targetConsumerId = node.getTargetConsumerId();
101        if (targetConsumerId != null) {
102            if (!targetConsumerId.equals(info.getConsumerId())) {
103                return false;
104            }
105        }
106        try {
107            return (selectorExpression == null || selectorExpression.matches(context)) && this.context.isAllowedToConsume(node);
108        } catch (JMSException e) {
109            LOG.info("Selector failed to evaluate: {}", e.getMessage(), e);
110            return false;
111        }
112    }
113
114    @Override
115    public boolean isWildcard() {
116        return destinationFilter.isWildcard();
117    }
118
119    @Override
120    public boolean matches(ActiveMQDestination destination) {
121        return destinationFilter.matches(destination);
122    }
123
124    @Override
125    public void add(ConnectionContext context, Destination destination) throws Exception {
126        destinations.add(destination);
127    }
128
129    @Override
130    public List<MessageReference> remove(ConnectionContext context, Destination destination) throws Exception {
131        destinations.remove(destination);
132        return Collections.EMPTY_LIST;
133    }
134
135    @Override
136    public ConsumerInfo getConsumerInfo() {
137        return info;
138    }
139
140    @Override
141    public void gc() {
142    }
143
144    @Override
145    public ConnectionContext getContext() {
146        return context;
147    }
148
149    public ConsumerInfo getInfo() {
150        return info;
151    }
152
153    public BooleanExpression getSelectorExpression() {
154        return selectorExpression;
155    }
156
157    @Override
158    public String getSelector() {
159        return info.getSelector();
160    }
161
162    @Override
163    public void setSelector(String selector) throws InvalidSelectorException {
164        ConsumerInfo copy = info.copy();
165        copy.setSelector(selector);
166        BooleanExpression newSelector = parseSelector(copy);
167        // its valid so lets actually update it now
168        info.setSelector(selector);
169        this.selectorExpression = newSelector;
170    }
171
172    @Override
173    public ObjectName getObjectName() {
174        return objectName;
175    }
176
177    @Override
178    public void setObjectName(ObjectName objectName) {
179        this.objectName = objectName;
180    }
181
182    @Override
183    public int getPrefetchSize() {
184        return info.getPrefetchSize();
185    }
186    public void setPrefetchSize(int newSize) {
187        info.setPrefetchSize(newSize);
188    }
189
190    @Override
191    public boolean isRecoveryRequired() {
192        return true;
193    }
194
195    @Override
196    public boolean isSlowConsumer() {
197        return slowConsumer;
198    }
199
200    public void setSlowConsumer(boolean val) {
201        slowConsumer = val;
202    }
203
204    @Override
205    public boolean addRecoveredMessage(ConnectionContext context, MessageReference message) throws Exception {
206        boolean result = false;
207        MessageEvaluationContext msgContext = context.getMessageEvaluationContext();
208        try {
209            Destination regionDestination = (Destination) message.getRegionDestination();
210            msgContext.setDestination(regionDestination.getActiveMQDestination());
211            msgContext.setMessageReference(message);
212            result = matches(message, msgContext);
213            if (result) {
214                doAddRecoveredMessage(message);
215            }
216
217        } finally {
218            msgContext.clear();
219        }
220        return result;
221    }
222
223    @Override
224    public ActiveMQDestination getActiveMQDestination() {
225        return info != null ? info.getDestination() : null;
226    }
227
228    @Override
229    public boolean isBrowser() {
230        return info != null && info.isBrowser();
231    }
232
233    @Override
234    public long getInFlightMessageSize() {
235        return subscriptionStatistics.getInflightMessageSize().getTotalSize();
236    }
237
238    @Override
239    public int getInFlightUsage() {
240        if (info.getPrefetchSize() > 0) {
241            return (getInFlightSize() * 100)/info.getPrefetchSize();
242        }
243        return Integer.MAX_VALUE;
244    }
245
246    /**
247     * Add a destination
248     * @param destination
249     */
250    public void addDestination(Destination destination) {
251
252    }
253
254    /**
255     * Remove a destination
256     * @param destination
257     */
258    public void removeDestination(Destination destination) {
259
260    }
261
262    @Override
263    public int getCursorMemoryHighWaterMark(){
264        return this.cursorMemoryHighWaterMark;
265    }
266
267    @Override
268    public void setCursorMemoryHighWaterMark(int cursorMemoryHighWaterMark){
269        this.cursorMemoryHighWaterMark=cursorMemoryHighWaterMark;
270    }
271
272    @Override
273    public int countBeforeFull() {
274        return getDispatchedQueueSize() - info.getPrefetchSize();
275    }
276
277    @Override
278    public void unmatched(MessageReference node) throws IOException {
279        // only durable topic subs have something to do here
280    }
281
282    protected void doAddRecoveredMessage(MessageReference message) throws Exception {
283        add(message);
284    }
285
286    @Override
287    public long getTimeOfLastMessageAck() {
288        return lastAckTime;
289    }
290
291    public void setTimeOfLastMessageAck(long value) {
292        this.lastAckTime = value;
293    }
294
295    public long getConsumedCount(){
296        return subscriptionStatistics.getConsumedCount().getCount();
297    }
298
299    public void incrementConsumedCount(){
300        subscriptionStatistics.getConsumedCount().increment();
301    }
302
303    public void resetConsumedCount(){
304        subscriptionStatistics.getConsumedCount().reset();
305    }
306
307    @Override
308    public SubscriptionStatistics getSubscriptionStatistics() {
309        return subscriptionStatistics;
310    }
311
312    public void wakeupDestinationsForDispatch() {
313        for (Destination dest : destinations) {
314            dest.wakeup();
315        }
316    }
317
318    public AtomicInteger getPrefetchExtension() {
319        return this.prefetchExtension;
320    }
321}