Java tutorial
/* * * 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.qpid.server.virtualhost; import static java.util.Collections.newSetFromMap; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.AccessControlContext; import java.security.Principal; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.security.auth.Subject; import com.google.common.base.Function; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.qpid.exchange.ExchangeDefaults; import org.apache.qpid.pool.SuppressingInheritedAccessControlContextThreadFactory; import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.configuration.updater.Task; import org.apache.qpid.server.configuration.updater.TaskExecutor; import org.apache.qpid.server.configuration.updater.TaskExecutorImpl; import org.apache.qpid.server.exchange.DefaultDestination; import org.apache.qpid.server.logging.EventLogger; import org.apache.qpid.server.logging.messages.MessageStoreMessages; import org.apache.qpid.server.logging.messages.VirtualHostMessages; import org.apache.qpid.server.logging.subjects.MessageStoreLogSubject; import org.apache.qpid.server.message.AMQMessageHeader; import org.apache.qpid.server.message.InstanceProperties; import org.apache.qpid.server.message.MessageDestination; import org.apache.qpid.server.message.MessageNode; import org.apache.qpid.server.message.MessageSource; import org.apache.qpid.server.message.RoutingResult; import org.apache.qpid.server.message.ServerMessage; import org.apache.qpid.server.message.internal.InternalMessage; import org.apache.qpid.server.model.*; import org.apache.qpid.server.model.port.AmqpPort; import org.apache.qpid.server.model.preferences.Preference; import org.apache.qpid.server.model.preferences.UserPreferences; import org.apache.qpid.server.model.preferences.UserPreferencesImpl; import org.apache.qpid.server.plugin.ConnectionValidator; import org.apache.qpid.server.plugin.QpidServiceLoader; import org.apache.qpid.server.plugin.SystemNodeCreator; import org.apache.qpid.server.protocol.LinkRegistry; import org.apache.qpid.server.protocol.LinkRegistryImpl; import org.apache.qpid.server.queue.QueueEntry; import org.apache.qpid.server.security.AccessControl; import org.apache.qpid.server.security.CompoundAccessControl; import org.apache.qpid.server.security.Result; import org.apache.qpid.server.security.SubjectFixedResultAccessControl; import org.apache.qpid.server.security.SubjectFixedResultAccessControl.ResultCalculator; import org.apache.qpid.server.security.access.Operation; import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; import org.apache.qpid.server.security.auth.SocketConnectionMetaData; import org.apache.qpid.server.stats.StatisticsCounter; import org.apache.qpid.server.store.ConfiguredObjectRecord; import org.apache.qpid.server.store.DurableConfigurationStore; import org.apache.qpid.server.store.Event; import org.apache.qpid.server.store.GenericRecoverer; import org.apache.qpid.server.store.MessageEnqueueRecord; import org.apache.qpid.server.store.MessageStore; import org.apache.qpid.server.store.MessageStoreProvider; import org.apache.qpid.server.store.StoreException; import org.apache.qpid.server.store.StoredMessage; import org.apache.qpid.server.store.handler.ConfiguredObjectRecordHandler; import org.apache.qpid.server.store.handler.DistributedTransactionHandler; import org.apache.qpid.server.store.handler.MessageHandler; import org.apache.qpid.server.store.handler.MessageInstanceHandler; import org.apache.qpid.server.store.preferences.PreferenceRecord; import org.apache.qpid.server.store.preferences.PreferenceStore; import org.apache.qpid.server.store.preferences.PreferenceStoreUpdater; import org.apache.qpid.server.store.preferences.PreferenceStoreUpdaterImpl; import org.apache.qpid.server.store.preferences.PreferencesRecoverer; import org.apache.qpid.server.store.preferences.PreferencesRoot; import org.apache.qpid.server.store.serializer.MessageStoreSerializer; import org.apache.qpid.server.transport.AMQPConnection; import org.apache.qpid.server.transport.NetworkConnectionScheduler; import org.apache.qpid.server.txn.AutoCommitTransaction; import org.apache.qpid.server.txn.DtxRegistry; import org.apache.qpid.server.txn.LocalTransaction; import org.apache.qpid.server.txn.ServerTransaction; import org.apache.qpid.server.util.Action; import org.apache.qpid.server.util.ConnectionScopedRuntimeException; import org.apache.qpid.server.util.HousekeepingExecutor; import org.apache.qpid.server.util.MapValueConverter; import org.apache.qpid.util.Strings; public abstract class AbstractVirtualHost<X extends AbstractVirtualHost<X>> extends AbstractConfiguredObject<X> implements QueueManagingVirtualHost<X> { private final Collection<ConnectionValidator> _connectionValidators = new ArrayList<>(); private final Set<AMQPConnection<?>> _connections = newSetFromMap( new ConcurrentHashMap<AMQPConnection<?>, Boolean>()); private final AccessControlContext _housekeepingJobContext; private final AccessControlContext _fileSystemSpaceCheckerJobContext; private final AtomicBoolean _acceptsConnections = new AtomicBoolean(false); private TaskExecutor _preferenceTaskExecutor; private static enum BlockingType { STORE, FILESYSTEM }; private static final String USE_ASYNC_RECOVERY = "use_async_message_store_recovery"; public static final String DEFAULT_DLQ_NAME_SUFFIX = "_DLQ"; public static final String DLQ_ROUTING_KEY = "dlq"; public static final String CREATE_DLQ_ON_CREATION = "x-qpid-dlq-enabled"; // TODO - this value should change private static final int MAX_LENGTH = 255; private static final Logger _logger = LoggerFactory.getLogger(AbstractVirtualHost.class); private static final int HOUSEKEEPING_SHUTDOWN_TIMEOUT = 5; private ScheduledThreadPoolExecutor _houseKeepingTaskExecutor; private final Broker<?> _broker; private final DtxRegistry _dtxRegistry; private final SystemNodeRegistry _systemNodeRegistry = new SystemNodeRegistry(); private final StatisticsCounter _messagesDelivered, _dataDelivered, _messagesReceived, _dataReceived; private final Map<String, LinkRegistry> _linkRegistry = new HashMap<String, LinkRegistry>(); private AtomicBoolean _blocked = new AtomicBoolean(); private final Map<String, MessageDestination> _systemNodeDestinations = Collections .synchronizedMap(new HashMap<String, MessageDestination>()); private final Map<String, MessageSource> _systemNodeSources = Collections .synchronizedMap(new HashMap<String, MessageSource>()); private final EventLogger _eventLogger; private final VirtualHostNode<?> _virtualHostNode; private final AtomicLong _targetSize = new AtomicLong(100 * 1024 * 1024); private MessageStoreLogSubject _messageStoreLogSubject; private final Set<BlockingType> _blockingReasons = Collections .synchronizedSet(EnumSet.noneOf(BlockingType.class)); private NetworkConnectionScheduler _networkConnectionScheduler; private final VirtualHostPrincipal _principal; private ConfigurationChangeListener _accessControlProviderListener = new AccessControlProviderListener(); private final AccessControl _accessControl; private volatile boolean _createDefaultExchanges; private final AccessControl _systemUserAllowed = new SubjectFixedResultAccessControl(new ResultCalculator() { @Override public Result getResult(final Subject subject) { return isSystemSubject(subject) ? Result.ALLOWED : Result.DEFER; } }, Result.DEFER); @ManagedAttributeField private boolean _queue_deadLetterQueueEnabled; @ManagedAttributeField private long _housekeepingCheckPeriod; @ManagedAttributeField private long _storeTransactionIdleTimeoutClose; @ManagedAttributeField private long _storeTransactionIdleTimeoutWarn; @ManagedAttributeField private long _storeTransactionOpenTimeoutClose; @ManagedAttributeField private long _storeTransactionOpenTimeoutWarn; @ManagedAttributeField private int _housekeepingThreadCount; @ManagedAttributeField private int _connectionThreadPoolSize; @ManagedAttributeField private int _numberOfSelectors; @ManagedAttributeField private List<String> _enabledConnectionValidators; @ManagedAttributeField private List<String> _disabledConnectionValidators; @ManagedAttributeField private List<String> _globalAddressDomains; @ManagedAttributeField private List<NodeAutoCreationPolicy> _nodeAutoCreationPolicies; private boolean _useAsyncRecoverer; private MessageDestination _defaultDestination; private MessageStore _messageStore; private MessageStoreRecoverer _messageStoreRecoverer; private final FileSystemSpaceChecker _fileSystemSpaceChecker; private int _fileSystemMaxUsagePercent; private Collection<VirtualHostLogger> _virtualHostLoggersToClose; private PreferenceStore _preferenceStore; public AbstractVirtualHost(final Map<String, Object> attributes, VirtualHostNode<?> virtualHostNode) { super(virtualHostNode, attributes); _broker = (Broker<?>) virtualHostNode.getParent(); _virtualHostNode = virtualHostNode; _dtxRegistry = new DtxRegistry(this); final SystemConfig systemConfig = (SystemConfig) _broker.getParent(); _eventLogger = systemConfig.getEventLogger(); _eventLogger.message(VirtualHostMessages.CREATED(getName())); _messagesDelivered = new StatisticsCounter("messages-delivered-" + getName()); _dataDelivered = new StatisticsCounter("bytes-delivered-" + getName()); _messagesReceived = new StatisticsCounter("messages-received-" + getName()); _dataReceived = new StatisticsCounter("bytes-received-" + getName()); _principal = new VirtualHostPrincipal(this); if (systemConfig.isManagementMode()) { _accessControl = AccessControl.ALWAYS_ALLOWED; } else { _accessControl = new CompoundAccessControl(Collections.<AccessControl<?>>emptyList(), Result.DEFER); } _defaultDestination = new DefaultDestination(this, _accessControl); _housekeepingJobContext = getSystemTaskControllerContext("Housekeeping[" + getName() + "]", _principal); _fileSystemSpaceCheckerJobContext = getSystemTaskControllerContext( "FileSystemSpaceChecker[" + getName() + "]", _principal); _fileSystemSpaceChecker = new FileSystemSpaceChecker(); addChangeListener(new TargetSizeAssigningListener()); } private void updateAccessControl() { if (!((SystemConfig) _broker.getParent()).isManagementMode()) { List<VirtualHostAccessControlProvider> children = new ArrayList<>( getChildren(VirtualHostAccessControlProvider.class)); _logger.debug("Updating access control list with {} provider children", children.size()); Collections.sort(children, VirtualHostAccessControlProvider.ACCESS_CONTROL_PROVIDER_COMPARATOR); List<AccessControl<?>> accessControls = new ArrayList<>(children.size() + 2); accessControls.add(_systemUserAllowed); for (VirtualHostAccessControlProvider prov : children) { if (prov.getState() == State.ERRORED) { accessControls.clear(); accessControls.add(AccessControl.ALWAYS_DENIED); break; } else if (prov.getState() == State.ACTIVE) { accessControls.add(prov.getAccessControl()); } } accessControls.add(getParentAccessControl()); ((CompoundAccessControl) _accessControl).setAccessControls(accessControls); } } @Override protected void onCreate() { super.onCreate(); _createDefaultExchanges = true; } @Override public void setFirstOpening(boolean firstOpening) { _createDefaultExchanges = firstOpening; } public void onValidate() { super.onValidate(); String name = getName(); if (name == null || "".equals(name.trim())) { throw new IllegalConfigurationException("Virtual host name must be specified"); } String type = getType(); if (type == null || "".equals(type.trim())) { throw new IllegalConfigurationException("Virtual host type must be specified"); } if (!isDurable()) { throw new IllegalArgumentException(getClass().getSimpleName() + " must be durable"); } if (getGlobalAddressDomains() != null) { for (String domain : getGlobalAddressDomains()) { validateGlobalAddressDomain(domain); } } if (getNodeAutoCreationPolicies() != null) { for (NodeAutoCreationPolicy policy : getNodeAutoCreationPolicies()) { validateNodeAutoCreationPolicy(policy); } } validateConnectionThreadPoolSettings(this); validateMessageStoreCreation(); } @Override protected void validateChange(final ConfiguredObject<?> proxyForValidation, final Set<String> changedAttributes) { super.validateChange(proxyForValidation, changedAttributes); QueueManagingVirtualHost<?> virtualHost = (QueueManagingVirtualHost<?>) proxyForValidation; if (changedAttributes.contains(GLOBAL_ADDRESS_DOMAINS)) { if (virtualHost.getGlobalAddressDomains() != null) { for (String name : virtualHost.getGlobalAddressDomains()) { validateGlobalAddressDomain(name); } } } if (changedAttributes.contains(NODE_AUTO_CREATION_POLICIES)) { if (getNodeAutoCreationPolicies() != null) { for (NodeAutoCreationPolicy policy : virtualHost.getNodeAutoCreationPolicies()) { validateNodeAutoCreationPolicy(policy); } } } if (changedAttributes.contains(CONNECTION_THREAD_POOL_SIZE) || changedAttributes.contains(NUMBER_OF_SELECTORS)) { validateConnectionThreadPoolSettings(virtualHost); } } @Override protected AccessControl getAccessControl() { return _accessControl; } private AccessControl getParentAccessControl() { return super.getAccessControl(); } @Override protected void postResolveChildren() { super.postResolveChildren(); addChangeListener(_accessControlProviderListener); } private void validateNodeAutoCreationPolicy(final NodeAutoCreationPolicy policy) { String pattern = policy.getPattern(); if (pattern == null) { throw new IllegalArgumentException( "The 'pattern' attribute of a NodeAutoCreationPattern MUST be supplied: " + policy); } try { Pattern.compile(pattern); } catch (PatternSyntaxException e) { throw new IllegalArgumentException( "The 'pattern' attribute of a NodeAutoCreationPattern MUST be a valid " + "Java Regular Expression Pattern, the value '" + pattern + "' is not: " + policy); } String nodeType = policy.getNodeType(); Class<? extends ConfiguredObject> sourceClass = null; for (Class<? extends ConfiguredObject> childClass : getModel().getChildTypes(getCategoryClass())) { if (childClass.getSimpleName().equalsIgnoreCase(nodeType.trim())) { sourceClass = childClass; break; } } if (sourceClass == null) { throw new IllegalArgumentException( "The node type of a NodeAutoCreationPattern must be a valid child type " + "of a VirtualHost, '" + nodeType + "' is not."); } if (policy.isCreatedOnConsume() && !MessageSource.class.isAssignableFrom(sourceClass)) { throw new IllegalArgumentException( "A NodeAutoCreationPattern which creates nodes on consume must have a " + "nodeType which implements MessageSource, '" + nodeType + "' does not."); } if (policy.isCreatedOnPublish() && !MessageDestination.class.isAssignableFrom(sourceClass)) { throw new IllegalArgumentException( "A NodeAutoCreationPattern which creates nodes on publish must have a " + "nodeType which implements MessageDestination, '" + nodeType + "' does not."); } if (!(policy.isCreatedOnConsume() || policy.isCreatedOnPublish())) { throw new IllegalArgumentException( "A NodeAutoCreationPattern must create on consume, create on publish or both."); } } private void validateGlobalAddressDomain(final String name) { String regex = "/(/?)([\\w_\\-:.\\$]+/)*[\\w_\\-:.\\$]+"; if (!name.matches(regex)) { throw new IllegalArgumentException("'" + name + "' is not a valid global address domain"); } } @Override public MessageStore getMessageStore() { return _messageStore; } private void validateConnectionThreadPoolSettings(QueueManagingVirtualHost<?> virtualHost) { if (virtualHost.getConnectionThreadPoolSize() < 1) { throw new IllegalConfigurationException( String.format("Thread pool size %d on VirtualHost %s must be greater than zero.", virtualHost.getConnectionThreadPoolSize(), getName())); } if (virtualHost.getNumberOfSelectors() < 1) { throw new IllegalConfigurationException( String.format("Number of Selectors %d on VirtualHost %s must be greater than zero.", virtualHost.getNumberOfSelectors(), getName())); } if (virtualHost.getConnectionThreadPoolSize() <= virtualHost.getNumberOfSelectors()) { throw new IllegalConfigurationException(String.format( "Number of Selectors %d on VirtualHost %s must be less than the connection pool size %d.", virtualHost.getNumberOfSelectors(), getName(), virtualHost.getConnectionThreadPoolSize())); } } protected void validateMessageStoreCreation() { MessageStore store = createMessageStore(); if (store != null) { try { store.openMessageStore(this); } catch (Exception e) { throw new IllegalConfigurationException("Cannot open virtual host message store:" + e.getMessage(), e); } finally { try { store.closeMessageStore(); } catch (Exception e) { _logger.warn("Failed to close database", e); } } } } @Override protected void onExceptionInOpen(RuntimeException e) { super.onExceptionInOpen(e); shutdownHouseKeeping(); closeNetworkConnectionScheduler(); closeMessageStore(); stopPreferenceTaskExecutor(); closePreferenceStore(); stopLogging(new ArrayList<>(getChildren(VirtualHostLogger.class))); } @Override protected void onOpen() { super.onOpen(); registerSystemNodes(); _messageStore = createMessageStore(); _messageStoreLogSubject = new MessageStoreLogSubject(getName(), _messageStore.getClass().getSimpleName()); _messageStore.addEventListener(this, Event.PERSISTENT_MESSAGE_SIZE_OVERFULL); _messageStore.addEventListener(this, Event.PERSISTENT_MESSAGE_SIZE_UNDERFULL); _fileSystemMaxUsagePercent = getContextValue(Integer.class, Broker.STORE_FILESYSTEM_MAX_USAGE_PERCENT); QpidServiceLoader serviceLoader = new QpidServiceLoader(); for (ConnectionValidator validator : serviceLoader.instancesOf(ConnectionValidator.class)) { if ((_enabledConnectionValidators.isEmpty() && (_disabledConnectionValidators.isEmpty()) || !_disabledConnectionValidators.contains(validator.getType())) || _enabledConnectionValidators.contains(validator.getType())) { _connectionValidators.add(validator); } } PreferencesRoot preferencesRoot = (VirtualHostNode) getParent(); _preferenceStore = preferencesRoot.createPreferenceStore(); createHousekeepingExecutor(); } private void createHousekeepingExecutor() { if (_houseKeepingTaskExecutor == null || _houseKeepingTaskExecutor.isTerminated()) { _houseKeepingTaskExecutor = new HousekeepingExecutor("virtualhost-" + getName() + "-pool", getHousekeepingThreadCount(), getSystemTaskSubject("Housekeeping", getPrincipal())); } } private void checkVHostStateIsActive() { if (getState() != State.ACTIVE) { throw new IllegalStateException( "The virtual host state of " + getState() + " does not permit this operation."); } } @Override public boolean isActive() { return getState() == State.ACTIVE; } private void registerSystemNodes() { QpidServiceLoader qpidServiceLoader = new QpidServiceLoader(); Iterable<SystemNodeCreator> factories = qpidServiceLoader.instancesOf(SystemNodeCreator.class); for (SystemNodeCreator creator : factories) { creator.register(_systemNodeRegistry); } } protected abstract MessageStore createMessageStore(); private ListenableFuture<List<Void>> createDefaultExchanges() { return Subject.doAs(getSubjectWithAddedSystemRights(), new PrivilegedAction<ListenableFuture<List<Void>>>() { @Override public ListenableFuture<List<Void>> run() { List<ListenableFuture<Void>> standardExchangeFutures = new ArrayList<>(); standardExchangeFutures.add(addStandardExchange(ExchangeDefaults.DIRECT_EXCHANGE_NAME, ExchangeDefaults.DIRECT_EXCHANGE_CLASS)); standardExchangeFutures.add(addStandardExchange(ExchangeDefaults.TOPIC_EXCHANGE_NAME, ExchangeDefaults.TOPIC_EXCHANGE_CLASS)); standardExchangeFutures.add(addStandardExchange(ExchangeDefaults.HEADERS_EXCHANGE_NAME, ExchangeDefaults.HEADERS_EXCHANGE_CLASS)); standardExchangeFutures.add(addStandardExchange(ExchangeDefaults.FANOUT_EXCHANGE_NAME, ExchangeDefaults.FANOUT_EXCHANGE_CLASS)); return Futures.allAsList(standardExchangeFutures); } ListenableFuture<Void> addStandardExchange(String name, String type) { Map<String, Object> attributes = new HashMap<String, Object>(); attributes.put(Exchange.NAME, name); attributes.put(Exchange.TYPE, type); attributes.put(Exchange.ID, UUIDGenerator.generateExchangeUUID(name, getName())); final ListenableFuture<Exchange<?>> future = addExchangeAsync(attributes); final SettableFuture<Void> returnVal = SettableFuture.create(); addFutureCallback(future, new FutureCallback<Exchange<?>>() { @Override public void onSuccess(final Exchange<?> result) { try { childAdded(result); returnVal.set(null); } catch (Throwable t) { returnVal.setException(t); } } @Override public void onFailure(final Throwable t) { returnVal.setException(t); } }, getTaskExecutor()); return returnVal; } }); } protected MessageStoreLogSubject getMessageStoreLogSubject() { return _messageStoreLogSubject; } @Override public Collection<? extends Connection<?>> getConnections() { return _connections; } @Override public Connection<?> getConnection(String name) { for (Connection<?> connection : _connections) { if (connection.getName().equals(name)) { return connection; } } return null; } @Override public int publishMessage(@Param(name = "message") final ManageableMessage message) { final String address = message.getAddress(); MessageDestination destination = address == null ? getDefaultDestination() : getAttainedMessageDestination(address); if (destination == null) { destination = getDefaultDestination(); } final AMQMessageHeader header = new MessageHeaderImpl(message); Serializable body = null; Object messageContent = message.getContent(); if (messageContent != null) { if (messageContent instanceof Map || messageContent instanceof List) { if (message.getMimeType() != null || message.getEncoding() != null) { throw new IllegalArgumentException( "If the message content is provided as map or list, the mime type and encoding must be left unset"); } body = (Serializable) messageContent; } else if (messageContent instanceof String) { String contentTransferEncoding = message.getContentTransferEncoding(); if ("base64".equalsIgnoreCase(contentTransferEncoding)) { body = Strings.decodeBase64((String) messageContent); } else if (contentTransferEncoding == null || contentTransferEncoding.trim().equals("") || contentTransferEncoding.trim().equalsIgnoreCase("identity")) { String mimeType = message.getMimeType(); if (mimeType != null && !(mimeType = mimeType.trim().toLowerCase()).equals("")) { if (!(mimeType.startsWith("text/") || Arrays.asList("application/json", "application/xml").contains(mimeType))) { throw new IllegalArgumentException(message.getMimeType() + " is invalid as a MIME type for this message. " + "Only MIME types of the text type can be used if a string is supplied as the content"); } else if (mimeType.matches(".*;\\s*charset\\s*=.*")) { throw new IllegalArgumentException(message.getMimeType() + " is invalid as a MIME type for this message. " + "If a string is supplied as the content, the MIME type must not include a charset parameter"); } } body = (String) messageContent; } else { throw new IllegalArgumentException("contentTransferEncoding value '" + contentTransferEncoding + "' is invalid. The only valid values are base64 and identity"); } } else { throw new IllegalArgumentException( "The message content (if present) can only be a string, map or list"); } } InternalMessage internalMessage = InternalMessage.createMessage(getMessageStore(), header, body, message.isPersistent()); AutoCommitTransaction txn = new AutoCommitTransaction(getMessageStore()); final InstanceProperties instanceProperties = new InstanceProperties() { @Override public Object getProperty(final Property prop) { switch (prop) { case EXPIRATION: Date expiration = message.getExpiration(); return expiration == null ? 0 : expiration.getTime(); case IMMEDIATE: return false; case PERSISTENT: return message.isPersistent(); case MANDATORY: return false; case REDELIVERED: return false; default: return null; } } }; final RoutingResult<InternalMessage> result = destination.route(internalMessage, address, instanceProperties); return result.send(txn, null); } @Override protected <C extends ConfiguredObject> ListenableFuture<C> addChildAsync(Class<C> childClass, Map<String, Object> attributes) { checkVHostStateIsActive(); if (childClass == Exchange.class) { return (ListenableFuture<C>) addExchangeAsync(attributes); } else if (childClass == Queue.class) { return (ListenableFuture<C>) addQueueAsync(attributes); } else if (childClass == VirtualHostAlias.class) { throw new UnsupportedOperationException(); } else if (childClass == VirtualHostLogger.class || childClass == VirtualHostAccessControlProvider.class) { return getObjectFactory().createAsync(childClass, attributes, this); } throw new IllegalArgumentException("Cannot create a child of class " + childClass.getSimpleName()); } @Override public EventLogger getEventLogger() { return _eventLogger; } @Override public Map<String, Object> extractConfig(final boolean includeSecureAttributes) { return doSync(doOnConfigThread(new Task<ListenableFuture<Map<String, Object>>, RuntimeException>() { @Override public ListenableFuture<Map<String, Object>> execute() throws RuntimeException { ConfigurationExtractor configExtractor = new ConfigurationExtractor(); Map<String, Object> config = configExtractor.extractConfig(AbstractVirtualHost.this, includeSecureAttributes); return Futures.immediateFuture(config); } @Override public String getObject() { return AbstractVirtualHost.this.toString(); } @Override public String getAction() { return "extractConfig"; } @Override public String getArguments() { return "includeSecureAttributes=" + String.valueOf(includeSecureAttributes); } })); } @Override public Content exportMessageStore() { return new MessageStoreContent(); } private class MessageStoreContent implements Content, CustomRestHeaders { @Override public void write(final OutputStream outputStream) throws IOException { doSync(doOnConfigThread(new Task<ListenableFuture<Void>, IOException>() { @Override public ListenableFuture<Void> execute() throws IOException { if (getState() != State.STOPPED) { throw new IllegalArgumentException( "The exportMessageStore operation can only be called when the virtual host is stopped"); } _messageStore.openMessageStore(AbstractVirtualHost.this); try { final Map<UUID, String> queueMap = new HashMap<>(); getDurableConfigurationStore().reload(new ConfiguredObjectRecordHandler() { @Override public void handle(final ConfiguredObjectRecord record) { if (record.getType().equals(Queue.class.getSimpleName())) { queueMap.put(record.getId(), (String) record.getAttributes().get(ConfiguredObject.NAME)); } } }); MessageStoreSerializer serializer = new QpidServiceLoader() .getInstancesByType(MessageStoreSerializer.class) .get(MessageStoreSerializer.LATEST); MessageStore.MessageStoreReader reader = _messageStore.newMessageStoreReader(); serializer.serialize(queueMap, reader, outputStream); } finally { _messageStore.closeMessageStore(); } return Futures.immediateFuture(null); } @Override public String getObject() { return AbstractVirtualHost.this.toString(); } @Override public String getAction() { return "exportMessageStore"; } @Override public String getArguments() { return null; } })); } @Override public void release() { } @RestContentHeader("Content-Type") public String getContentType() { return "application/octet-stream"; } @SuppressWarnings("unused") @RestContentHeader("Content-Disposition") public String getContentDisposition() { try { String vhostName = getName(); // replace all non-ascii and non-printable characters and all backslashes and percent encoded characters // as suggested by rfc6266 Appendix D String asciiName = vhostName.replaceAll("[^\\x20-\\x7E]", "?").replace('\\', '?') .replaceAll("%[0-9a-fA-F]{2}", "?"); String disposition = String.format( "attachment; filename=\"%s_messages.bin\"; filename*=\"UTF-8''%s_messages.bin\"", asciiName, URLEncoder.encode(vhostName, StandardCharsets.UTF_8.name())); return disposition; } catch (UnsupportedEncodingException e) { throw new RuntimeException("JVM does not support UTF8", e); } } } @Override public void importMessageStore(final String source) { try { final URL url = convertStringToURL(source); try (InputStream input = url.openStream(); BufferedInputStream bufferedInputStream = new BufferedInputStream(input); DataInputStream data = new DataInputStream(bufferedInputStream)) { final MessageStoreSerializer serializer = MessageStoreSerializer.FACTORY.newInstance(data); doSync(doOnConfigThread(new Task<ListenableFuture<Void>, IOException>() { @Override public ListenableFuture<Void> execute() throws IOException { if (getState() != State.STOPPED) { throw new IllegalArgumentException( "The importMessageStore operation can only be called when the virtual host is stopped"); } try { _messageStore.openMessageStore(AbstractVirtualHost.this); checkMessageStoreEmpty(); final Map<String, UUID> queueMap = new HashMap<>(); getDurableConfigurationStore().reload(new ConfiguredObjectRecordHandler() { @Override public void handle(final ConfiguredObjectRecord record) { if (record.getType().equals(Queue.class.getSimpleName())) { queueMap.put((String) record.getAttributes().get(ConfiguredObject.NAME), record.getId()); } } }); serializer.deserialize(queueMap, _messageStore, data); } finally { _messageStore.closeMessageStore(); } return Futures.immediateFuture(null); } @Override public String getObject() { return AbstractVirtualHost.this.toString(); } @Override public String getAction() { return "importMessageStore"; } @Override public String getArguments() { if (url.getProtocol().equalsIgnoreCase("http") || url.getProtocol().equalsIgnoreCase("https") || url.getProtocol().equalsIgnoreCase("file")) { return "source=" + source; } else if (url.getProtocol().equalsIgnoreCase("data")) { return "source=<data stream>"; } else { return "source=<unknown source type>"; } } })); } } catch (IOException e) { throw new IllegalConfigurationException("Cannot convert '" + source + "' to a readable resource", e); } } private void checkMessageStoreEmpty() { final MessageStore.MessageStoreReader reader = _messageStore.newMessageStoreReader(); final StoreEmptyCheckingHandler handler = new StoreEmptyCheckingHandler(); reader.visitMessages(handler); if (handler.isEmpty()) { reader.visitMessageInstances(handler); if (handler.isEmpty()) { reader.visitDistributedTransactions(handler); } } if (!handler.isEmpty()) { throw new IllegalArgumentException("The message store is not empty"); } } private static URL convertStringToURL(final String source) { URL url; try { url = new URL(source); } catch (MalformedURLException e) { File file = new File(source); try { url = file.toURI().toURL(); } catch (MalformedURLException notAFile) { throw new IllegalConfigurationException("Cannot convert " + source + " to a readable resource", notAFile); } } return url; } @Override public boolean authoriseCreateConnection(final AMQPConnection<?> connection) { authorise(Operation.ACTION("connect")); for (ConnectionValidator validator : _connectionValidators) { if (!validator.validateConnectionCreation(connection, this)) { return false; } } return true; } /** * Initialise a housekeeping task to iterate over queues cleaning expired messages with no consumers * and checking for idle or open transactions that have exceeded the permitted thresholds. * * @param period */ private void initialiseHouseKeeping(long period) { if (period > 0L) { scheduleHouseKeepingTask(period, new VirtualHostHouseKeepingTask()); } } protected void shutdownHouseKeeping() { if (_houseKeepingTaskExecutor != null) { _houseKeepingTaskExecutor.shutdown(); try { if (!_houseKeepingTaskExecutor.awaitTermination(HOUSEKEEPING_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS)) { _houseKeepingTaskExecutor.shutdownNow(); } } catch (InterruptedException e) { _logger.warn("Interrupted during Housekeeping shutdown:", e); Thread.currentThread().interrupt(); } } } protected void removeHouseKeepingTasks() { BlockingQueue<Runnable> taskQueue = _houseKeepingTaskExecutor.getQueue(); for (final Runnable runnable : taskQueue) { _houseKeepingTaskExecutor.remove(runnable); } } private void closeNetworkConnectionScheduler() { if (_networkConnectionScheduler != null) { _networkConnectionScheduler.close(); _networkConnectionScheduler = null; } } /** * Allow other broker components to register a HouseKeepingTask * * @param period How often this task should run, in ms. * @param task The task to run. */ public void scheduleHouseKeepingTask(long period, HouseKeepingTask task) { task.setFuture( _houseKeepingTaskExecutor.scheduleAtFixedRate(task, period / 2, period, TimeUnit.MILLISECONDS)); } public ScheduledFuture<?> scheduleTask(long delay, Runnable task) { return _houseKeepingTaskExecutor.schedule(task, delay, TimeUnit.MILLISECONDS); } @Override public void executeTask(final String name, final Runnable task, AccessControlContext context) { _houseKeepingTaskExecutor.execute(new HouseKeepingTask(name, this, context) { @Override public void execute() { task.run(); } }); } @Override public List<String> getEnabledConnectionValidators() { return _enabledConnectionValidators; } @Override public List<String> getDisabledConnectionValidators() { return _disabledConnectionValidators; } @Override public List<String> getGlobalAddressDomains() { return _globalAddressDomains; } @Override public List<NodeAutoCreationPolicy> getNodeAutoCreationPolicies() { return _nodeAutoCreationPolicies; } @Override public MessageSource getAttainedMessageSource(final String name) { MessageSource messageSource = _systemNodeSources.get(name); if (messageSource == null) { messageSource = awaitChildClassToAttainState(Queue.class, name); } if (messageSource == null) { messageSource = autoCreateNode(name, MessageSource.class, false); } return messageSource; } private <T> T autoCreateNode(final String name, final Class<T> clazz, boolean publish) { for (NodeAutoCreationPolicy policy : getNodeAutoCreationPolicies()) { String pattern = policy.getPattern(); if (name.matches(pattern) && ((publish && policy.isCreatedOnPublish()) || (!publish && policy.isCreatedOnConsume()))) { String nodeType = policy.getNodeType(); Class<? extends ConfiguredObject> sourceClass = null; for (Class<? extends ConfiguredObject> childClass : getModel().getChildTypes(getCategoryClass())) { if (childClass.getSimpleName().equalsIgnoreCase(nodeType.trim()) && clazz.isAssignableFrom(childClass)) { sourceClass = childClass; } } if (sourceClass != null) { final Map<String, Object> attributes = new HashMap<>(policy.getAttributes()); attributes.remove(ConfiguredObject.ID); attributes.put(ConfiguredObject.NAME, name); final Class<? extends ConfiguredObject> childClass = sourceClass; try { final T node = Subject.doAs(getSubjectWithAddedSystemRights(), new PrivilegedAction<T>() { @Override public T run() { return (T) doSync(createChildAsync(childClass, attributes)); } }); if (node != null) { return node; } } catch (RuntimeException e) { _logger.info("Unable to auto create a node named {} due to exception", name, e); } } } } return null; } @Override public Queue<?> getAttainedQueue(UUID id) { return (Queue<?>) awaitChildClassToAttainState(Queue.class, id); } @Override public Queue<?> getAttainedQueue(final String name) { return (Queue<?>) awaitChildClassToAttainState(Queue.class, name); } @Override public Broker<?> getBroker() { return _broker; } private ListenableFuture<? extends Queue<?>> addQueueAsync(Map<String, Object> attributes) { if (shouldCreateDLQ(attributes)) { // TODO - this isn't really correct - what if the name has ${foo} in it? String queueName = String.valueOf(attributes.get(Queue.NAME)); validateDLNames(queueName); String altExchangeName = createDLQ(queueName); attributes = new LinkedHashMap<String, Object>(attributes); attributes.put(Queue.ALTERNATE_EXCHANGE, altExchangeName); } return Futures.immediateFuture(addQueueWithoutDLQ(attributes)); } private Queue<?> addQueueWithoutDLQ(Map<String, Object> attributes) { return (Queue) getObjectFactory().create(Queue.class, attributes, this); } @Override public MessageDestination getAttainedMessageDestination(final String name) { MessageDestination destination = _systemNodeDestinations.get(name); if (destination == null) { destination = getAttainedChildFromAddress(Exchange.class, name); } if (destination == null) { destination = getAttainedChildFromAddress(Queue.class, name); } if (destination == null) { destination = autoCreateNode(name, MessageDestination.class, true); } return destination; } @Override public MessageDestination getSystemDestination(final String name) { return _systemNodeDestinations.get(name); } @Override public <T extends ConfiguredObject<?>> T getAttainedChildFromAddress(final Class<T> childClass, final String address) { T child = awaitChildClassToAttainState(childClass, address); if (child == null && getGlobalAddressDomains() != null) { for (String domain : getGlobalAddressDomains()) { if (address.startsWith(domain + "/")) { child = awaitChildClassToAttainState(childClass, address.substring(domain.length())); if (child != null) { break; } } } } return child; } @Override public MessageDestination getDefaultDestination() { return _defaultDestination; } private ListenableFuture<Exchange<?>> addExchangeAsync(Map<String, Object> attributes) throws ReservedExchangeNameException, NoFactoryForTypeException { final SettableFuture<Exchange<?>> returnVal = SettableFuture.create(); addFutureCallback(getObjectFactory().createAsync(Exchange.class, attributes, this), new FutureCallback<Exchange>() { @Override public void onSuccess(final Exchange result) { returnVal.set(result); } @Override public void onFailure(final Throwable t) { returnVal.setException(t); } }, getTaskExecutor()); return returnVal; } @Override public String getLocalAddress(final String routingAddress) { String localAddress = routingAddress; if (getGlobalAddressDomains() != null) { for (String domain : getGlobalAddressDomains()) { if (localAddress.length() > routingAddress.length() - domain.length() && routingAddress.startsWith(domain + "/")) { localAddress = routingAddress.substring(domain.length()); } } } return localAddress; } @Override protected ListenableFuture<Void> beforeClose() { setState(State.UNAVAILABLE); _virtualHostLoggersToClose = new ArrayList(getChildren(VirtualHostLogger.class)); //Stop Connections return closeConnections(); } @Override protected ListenableFuture<Void> onClose() { _dtxRegistry.close(); shutdownHouseKeeping(); closeMessageStore(); stopPreferenceTaskExecutor(); closePreferenceStore(); closeNetworkConnectionScheduler(); _eventLogger.message(VirtualHostMessages.CLOSED(getName())); stopLogging(_virtualHostLoggersToClose); return Futures.immediateFuture(null); } public ListenableFuture<Void> closeConnections() { if (_logger.isDebugEnabled()) { _logger.debug("Closing connection registry :" + _connections.size() + " connections."); } _acceptsConnections.set(false); for (AMQPConnection<?> conn : _connections) { conn.stopConnection(); } List<ListenableFuture<Void>> connectionCloseFutures = new ArrayList<>(); while (!_connections.isEmpty()) { Iterator<AMQPConnection<?>> itr = _connections.iterator(); while (itr.hasNext()) { Connection<?> connection = itr.next(); try { connectionCloseFutures.add(connection.closeAsync()); } catch (Exception e) { _logger.warn("Exception closing connection " + connection.getName() + " from " + connection.getRemoteAddress(), e); } finally { itr.remove(); } } } ListenableFuture<List<Void>> combinedFuture = Futures.allAsList(connectionCloseFutures); return Futures.transform(combinedFuture, new Function<List<Void>, Void>() { @Override public Void apply(List<Void> voids) { return null; } }); } private void closeMessageStore() { if (getMessageStore() != null) { try { if (_messageStoreRecoverer != null) { _messageStoreRecoverer.cancel(); } getMessageStore().closeMessageStore(); } catch (StoreException e) { _logger.error("Failed to close message store", e); } if (!(_virtualHostNode.getConfigurationStore() instanceof MessageStoreProvider)) { getEventLogger().message(getMessageStoreLogSubject(), MessageStoreMessages.CLOSED()); } } } public void registerMessageDelivered(long messageSize) { _messagesDelivered.registerEvent(1L); _dataDelivered.registerEvent(messageSize); _broker.registerMessageDelivered(messageSize); } public void registerMessageReceived(long messageSize, long timestamp) { _messagesReceived.registerEvent(1L, timestamp); _dataReceived.registerEvent(messageSize, timestamp); _broker.registerMessageReceived(messageSize, timestamp); } public StatisticsCounter getMessageReceiptStatistics() { return _messagesReceived; } public StatisticsCounter getDataReceiptStatistics() { return _dataReceived; } public StatisticsCounter getMessageDeliveryStatistics() { return _messagesDelivered; } public StatisticsCounter getDataDeliveryStatistics() { return _dataDelivered; } @Override public void resetStatistics() { _messagesDelivered.reset(); _dataDelivered.reset(); _messagesReceived.reset(); _dataReceived.reset(); for (AMQPConnection<?> connection : _connections) { connection.resetStatistics(); } for (Queue<?> queue : getChildren(Queue.class)) { queue.resetStatistics(); } } public synchronized LinkRegistry getLinkRegistry(String remoteContainerId) { LinkRegistry linkRegistry = _linkRegistry.get(remoteContainerId); if (linkRegistry == null) { linkRegistry = new LinkRegistryImpl(); _linkRegistry.put(remoteContainerId, linkRegistry); } return linkRegistry; } public DtxRegistry getDtxRegistry() { return _dtxRegistry; } private void block(BlockingType blockingType) { synchronized (_connections) { _blockingReasons.add(blockingType); if (_blocked.compareAndSet(false, true)) { for (AMQPConnection<?> conn : _connections) { conn.block(); } } } } private void unblock(BlockingType blockingType) { synchronized (_connections) { _blockingReasons.remove(blockingType); if (_blockingReasons.isEmpty() && _blocked.compareAndSet(true, false)) { for (AMQPConnection<?> conn : _connections) { conn.unblock(); } } } } public void event(final Event event) { switch (event) { case PERSISTENT_MESSAGE_SIZE_OVERFULL: block(BlockingType.STORE); _eventLogger.message(getMessageStoreLogSubject(), MessageStoreMessages.OVERFULL()); break; case PERSISTENT_MESSAGE_SIZE_UNDERFULL: unblock(BlockingType.STORE); _eventLogger.message(getMessageStoreLogSubject(), MessageStoreMessages.UNDERFULL()); break; } } protected void reportIfError(State state) { if (state == State.ERRORED) { _eventLogger.message(VirtualHostMessages.ERRORED(getName())); } } @Override public String getRedirectHost(final AmqpPort<?> port) { return null; } private static class MessageHeaderImpl implements AMQMessageHeader { private final String _userName; private final long _timestamp; private final ManageableMessage _message; public MessageHeaderImpl(final ManageableMessage message) { _message = message; _userName = AuthenticatedPrincipal.getCurrentUser().getName(); _timestamp = System.currentTimeMillis(); } @Override public String getCorrelationId() { return _message.getCorrelationId(); } @Override public long getExpiration() { Date expiration = _message.getExpiration(); return expiration == null ? 0 : expiration.getTime(); } @Override public String getUserId() { return _userName; } @Override public String getAppId() { return null; } @Override public String getMessageId() { return _message.getMessageId(); } @Override public String getMimeType() { return _message.getMimeType(); } @Override public String getEncoding() { return _message.getEncoding(); } @Override public byte getPriority() { return (byte) _message.getPriority(); } @Override public long getTimestamp() { return _timestamp; } @Override public long getNotValidBefore() { final Date notValidBefore = _message.getNotValidBefore(); return notValidBefore == null ? 0 : notValidBefore.getTime(); } @Override public String getType() { return null; } @Override public String getReplyTo() { return _message.getReplyTo(); } @Override public Object getHeader(final String name) { return getHeaders().get(name); } @Override public boolean containsHeaders(final Set<String> names) { return getHeaders().keySet().containsAll(names); } @Override public boolean containsHeader(final String name) { return getHeaders().keySet().contains(name); } @Override public Collection<String> getHeaderNames() { return Collections.unmodifiableCollection(getHeaders().keySet()); } private Map<String, Object> getHeaders() { return _message.getHeaders() == null ? Collections.<String, Object>emptyMap() : _message.getHeaders(); } } private class TargetSizeAssigningListener extends AbstractConfigurationChangeListener { @Override public void childAdded(final ConfiguredObject<?> object, final ConfiguredObject<?> child) { if (child instanceof Queue) { allocateTargetSizeToQueues(); } } @Override public void childRemoved(final ConfiguredObject<?> object, final ConfiguredObject<?> child) { if (child instanceof Queue) { allocateTargetSizeToQueues(); } } } private class VirtualHostHouseKeepingTask extends HouseKeepingTask { public VirtualHostHouseKeepingTask() { super("Housekeeping[" + AbstractVirtualHost.this.getName() + "]", AbstractVirtualHost.this, _housekeepingJobContext); } public void execute() { Broker<?> broker = getAncestor(Broker.class); broker.assignTargetSizes(); for (Queue<?> q : getChildren(Queue.class)) { if (q.getState() == State.ACTIVE) { _logger.debug("Checking message status for queue: {}", q.getName()); q.checkMessageStatus(); } } } } private class SystemNodeRegistry implements SystemNodeCreator.SystemNodeRegistry { @Override public void registerSystemNode(final MessageNode node) { if (node instanceof MessageDestination) { _systemNodeDestinations.put(node.getName(), (MessageDestination) node); } if (node instanceof MessageSource) { _systemNodeSources.put(node.getName(), (MessageSource) node); } } @Override public void removeSystemNode(final MessageNode node) { if (node instanceof MessageDestination) { _systemNodeDestinations.remove(node.getName()); } if (node instanceof MessageSource) { _systemNodeSources.remove(node.getName()); } } @Override public void removeSystemNode(final String name) { _systemNodeDestinations.remove(name); _systemNodeSources.remove(name); } @Override public VirtualHostNode<?> getVirtualHostNode() { return (VirtualHostNode) getParent(); } @Override public VirtualHost<?> getVirtualHost() { return AbstractVirtualHost.this; } @Override public boolean hasSystemNode(final String name) { return _systemNodeSources.containsKey(name) || _systemNodeDestinations.containsKey(name); } } public void executeTransaction(TransactionalOperation op) { final MessageStore store = getMessageStore(); final LocalTransaction txn = new LocalTransaction(store); op.withinTransaction(new Transaction() { public void dequeue(final QueueEntry messageInstance) { final ServerTransaction.Action deleteAction = new ServerTransaction.Action() { public void postCommit() { messageInstance.delete(); } public void onRollback() { } }; boolean acquired = messageInstance.acquireOrSteal(new Runnable() { @Override public void run() { ServerTransaction txn = new AutoCommitTransaction(store); txn.dequeue(messageInstance.getEnqueueRecord(), deleteAction); } }); if (acquired) { txn.dequeue(messageInstance.getEnqueueRecord(), deleteAction); } } public void copy(QueueEntry entry, final Queue<?> queue) { final ServerMessage message = entry.getMessage(); txn.enqueue(queue, message, new ServerTransaction.EnqueueAction() { public void postCommit(MessageEnqueueRecord... records) { queue.enqueue(message, null, records[0]); } public void onRollback() { } }); } public void move(final QueueEntry entry, final Queue<?> queue) { final ServerMessage message = entry.getMessage(); if (entry.acquire()) { txn.enqueue(queue, message, new ServerTransaction.EnqueueAction() { public void postCommit(MessageEnqueueRecord... records) { queue.enqueue(message, null, records[0]); } public void onRollback() { entry.release(); } }); txn.dequeue(entry.getEnqueueRecord(), new ServerTransaction.Action() { public void postCommit() { entry.delete(); } public void onRollback() { } }); } } }); txn.commit(); } @Override public boolean isQueue_deadLetterQueueEnabled() { return _queue_deadLetterQueueEnabled; } @Override public long getHousekeepingCheckPeriod() { return _housekeepingCheckPeriod; } @Override public long getStoreTransactionIdleTimeoutClose() { return _storeTransactionIdleTimeoutClose; } @Override public long getStoreTransactionIdleTimeoutWarn() { return _storeTransactionIdleTimeoutWarn; } @Override public long getStoreTransactionOpenTimeoutClose() { return _storeTransactionOpenTimeoutClose; } @Override public long getStoreTransactionOpenTimeoutWarn() { return _storeTransactionOpenTimeoutWarn; } @Override public long getQueueCount() { return getChildren(Queue.class).size(); } @Override public long getExchangeCount() { return getChildren(Exchange.class).size(); } @Override public long getConnectionCount() { return _connections.size(); } @Override public long getBytesIn() { return getDataReceiptStatistics().getTotal(); } @Override public long getBytesOut() { return getDataDeliveryStatistics().getTotal(); } @Override public long getMessagesIn() { return getMessageReceiptStatistics().getTotal(); } @Override public long getMessagesOut() { return getMessageDeliveryStatistics().getTotal(); } @Override public int getHousekeepingThreadCount() { return _housekeepingThreadCount; } @Override public int getConnectionThreadPoolSize() { return _connectionThreadPoolSize; } @Override public int getNumberOfSelectors() { return _numberOfSelectors; } @StateTransition(currentState = { State.UNINITIALIZED, State.ACTIVE, State.ERRORED }, desiredState = State.STOPPED) protected ListenableFuture<Void> doStop() { final List<VirtualHostLogger> loggers = new ArrayList<>(getChildren(VirtualHostLogger.class)); return doAfter(closeConnections(), new Callable<ListenableFuture<Void>>() { @Override public ListenableFuture<Void> call() throws Exception { return closeChildren(); } }).then(new Runnable() { @Override public void run() { shutdownHouseKeeping(); closeNetworkConnectionScheduler(); closeMessageStore(); stopPreferenceTaskExecutor(); closePreferenceStore(); setState(State.STOPPED); stopLogging(loggers); } }); } @Override public UserPreferences createUserPreferences(ConfiguredObject<?> object) { if (_preferenceTaskExecutor == null || !_preferenceTaskExecutor.isRunning()) { throw new IllegalStateException("Cannot create user preferences in not fully initialized virtual host"); } return new UserPreferencesImpl(_preferenceTaskExecutor, object, _preferenceStore, Collections.<Preference>emptySet()); } private void stopPreferenceTaskExecutor() { if (_preferenceTaskExecutor != null) { _preferenceTaskExecutor.stop(); } } private void closePreferenceStore() { if (_preferenceStore != null) { _preferenceStore.close(); } } private void stopLogging(Collection<VirtualHostLogger> loggers) { for (VirtualHostLogger logger : loggers) { logger.stopLogging(); } } @StateTransition(currentState = { State.ACTIVE, State.ERRORED }, desiredState = State.DELETED) private ListenableFuture<Void> doDelete() { return doAfterAlways(closeAsync(), new Runnable() { @Override public void run() { MessageStore ms = getMessageStore(); if (ms != null) { try { ms.onDelete(AbstractVirtualHost.this); } catch (Exception e) { _logger.warn("Exception occurred on message store deletion", e); } } PreferenceStore ps = _preferenceStore; if (ps != null) { try { ps.onDelete(); } catch (Exception e) { _logger.warn("Exception occurred on preference store deletion", e); } } deleted(); setState(State.DELETED); } }); } private String createDLQ(final String queueName) { final String dlExchangeName = getDeadLetterExchangeName(queueName); final String dlQueueName = getDeadLetterQueueName(queueName); Exchange<?> dlExchange = null; final UUID dlExchangeId = UUID.randomUUID(); try { Map<String, Object> attributes = new HashMap<String, Object>(); attributes.put(org.apache.qpid.server.model.Exchange.ID, dlExchangeId); attributes.put(org.apache.qpid.server.model.Exchange.NAME, dlExchangeName); attributes.put(org.apache.qpid.server.model.Exchange.TYPE, ExchangeDefaults.FANOUT_EXCHANGE_CLASS); attributes.put(org.apache.qpid.server.model.Exchange.DURABLE, true); attributes.put(org.apache.qpid.server.model.Exchange.LIFETIME_POLICY, false ? LifetimePolicy.DELETE_ON_NO_LINKS : LifetimePolicy.PERMANENT); attributes.put(org.apache.qpid.server.model.Exchange.ALTERNATE_EXCHANGE, null); dlExchange = (Exchange<?>) createChild(Exchange.class, attributes); ; } catch (AbstractConfiguredObject.DuplicateNameException e) { // We're ok if the exchange already exists dlExchange = (Exchange<?>) e.getExisting(); } catch (ReservedExchangeNameException | NoFactoryForTypeException | UnknownConfiguredObjectException e) { throw new ConnectionScopedRuntimeException("Attempt to create an alternate exchange for a queue failed", e); } Queue<?> dlQueue = null; { dlQueue = (Queue<?>) getChildByName(Queue.class, dlQueueName); if (dlQueue == null) { //set args to disable DLQ-ing/MDC from the DLQ itself, preventing loops etc final Map<String, Object> args = new HashMap<String, Object>(); args.put(CREATE_DLQ_ON_CREATION, false); args.put(Queue.MAXIMUM_DELIVERY_ATTEMPTS, 0); args.put(Queue.ID, UUID.randomUUID()); args.put(Queue.NAME, dlQueueName); args.put(Queue.DURABLE, true); dlQueue = addQueueWithoutDLQ(args); childAdded(dlQueue); } } //ensure the queue is bound to the exchange if (!dlExchange.isBound(AbstractVirtualHost.DLQ_ROUTING_KEY, dlQueue)) { //actual routing key used does not matter due to use of fanout exchange, //but we will make the key 'dlq' as it can be logged at creation. dlExchange.addBinding(AbstractVirtualHost.DLQ_ROUTING_KEY, dlQueue, null); } return dlExchangeName; } private static void validateDLNames(String name) { // check if DLQ name and DLQ exchange name do not exceed 255 String exchangeName = getDeadLetterExchangeName(name); if (exchangeName.length() > MAX_LENGTH) { throw new IllegalArgumentException("DL exchange name '" + exchangeName + "' length exceeds limit of " + MAX_LENGTH + " characters for queue " + name); } String queueName = getDeadLetterQueueName(name); if (queueName.length() > MAX_LENGTH) { throw new IllegalArgumentException("DLQ queue name '" + queueName + "' length exceeds limit of " + MAX_LENGTH + " characters for queue " + name); } } private boolean shouldCreateDLQ(Map<String, Object> arguments) { boolean autoDelete = MapValueConverter.getEnumAttribute(LifetimePolicy.class, Queue.LIFETIME_POLICY, arguments, LifetimePolicy.PERMANENT) != LifetimePolicy.PERMANENT; //feature is not to be enabled for temporary queues or when explicitly disabled by argument if (!(autoDelete || (arguments != null && arguments.containsKey(Queue.ALTERNATE_EXCHANGE)))) { boolean dlqArgumentPresent = arguments != null && arguments.containsKey(CREATE_DLQ_ON_CREATION); if (dlqArgumentPresent) { boolean dlqEnabled = true; if (dlqArgumentPresent) { Object argument = arguments.get(CREATE_DLQ_ON_CREATION); dlqEnabled = (argument instanceof Boolean && ((Boolean) argument).booleanValue()) || (argument instanceof String && Boolean.parseBoolean(argument.toString())); } return dlqEnabled; } return isQueue_deadLetterQueueEnabled(); } return false; } private static String getDeadLetterQueueName(String name) { return name + System.getProperty(QueueManagingVirtualHost.PROPERTY_DEAD_LETTER_QUEUE_SUFFIX, AbstractVirtualHost.DEFAULT_DLQ_NAME_SUFFIX); } private static String getDeadLetterExchangeName(String name) { return name + System.getProperty(QueueManagingVirtualHost.PROPERTY_DEAD_LETTER_EXCHANGE_SUFFIX, QueueManagingVirtualHost.DEFAULT_DLE_NAME_SUFFIX); } @Override public String getModelVersion() { return BrokerModel.MODEL_VERSION; } @Override public String getProductVersion() { return _broker.getProductVersion(); } @Override public DurableConfigurationStore getDurableConfigurationStore() { return _virtualHostNode.getConfigurationStore(); } @Override public void setTargetSize(final long targetSize) { _targetSize.set(targetSize); allocateTargetSizeToQueues(); } public long getTargetSize() { return _targetSize.get(); } private void allocateTargetSizeToQueues() { long targetSize = _targetSize.get(); Collection<Queue> queues = getChildren(Queue.class); long totalSize = calculateTotalEnqueuedSize(queues); _logger.debug("Allocating target size to queues, total target: {} ; total enqueued size {}", targetSize, totalSize); if (targetSize > 0l) { for (Queue<?> q : queues) { long size = (long) ((((double) q.getPotentialMemoryFootprint() / (double) totalSize)) * (double) targetSize); q.setTargetSize(size); } } } @Override public long getTotalQueueDepthBytes() { return calculateTotalEnqueuedSize(getChildren(Queue.class)); } @Override public Principal getPrincipal() { return _principal; } @Override public void registerConnection(final AMQPConnection<?> connection) { doSync(registerConnectionAsync(connection)); } public ListenableFuture<Void> registerConnectionAsync(final AMQPConnection<?> connection) { return doOnConfigThread(new Task<ListenableFuture<Void>, RuntimeException>() { @Override public ListenableFuture<Void> execute() { if (_acceptsConnections.get()) { _connections.add(connection); if (_blocked.get()) { connection.block(); } connection.pushScheduler(_networkConnectionScheduler); return Futures.immediateFuture(null); } else { final VirtualHostUnavailableException exception = new VirtualHostUnavailableException( String.format("VirtualHost '%s' not accepting connections", getName())); return Futures.immediateFailedFuture(exception); } } @Override public String getObject() { return AbstractVirtualHost.this.toString(); } @Override public String getAction() { return "register connection"; } @Override public String getArguments() { return String.valueOf(connection); } }); } @Override public void deregisterConnection(final AMQPConnection<?> connection) { doSync(deregisterConnectionAsync(connection)); } public ListenableFuture<Void> deregisterConnectionAsync(final AMQPConnection<?> connection) { return doOnConfigThread(new Task<ListenableFuture<Void>, RuntimeException>() { @Override public ListenableFuture<Void> execute() { connection.popScheduler(); _connections.remove(connection); return Futures.immediateFuture(null); } @Override public String getObject() { return AbstractVirtualHost.this.toString(); } @Override public String getAction() { return "deregister connection"; } @Override public String getArguments() { return String.valueOf(connection); } }); } private long calculateTotalEnqueuedSize(final Collection<Queue> queues) { long total = 0; for (Queue<?> queue : queues) { total += queue.getPotentialMemoryFootprint(); } return total; } @StateTransition(currentState = { State.UNINITIALIZED, State.ERRORED }, desiredState = State.ACTIVE) private ListenableFuture<Void> onActivate() { long threadPoolKeepAliveTimeout = getContextValue(Long.class, CONNECTION_THREAD_POOL_KEEP_ALIVE_TIMEOUT); final SuppressingInheritedAccessControlContextThreadFactory connectionThreadFactory = new SuppressingInheritedAccessControlContextThreadFactory( "virtualhost-" + getName() + "-iopool", getSystemTaskSubject("IO Pool", getPrincipal())); _networkConnectionScheduler = new NetworkConnectionScheduler("virtualhost-" + getName() + "-iopool", getNumberOfSelectors(), getConnectionThreadPoolSize(), threadPoolKeepAliveTimeout, connectionThreadFactory); _networkConnectionScheduler.start(); updateAccessControl(); MessageStore messageStore = getMessageStore(); messageStore.openMessageStore(this); startFileSystemSpaceChecking(); if (!(_virtualHostNode.getConfigurationStore() instanceof MessageStoreProvider)) { getEventLogger().message(getMessageStoreLogSubject(), MessageStoreMessages.CREATED()); getEventLogger().message(getMessageStoreLogSubject(), MessageStoreMessages.STORE_LOCATION(messageStore.getStoreLocation())); } messageStore.upgradeStoreStructure(); getBroker().assignTargetSizes(); final PreferenceStoreUpdater updater = new PreferenceStoreUpdaterImpl(); Collection<PreferenceRecord> records = _preferenceStore.openAndLoad(updater); _preferenceTaskExecutor = new TaskExecutorImpl("virtualhost-" + getName() + "-preferences", null); _preferenceTaskExecutor.start(); PreferencesRecoverer preferencesRecoverer = new PreferencesRecoverer(_preferenceTaskExecutor); preferencesRecoverer.recoverPreferences(this, records, _preferenceStore); if (_createDefaultExchanges) { return doAfter(createDefaultExchanges(), new Runnable() { @Override public void run() { _createDefaultExchanges = false; postCreateDefaultExchangeTasks(); } }); } else { postCreateDefaultExchangeTasks(); return Futures.immediateFuture(null); } } private void postCreateDefaultExchangeTasks() { if (getContextValue(Boolean.class, USE_ASYNC_RECOVERY)) { _messageStoreRecoverer = new AsynchronousMessageStoreRecoverer(); } else { _messageStoreRecoverer = new SynchronousMessageStoreRecoverer(); } // propagate any exception thrown during recovery into HouseKeepingTaskExecutor to handle them accordingly // TODO if message recovery fails we ought to be transitioning the VH into ERROR and releasing the thread-pools etc. final ListenableFuture<Void> recoveryResult = _messageStoreRecoverer.recover(this); recoveryResult.addListener(new Runnable() { @Override public void run() { Futures.getUnchecked(recoveryResult); } }, _houseKeepingTaskExecutor); State finalState = State.ERRORED; try { initialiseHouseKeeping(getHousekeepingCheckPeriod()); finalState = State.ACTIVE; _acceptsConnections.set(true); } finally { setState(finalState); reportIfError(getState()); } } @Override protected void logOperation(final String operation) { getEventLogger().message(VirtualHostMessages.OPERATION(operation)); } protected void startFileSystemSpaceChecking() { long housekeepingCheckPeriod = getHousekeepingCheckPeriod(); File storeLocationAsFile = _messageStore.getStoreLocationAsFile(); if (storeLocationAsFile != null && _fileSystemMaxUsagePercent > 0 && housekeepingCheckPeriod > 0) { _fileSystemSpaceChecker.setFileSystem(storeLocationAsFile); scheduleHouseKeepingTask(housekeepingCheckPeriod, _fileSystemSpaceChecker); } } @Override public SocketConnectionMetaData getConnectionMetaData() { return getBroker().getConnectionMetaData(); } @StateTransition(currentState = { State.STOPPED }, desiredState = State.ACTIVE) private ListenableFuture<Void> onRestart() { resetStatistics(); createHousekeepingExecutor(); final List<ConfiguredObjectRecord> records = new ArrayList<>(); // Transitioning to STOPPED will have closed all our children. Now we are transition // back to ACTIVE, we need to recover and re-open them. getDurableConfigurationStore().reload(new ConfiguredObjectRecordHandler() { @Override public void handle(final ConfiguredObjectRecord record) { records.add(record); } }); new GenericRecoverer(this).recover(records, false); final List<ListenableFuture<Void>> childOpenFutures = new ArrayList<>(); Subject.doAs(getSubjectWithAddedSystemRights(), new PrivilegedAction<Object>() { @Override public Object run() { applyToChildren(new Action<ConfiguredObject<?>>() { @Override public void performAction(final ConfiguredObject<?> child) { final ListenableFuture<Void> childOpenFuture = child.openAsync(); childOpenFutures.add(childOpenFuture); addFutureCallback(childOpenFuture, new FutureCallback<Void>() { @Override public void onSuccess(final Void result) { } @Override public void onFailure(final Throwable t) { _logger.error("Exception occurred while opening {} : {}", child.getClass().getSimpleName(), child.getName(), t); } }, getTaskExecutor()); } }); return null; } }); ListenableFuture<List<Void>> combinedFuture = Futures.allAsList(childOpenFutures); return Futures.transform(combinedFuture, new AsyncFunction<List<Void>, Void>() { @Override public ListenableFuture<Void> apply(final List<Void> input) throws Exception { return onActivate(); } }); } private class FileSystemSpaceChecker extends HouseKeepingTask { private boolean _fileSystemFull; private File _fileSystem; public FileSystemSpaceChecker() { super("FileSystemSpaceChecker[" + AbstractVirtualHost.this.getName() + "]", AbstractVirtualHost.this, _fileSystemSpaceCheckerJobContext); } @Override public void execute() { long totalSpace = _fileSystem.getTotalSpace(); long freeSpace = _fileSystem.getFreeSpace(); if (totalSpace == 0) { _logger.warn("Cannot check file system for disk space because store path '{}' is not valid", _fileSystem.getPath()); return; } long usagePercent = (100l * (totalSpace - freeSpace)) / totalSpace; if (_fileSystemFull && (usagePercent < _fileSystemMaxUsagePercent)) { _fileSystemFull = false; getEventLogger().message(getMessageStoreLogSubject(), VirtualHostMessages.FILESYSTEM_NOTFULL(_fileSystemMaxUsagePercent)); unblock(BlockingType.FILESYSTEM); } else if (!_fileSystemFull && usagePercent > _fileSystemMaxUsagePercent) { _fileSystemFull = true; getEventLogger().message(getMessageStoreLogSubject(), VirtualHostMessages.FILESYSTEM_FULL(_fileSystemMaxUsagePercent)); block(BlockingType.FILESYSTEM); } } public void setFileSystem(final File fileSystem) { _fileSystem = fileSystem; } } @Override public <T extends MessageSource> T createMessageSource(final Class<T> clazz, final Map<String, Object> attributes) { if (Queue.class.isAssignableFrom(clazz)) { return (T) createChild((Class<? extends Queue>) clazz, attributes); } else if (clazz.isAssignableFrom(Queue.class)) { return (T) createChild(Queue.class, attributes); } else { throw new IllegalArgumentException( "Cannot create message source children of class " + clazz.getSimpleName()); } } @Override public <T extends MessageDestination> T createMessageDestination(final Class<T> clazz, final Map<String, Object> attributes) { if (Exchange.class.isAssignableFrom(clazz)) { return (T) createChild((Class<? extends Exchange>) clazz, attributes); } else if (Queue.class.isAssignableFrom(clazz)) { return (T) createChild((Class<? extends Queue>) clazz, attributes); } else if (clazz.isAssignableFrom(Queue.class)) { return (T) createChild(Queue.class, attributes); } else { throw new IllegalArgumentException( "Cannot create message destination children of class " + clazz.getSimpleName()); } } @Override public boolean hasMessageSources() { return !(_systemNodeSources.isEmpty() && getChildren(Queue.class).isEmpty()); } private final class AccessControlProviderListener extends AbstractConfigurationChangeListener { private final Set<ConfiguredObject<?>> _bulkChanges = new HashSet<>(); @Override public void childAdded(final ConfiguredObject<?> object, final ConfiguredObject<?> child) { if (object.getCategoryClass() == VirtualHost.class && child.getCategoryClass() == VirtualHostAccessControlProvider.class) { child.addChangeListener(this); AbstractVirtualHost.this.updateAccessControl(); } } @Override public void childRemoved(final ConfiguredObject<?> object, final ConfiguredObject<?> child) { if (object.getCategoryClass() == VirtualHost.class && child.getCategoryClass() == VirtualHostAccessControlProvider.class) { AbstractVirtualHost.this.updateAccessControl(); } } @Override public void attributeSet(final ConfiguredObject<?> object, final String attributeName, final Object oldAttributeValue, final Object newAttributeValue) { if (object.getCategoryClass() == VirtualHostAccessControlProvider.class && !_bulkChanges.contains(object)) { AbstractVirtualHost.this.updateAccessControl(); } } @Override public void bulkChangeStart(final ConfiguredObject<?> object) { if (object.getCategoryClass() == VirtualHostAccessControlProvider.class) { _bulkChanges.add(object); } } @Override public void bulkChangeEnd(final ConfiguredObject<?> object) { if (object.getCategoryClass() == VirtualHostAccessControlProvider.class) { _bulkChanges.remove(object); AbstractVirtualHost.this.updateAccessControl(); } } } private class StoreEmptyCheckingHandler implements MessageHandler, MessageInstanceHandler, DistributedTransactionHandler { private boolean _empty = true; @Override public boolean handle(final StoredMessage<?> storedMessage) { _empty = false; return false; } @Override public boolean handle(final MessageEnqueueRecord record) { _empty = false; return false; } @Override public boolean handle(final org.apache.qpid.server.store.Transaction.StoredXidRecord storedXid, final org.apache.qpid.server.store.Transaction.EnqueueRecord[] enqueues, final org.apache.qpid.server.store.Transaction.DequeueRecord[] dequeues) { _empty = false; return false; } public boolean isEmpty() { return _empty; } } }