/*
* 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.client.internal.store.operations;
import org.ehcache.clustered.client.internal.store.operations.codecs.CodecException;
import org.ehcache.spi.serialization.Serializer;
import java.nio.ByteBuffer;
abstract class BaseKeyValueOperation<K, V> implements Operation<K, V> {
private final K key;
private final LazyValueHolder<V> valueHolder;
private final long timeStamp;
BaseKeyValueOperation(K key, V value, long timeStamp) {
if(key == null) {
throw new NullPointerException("Key can not be null");
}
if(value == null) {
throw new NullPointerException("Value can not be null");
}
this.key = key;
this.valueHolder = new LazyValueHolder<V>(value);
this.timeStamp = timeStamp;
}
BaseKeyValueOperation(ByteBuffer buffer, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
OperationCode opCode = OperationCode.valueOf(buffer.get());
if (opCode != getOpCode()) {
throw new IllegalArgumentException("Invalid operation: " + opCode);
}
this.timeStamp = buffer.getLong();
int keySize = buffer.getInt();
int maxLimit = buffer.limit();
buffer.limit(buffer.position() + keySize);
ByteBuffer keyBlob = buffer.slice();
buffer.position(buffer.limit());
buffer.limit(maxLimit);
try {
this.key = keySerializer.read(keyBlob);
} catch (ClassNotFoundException e) {
throw new CodecException(e);
}
this.valueHolder = new LazyValueHolder<V>(buffer.slice(), valueSerializer);
}
public K getKey() {
return key;
}
public V getValue() {
return valueHolder.getValue();
}
/**
* Here we need to encode two objects of unknown size: the key and the value.
* Encoding should be done in such a way that the key and value can be read
* separately while decoding the bytes.
* So the way it is done here is by writing the size of the payload along with
* the payload. That is, the size of the key payload is written before the key
* itself. The value payload is written after that.
*
* While decoding, the size is read first and then reading the same number of
* bytes will get you the key payload. Whatever that is left is the value payload.
*/
@Override
public ByteBuffer encode(final Serializer<K> keySerializer, final Serializer<V> valueSerializer) {
ByteBuffer keyBuf = keySerializer.serialize(key);
ByteBuffer valueBuf = valueHolder.encode(valueSerializer);
int size = BYTE_SIZE_BYTES + // Operation type
INT_SIZE_BYTES + // Size of the key payload
LONG_SIZE_BYTES + // Size of expiration time stamp
keyBuf.remaining() + // the key payload itself
valueBuf.remaining(); // the value payload
ByteBuffer buffer = ByteBuffer.allocate(size);
buffer.put(getOpCode().getValue());
buffer.putLong(this.timeStamp);
buffer.putInt(keyBuf.remaining());
buffer.put(keyBuf);
buffer.put(valueBuf);
buffer.flip();
return buffer;
}
@Override
public String toString() {
return "{" + getOpCode() + "# key: " + key + ", value: " + getValue() + "}";
}
@Override
public boolean equals(final Object obj) {
if(obj == null) {
return false;
}
if(!(obj instanceof BaseKeyValueOperation)) {
return false;
}
BaseKeyValueOperation other = (BaseKeyValueOperation) obj;
if(this.getOpCode() != other.getOpCode()) {
return false;
}
if(!this.getKey().equals(other.getKey())) {
return false;
}
if(!this.getValue().equals(other.getValue())) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = getOpCode().hashCode();
hash = hash * 31 + key.hashCode();
hash = hash * 31 + getValue().hashCode();
return hash;
}
@Override
public long timeStamp() {
if (!isExpiryAvailable()) {
return this.timeStamp;
} else {
throw new RuntimeException("Timestamp not available");
}
}
@Override
public boolean isExpiryAvailable() {
return timeStamp < 0;
}
@Override
public long expirationTime() {
if (isExpiryAvailable()) {
return - this.timeStamp;
} else {
throw new RuntimeException("Expiry not available");
}
}
}