/*
* 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;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse;
import org.ehcache.clustered.common.internal.messages.EhcacheResponseType;
import org.ehcache.clustered.common.internal.messages.ServerStoreMessageFactory;
import org.ehcache.clustered.common.internal.store.Chain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeoutException;
/**
* Provides client-side access to the services of a {@code ServerStore}.
*/
class CommonServerStoreProxy implements ServerStoreProxy {
private static final Logger LOGGER = LoggerFactory.getLogger(CommonServerStoreProxy.class);
private final String cacheId;
private final ServerStoreMessageFactory messageFactory;
private final ClusterTierClientEntity entity;
private final List<InvalidationListener> invalidationListeners = new CopyOnWriteArrayList<InvalidationListener>();
private final Map<Class<? extends EhcacheEntityResponse>, SimpleClusterTierClientEntity.ResponseListener<? extends EhcacheEntityResponse>> responseListeners
= new ConcurrentHashMap<Class<? extends EhcacheEntityResponse>, SimpleClusterTierClientEntity.ResponseListener<? extends EhcacheEntityResponse>>();
CommonServerStoreProxy(final String cacheId, final ServerStoreMessageFactory messageFactory, final ClusterTierClientEntity entity) {
this.cacheId = cacheId;
this.messageFactory = messageFactory;
this.entity = entity;
this.responseListeners.put(EhcacheEntityResponse.ServerInvalidateHash.class, new SimpleClusterTierClientEntity.ResponseListener<EhcacheEntityResponse.ServerInvalidateHash>() {
@Override
public void onResponse(EhcacheEntityResponse.ServerInvalidateHash response) {
long key = response.getKey();
LOGGER.debug("CLIENT: on cache {}, server requesting hash {} to be invalidated", cacheId, key);
for (InvalidationListener listener : invalidationListeners) {
listener.onInvalidateHash(key);
}
}
});
this.responseListeners.put(EhcacheEntityResponse.ClientInvalidateHash.class, new SimpleClusterTierClientEntity.ResponseListener<EhcacheEntityResponse.ClientInvalidateHash>() {
@Override
public void onResponse(EhcacheEntityResponse.ClientInvalidateHash response) {
final long key = response.getKey();
final int invalidationId = response.getInvalidationId();
LOGGER.debug("CLIENT: doing work to invalidate hash {} from cache {} (ID {})", key, cacheId, invalidationId);
for (InvalidationListener listener : invalidationListeners) {
listener.onInvalidateHash(key);
}
try {
LOGGER.debug("CLIENT: ack'ing invalidation of hash {} from cache {} (ID {})", key, cacheId, invalidationId);
entity.invokeServerStoreOperationAsync(messageFactory.clientInvalidationAck(key, invalidationId), false);
} catch (Exception e) {
//TODO: what should be done here?
LOGGER.error("error acking client invalidation of hash {} on cache {}", key, cacheId, e);
}
}
});
this.responseListeners.put(EhcacheEntityResponse.ClientInvalidateAll.class, new SimpleClusterTierClientEntity.ResponseListener<EhcacheEntityResponse.ClientInvalidateAll>() {
@Override
public void onResponse(EhcacheEntityResponse.ClientInvalidateAll response) {
final int invalidationId = response.getInvalidationId();
LOGGER.debug("CLIENT: doing work to invalidate all from cache {} (ID {})", cacheId, invalidationId);
for (InvalidationListener listener : invalidationListeners) {
listener.onInvalidateAll();
}
try {
LOGGER.debug("CLIENT: ack'ing invalidation of all from cache {} (ID {})", cacheId, invalidationId);
entity.invokeServerStoreOperationAsync(messageFactory.clientInvalidationAllAck(invalidationId), false);
} catch (Exception e) {
//TODO: what should be done here?
LOGGER.error("error acking client invalidation of all on cache {}", cacheId, e);
}
}
});
addResponseListenersToEntity();
}
@SuppressWarnings("unchecked")
private void addResponseListenersToEntity() {
for (Map.Entry<Class<? extends EhcacheEntityResponse>, SimpleClusterTierClientEntity.ResponseListener<? extends EhcacheEntityResponse>> classResponseListenerEntry :
this.responseListeners.entrySet()) {
this.entity.addResponseListener(classResponseListenerEntry.getKey(), (SimpleClusterTierClientEntity.ResponseListener)classResponseListenerEntry.getValue());
}
}
@Override
public String getCacheId() {
return cacheId;
}
@Override
public void addInvalidationListener(InvalidationListener listener) {
invalidationListeners.add(listener);
}
@Override
public boolean removeInvalidationListener(InvalidationListener listener) {
return invalidationListeners.remove(listener);
}
<T extends EhcacheEntityResponse> void addResponseListeners(Class<T> listenerClass, SimpleClusterTierClientEntity.ResponseListener<T> listener) {
this.responseListeners.put(listenerClass, listener);
this.entity.addResponseListener(listenerClass, listener);
}
@SuppressWarnings("unchecked")
@Override
public void close() {
entity.close();
}
@Override
public Chain get(long key) throws TimeoutException {
EhcacheEntityResponse response;
try {
response = entity.invokeServerStoreOperation(messageFactory.getOperation(key), false);
} catch (TimeoutException e) {
throw e;
} catch (Exception e) {
throw new ServerStoreProxyException(e);
}
if (response != null && response.getResponseType() == EhcacheResponseType.GET_RESPONSE) {
return ((EhcacheEntityResponse.GetResponse)response).getChain();
} else {
throw new ServerStoreProxyException("Response for get operation was invalid : " +
(response != null ? response.getResponseType() : "null message"));
}
}
@Override
public void append(long key, ByteBuffer payLoad) throws TimeoutException {
try {
entity.invokeServerStoreOperation(messageFactory.appendOperation(key, payLoad), true);
} catch (TimeoutException e) {
throw e;
} catch (Exception e) {
throw new ServerStoreProxyException(e);
}
}
@Override
public Chain getAndAppend(long key, ByteBuffer payLoad) throws TimeoutException {
EhcacheEntityResponse response;
try {
response = entity.invokeServerStoreOperation(messageFactory.getAndAppendOperation(key, payLoad), true);
} catch (TimeoutException e) {
throw e;
} catch (Exception e) {
throw new ServerStoreProxyException(e);
}
if (response != null && response.getResponseType() == EhcacheResponseType.GET_RESPONSE) {
return ((EhcacheEntityResponse.GetResponse)response).getChain();
} else {
throw new ServerStoreProxyException("Response for getAndAppend operation was invalid : " +
(response != null ? response.getResponseType() : "null message"));
}
}
@Override
public void replaceAtHead(long key, Chain expect, Chain update) {
// TODO: Optimize this method to just send sequences for expect Chain
try {
entity.invokeServerStoreOperationAsync(messageFactory.replaceAtHeadOperation(key, expect, update), false);
} catch (Exception e) {
throw new ServerStoreProxyException(e);
}
}
@Override
public void clear() throws TimeoutException {
try {
entity.invokeServerStoreOperation(messageFactory.clearOperation(), true);
} catch (TimeoutException e) {
throw e;
} catch (Exception e) {
throw new ServerStoreProxyException(e);
}
}
}