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.cursors; 018 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.concurrent.CopyOnWriteArrayList; 024 025import org.apache.activemq.advisory.AdvisorySupport; 026import org.apache.activemq.broker.Broker; 027import org.apache.activemq.broker.ConnectionContext; 028import org.apache.activemq.broker.region.Destination; 029import org.apache.activemq.broker.region.DurableTopicSubscription; 030import org.apache.activemq.broker.region.MessageReference; 031import org.apache.activemq.broker.region.Topic; 032import org.apache.activemq.command.Message; 033import org.apache.activemq.usage.SystemUsage; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037/** 038 * persist pending messages pending message (messages awaiting dispatch to a 039 * consumer) cursor 040 */ 041public class StoreDurableSubscriberCursor extends AbstractPendingMessageCursor { 042 043 private static final Logger LOG = LoggerFactory.getLogger(StoreDurableSubscriberCursor.class); 044 private final String clientId; 045 private final String subscriberName; 046 private final Map<Destination, TopicStorePrefetch> topics = new HashMap<Destination, TopicStorePrefetch>(); 047 private final List<PendingMessageCursor> storePrefetches = new CopyOnWriteArrayList<PendingMessageCursor>(); 048 private final PendingMessageCursor nonPersistent; 049 private PendingMessageCursor currentCursor; 050 private final DurableTopicSubscription subscription; 051 private boolean immediatePriorityDispatch = true; 052 053 /** 054 * @param broker Broker for this cursor 055 * @param clientId clientId for this cursor 056 * @param subscriberName subscriber name for this cursor 057 * @param maxBatchSize currently ignored 058 * @param subscription subscription for this cursor 059 */ 060 public StoreDurableSubscriberCursor(Broker broker,String clientId, String subscriberName,int maxBatchSize, DurableTopicSubscription subscription) { 061 super(AbstractPendingMessageCursor.isPrioritizedMessageSubscriber(broker,subscription)); 062 this.subscription=subscription; 063 this.clientId = clientId; 064 this.subscriberName = subscriberName; 065 if (broker.getBrokerService().isPersistent()) { 066 this.nonPersistent = new FilePendingMessageCursor(broker,clientId + subscriberName,this.prioritizedMessages); 067 } else { 068 this.nonPersistent = new VMPendingMessageCursor(this.prioritizedMessages); 069 } 070 071 this.nonPersistent.setMaxBatchSize(maxBatchSize); 072 this.nonPersistent.setSystemUsage(systemUsage); 073 this.storePrefetches.add(this.nonPersistent); 074 075 if (prioritizedMessages) { 076 setMaxAuditDepth(10*getMaxAuditDepth()); 077 } 078 } 079 080 @Override 081 public synchronized void start() throws Exception { 082 if (!isStarted()) { 083 super.start(); 084 for (PendingMessageCursor tsp : storePrefetches) { 085 tsp.setMessageAudit(getMessageAudit()); 086 tsp.start(); 087 } 088 } 089 } 090 091 @Override 092 public synchronized void stop() throws Exception { 093 if (isStarted()) { 094 if (subscription.isKeepDurableSubsActive()) { 095 super.gc(); 096 for (PendingMessageCursor tsp : storePrefetches) { 097 tsp.gc(); 098 } 099 } else { 100 super.stop(); 101 for (PendingMessageCursor tsp : storePrefetches) { 102 tsp.stop(); 103 } 104 getMessageAudit().clear(); 105 } 106 } 107 } 108 109 /** 110 * Add a destination 111 * 112 * @param context 113 * @param destination 114 * @throws Exception 115 */ 116 @Override 117 public synchronized void add(ConnectionContext context, Destination destination) throws Exception { 118 if (destination != null && !AdvisorySupport.isAdvisoryTopic(destination.getActiveMQDestination())) { 119 TopicStorePrefetch tsp = new TopicStorePrefetch(this.subscription,(Topic)destination, clientId, subscriberName); 120 tsp.setMaxBatchSize(destination.getMaxPageSize()); 121 tsp.setSystemUsage(systemUsage); 122 tsp.setMessageAudit(getMessageAudit()); 123 tsp.setEnableAudit(isEnableAudit()); 124 tsp.setMemoryUsageHighWaterMark(getMemoryUsageHighWaterMark()); 125 tsp.setUseCache(isUseCache()); 126 tsp.setCacheEnabled(isUseCache() && tsp.isEmpty()); 127 topics.put(destination, tsp); 128 storePrefetches.add(tsp); 129 if (isStarted()) { 130 tsp.start(); 131 } 132 } 133 } 134 135 /** 136 * remove a destination 137 * 138 * @param context 139 * @param destination 140 * @throws Exception 141 */ 142 @Override 143 public synchronized List<MessageReference> remove(ConnectionContext context, Destination destination) throws Exception { 144 PendingMessageCursor tsp = topics.remove(destination); 145 if (tsp != null) { 146 storePrefetches.remove(tsp); 147 } 148 return Collections.EMPTY_LIST; 149 } 150 151 /** 152 * @return true if there are no pending messages 153 */ 154 @Override 155 public synchronized boolean isEmpty() { 156 for (PendingMessageCursor tsp : storePrefetches) { 157 if( !tsp.isEmpty() ) 158 return false; 159 } 160 return true; 161 } 162 163 @Override 164 public synchronized boolean isEmpty(Destination destination) { 165 boolean result = true; 166 TopicStorePrefetch tsp = topics.get(destination); 167 if (tsp != null) { 168 result = tsp.isEmpty(); 169 } 170 return result; 171 } 172 173 /** 174 * Informs the Broker if the subscription needs to intervention to recover 175 * it's state e.g. DurableTopicSubscriber may do 176 * 177 * @see org.apache.activemq.broker.region.cursors.AbstractPendingMessageCursor 178 * @return true if recovery required 179 */ 180 @Override 181 public boolean isRecoveryRequired() { 182 return false; 183 } 184 185 @Override 186 public synchronized boolean tryAddMessageLast(MessageReference node, long wait) throws Exception { 187 if (node != null) { 188 Message msg = node.getMessage(); 189 if (isStarted()) { 190 if (!msg.isPersistent()) { 191 nonPersistent.tryAddMessageLast(node, wait); 192 } 193 } 194 if (msg.isPersistent()) { 195 Destination dest = (Destination) msg.getRegionDestination(); 196 TopicStorePrefetch tsp = topics.get(dest); 197 if (tsp != null) { 198 tsp.addMessageLast(node); 199 if (prioritizedMessages && immediatePriorityDispatch && tsp.isPaging()) { 200 if (msg.getPriority() > tsp.getLastRecoveredPriority()) { 201 tsp.recoverMessage(node.getMessage(), true); 202 LOG.trace("cached high priority ({}) message: {}, current paged batch priority: {}, cache size: {}", 203 msg.getPriority(), msg.getMessageId(), tsp.getLastRecoveredPriority(), tsp.batchList.size()); 204 } 205 } 206 } 207 } 208 209 } 210 return true; 211 } 212 213 @Override 214 public boolean isTransient() { 215 return subscription.isKeepDurableSubsActive(); 216 } 217 218 @Override 219 public void addMessageFirst(MessageReference node) throws Exception { 220 // for keep durable subs active, need to deal with redispatch 221 if (node != null) { 222 Message msg = node.getMessage(); 223 if (!msg.isPersistent()) { 224 nonPersistent.addMessageFirst(node); 225 } else { 226 Destination dest = (Destination) msg.getRegionDestination(); 227 TopicStorePrefetch tsp = topics.get(dest); 228 if (tsp != null) { 229 tsp.addMessageFirst(node); 230 } 231 } 232 } 233 } 234 235 @Override 236 public synchronized void addRecoveredMessage(MessageReference node) throws Exception { 237 nonPersistent.addMessageLast(node); 238 } 239 240 @Override 241 public synchronized void clear() { 242 for (PendingMessageCursor tsp : storePrefetches) { 243 tsp.clear(); 244 } 245 } 246 247 @Override 248 public synchronized boolean hasNext() { 249 boolean result = true; 250 if (result) { 251 try { 252 currentCursor = getNextCursor(); 253 } catch (Exception e) { 254 LOG.error("Failed to get current cursor ", e); 255 throw new RuntimeException(e); 256 } 257 result = currentCursor != null ? currentCursor.hasNext() : false; 258 } 259 return result; 260 } 261 262 @Override 263 public synchronized MessageReference next() { 264 MessageReference result = currentCursor != null ? currentCursor.next() : null; 265 return result; 266 } 267 268 @Override 269 public synchronized void remove() { 270 if (currentCursor != null) { 271 currentCursor.remove(); 272 } 273 } 274 275 @Override 276 public synchronized void remove(MessageReference node) { 277 for (PendingMessageCursor tsp : storePrefetches) { 278 tsp.remove(node); 279 } 280 } 281 282 @Override 283 public synchronized void reset() { 284 for (PendingMessageCursor storePrefetch : storePrefetches) { 285 storePrefetch.reset(); 286 } 287 } 288 289 @Override 290 public synchronized void release() { 291 this.currentCursor = null; 292 for (PendingMessageCursor storePrefetch : storePrefetches) { 293 storePrefetch.release(); 294 } 295 } 296 297 @Override 298 public synchronized int size() { 299 int pendingCount=0; 300 for (PendingMessageCursor tsp : storePrefetches) { 301 pendingCount += tsp.size(); 302 } 303 return pendingCount; 304 } 305 306 @Override 307 public synchronized long messageSize() { 308 long pendingSize=0; 309 for (PendingMessageCursor tsp : storePrefetches) { 310 pendingSize += tsp.messageSize(); 311 } 312 return pendingSize; 313 } 314 315 @Override 316 public void setMaxBatchSize(int newMaxBatchSize) { 317 for (PendingMessageCursor storePrefetch : storePrefetches) { 318 storePrefetch.setMaxBatchSize(newMaxBatchSize); 319 } 320 super.setMaxBatchSize(newMaxBatchSize); 321 } 322 323 @Override 324 public synchronized void gc() { 325 for (PendingMessageCursor tsp : storePrefetches) { 326 tsp.gc(); 327 } 328 } 329 330 @Override 331 public void setSystemUsage(SystemUsage usageManager) { 332 super.setSystemUsage(usageManager); 333 for (PendingMessageCursor tsp : storePrefetches) { 334 tsp.setSystemUsage(usageManager); 335 } 336 } 337 338 @Override 339 public void setMemoryUsageHighWaterMark(int memoryUsageHighWaterMark) { 340 super.setMemoryUsageHighWaterMark(memoryUsageHighWaterMark); 341 for (PendingMessageCursor cursor : storePrefetches) { 342 cursor.setMemoryUsageHighWaterMark(memoryUsageHighWaterMark); 343 } 344 } 345 346 @Override 347 public void setMaxProducersToAudit(int maxProducersToAudit) { 348 super.setMaxProducersToAudit(maxProducersToAudit); 349 for (PendingMessageCursor cursor : storePrefetches) { 350 cursor.setMaxProducersToAudit(maxProducersToAudit); 351 } 352 } 353 354 @Override 355 public void setMaxAuditDepth(int maxAuditDepth) { 356 super.setMaxAuditDepth(maxAuditDepth); 357 for (PendingMessageCursor cursor : storePrefetches) { 358 cursor.setMaxAuditDepth(maxAuditDepth); 359 } 360 } 361 362 @Override 363 public void setEnableAudit(boolean enableAudit) { 364 super.setEnableAudit(enableAudit); 365 for (PendingMessageCursor cursor : storePrefetches) { 366 cursor.setEnableAudit(enableAudit); 367 } 368 } 369 370 @Override 371 public void setUseCache(boolean useCache) { 372 super.setUseCache(useCache); 373 for (PendingMessageCursor cursor : storePrefetches) { 374 cursor.setUseCache(useCache); 375 } 376 } 377 378 protected synchronized PendingMessageCursor getNextCursor() throws Exception { 379 if (currentCursor == null || currentCursor.isEmpty()) { 380 currentCursor = null; 381 for (PendingMessageCursor tsp : storePrefetches) { 382 if (tsp.hasNext()) { 383 currentCursor = tsp; 384 break; 385 } 386 } 387 // round-robin 388 if (storePrefetches.size()>1) { 389 PendingMessageCursor first = storePrefetches.remove(0); 390 storePrefetches.add(first); 391 } 392 } 393 return currentCursor; 394 } 395 396 @Override 397 public String toString() { 398 return "StoreDurableSubscriber(" + clientId + ":" + subscriberName + ")"; 399 } 400 401 public boolean isImmediatePriorityDispatch() { 402 return immediatePriorityDispatch; 403 } 404 405 public void setImmediatePriorityDispatch(boolean immediatePriorityDispatch) { 406 this.immediatePriorityDispatch = immediatePriorityDispatch; 407 } 408 409}