/*
* Copyright Terracotta, Inc.
*
* 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 org.ehcache.clustered.common.internal.messages;
import org.ehcache.clustered.common.internal.exceptions.ClusterException;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse.PrepareForDestroy;
import org.ehcache.clustered.common.internal.store.Util;
import org.terracotta.runnel.Struct;
import org.terracotta.runnel.StructBuilder;
import org.terracotta.runnel.decoding.ArrayDecoder;
import org.terracotta.runnel.decoding.Enm;
import org.terracotta.runnel.decoding.StructDecoder;
import org.terracotta.runnel.encoding.ArrayEncoder;
import org.terracotta.runnel.encoding.StructEncoder;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Set;
import static java.nio.ByteBuffer.wrap;
import static org.ehcache.clustered.common.internal.messages.ChainCodec.CHAIN_ENCODER_FUNCTION;
import static org.ehcache.clustered.common.internal.messages.ChainCodec.CHAIN_STRUCT;
import static org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse.AllInvalidationDone;
import static org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse.ClientInvalidateAll;
import static org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse.ClientInvalidateHash;
import static org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse.HashInvalidationDone;
import static org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse.ServerInvalidateHash;
import static org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse.MapValue;
import static org.ehcache.clustered.common.internal.messages.EhcacheResponseType.EHCACHE_RESPONSE_TYPES_ENUM_MAPPING;
import static org.ehcache.clustered.common.internal.messages.EhcacheResponseType.RESPONSE_TYPE_FIELD_INDEX;
import static org.ehcache.clustered.common.internal.messages.EhcacheResponseType.RESPONSE_TYPE_FIELD_NAME;
import static org.ehcache.clustered.common.internal.messages.ExceptionCodec.EXCEPTION_ENCODER_FUNCTION;
import static org.ehcache.clustered.common.internal.messages.MessageCodecUtils.KEY_FIELD;
import static org.ehcache.clustered.common.internal.messages.MessageCodecUtils.SERVER_STORE_NAME_FIELD;
import static org.terracotta.runnel.StructBuilder.newStructBuilder;
public class ResponseCodec {
private static final String EXCEPTION_FIELD = "exception";
private static final String INVALIDATION_ID_FIELD = "invalidationId";
private static final String CHAIN_FIELD = "chain";
private static final String MAP_VALUE_FIELD = "mapValue";
private static final String STORES_FIELD = "stores";
private static final Struct SUCCESS_RESPONSE_STRUCT = StructBuilder.newStructBuilder()
.enm(RESPONSE_TYPE_FIELD_NAME, RESPONSE_TYPE_FIELD_INDEX, EHCACHE_RESPONSE_TYPES_ENUM_MAPPING)
.build();
private static final Struct FAILURE_RESPONSE_STRUCT = StructBuilder.newStructBuilder()
.enm(RESPONSE_TYPE_FIELD_NAME, RESPONSE_TYPE_FIELD_INDEX, EHCACHE_RESPONSE_TYPES_ENUM_MAPPING)
.struct(EXCEPTION_FIELD, 20, ExceptionCodec.EXCEPTION_STRUCT)
.build();
private static final Struct GET_RESPONSE_STRUCT = StructBuilder.newStructBuilder()
.enm(RESPONSE_TYPE_FIELD_NAME, RESPONSE_TYPE_FIELD_INDEX, EHCACHE_RESPONSE_TYPES_ENUM_MAPPING)
.struct(CHAIN_FIELD, 20, CHAIN_STRUCT)
.build();
private static final Struct HASH_INVALIDATION_DONE_RESPONSE_STRUCT = StructBuilder.newStructBuilder()
.enm(RESPONSE_TYPE_FIELD_NAME, RESPONSE_TYPE_FIELD_INDEX, EHCACHE_RESPONSE_TYPES_ENUM_MAPPING)
.int64(KEY_FIELD, 20)
.build();
private static final Struct ALL_INVALIDATION_DONE_RESPONSE_STRUCT = StructBuilder.newStructBuilder()
.enm(RESPONSE_TYPE_FIELD_NAME, RESPONSE_TYPE_FIELD_INDEX, EHCACHE_RESPONSE_TYPES_ENUM_MAPPING)
.build();
private static final Struct CLIENT_INVALIDATE_HASH_RESPONSE_STRUCT = StructBuilder.newStructBuilder()
.enm(RESPONSE_TYPE_FIELD_NAME, RESPONSE_TYPE_FIELD_INDEX, EHCACHE_RESPONSE_TYPES_ENUM_MAPPING)
.int64(KEY_FIELD, 20)
.int32(INVALIDATION_ID_FIELD, 30)
.build();
private static final Struct CLIENT_INVALIDATE_ALL_RESPONSE_STRUCT = StructBuilder.newStructBuilder()
.enm(RESPONSE_TYPE_FIELD_NAME, RESPONSE_TYPE_FIELD_INDEX, EHCACHE_RESPONSE_TYPES_ENUM_MAPPING)
.int32(INVALIDATION_ID_FIELD, 20)
.build();
private static final Struct SERVER_INVALIDATE_HASH_RESPONSE_STRUCT = StructBuilder.newStructBuilder()
.enm(RESPONSE_TYPE_FIELD_NAME, RESPONSE_TYPE_FIELD_INDEX, EHCACHE_RESPONSE_TYPES_ENUM_MAPPING)
.int64(KEY_FIELD, 20)
.build();
private static final Struct MAP_VALUE_RESPONSE_STRUCT = StructBuilder.newStructBuilder()
.enm(RESPONSE_TYPE_FIELD_NAME, RESPONSE_TYPE_FIELD_INDEX, EHCACHE_RESPONSE_TYPES_ENUM_MAPPING)
.byteBuffer(MAP_VALUE_FIELD, 20)
.build();
private static final Struct PREPARE_FOR_DESTROY_RESPONSE_STRUCT = newStructBuilder()
.enm(RESPONSE_TYPE_FIELD_NAME, RESPONSE_TYPE_FIELD_INDEX, EHCACHE_RESPONSE_TYPES_ENUM_MAPPING)
.strings(STORES_FIELD, 20)
.build();
public byte[] encode(EhcacheEntityResponse response) {
switch (response.getResponseType()) {
case FAILURE:
final EhcacheEntityResponse.Failure failure = (EhcacheEntityResponse.Failure)response;
return FAILURE_RESPONSE_STRUCT.encoder()
.enm(RESPONSE_TYPE_FIELD_NAME, failure.getResponseType())
.struct(EXCEPTION_FIELD, failure.getCause(), EXCEPTION_ENCODER_FUNCTION)
.encode().array();
case SUCCESS:
return SUCCESS_RESPONSE_STRUCT.encoder()
.enm(RESPONSE_TYPE_FIELD_NAME, response.getResponseType())
.encode().array();
case GET_RESPONSE:
final EhcacheEntityResponse.GetResponse getResponse = (EhcacheEntityResponse.GetResponse)response;
return GET_RESPONSE_STRUCT.encoder()
.enm(RESPONSE_TYPE_FIELD_NAME, getResponse.getResponseType())
.struct(CHAIN_FIELD, getResponse.getChain(), CHAIN_ENCODER_FUNCTION)
.encode().array();
case HASH_INVALIDATION_DONE: {
HashInvalidationDone hashInvalidationDone = (HashInvalidationDone) response;
return HASH_INVALIDATION_DONE_RESPONSE_STRUCT.encoder()
.enm(RESPONSE_TYPE_FIELD_NAME, hashInvalidationDone.getResponseType())
.int64(KEY_FIELD, hashInvalidationDone.getKey())
.encode().array();
}
case ALL_INVALIDATION_DONE: {
AllInvalidationDone allInvalidationDone = (AllInvalidationDone) response;
return ALL_INVALIDATION_DONE_RESPONSE_STRUCT.encoder()
.enm(RESPONSE_TYPE_FIELD_NAME, allInvalidationDone.getResponseType())
.encode().array();
}
case CLIENT_INVALIDATE_HASH: {
ClientInvalidateHash clientInvalidateHash = (ClientInvalidateHash) response;
return CLIENT_INVALIDATE_HASH_RESPONSE_STRUCT.encoder()
.enm(RESPONSE_TYPE_FIELD_NAME, clientInvalidateHash.getResponseType())
.int64(KEY_FIELD, clientInvalidateHash.getKey())
.int32(INVALIDATION_ID_FIELD, clientInvalidateHash.getInvalidationId())
.encode().array();
}
case CLIENT_INVALIDATE_ALL: {
ClientInvalidateAll clientInvalidateAll = (ClientInvalidateAll) response;
return CLIENT_INVALIDATE_ALL_RESPONSE_STRUCT.encoder()
.enm(RESPONSE_TYPE_FIELD_NAME, clientInvalidateAll.getResponseType())
.int32(INVALIDATION_ID_FIELD, clientInvalidateAll.getInvalidationId())
.encode().array();
}
case SERVER_INVALIDATE_HASH: {
ServerInvalidateHash serverInvalidateHash = (ServerInvalidateHash) response;
return SERVER_INVALIDATE_HASH_RESPONSE_STRUCT.encoder()
.enm(RESPONSE_TYPE_FIELD_NAME, serverInvalidateHash.getResponseType())
.int64(KEY_FIELD, serverInvalidateHash.getKey())
.encode().array();
}
case MAP_VALUE: {
MapValue mapValue = (MapValue) response;
byte[] encodedMapValue = Util.marshall(mapValue.getValue());
return MAP_VALUE_RESPONSE_STRUCT.encoder()
.enm(RESPONSE_TYPE_FIELD_NAME, mapValue.getResponseType())
.byteBuffer(MAP_VALUE_FIELD, wrap(encodedMapValue))
.encode().array();
}
case PREPARE_FOR_DESTROY: {
PrepareForDestroy prepare = (PrepareForDestroy) response;
StructEncoder<Void> encoder = PREPARE_FOR_DESTROY_RESPONSE_STRUCT.encoder()
.enm(RESPONSE_TYPE_FIELD_NAME, prepare.getResponseType());
ArrayEncoder<String, StructEncoder<Void>> storesEncoder = encoder.strings(STORES_FIELD);
for (String storeName : prepare.getStores()) {
storesEncoder.value(storeName);
}
return encoder
.encode().array();
}
default:
throw new UnsupportedOperationException("The operation is not supported : " + response.getResponseType());
}
}
public EhcacheEntityResponse decode(byte[] payload) {
ByteBuffer buffer = wrap(payload);
StructDecoder<Void> decoder = SUCCESS_RESPONSE_STRUCT.decoder(buffer);
Enm<EhcacheResponseType> opCodeEnm = decoder.enm(RESPONSE_TYPE_FIELD_NAME);
if (!opCodeEnm.isFound()) {
throw new AssertionError("Got a response without an opCode");
}
if (!opCodeEnm.isValid()) {
// Need to ignore the response here as we do not understand its type - coming from the future?
return null;
}
EhcacheResponseType opCode = opCodeEnm.get();
buffer.rewind();
switch (opCode) {
case SUCCESS:
return EhcacheEntityResponse.Success.INSTANCE;
case FAILURE:
decoder = FAILURE_RESPONSE_STRUCT.decoder(buffer);
ClusterException exception = ExceptionCodec.decode(decoder.struct(EXCEPTION_FIELD));
return new EhcacheEntityResponse.Failure(exception.withClientStackTrace());
case GET_RESPONSE:
decoder = GET_RESPONSE_STRUCT.decoder(buffer);
return new EhcacheEntityResponse.GetResponse(ChainCodec.decode(decoder.struct(CHAIN_FIELD)));
case HASH_INVALIDATION_DONE: {
decoder = HASH_INVALIDATION_DONE_RESPONSE_STRUCT.decoder(buffer);
long key = decoder.int64(KEY_FIELD);
return EhcacheEntityResponse.hashInvalidationDone(key);
}
case ALL_INVALIDATION_DONE: {
return EhcacheEntityResponse.allInvalidationDone();
}
case CLIENT_INVALIDATE_HASH: {
decoder = CLIENT_INVALIDATE_HASH_RESPONSE_STRUCT.decoder(buffer);
long key = decoder.int64(KEY_FIELD);
int invalidationId = decoder.int32(INVALIDATION_ID_FIELD);
return EhcacheEntityResponse.clientInvalidateHash(key, invalidationId);
}
case CLIENT_INVALIDATE_ALL: {
decoder = CLIENT_INVALIDATE_ALL_RESPONSE_STRUCT.decoder(buffer);
int invalidationId = decoder.int32(INVALIDATION_ID_FIELD);
return EhcacheEntityResponse.clientInvalidateAll(invalidationId);
}
case SERVER_INVALIDATE_HASH: {
decoder = SERVER_INVALIDATE_HASH_RESPONSE_STRUCT.decoder(buffer);
long key = decoder.int64(KEY_FIELD);
return EhcacheEntityResponse.serverInvalidateHash(key);
}
case MAP_VALUE: {
decoder = MAP_VALUE_RESPONSE_STRUCT.decoder(buffer);
return EhcacheEntityResponse.mapValue(Util.unmarshall(decoder.byteBuffer(MAP_VALUE_FIELD)));
}
case PREPARE_FOR_DESTROY: {
decoder = PREPARE_FOR_DESTROY_RESPONSE_STRUCT.decoder(buffer);
ArrayDecoder<String, StructDecoder<Void>> storesDecoder = decoder.strings(STORES_FIELD);
Set<String> stores = new HashSet<String>();
for (int i = 0; i < storesDecoder.length(); i++) {
stores.add(storesDecoder.value());
}
return new PrepareForDestroy(stores);
}
default:
throw new UnsupportedOperationException("The operation is not supported with opCode : " + opCode);
}
}
}