/*
* 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.server.internal.messages;
import org.ehcache.clustered.common.internal.messages.ChainCodec;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityMessage;
import org.ehcache.clustered.common.internal.store.Chain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terracotta.entity.MessageCodecException;
import org.terracotta.entity.SyncMessageCodec;
import org.terracotta.runnel.Struct;
import org.terracotta.runnel.decoding.Enm;
import org.terracotta.runnel.decoding.StructArrayDecoder;
import org.terracotta.runnel.decoding.StructDecoder;
import org.terracotta.runnel.encoding.StructEncoder;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
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.MessageCodecUtils.KEY_FIELD;
import static org.ehcache.clustered.common.internal.messages.MessageCodecUtils.SERVER_STORE_NAME_FIELD;
import static org.ehcache.clustered.common.internal.store.Util.marshall;
import static org.ehcache.clustered.common.internal.store.Util.unmarshall;
import static org.ehcache.clustered.server.internal.messages.SyncMessageType.DATA;
import static org.ehcache.clustered.server.internal.messages.SyncMessageType.STATE_REPO;
import static org.ehcache.clustered.server.internal.messages.SyncMessageType.SYNC_MESSAGE_TYPE_FIELD_INDEX;
import static org.ehcache.clustered.server.internal.messages.SyncMessageType.SYNC_MESSAGE_TYPE_FIELD_NAME;
import static org.ehcache.clustered.server.internal.messages.SyncMessageType.SYNC_MESSAGE_TYPE_MAPPING;
import static org.terracotta.runnel.StructBuilder.newStructBuilder;
public class EhcacheSyncMessageCodec implements SyncMessageCodec<EhcacheEntityMessage> {
private static final Logger LOGGER = LoggerFactory.getLogger(EhcacheSyncMessageCodec.class);
private static final String CHAIN_FIELD = "chain";
private static final String CHAIN_MAP_ENTRIES_SUB_STRUCT = "entries";
private static final String STATE_REPO_ENTRIES_SUB_STRUCT = "mappings";
private static final String STATE_REPO_VALUE_FIELD = "value";
private static final String STATE_REPO_MAP_NAME_FIELD = "mapName";
private static final Struct CHAIN_MAP_ENTRY_STRUCT = newStructBuilder()
.int64(KEY_FIELD, 10)
.struct(CHAIN_FIELD, 20, CHAIN_STRUCT)
.build();
private static final Struct DATA_SYNC_STRUCT = newStructBuilder()
.enm(SYNC_MESSAGE_TYPE_FIELD_NAME, SYNC_MESSAGE_TYPE_FIELD_INDEX, SYNC_MESSAGE_TYPE_MAPPING)
.structs(CHAIN_MAP_ENTRIES_SUB_STRUCT, 20, CHAIN_MAP_ENTRY_STRUCT)
.build();
private static final Struct STATE_REPO_ENTRY_STRUCT = newStructBuilder()
.byteBuffer(KEY_FIELD, 10)
.byteBuffer(STATE_REPO_VALUE_FIELD, 20)
.build();
private static final Struct STATE_REPO_SYNC_STRUCT = newStructBuilder()
.enm(SYNC_MESSAGE_TYPE_FIELD_NAME, SYNC_MESSAGE_TYPE_FIELD_INDEX, SYNC_MESSAGE_TYPE_MAPPING)
.string(SERVER_STORE_NAME_FIELD, 20)
.string(STATE_REPO_MAP_NAME_FIELD, 30)
.structs(STATE_REPO_ENTRIES_SUB_STRUCT, 40, STATE_REPO_ENTRY_STRUCT)
.build();
public EhcacheSyncMessageCodec() {
}
@Override
public byte[] encode(final int concurrencyKey, final EhcacheEntityMessage message) throws MessageCodecException {
if (message instanceof EhcacheSyncMessage) {
EhcacheSyncMessage syncMessage = (EhcacheSyncMessage) message;
StructEncoder<Void> encoder;
switch (syncMessage.getMessageType()) {
case DATA: {
encoder = DATA_SYNC_STRUCT.encoder();
EhcacheDataSyncMessage dataSyncMessage = (EhcacheDataSyncMessage)syncMessage;
encoder.enm(SYNC_MESSAGE_TYPE_FIELD_NAME, DATA);
encoder.structs(CHAIN_MAP_ENTRIES_SUB_STRUCT,
dataSyncMessage.getChainMap().entrySet(), (entryEncoder, entry) -> {
entryEncoder.int64(KEY_FIELD, entry.getKey());
entryEncoder.struct(CHAIN_FIELD, entry.getValue(), CHAIN_ENCODER_FUNCTION);
});
return encoder.encode().array();
}
case STATE_REPO: {
encoder = STATE_REPO_SYNC_STRUCT.encoder();
EhcacheStateRepoSyncMessage stateRepoSyncMessage = (EhcacheStateRepoSyncMessage) syncMessage;
encoder.enm(SYNC_MESSAGE_TYPE_FIELD_NAME, STATE_REPO)
.string(SERVER_STORE_NAME_FIELD, stateRepoSyncMessage.getCacheId())
.string(STATE_REPO_MAP_NAME_FIELD, stateRepoSyncMessage.getMapId());
encoder.structs(STATE_REPO_ENTRIES_SUB_STRUCT, stateRepoSyncMessage.getMappings().entrySet(),
(entryEncoder, entry) -> entryEncoder.byteBuffer(KEY_FIELD, wrap(marshall(entry.getKey())))
.byteBuffer(STATE_REPO_VALUE_FIELD, wrap(marshall(entry.getValue()))));
return encoder.encode().array();
}
default:
throw new IllegalArgumentException("Sync message codec can not encode " + syncMessage.getMessageType());
}
} else {
throw new IllegalArgumentException(this.getClass().getName() + " can not encode " + message + " which is not a " + EhcacheSyncMessage.class);
}
}
@Override
public EhcacheSyncMessage decode(final int concurrencyKey, final byte[] payload) throws MessageCodecException {
ByteBuffer message = wrap(payload);
StructDecoder<Void> decoder = DATA_SYNC_STRUCT.decoder(message);
Enm<SyncMessageType> enm = decoder.enm(SYNC_MESSAGE_TYPE_FIELD_NAME);
if (!enm.isFound()) {
throw new AssertionError("Invalid message format - misses the message type field");
}
if (!enm.isValid()) {
LOGGER.warn("Unknown sync message received - ignoring {}", enm.raw());
return null;
}
switch (enm.get()) {
case DATA:
message.rewind();
decoder = DATA_SYNC_STRUCT.decoder(message);
Map<Long, Chain> chainMap = decodeChainMapEntries(decoder);
return new EhcacheDataSyncMessage(chainMap);
case STATE_REPO:
message.rewind();
decoder = STATE_REPO_SYNC_STRUCT.decoder(message);
String storeId = decoder.string(SERVER_STORE_NAME_FIELD);
String mapId = decoder.string(STATE_REPO_MAP_NAME_FIELD);
ConcurrentMap<Object, Object> mappings = new ConcurrentHashMap<>();
StructArrayDecoder<StructDecoder<Void>> structsDecoder = decoder.structs(STATE_REPO_ENTRIES_SUB_STRUCT);
if (structsDecoder != null) {
while (structsDecoder.hasNext()) {
StructDecoder<StructArrayDecoder<StructDecoder<Void>>> structDecoder = structsDecoder.next();
Object key = unmarshall(structDecoder.byteBuffer(KEY_FIELD));
Object value = unmarshall(structDecoder.byteBuffer(STATE_REPO_VALUE_FIELD));
mappings.put(key, value);
}
}
return new EhcacheStateRepoSyncMessage(storeId, mapId, mappings);
default:
throw new AssertionError("Cannot happen given earlier checks");
}
}
private Map<Long, Chain> decodeChainMapEntries(StructDecoder<Void> decoder) {
Map<Long, Chain> chainMap = new HashMap<>();
StructArrayDecoder<? extends StructDecoder<?>> entriesDecoder = decoder.structs(CHAIN_MAP_ENTRIES_SUB_STRUCT);
if (entriesDecoder != null) {
for (int i = 0; i < entriesDecoder.length(); i++) {
StructDecoder<?> entryDecoder = entriesDecoder.next();
Long key = entryDecoder.int64(KEY_FIELD);
StructDecoder<?> chainDecoder = entryDecoder.struct(CHAIN_FIELD);
Chain chain = ChainCodec.decode(chainDecoder);
chainMap.put(key, chain);
entryDecoder.end();
}
}
return chainMap;
}
}