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.util.ArrayList;
020import java.util.HashMap;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.concurrent.locks.ReentrantReadWriteLock;
027
028import javax.jms.JMSException;
029import org.apache.activemq.broker.ConnectionContext;
030import org.apache.activemq.broker.ConsumerBrokerExchange;
031import org.apache.activemq.DestinationDoesNotExistException;
032import org.apache.activemq.broker.ProducerBrokerExchange;
033import org.apache.activemq.broker.region.policy.PolicyEntry;
034import org.apache.activemq.broker.region.virtual.CompositeDestinationFilter;
035import org.apache.activemq.command.ActiveMQDestination;
036import org.apache.activemq.command.ConsumerControl;
037import org.apache.activemq.command.ConsumerId;
038import org.apache.activemq.command.ConsumerInfo;
039import org.apache.activemq.command.Message;
040import org.apache.activemq.command.MessageAck;
041import org.apache.activemq.command.MessageDispatchNotification;
042import org.apache.activemq.command.MessagePull;
043import org.apache.activemq.command.ProducerInfo;
044import org.apache.activemq.command.RemoveSubscriptionInfo;
045import org.apache.activemq.command.Response;
046import org.apache.activemq.filter.DestinationFilter;
047import org.apache.activemq.filter.DestinationMap;
048import org.apache.activemq.security.SecurityContext;
049import org.apache.activemq.thread.TaskRunnerFactory;
050import org.apache.activemq.usage.SystemUsage;
051import org.slf4j.Logger;
052import org.slf4j.LoggerFactory;
053
054/**
055 *
056 */
057public abstract class AbstractRegion implements Region {
058
059    private static final Logger LOG = LoggerFactory.getLogger(AbstractRegion.class);
060
061    protected final Map<ActiveMQDestination, Destination> destinations = new ConcurrentHashMap<ActiveMQDestination, Destination>();
062    protected final DestinationMap destinationMap = new DestinationMap();
063    protected final Map<ConsumerId, Subscription> subscriptions = new ConcurrentHashMap<ConsumerId, Subscription>();
064    protected final SystemUsage usageManager;
065    protected final DestinationFactory destinationFactory;
066    protected final DestinationStatistics destinationStatistics;
067    protected final RegionBroker broker;
068    protected boolean autoCreateDestinations = true;
069    protected final TaskRunnerFactory taskRunnerFactory;
070    protected final ReentrantReadWriteLock destinationsLock = new ReentrantReadWriteLock();
071    protected final Map<ConsumerId, Object> consumerChangeMutexMap = new HashMap<ConsumerId, Object>();
072    protected boolean started;
073
074    public AbstractRegion(RegionBroker broker, DestinationStatistics destinationStatistics, SystemUsage memoryManager,
075            TaskRunnerFactory taskRunnerFactory, DestinationFactory destinationFactory) {
076        if (broker == null) {
077            throw new IllegalArgumentException("null broker");
078        }
079        this.broker = broker;
080        this.destinationStatistics = destinationStatistics;
081        this.usageManager = memoryManager;
082        this.taskRunnerFactory = taskRunnerFactory;
083        if (destinationFactory == null) {
084            throw new IllegalArgumentException("null destinationFactory");
085        }
086        this.destinationFactory = destinationFactory;
087    }
088
089    public final void start() throws Exception {
090        started = true;
091
092        Set<ActiveMQDestination> inactiveDests = getInactiveDestinations();
093        for (Iterator<ActiveMQDestination> iter = inactiveDests.iterator(); iter.hasNext();) {
094            ActiveMQDestination dest = iter.next();
095
096            ConnectionContext context = new ConnectionContext();
097            context.setBroker(broker.getBrokerService().getBroker());
098            context.setSecurityContext(SecurityContext.BROKER_SECURITY_CONTEXT);
099            context.getBroker().addDestination(context, dest, false);
100        }
101        destinationsLock.readLock().lock();
102        try{
103            for (Iterator<Destination> i = destinations.values().iterator(); i.hasNext();) {
104                Destination dest = i.next();
105                dest.start();
106            }
107        } finally {
108            destinationsLock.readLock().unlock();
109        }
110    }
111
112    public void stop() throws Exception {
113        started = false;
114        destinationsLock.readLock().lock();
115        try{
116            for (Iterator<Destination> i = destinations.values().iterator(); i.hasNext();) {
117                Destination dest = i.next();
118                dest.stop();
119            }
120        } finally {
121            destinationsLock.readLock().unlock();
122        }
123        destinations.clear();
124    }
125
126    public Destination addDestination(ConnectionContext context, ActiveMQDestination destination,
127            boolean createIfTemporary) throws Exception {
128
129        destinationsLock.writeLock().lock();
130        try {
131            Destination dest = destinations.get(destination);
132            if (dest == null) {
133                if (destination.isTemporary() == false || createIfTemporary) {
134                    LOG.debug("{} adding destination: {}", broker.getBrokerName(), destination);
135                    dest = createDestination(context, destination);
136                    // intercept if there is a valid interceptor defined
137                    DestinationInterceptor destinationInterceptor = broker.getDestinationInterceptor();
138                    if (destinationInterceptor != null) {
139                        dest = destinationInterceptor.intercept(dest);
140                    }
141                    dest.start();
142                    addSubscriptionsForDestination(context, dest);
143                    destinations.put(destination, dest);
144                    destinationMap.put(destination, dest);
145                }
146                if (dest == null) {
147                    throw new DestinationDoesNotExistException(destination.getQualifiedName());
148                }
149            }
150            return dest;
151        } finally {
152            destinationsLock.writeLock().unlock();
153        }
154    }
155
156    public Map<ConsumerId, Subscription> getSubscriptions() {
157        return subscriptions;
158    }
159
160    protected List<Subscription> addSubscriptionsForDestination(ConnectionContext context, Destination dest)
161            throws Exception {
162
163        List<Subscription> rc = new ArrayList<Subscription>();
164        // Add all consumers that are interested in the destination.
165        for (Iterator<Subscription> iter = subscriptions.values().iterator(); iter.hasNext();) {
166            Subscription sub = iter.next();
167            if (sub.matches(dest.getActiveMQDestination())) {
168                try {
169                    dest.addSubscription(context, sub);
170                    rc.add(sub);
171                } catch (SecurityException e) {
172                    if (sub.isWildcard()) {
173                        LOG.debug("Subscription denied for " + sub + " to destination " +
174                            dest.getActiveMQDestination() +  ": " + e.getMessage());
175                    } else {
176                        throw e;
177                    }
178                }
179            }
180        }
181        return rc;
182
183    }
184
185    public void removeDestination(ConnectionContext context, ActiveMQDestination destination, long timeout)
186            throws Exception {
187
188        // No timeout.. then try to shut down right way, fails if there are
189        // current subscribers.
190        if (timeout == 0) {
191            for (Iterator<Subscription> iter = subscriptions.values().iterator(); iter.hasNext();) {
192                Subscription sub = iter.next();
193                if (sub.matches(destination) ) {
194                    throw new JMSException("Destination still has an active subscription: " + destination);
195                }
196            }
197        }
198
199        if (timeout > 0) {
200            // TODO: implement a way to notify the subscribers that we want to
201            // take the down
202            // the destination and that they should un-subscribe.. Then wait up
203            // to timeout time before
204            // dropping the subscription.
205        }
206
207        LOG.debug("{} removing destination: {}", broker.getBrokerName(), destination);
208
209        destinationsLock.writeLock().lock();
210        try {
211            Destination dest = destinations.remove(destination);
212            if (dest != null) {
213                // timeout<0 or we timed out, we now force any remaining
214                // subscriptions to un-subscribe.
215                for (Iterator<Subscription> iter = subscriptions.values().iterator(); iter.hasNext();) {
216                    Subscription sub = iter.next();
217                    if (sub.matches(destination)) {
218                        dest.removeSubscription(context, sub, 0l);
219                    }
220                }
221                destinationMap.remove(destination, dest);
222                dispose(context, dest);
223                DestinationInterceptor destinationInterceptor = broker.getDestinationInterceptor();
224                if (destinationInterceptor != null) {
225                    destinationInterceptor.remove(dest);
226                }
227
228            } else {
229                LOG.debug("Cannot remove a destination that doesn't exist: {}", destination);
230            }
231        } finally {
232            destinationsLock.writeLock().unlock();
233        }
234    }
235
236    /**
237     * Provide an exact or wildcard lookup of destinations in the region
238     *
239     * @return a set of matching destination objects.
240     */
241    @SuppressWarnings("unchecked")
242    public Set<Destination> getDestinations(ActiveMQDestination destination) {
243        destinationsLock.readLock().lock();
244        try{
245            return destinationMap.get(destination);
246        } finally {
247            destinationsLock.readLock().unlock();
248        }
249    }
250
251    public Map<ActiveMQDestination, Destination> getDestinationMap() {
252        return destinations;
253    }
254
255    @SuppressWarnings("unchecked")
256    public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
257        LOG.debug("{} adding consumer: {} for destination: {}", new Object[]{ broker.getBrokerName(), info.getConsumerId(), info.getDestination() });
258        ActiveMQDestination destination = info.getDestination();
259        if (destination != null && !destination.isPattern() && !destination.isComposite()) {
260            // lets auto-create the destination
261            lookup(context, destination,true);
262        }
263
264        Object addGuard;
265        synchronized (consumerChangeMutexMap) {
266            addGuard = consumerChangeMutexMap.get(info.getConsumerId());
267            if (addGuard == null) {
268                addGuard = new Object();
269                consumerChangeMutexMap.put(info.getConsumerId(), addGuard);
270            }
271        }
272        synchronized (addGuard) {
273            Subscription o = subscriptions.get(info.getConsumerId());
274            if (o != null) {
275                LOG.warn("A duplicate subscription was detected. Clients may be misbehaving. Later warnings you may see about subscription removal are a consequence of this.");
276                return o;
277            }
278
279            // We may need to add some destinations that are in persistent store
280            // but not active
281            // in the broker.
282            //
283            // TODO: think about this a little more. This is good cause
284            // destinations are not loaded into
285            // memory until a client needs to use the queue, but a management
286            // agent viewing the
287            // broker will not see a destination that exists in persistent
288            // store. We may want to
289            // eagerly load all destinations into the broker but have an
290            // inactive state for the
291            // destination which has reduced memory usage.
292            //
293            DestinationFilter.parseFilter(info.getDestination());
294
295            Subscription sub = createSubscription(context, info);
296
297            // At this point we're done directly manipulating subscriptions,
298            // but we need to retain the synchronized block here. Consider
299            // otherwise what would happen if at this point a second
300            // thread added, then removed, as would be allowed with
301            // no mutex held. Remove is only essentially run once
302            // so everything after this point would be leaked.
303
304            // Add the subscription to all the matching queues.
305            // But copy the matches first - to prevent deadlocks
306            List<Destination> addList = new ArrayList<Destination>();
307            destinationsLock.readLock().lock();
308            try {
309                for (Destination dest : (Set<Destination>) destinationMap.get(info.getDestination())) {
310                    addList.add(dest);
311                }
312                // ensure sub visible to any new dest addSubscriptionsForDestination
313                subscriptions.put(info.getConsumerId(), sub);
314            } finally {
315                destinationsLock.readLock().unlock();
316            }
317
318            List<Destination> removeList = new ArrayList<Destination>();
319            for (Destination dest : addList) {
320                try {
321                    dest.addSubscription(context, sub);
322                    removeList.add(dest);
323                } catch (SecurityException e){
324                    if (sub.isWildcard()) {
325                        LOG.debug("Subscription denied for " + sub + " to destination " +
326                            dest.getActiveMQDestination() + ": " + e.getMessage());
327                    } else {
328                        // remove partial subscriptions
329                        for (Destination remove : removeList) {
330                            try {
331                                remove.removeSubscription(context, sub, info.getLastDeliveredSequenceId());
332                            } catch (Exception ex) {
333                                LOG.error("Error unsubscribing " + sub + " from " + remove + ": " + ex.getMessage(), ex);
334                            }
335                        }
336                        subscriptions.remove(info.getConsumerId());
337                        removeList.clear();
338                        throw e;
339                    }
340                }
341            }
342            removeList.clear();
343
344            if (info.isBrowser()) {
345                ((QueueBrowserSubscription) sub).destinationsAdded();
346            }
347
348            return sub;
349        }
350    }
351
352    /**
353     * Get all the Destinations that are in storage
354     *
355     * @return Set of all stored destinations
356     */
357    @SuppressWarnings("rawtypes")
358    public Set getDurableDestinations() {
359        return destinationFactory.getDestinations();
360    }
361
362    /**
363     * @return all Destinations that don't have active consumers
364     */
365    protected Set<ActiveMQDestination> getInactiveDestinations() {
366        Set<ActiveMQDestination> inactiveDests = destinationFactory.getDestinations();
367        destinationsLock.readLock().lock();
368        try {
369            inactiveDests.removeAll(destinations.keySet());
370        } finally {
371            destinationsLock.readLock().unlock();
372        }
373        return inactiveDests;
374    }
375
376    @SuppressWarnings("unchecked")
377    public void removeConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
378        LOG.debug("{} removing consumer: {} for destination: {}", new Object[]{ broker.getBrokerName(), info.getConsumerId(), info.getDestination() });
379
380        Subscription sub = subscriptions.remove(info.getConsumerId());
381        // The sub could be removed elsewhere - see ConnectionSplitBroker
382        if (sub != null) {
383
384            // remove the subscription from all the matching queues.
385            List<Destination> removeList = new ArrayList<Destination>();
386            destinationsLock.readLock().lock();
387            try {
388                for (Destination dest : (Set<Destination>) destinationMap.get(info.getDestination())) {
389                    removeList.add(dest);
390                }
391            } finally {
392                destinationsLock.readLock().unlock();
393            }
394            for (Destination dest : removeList) {
395                dest.removeSubscription(context, sub, info.getLastDeliveredSequenceId());
396            }
397
398            destroySubscription(sub);
399        }
400        synchronized (consumerChangeMutexMap) {
401            consumerChangeMutexMap.remove(info.getConsumerId());
402        }
403    }
404
405    protected void destroySubscription(Subscription sub) {
406        sub.destroy();
407    }
408
409    public void removeSubscription(ConnectionContext context, RemoveSubscriptionInfo info) throws Exception {
410        throw new JMSException("Invalid operation.");
411    }
412
413    public void send(final ProducerBrokerExchange producerExchange, Message messageSend) throws Exception {
414        final ConnectionContext context = producerExchange.getConnectionContext();
415
416        if (producerExchange.isMutable() || producerExchange.getRegionDestination() == null) {
417            final Destination regionDestination = lookup(context, messageSend.getDestination(),false);
418            producerExchange.setRegionDestination(regionDestination);
419        }
420
421        producerExchange.getRegionDestination().send(producerExchange, messageSend);
422
423        if (producerExchange.getProducerState() != null && producerExchange.getProducerState().getInfo() != null){
424            producerExchange.getProducerState().getInfo().incrementSentCount();
425        }
426    }
427
428    public void acknowledge(ConsumerBrokerExchange consumerExchange, MessageAck ack) throws Exception {
429        Subscription sub = consumerExchange.getSubscription();
430        if (sub == null) {
431            sub = subscriptions.get(ack.getConsumerId());
432            if (sub == null) {
433                if (!consumerExchange.getConnectionContext().isInRecoveryMode()) {
434                    LOG.warn("Ack for non existent subscription, ack: {}", ack);
435                    throw new IllegalArgumentException("The subscription does not exist: " + ack.getConsumerId());
436                } else {
437                    LOG.debug("Ack for non existent subscription in recovery, ack: {}", ack);
438                    return;
439                }
440            }
441            consumerExchange.setSubscription(sub);
442        }
443        sub.acknowledge(consumerExchange.getConnectionContext(), ack);
444    }
445
446    public Response messagePull(ConnectionContext context, MessagePull pull) throws Exception {
447        Subscription sub = subscriptions.get(pull.getConsumerId());
448        if (sub == null) {
449            throw new IllegalArgumentException("The subscription does not exist: " + pull.getConsumerId());
450        }
451        return sub.pullMessage(context, pull);
452    }
453
454    protected Destination lookup(ConnectionContext context, ActiveMQDestination destination,boolean createTemporary) throws Exception {
455        Destination dest = null;
456
457        destinationsLock.readLock().lock();
458        try {
459            dest = destinations.get(destination);
460        } finally {
461            destinationsLock.readLock().unlock();
462        }
463
464        if (dest == null) {
465            if (isAutoCreateDestinations()) {
466                // Try to auto create the destination... re-invoke broker
467                // from the
468                // top so that the proper security checks are performed.
469                context.getBroker().addDestination(context, destination, createTemporary);
470                dest = addDestination(context, destination, false);
471                // We should now have the dest created.
472                destinationsLock.readLock().lock();
473                try {
474                    dest = destinations.get(destination);
475                } finally {
476                    destinationsLock.readLock().unlock();
477                }
478            }
479
480            if (dest == null) {
481                throw new JMSException("The destination " + destination + " does not exist.");
482            }
483        }
484        return dest;
485    }
486
487    public void processDispatchNotification(MessageDispatchNotification messageDispatchNotification) throws Exception {
488        Subscription sub = subscriptions.get(messageDispatchNotification.getConsumerId());
489        if (sub != null) {
490            sub.processMessageDispatchNotification(messageDispatchNotification);
491        } else {
492            throw new JMSException("Slave broker out of sync with master - Subscription: "
493                    + messageDispatchNotification.getConsumerId() + " on "
494                    + messageDispatchNotification.getDestination() + " does not exist for dispatch of message: "
495                    + messageDispatchNotification.getMessageId());
496        }
497    }
498
499    /*
500     * For a Queue/TempQueue, dispatch order is imperative to match acks, so the
501     * dispatch is deferred till the notification to ensure that the
502     * subscription chosen by the master is used. AMQ-2102
503     */
504    protected void processDispatchNotificationViaDestination(MessageDispatchNotification messageDispatchNotification)
505            throws Exception {
506        Destination dest = null;
507        destinationsLock.readLock().lock();
508        try {
509            dest = destinations.get(messageDispatchNotification.getDestination());
510        } finally {
511            destinationsLock.readLock().unlock();
512        }
513
514        if (dest != null) {
515            dest.processDispatchNotification(messageDispatchNotification);
516        } else {
517            throw new JMSException("Slave broker out of sync with master - Destination: "
518                    + messageDispatchNotification.getDestination() + " does not exist for consumer "
519                    + messageDispatchNotification.getConsumerId() + " with message: "
520                    + messageDispatchNotification.getMessageId());
521        }
522    }
523
524    public void gc() {
525        for (Subscription sub : subscriptions.values()) {
526            sub.gc();
527        }
528
529        destinationsLock.readLock().lock();
530        try {
531            for (Destination dest : destinations.values()) {
532                dest.gc();
533            }
534        } finally {
535            destinationsLock.readLock().unlock();
536        }
537    }
538
539    protected abstract Subscription createSubscription(ConnectionContext context, ConsumerInfo info) throws Exception;
540
541    protected Destination createDestination(ConnectionContext context, ActiveMQDestination destination)
542            throws Exception {
543        return destinationFactory.createDestination(context, destination, destinationStatistics);
544    }
545
546    public boolean isAutoCreateDestinations() {
547        return autoCreateDestinations;
548    }
549
550    public void setAutoCreateDestinations(boolean autoCreateDestinations) {
551        this.autoCreateDestinations = autoCreateDestinations;
552    }
553
554    @SuppressWarnings("unchecked")
555    public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception {
556        destinationsLock.readLock().lock();
557        try {
558            for (Destination dest : (Set<Destination>) destinationMap.get(info.getDestination())) {
559                dest.addProducer(context, info);
560            }
561        } finally {
562            destinationsLock.readLock().unlock();
563        }
564    }
565
566    /**
567     * Removes a Producer.
568     *
569     * @param context
570     *            the environment the operation is being executed under.
571     * @throws Exception
572     *             TODO
573     */
574    @SuppressWarnings("unchecked")
575    public void removeProducer(ConnectionContext context, ProducerInfo info) throws Exception {
576        destinationsLock.readLock().lock();
577        try {
578            for (Destination dest : (Set<Destination>) destinationMap.get(info.getDestination())) {
579                dest.removeProducer(context, info);
580            }
581        } finally {
582            destinationsLock.readLock().unlock();
583        }
584    }
585
586    protected void dispose(ConnectionContext context, Destination dest) throws Exception {
587        dest.dispose(context);
588        dest.stop();
589        destinationFactory.removeDestination(dest);
590    }
591
592    public void processConsumerControl(ConsumerBrokerExchange consumerExchange, ConsumerControl control) {
593        Subscription sub = subscriptions.get(control.getConsumerId());
594        if (sub != null && sub instanceof AbstractSubscription) {
595            ((AbstractSubscription) sub).setPrefetchSize(control.getPrefetch());
596            if (broker.getDestinationPolicy() != null) {
597                PolicyEntry entry = broker.getDestinationPolicy().getEntryFor(control.getDestination());
598                if (entry != null) {
599                    entry.configurePrefetch(sub);
600                }
601            }
602            LOG.debug("setting prefetch: {}, on subscription: {}; resulting value: {}", new Object[]{ control.getPrefetch(), control.getConsumerId(), sub.getConsumerInfo().getPrefetchSize()});
603            try {
604                lookup(consumerExchange.getConnectionContext(), control.getDestination(),false).wakeup();
605            } catch (Exception e) {
606                LOG.warn("failed to deliver post consumerControl dispatch-wakeup, to destination: {}", control.getDestination(), e);
607            }
608        }
609    }
610
611    public void reapplyInterceptor() {
612        destinationsLock.writeLock().lock();
613        try {
614            DestinationInterceptor destinationInterceptor = broker.getDestinationInterceptor();
615            Map<ActiveMQDestination, Destination> map = getDestinationMap();
616            for (ActiveMQDestination key : map.keySet()) {
617                Destination destination = map.get(key);
618                if (destination instanceof CompositeDestinationFilter) {
619                    destination = ((CompositeDestinationFilter) destination).next;
620                }
621                if (destinationInterceptor != null) {
622                    destination = destinationInterceptor.intercept(destination);
623                }
624                getDestinationMap().put(key, destination);
625                destinations.put(key, destination);
626            }
627        } finally {
628            destinationsLock.writeLock().unlock();
629        }
630    }
631}