/*
* 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.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import io.netty.channel.ChannelFutureListener;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.core.security.ActiveMQPrincipal;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.remoting.BaseConnectionLifeCycleListener;
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.ReadyListener;
import org.apache.activemq.artemis.utils.UUIDGenerator;
import org.jboss.logging.Logger;
public class InVMConnection implements Connection {
private static final Logger logger = Logger.getLogger(InVMConnection.class);
private final BufferHandler handler;
private final BaseConnectionLifeCycleListener listener;
private final String id;
private boolean closed;
// Used on tests
private static boolean flushEnabled = true;
private final int serverID;
private final Executor executor;
private volatile boolean closing;
private final ActiveMQPrincipal defaultActiveMQPrincipal;
private RemotingConnection protocolConnection;
private boolean bufferPoolingEnabled = TransportConstants.DEFAULT_BUFFER_POOLING;
public InVMConnection(final int serverID,
final BufferHandler handler,
final BaseConnectionLifeCycleListener listener,
final Executor executor) {
this(serverID, UUIDGenerator.getInstance().generateSimpleStringUUID().toString(), handler, listener, executor);
}
public InVMConnection(final int serverID,
final String id,
final BufferHandler handler,
final BaseConnectionLifeCycleListener listener,
final Executor executor) {
this(serverID, id, handler, listener, executor, null);
}
public InVMConnection(final int serverID,
final String id,
final BufferHandler handler,
final BaseConnectionLifeCycleListener listener,
final Executor executor,
final ActiveMQPrincipal defaultActiveMQPrincipal) {
this.serverID = serverID;
this.handler = handler;
this.listener = listener;
this.id = id;
this.executor = executor;
this.defaultActiveMQPrincipal = defaultActiveMQPrincipal;
}
public void setEnableBufferPooling(boolean enableBufferPooling) {
this.bufferPoolingEnabled = enableBufferPooling;
}
@Override
public void forceClose() {
// no op
}
@Override
public boolean isWritable(ReadyListener listener) {
return true;
}
@Override
public void fireReady(boolean ready) {
}
@Override
public RemotingConnection getProtocolConnection() {
return this.protocolConnection;
}
@Override
public void setProtocolConnection(RemotingConnection connection) {
this.protocolConnection = connection;
}
@Override
public void close() {
if (closing) {
return;
}
closing = true;
synchronized (this) {
if (!closed) {
listener.connectionDestroyed(id);
closed = true;
}
}
}
@Override
public void setAutoRead(boolean autoRead) {
// nothing to be done on the INVM.
// maybe we could eventually implement something, but not needed now
}
@Override
public ActiveMQBuffer createTransportBuffer(final int size) {
if (bufferPoolingEnabled) {
return ActiveMQBuffers.pooledBuffer( size );
}
return ActiveMQBuffers.dynamicBuffer( size );
}
@Override
public Object getID() {
return id;
}
@Override
public void checkFlushBatchBuffer() {
}
@Override
public void write(final ActiveMQBuffer buffer) {
write(buffer, false, false, null);
}
@Override
public void write(final ActiveMQBuffer buffer, final boolean flush, final boolean batch) {
write(buffer, flush, batch, null);
}
@Override
public void write(final ActiveMQBuffer buffer,
final boolean flush,
final boolean batch,
final ChannelFutureListener futureListener) {
try {
executor.execute(new Runnable() {
@Override
public void run() {
try {
if (!closed) {
buffer.readInt(); // read and discard
if (logger.isTraceEnabled()) {
logger.trace(InVMConnection.this + "::Sending inVM packet");
}
handler.bufferReceived(id, buffer);
if (futureListener != null) {
futureListener.operationComplete(null);
}
}
} catch (Exception e) {
final String msg = "Failed to write to handler on connector " + this;
ActiveMQServerLogger.LOGGER.errorWritingToInvmConnector(e, this);
throw new IllegalStateException(msg, e);
} finally {
buffer.release();
if (logger.isTraceEnabled()) {
logger.trace(InVMConnection.this + "::packet sent done");
}
}
}
});
if (flush && flushEnabled) {
final CountDownLatch latch = new CountDownLatch(1);
executor.execute(new Runnable() {
@Override
public void run() {
latch.countDown();
}
});
try {
if (!latch.await(10, TimeUnit.SECONDS)) {
ActiveMQServerLogger.LOGGER.timedOutFlushingInvmChannel();
}
} catch (InterruptedException e) {
throw new ActiveMQInterruptedException(e);
}
}
} catch (RejectedExecutionException e) {
// Ignore - this can happen if server/client is shutdown and another request comes in
}
}
@Override
public String getRemoteAddress() {
return "invm:" + serverID;
}
@Override
public String getLocalAddress() {
return "invm:" + serverID;
}
public int getBatchingBufferSize() {
return -1;
}
@Override
public boolean isUsingProtocolHandling() {
return false;
}
@Override
public ActiveMQPrincipal getDefaultActiveMQPrincipal() {
return defaultActiveMQPrincipal;
}
public static void setFlushEnabled(boolean enable) {
flushEnabled = enable;
}
public Executor getExecutor() {
return executor;
}
@Override
public TransportConfiguration getConnectorConfig() {
Map<String, Object> params = new HashMap<>();
params.put(org.apache.activemq.artemis.core.remoting.impl.invm.TransportConstants.SERVER_ID_PROP_NAME, serverID);
return new TransportConfiguration(InVMConnectorFactory.class.getName(), params);
}
@Override
public String toString() {
return "InVMConnection [serverID=" + serverID + ", id=" + id + "]";
}
}