/* * 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.remoting.impl.invm; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.client.ActiveMQClient; import org.apache.activemq.artemis.api.core.management.CoreNotificationType; import org.apache.activemq.artemis.core.remoting.impl.AbstractAcceptor; import org.apache.activemq.artemis.core.security.ActiveMQPrincipal; import org.apache.activemq.artemis.core.server.ActiveMQComponent; import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle; import org.apache.activemq.artemis.core.server.cluster.ClusterConnection; import org.apache.activemq.artemis.core.server.management.Notification; import org.apache.activemq.artemis.core.server.management.NotificationService; import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager; import org.apache.activemq.artemis.spi.core.remoting.BufferHandler; import org.apache.activemq.artemis.spi.core.remoting.Connection; import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener; import org.apache.activemq.artemis.utils.ConfigurationHelper; import org.apache.activemq.artemis.utils.ExecutorFactory; import org.apache.activemq.artemis.utils.OrderedExecutorFactory; import org.apache.activemq.artemis.utils.collections.TypedProperties; import org.jboss.logging.Logger; public final class InVMAcceptor extends AbstractAcceptor { private final int id; private final BufferHandler handler; private final ServerConnectionLifeCycleListener listener; private final ConcurrentMap<String, Connection> connections = new ConcurrentHashMap<>(); private volatile boolean started; private final ExecutorFactory executorFactory; private final ClusterConnection clusterConnection; private boolean paused; private NotificationService notificationService; private final Map<String, Object> configuration; private ActiveMQPrincipal defaultActiveMQPrincipal; private final long connectionsAllowed; private final String name; private static final Logger logger = Logger.getLogger(InVMAcceptor.class); private final boolean enableBufferPooling; public InVMAcceptor(final String name, final ClusterConnection clusterConnection, final Map<String, Object> configuration, final BufferHandler handler, final ServerConnectionLifeCycleListener listener, final Map<String, ProtocolManager> protocolMap, final Executor threadPool) { super(protocolMap); this.name = name; this.clusterConnection = clusterConnection; this.configuration = configuration; this.handler = handler; this.listener = listener; id = ConfigurationHelper.getIntProperty(TransportConstants.SERVER_ID_PROP_NAME, 0, configuration); executorFactory = new OrderedExecutorFactory(threadPool); connectionsAllowed = ConfigurationHelper.getLongProperty(TransportConstants.CONNECTIONS_ALLOWED, TransportConstants.DEFAULT_CONNECTIONS_ALLOWED, configuration); enableBufferPooling = ConfigurationHelper.getBooleanProperty(TransportConstants.BUFFER_POOLING, TransportConstants.DEFAULT_BUFFER_POOLING, configuration); } @Override public String getName() { return name; } @Override public Map<String, Object> getConfiguration() { return configuration; } @Override public ClusterConnection getClusterConnection() { return clusterConnection; } public long getConnectionsAllowed() { return connectionsAllowed; } public int getConnectionCount() { return connections.size(); } @Override public synchronized void start() throws Exception { if (started) { return; } InVMRegistry.instance.registerAcceptor(id, this); if (notificationService != null) { TypedProperties props = new TypedProperties(); props.putSimpleStringProperty(new SimpleString("factory"), new SimpleString(InVMAcceptorFactory.class.getName())); props.putIntProperty(new SimpleString("id"), id); Notification notification = new Notification(null, CoreNotificationType.ACCEPTOR_STARTED, props); notificationService.sendNotification(notification); } started = true; paused = false; } @Override public synchronized void stop() { if (!started) { return; } if (!paused) { InVMRegistry.instance.unregisterAcceptor(id); } for (Connection connection : connections.values()) { listener.connectionDestroyed(connection.getID()); } connections.clear(); if (notificationService != null) { TypedProperties props = new TypedProperties(); props.putSimpleStringProperty(new SimpleString("factory"), new SimpleString(InVMAcceptorFactory.class.getName())); props.putIntProperty(new SimpleString("id"), id); Notification notification = new Notification(null, CoreNotificationType.ACCEPTOR_STOPPED, props); try { notificationService.sendNotification(notification); } catch (Exception e) { logger.warn("failed to send notification", e.getMessage(), e); } } started = false; paused = false; } @Override public synchronized boolean isStarted() { return started; } /* * Stop accepting new connections */ @Override public synchronized void pause() { if (!started || paused) { return; } InVMRegistry.instance.unregisterAcceptor(id); paused = true; } @Override public synchronized void setNotificationService(final NotificationService notificationService) { this.notificationService = notificationService; } public BufferHandler getHandler() { if (!started) { throw new IllegalStateException("Acceptor is not started"); } return handler; } public ExecutorFactory getExecutorFactory() { return executorFactory; } public void connect(final String connectionID, final BufferHandler remoteHandler, final InVMConnector connector, final Executor clientExecutor) { if (!started) { throw new IllegalStateException("Acceptor is not started"); } Listener connectionListener = new Listener(connector); InVMConnection inVMConnection = new InVMConnection(id, connectionID, remoteHandler, connectionListener, clientExecutor, defaultActiveMQPrincipal); inVMConnection.setEnableBufferPooling(enableBufferPooling); connectionListener.connectionCreated(this, inVMConnection, protocolMap.get(ActiveMQClient.DEFAULT_CORE_PROTOCOL)); } public void disconnect(final String connectionID) { if (!started) { return; } Connection conn = connections.get(connectionID); if (conn != null) { conn.close(); } } /** * we are InVM so allow unsecure connections * * @return true */ @Override public boolean isUnsecurable() { return true; } @Override public void reload() { throw new UnsupportedOperationException(); } @Override public void setDefaultActiveMQPrincipal(ActiveMQPrincipal defaultActiveMQPrincipal) { this.defaultActiveMQPrincipal = defaultActiveMQPrincipal; } private class Listener implements ServerConnectionLifeCycleListener { //private static Listener instance = new Listener(); private final InVMConnector connector; Listener(final InVMConnector connector) { this.connector = connector; } @Override public void connectionCreated(final ActiveMQComponent component, final Connection connection, final ProtocolManager protocol) { if (connections.putIfAbsent((String) connection.getID(), connection) != null) { throw ActiveMQMessageBundle.BUNDLE.connectionExists(connection.getID()); } listener.connectionCreated(component, connection, protocol); } @Override public void connectionDestroyed(final Object connectionID) { InVMConnection connection = (InVMConnection) connections.remove(connectionID); if (connection != null) { listener.connectionDestroyed(connectionID); // Execute on different thread after all the packets are sent, to avoid deadlocks connection.getExecutor().execute(new Runnable() { @Override public void run() { // Remove on the other side too connector.disconnect((String) connectionID); } }); } } @Override public void connectionException(final Object connectionID, final ActiveMQException me) { listener.connectionException(connectionID, me); } @Override public void connectionReadyForWrites(Object connectionID, boolean ready) { } } }