/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * Licensed 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 com.hazelcast.spi.impl.operationservice.impl; import com.hazelcast.instance.Node; import com.hazelcast.internal.serialization.InternalSerializationService; import com.hazelcast.logging.ILogger; import com.hazelcast.nio.Address; import com.hazelcast.nio.ConnectionManager; import com.hazelcast.nio.Packet; import com.hazelcast.nio.serialization.Data; import com.hazelcast.spi.Operation; import com.hazelcast.spi.OperationResponseHandler; import com.hazelcast.spi.impl.SpiDataSerializerHook; import com.hazelcast.spi.impl.operationservice.impl.responses.CallTimeoutResponse; import com.hazelcast.spi.impl.operationservice.impl.responses.ErrorResponse; import com.hazelcast.spi.impl.operationservice.impl.responses.NormalResponse; import com.hazelcast.spi.impl.operationservice.impl.responses.Response; import static com.hazelcast.internal.serialization.impl.SerializationConstants.CONSTANT_TYPE_DATA_SERIALIZABLE; import static com.hazelcast.internal.serialization.impl.SerializationConstants.CONSTANT_TYPE_NULL; import static com.hazelcast.nio.Bits.INT_SIZE_IN_BYTES; import static com.hazelcast.nio.Bits.writeInt; import static com.hazelcast.nio.Bits.writeIntB; import static com.hazelcast.nio.Bits.writeLong; import static com.hazelcast.nio.Packet.FLAG_OP_RESPONSE; import static com.hazelcast.nio.Packet.FLAG_URGENT; import static com.hazelcast.nio.Packet.Type.OPERATION; import static com.hazelcast.spi.impl.SpiDataSerializerHook.BACKUP_ACK_RESPONSE; import static com.hazelcast.spi.impl.SpiDataSerializerHook.NORMAL_RESPONSE; import static com.hazelcast.spi.impl.operationservice.impl.responses.BackupAckResponse.BACKUP_RESPONSE_SIZE_IN_BYTES; import static com.hazelcast.spi.impl.operationservice.impl.responses.NormalResponse.OFFSET_BACKUP_ACKS; import static com.hazelcast.spi.impl.operationservice.impl.responses.NormalResponse.OFFSET_DATA_LENGTH; import static com.hazelcast.spi.impl.operationservice.impl.responses.NormalResponse.OFFSET_DATA_PAYLOAD; import static com.hazelcast.spi.impl.operationservice.impl.responses.NormalResponse.OFFSET_IS_DATA; import static com.hazelcast.spi.impl.operationservice.impl.responses.NormalResponse.OFFSET_NOT_DATA; import static com.hazelcast.spi.impl.operationservice.impl.responses.Response.OFFSET_CALL_ID; import static com.hazelcast.spi.impl.operationservice.impl.responses.Response.OFFSET_IDENTIFIED; import static com.hazelcast.spi.impl.operationservice.impl.responses.Response.OFFSET_SERIALIZER_TYPE_ID; import static com.hazelcast.spi.impl.operationservice.impl.responses.Response.OFFSET_TYPE_FACTORY_ID; import static com.hazelcast.spi.impl.operationservice.impl.responses.Response.OFFSET_TYPE_ID; import static com.hazelcast.spi.impl.operationservice.impl.responses.Response.OFFSET_URGENT; import static com.hazelcast.util.Preconditions.checkNotNull; import static java.nio.ByteOrder.BIG_ENDIAN; /** * An {@link OperationResponseHandler} that is used for a remotely executed Operation. So when a calling member * sends an Operation to the receiving member, the receiving member attaches this OutboundResponseHandler * to that operation so that the response is returned to the right machine. */ public final class OutboundResponseHandler implements OperationResponseHandler { private final Address thisAddress; private final InternalSerializationService serializationService; private final boolean useBigEndian; private final ILogger logger; // it sucks we need to pass in Node as argument; but this is due to the ConnectionManager which is created after // the OperationService is created. private final Node node; OutboundResponseHandler(Address thisAddress, InternalSerializationService serializationService, Node node, ILogger logger) { this.thisAddress = thisAddress; this.serializationService = serializationService; this.useBigEndian = serializationService.getByteOrder() == BIG_ENDIAN; this.node = node; this.logger = logger; } @Override public void sendResponse(Operation operation, Object obj) { Address target = operation.getCallerAddress(); boolean send; if (obj == null) { send = sendNormalResponse(target, operation.getCallId(), 0, operation.isUrgent(), null); } else if (obj.getClass() == NormalResponse.class) { NormalResponse response = (NormalResponse) obj; send = sendNormalResponse( target, response.getCallId(), response.getBackupAcks(), response.isUrgent(), response.getValue()); } else if (obj.getClass() == ErrorResponse.class || obj.getClass() == CallTimeoutResponse.class) { send = send(target, (Response) obj); } else if (obj instanceof Throwable) { send = send(target, new ErrorResponse((Throwable) obj, operation.getCallId(), operation.isUrgent())); } else { // most regular responses not wrapped in a NormalResponse. So we are now completely skipping the // NormalResponse instance send = sendNormalResponse(target, operation.getCallId(), 0, operation.isUrgent(), obj); } if (!send) { logger.warning("Cannot send response: " + obj + " to " + target + ". " + operation); } } public boolean send(Address target, Response response) { checkNotNull(target, "Target is required!"); if (thisAddress.equals(target)) { throw new IllegalArgumentException("Target is this node! -> " + target + ", response: " + response); } byte[] bytes = serializationService.toBytes(response); Packet packet = newResponsePacket(bytes, response.isUrgent()); return transmit(target, packet); } private boolean sendNormalResponse(Address target, long callId, int backupAcks, boolean urgent, Object value) { checkTarget(target); Packet packet = toNormalResponsePacket(callId, (byte) backupAcks, urgent, value); return transmit(target, packet); } Packet toNormalResponsePacket(long callId, int backupAcks, boolean urgent, Object value) { byte[] bytes; boolean isData = value instanceof Data; if (isData) { Data data = (Data) value; int dataLengthInBytes = data.totalSize(); bytes = new byte[OFFSET_DATA_PAYLOAD + dataLengthInBytes]; writeInt(bytes, OFFSET_DATA_LENGTH, dataLengthInBytes, useBigEndian); // this is a crucial part. If data is NativeMemoryData, instead of calling Data.toByteArray which causes a // byte-array to be created and a intermediate copy of the data, we immediately copy the NativeMemoryData // into the bytes for the packet. data.copyTo(bytes, OFFSET_DATA_PAYLOAD); } else if (value == null) { // since there are many 'null' responses we optimize this case as well. bytes = new byte[OFFSET_NOT_DATA + INT_SIZE_IN_BYTES]; writeInt(bytes, OFFSET_NOT_DATA, CONSTANT_TYPE_NULL, useBigEndian); } else { // for regular object we currently can't guess how big the bytes will be; so we just hand it // over to the serializationService to deal with it. The negative part is that this does lead to // an intermediate copy of the data. bytes = serializationService.toBytes(value, OFFSET_NOT_DATA, false); } writeResponsePrologueBytes(bytes, NORMAL_RESPONSE, callId, urgent); // backup-acks (will fit in a byte) bytes[OFFSET_BACKUP_ACKS] = (byte) backupAcks; // isData bytes[OFFSET_IS_DATA] = (byte) (isData ? 1 : 0); //the remaining part of the byte array is already filled, so we are done. return newResponsePacket(bytes, urgent); } public void sendBackupAck(Address target, long callId, boolean urgent) { checkTarget(target); Packet packet = toBackupAckPacket(callId, urgent); transmit(target, packet); } Packet toBackupAckPacket(long callId, boolean urgent) { byte[] bytes = new byte[BACKUP_RESPONSE_SIZE_IN_BYTES]; writeResponsePrologueBytes(bytes, BACKUP_ACK_RESPONSE, callId, urgent); return newResponsePacket(bytes, urgent); } private void writeResponsePrologueBytes(byte[] bytes, int typeId, long callId, boolean urgent) { // partition hash (which is always 0 in case of response) writeIntB(bytes, 0, 0); // data-serializable type (this is always written with big endian) writeIntB(bytes, OFFSET_SERIALIZER_TYPE_ID, CONSTANT_TYPE_DATA_SERIALIZABLE); // identified or not bytes[OFFSET_IDENTIFIED] = 1; // factory id writeInt(bytes, OFFSET_TYPE_FACTORY_ID, SpiDataSerializerHook.F_ID, useBigEndian); // type id writeInt(bytes, OFFSET_TYPE_ID, typeId, useBigEndian); // call id writeLong(bytes, OFFSET_CALL_ID, callId, useBigEndian); // urgent bytes[OFFSET_URGENT] = (byte) (urgent ? 1 : 0); } private Packet newResponsePacket(byte[] bytes, boolean urgent) { Packet packet = new Packet(bytes, -1) .setPacketType(OPERATION) .raiseFlags(FLAG_OP_RESPONSE); if (urgent) { packet.raiseFlags(FLAG_URGENT); } return packet; } private boolean transmit(Address target, Packet packet) { ConnectionManager connectionManager = node.getConnectionManager(); return connectionManager.transmit(packet, target); } private void checkTarget(Address target) { checkNotNull(target, "Target is required!"); if (thisAddress.equals(target)) { throw new IllegalArgumentException("Target is this node! -> " + target); } } }