/* * 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.artemis.core.management.impl; import javax.json.JsonArray; import javax.json.JsonArrayBuilder; import javax.json.JsonObjectBuilder; import javax.management.MBeanAttributeInfo; import javax.management.MBeanOperationInfo; import javax.management.openmbean.CompositeData; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.JsonUtil; import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.management.MessageCounterInfo; import org.apache.activemq.artemis.api.core.management.QueueControl; import org.apache.activemq.artemis.core.filter.Filter; import org.apache.activemq.artemis.core.filter.impl.FilterImpl; import org.apache.activemq.artemis.core.management.impl.openmbean.OpenTypeSupport; import org.apache.activemq.artemis.core.message.impl.CoreMessage; import org.apache.activemq.artemis.core.messagecounter.MessageCounter; import org.apache.activemq.artemis.core.messagecounter.impl.MessageCounterHelper; import org.apache.activemq.artemis.core.persistence.StorageManager; import org.apache.activemq.artemis.core.postoffice.Binding; import org.apache.activemq.artemis.core.postoffice.PostOffice; import org.apache.activemq.artemis.core.security.CheckType; import org.apache.activemq.artemis.core.security.SecurityAuth; import org.apache.activemq.artemis.core.security.SecurityStore; import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle; import org.apache.activemq.artemis.core.server.Consumer; import org.apache.activemq.artemis.core.server.MessageReference; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.core.server.ServerConsumer; import org.apache.activemq.artemis.core.settings.HierarchicalRepository; import org.apache.activemq.artemis.core.settings.impl.AddressSettings; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.utils.Base64; import org.apache.activemq.artemis.utils.JsonLoader; import org.apache.activemq.artemis.utils.collections.LinkedListIterator; public class QueueControlImpl extends AbstractControl implements QueueControl { public static final int FLUSH_LIMIT = 500; // Constants ----------------------------------------------------- // Attributes ---------------------------------------------------- private final Queue queue; private final String address; private final PostOffice postOffice; private final StorageManager storageManager; private final SecurityStore securityStore; private final HierarchicalRepository<AddressSettings> addressSettingsRepository; private MessageCounter counter; // Static -------------------------------------------------------- private static String toJSON(final Map<String, Object>[] messages) { JsonArray array = toJSONMsgArray(messages); return array.toString(); } private static JsonArray toJSONMsgArray(final Map<String, Object>[] messages) { JsonArrayBuilder array = JsonLoader.createArrayBuilder(); for (Map<String, Object> message : messages) { array.add(JsonUtil.toJsonObject(message)); } return array.build(); } private static String toJSON(final Map<String, Map<String, Object>[]> messages) { JsonArrayBuilder arrayReturn = JsonLoader.createArrayBuilder(); for (Map.Entry<String, Map<String, Object>[]> entry : messages.entrySet()) { JsonObjectBuilder objectItem = JsonLoader.createObjectBuilder(); objectItem.add("consumerName", entry.getKey()); objectItem.add("elements", toJSONMsgArray(entry.getValue())); arrayReturn.add(objectItem); } return arrayReturn.build().toString(); } // Constructors -------------------------------------------------- public QueueControlImpl(final Queue queue, final String address, final PostOffice postOffice, final StorageManager storageManager, final SecurityStore securityStore, final HierarchicalRepository<AddressSettings> addressSettingsRepository) throws Exception { super(QueueControl.class, storageManager); this.queue = queue; this.address = address; this.postOffice = postOffice; this.storageManager = storageManager; this.securityStore = securityStore; this.addressSettingsRepository = addressSettingsRepository; } // Public -------------------------------------------------------- public void setMessageCounter(final MessageCounter counter) { this.counter = counter; } // QueueControlMBean implementation ------------------------------ @Override public String getName() { clearIO(); try { return queue.getName().toString(); } finally { blockOnIO(); } } @Override public String getAddress() { checkStarted(); return address; } @Override public String getFilter() { checkStarted(); clearIO(); try { Filter filter = queue.getFilter(); return filter != null ? filter.getFilterString().toString() : null; } finally { blockOnIO(); } } @Override public boolean isDurable() { checkStarted(); clearIO(); try { return queue.isDurable(); } finally { blockOnIO(); } } @Override public String getRoutingType() { checkStarted(); clearIO(); try { return queue.getRoutingType().toString(); } finally { blockOnIO(); } } @Override public boolean isTemporary() { checkStarted(); clearIO(); try { return queue.isTemporary(); } finally { blockOnIO(); } } @Override public long getMessageCount() { checkStarted(); clearIO(); try { return queue.getMessageCount(); } finally { blockOnIO(); } } @Override public int getConsumerCount() { checkStarted(); clearIO(); try { return queue.getConsumerCount(); } finally { blockOnIO(); } } @Override public int getDeliveringCount() { checkStarted(); clearIO(); try { return queue.getDeliveringCount(); } finally { blockOnIO(); } } @Override public long getMessagesAdded() { checkStarted(); clearIO(); try { return queue.getMessagesAdded(); } finally { blockOnIO(); } } @Override public long getMessagesAcknowledged() { checkStarted(); clearIO(); try { return queue.getMessagesAcknowledged(); } finally { blockOnIO(); } } @Override public long getMessagesExpired() { checkStarted(); clearIO(); try { return queue.getMessagesExpired(); } finally { blockOnIO(); } } @Override public long getMessagesKilled() { checkStarted(); clearIO(); try { return queue.getMessagesKilled(); } finally { blockOnIO(); } } @Override public long getID() { checkStarted(); clearIO(); try { return queue.getID(); } finally { blockOnIO(); } } @Override public long getScheduledCount() { checkStarted(); clearIO(); try { return queue.getScheduledCount(); } finally { blockOnIO(); } } @Override public String getDeadLetterAddress() { checkStarted(); clearIO(); try { AddressSettings addressSettings = addressSettingsRepository.getMatch(address); if (addressSettings != null && addressSettings.getDeadLetterAddress() != null) { return addressSettings.getDeadLetterAddress().toString(); } return null; } finally { blockOnIO(); } } @Override public String getExpiryAddress() { checkStarted(); clearIO(); try { AddressSettings addressSettings = addressSettingsRepository.getMatch(address); if (addressSettings != null && addressSettings.getExpiryAddress() != null) { return addressSettings.getExpiryAddress().toString(); } else { return null; } } finally { blockOnIO(); } } @Override public int getMaxConsumers() { checkStarted(); clearIO(); try { return queue.getMaxConsumers(); } finally { blockOnIO(); } } @Override public boolean isPurgeOnNoConsumers() { checkStarted(); clearIO(); try { return queue.isPurgeOnNoConsumers(); } finally { blockOnIO(); } } @Override public Map<String, Object>[] listScheduledMessages() throws Exception { checkStarted(); clearIO(); try { List<MessageReference> refs = queue.getScheduledMessages(); return convertMessagesToMaps(refs); } finally { blockOnIO(); } } @Override public String listScheduledMessagesAsJSON() throws Exception { checkStarted(); clearIO(); try { return QueueControlImpl.toJSON(listScheduledMessages()); } finally { blockOnIO(); } } /** * @param refs * @return */ private Map<String, Object>[] convertMessagesToMaps(List<MessageReference> refs) throws ActiveMQException { Map<String, Object>[] messages = new Map[refs.size()]; int i = 0; for (MessageReference ref : refs) { Message message = ref.getMessage(); messages[i++] = message.toMap(); } return messages; } @Override public Map<String, Map<String, Object>[]> listDeliveringMessages() throws ActiveMQException { checkStarted(); clearIO(); try { Map<String, List<MessageReference>> msgs = queue.getDeliveringMessages(); Map<String, Map<String, Object>[]> msgRet = new HashMap<>(); for (Map.Entry<String, List<MessageReference>> entry : msgs.entrySet()) { msgRet.put(entry.getKey(), convertMessagesToMaps(entry.getValue())); } return msgRet; } finally { blockOnIO(); } } @Override public String listDeliveringMessagesAsJSON() throws Exception { checkStarted(); clearIO(); try { return QueueControlImpl.toJSON(listDeliveringMessages()); } finally { blockOnIO(); } } @Override public Map<String, Object>[] listMessages(final String filterStr) throws Exception { checkStarted(); clearIO(); try { Filter filter = FilterImpl.createFilter(filterStr); List<Map<String, Object>> messages = new ArrayList<>(); queue.flushExecutor(); try (LinkedListIterator<MessageReference> iterator = queue.browserIterator()) { try { while (iterator.hasNext()) { MessageReference ref = iterator.next(); if (filter == null || filter.match(ref.getMessage())) { Message message = ref.getMessage(); messages.add(message.toMap()); } } } catch (NoSuchElementException ignored) { // this could happen through paging browsing } return messages.toArray(new Map[messages.size()]); } } catch (ActiveMQException e) { throw new IllegalStateException(e.getMessage()); } finally { blockOnIO(); } } @Override public String listMessagesAsJSON(final String filter) throws Exception { checkStarted(); clearIO(); try { return QueueControlImpl.toJSON(listMessages(filter)); } finally { blockOnIO(); } } protected Map<String, Object>[] getFirstMessage() throws Exception { checkStarted(); clearIO(); try { List<Map<String, Object>> messages = new ArrayList<>(); queue.flushExecutor(); try (LinkedListIterator<MessageReference> iterator = queue.browserIterator()) { // returns just the first, as it's the first only if (iterator.hasNext()) { MessageReference ref = iterator.next(); Message message = ref.getMessage(); messages.add(message.toMap()); } return messages.toArray(new Map[1]); } } finally { blockOnIO(); } } @Override public String getFirstMessageAsJSON() throws Exception { return toJSON(getFirstMessage()); } @Override public Long getFirstMessageTimestamp() throws Exception { Map<String, Object>[] _message = getFirstMessage(); if (_message == null || _message.length == 0 || _message[0] == null) { return null; } Map<String, Object> message = _message[0]; if (!message.containsKey("timestamp")) { return null; } return (Long) message.get("timestamp"); } @Override public Long getFirstMessageAge() throws Exception { Long firstMessageTimestamp = getFirstMessageTimestamp(); if (firstMessageTimestamp == null) { return null; } long now = new Date().getTime(); return now - firstMessageTimestamp.longValue(); } @Override public long countMessages(final String filterStr) throws Exception { checkStarted(); clearIO(); try { Filter filter = FilterImpl.createFilter(filterStr); if (filter == null) { return getMessageCount(); } else { try (LinkedListIterator<MessageReference> iterator = queue.browserIterator()) { int count = 0; try { while (iterator.hasNext()) { MessageReference ref = iterator.next(); if (filter.match(ref.getMessage())) { count++; } } } catch (NoSuchElementException ignored) { // this could happen through paging browsing } return count; } } } finally { blockOnIO(); } } @Override public boolean removeMessage(final long messageID) throws Exception { checkStarted(); clearIO(); try { return queue.deleteReference(messageID); } catch (ActiveMQException e) { throw new IllegalStateException(e.getMessage()); } finally { blockOnIO(); } } @Override public int removeMessages(final String filterStr) throws Exception { return removeMessages(FLUSH_LIMIT, filterStr); } @Override public int removeMessages(final int flushLimit, final String filterStr) throws Exception { checkStarted(); clearIO(); try { Filter filter = FilterImpl.createFilter(filterStr); return queue.deleteMatchingReferences(flushLimit, filter); } finally { blockOnIO(); } } @Override public boolean expireMessage(final long messageID) throws Exception { checkStarted(); clearIO(); try { return queue.expireReference(messageID); } finally { blockOnIO(); } } @Override public int expireMessages(final String filterStr) throws Exception { checkStarted(); clearIO(); try { Filter filter = FilterImpl.createFilter(filterStr); return queue.expireReferences(filter); } catch (ActiveMQException e) { throw new IllegalStateException(e.getMessage()); } finally { blockOnIO(); } } @Override public boolean retryMessage(final long messageID) throws Exception { checkStarted(); clearIO(); try { Filter singleMessageFilter = new Filter() { @Override public boolean match(Message message) { return message.getMessageID() == messageID; } @Override public SimpleString getFilterString() { return new SimpleString("custom filter for MESSAGEID= messageID"); } }; return queue.retryMessages(singleMessageFilter) > 0; } finally { blockOnIO(); } } @Override public int retryMessages() throws Exception { checkStarted(); clearIO(); try { return queue.retryMessages(null); } finally { blockOnIO(); } } @Override public boolean moveMessage(final long messageID, final String otherQueueName) throws Exception { return moveMessage(messageID, otherQueueName, false); } @Override public boolean moveMessage(final long messageID, final String otherQueueName, final boolean rejectDuplicates) throws Exception { checkStarted(); clearIO(); try { Binding binding = postOffice.getBinding(new SimpleString(otherQueueName)); if (binding == null) { throw ActiveMQMessageBundle.BUNDLE.noQueueFound(otherQueueName); } return queue.moveReference(messageID, binding.getAddress(), rejectDuplicates); } finally { blockOnIO(); } } @Override public int moveMessages(final String filterStr, final String otherQueueName) throws Exception { return moveMessages(filterStr, otherQueueName, false); } @Override public int moveMessages(final int flushLimit, final String filterStr, final String otherQueueName, final boolean rejectDuplicates) throws Exception { checkStarted(); clearIO(); try { Filter filter = FilterImpl.createFilter(filterStr); Binding binding = postOffice.getBinding(new SimpleString(otherQueueName)); if (binding == null) { throw ActiveMQMessageBundle.BUNDLE.noQueueFound(otherQueueName); } int retValue = queue.moveReferences(flushLimit, filter, binding.getAddress(), rejectDuplicates); return retValue; } finally { blockOnIO(); } } @Override public int moveMessages(final String filterStr, final String otherQueueName, final boolean rejectDuplicates) throws Exception { return moveMessages(FLUSH_LIMIT, filterStr, otherQueueName, rejectDuplicates); } @Override public int sendMessagesToDeadLetterAddress(final String filterStr) throws Exception { checkStarted(); clearIO(); try { Filter filter = FilterImpl.createFilter(filterStr); return queue.sendMessagesToDeadLetterAddress(filter); } finally { blockOnIO(); } } @Override public String sendMessage(final Map<String, String> headers, final int type, final String body, boolean durable, final String user, final String password) throws Exception { try { securityStore.check(queue.getAddress(), CheckType.SEND, new SecurityAuth() { @Override public String getUsername() { return user; } @Override public String getPassword() { return password; } @Override public RemotingConnection getRemotingConnection() { return null; } }); CoreMessage message = new CoreMessage(storageManager.generateID(), 50); if (headers != null) { for (String header : headers.keySet()) { message.putStringProperty(new SimpleString(header), new SimpleString(headers.get(header))); } } message.setType((byte) type); message.setDurable(durable); message.setTimestamp(System.currentTimeMillis()); if (body != null) { if (type == Message.TEXT_TYPE) { message.getBodyBuffer().writeNullableSimpleString(new SimpleString(body)); } else { message.getBodyBuffer().writeBytes(Base64.decode(body)); } } message.setAddress(queue.getAddress()); ByteBuffer buffer = ByteBuffer.allocate(8); buffer.putLong(queue.getID()); message.putBytesProperty(Message.HDR_ROUTE_TO_IDS, buffer.array()); postOffice.route(message, true); return "" + message.getMessageID(); } catch (ActiveMQException e) { throw new IllegalStateException(e.getMessage()); } } @Override public boolean sendMessageToDeadLetterAddress(final long messageID) throws Exception { checkStarted(); clearIO(); try { return queue.sendMessageToDeadLetterAddress(messageID); } finally { blockOnIO(); } } @Override public int changeMessagesPriority(final String filterStr, final int newPriority) throws Exception { checkStarted(); clearIO(); try { if (newPriority < 0 || newPriority > 9) { throw ActiveMQMessageBundle.BUNDLE.invalidNewPriority(newPriority); } Filter filter = FilterImpl.createFilter(filterStr); return queue.changeReferencesPriority(filter, (byte) newPriority); } finally { blockOnIO(); } } @Override public boolean changeMessagePriority(final long messageID, final int newPriority) throws Exception { checkStarted(); clearIO(); try { if (newPriority < 0 || newPriority > 9) { throw ActiveMQMessageBundle.BUNDLE.invalidNewPriority(newPriority); } return queue.changeReferencePriority(messageID, (byte) newPriority); } finally { blockOnIO(); } } @Override public String listMessageCounter() { checkStarted(); clearIO(); try { return MessageCounterInfo.toJSon(counter); } catch (Exception e) { throw new IllegalStateException(e); } finally { blockOnIO(); } } @Override public void resetMessageCounter() { checkStarted(); clearIO(); try { counter.resetCounter(); } finally { blockOnIO(); } } @Override public String listMessageCounterAsHTML() { checkStarted(); clearIO(); try { return MessageCounterHelper.listMessageCounterAsHTML(new MessageCounter[]{counter}); } finally { blockOnIO(); } } @Override public String listMessageCounterHistory() throws Exception { checkStarted(); clearIO(); try { return MessageCounterHelper.listMessageCounterHistory(counter); } finally { blockOnIO(); } } @Override public String listMessageCounterHistoryAsHTML() { checkStarted(); clearIO(); try { return MessageCounterHelper.listMessageCounterHistoryAsHTML(new MessageCounter[]{counter}); } finally { blockOnIO(); } } @Override public void pause() { checkStarted(); clearIO(); try { queue.pause(); } finally { blockOnIO(); } } @Override public void pause(boolean persist) { checkStarted(); clearIO(); try { queue.pause(persist); } finally { blockOnIO(); } } @Override public void resume() { checkStarted(); clearIO(); try { queue.resume(); } finally { blockOnIO(); } } @Override public boolean isPaused() throws Exception { checkStarted(); clearIO(); try { return queue.isPaused(); } finally { blockOnIO(); } } @Override public CompositeData[] browse() throws Exception { return browse(null); } @Override public CompositeData[] browse(String filter) throws Exception { checkStarted(); clearIO(); try { int pageSize = addressSettingsRepository.getMatch(queue.getName().toString()).getManagementBrowsePageSize(); int currentPageSize = 0; ArrayList<CompositeData> c = new ArrayList<>(); Filter thefilter = FilterImpl.createFilter(filter); queue.flushExecutor(); try (LinkedListIterator<MessageReference> iterator = queue.browserIterator()) { try { while (iterator.hasNext() && currentPageSize++ < pageSize) { MessageReference ref = iterator.next(); if (thefilter == null || thefilter.match(ref.getMessage())) { c.add(OpenTypeSupport.convert(ref)); } } } catch (NoSuchElementException ignored) { // this could happen through paging browsing } CompositeData[] rc = new CompositeData[c.size()]; c.toArray(rc); return rc; } } catch (ActiveMQException e) { throw new IllegalStateException(e.getMessage()); } finally { blockOnIO(); } } @Override public void flushExecutor() { checkStarted(); clearIO(); try { queue.flushExecutor(); } finally { blockOnIO(); } } @Override public String listConsumersAsJSON() throws Exception { checkStarted(); clearIO(); try { Collection<Consumer> consumers = queue.getConsumers(); JsonArrayBuilder jsonArray = JsonLoader.createArrayBuilder(); for (Consumer consumer : consumers) { if (consumer instanceof ServerConsumer) { ServerConsumer serverConsumer = (ServerConsumer) consumer; JsonObjectBuilder obj = JsonLoader.createObjectBuilder().add("consumerID", serverConsumer.getID()).add("connectionID", serverConsumer.getConnectionID().toString()).add("sessionID", serverConsumer.getSessionID()).add("browseOnly", serverConsumer.isBrowseOnly()).add("creationTime", serverConsumer.getCreationTime()); jsonArray.add(obj); } } return jsonArray.build().toString(); } finally { blockOnIO(); } } @Override protected MBeanOperationInfo[] fillMBeanOperationInfo() { return MBeanInfoHelper.getMBeanOperationsInfo(QueueControl.class); } @Override protected MBeanAttributeInfo[] fillMBeanAttributeInfo() { return MBeanInfoHelper.getMBeanAttributesInfo(QueueControl.class); } @Override public void resetMessagesAdded() throws Exception { checkStarted(); clearIO(); try { queue.resetMessagesAdded(); } finally { blockOnIO(); } } @Override public void resetMessagesAcknowledged() throws Exception { checkStarted(); clearIO(); try { queue.resetMessagesAcknowledged(); } finally { blockOnIO(); } } @Override public void resetMessagesExpired() throws Exception { checkStarted(); clearIO(); try { queue.resetMessagesExpired(); } finally { blockOnIO(); } } @Override public void resetMessagesKilled() throws Exception { checkStarted(); clearIO(); try { queue.resetMessagesKilled(); } finally { blockOnIO(); } } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- // Private ------------------------------------------------------- private void checkStarted() { if (!postOffice.isStarted()) { throw new IllegalStateException("Broker is not started. Queue can not be managed yet"); } } // Inner classes ------------------------------------------------- }