/*
* 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;
import org.ehcache.clustered.client.config.TimeoutDuration;
import org.ehcache.clustered.common.ServerSideConfiguration;
import org.ehcache.clustered.common.internal.exceptions.ClusterException;
import org.ehcache.clustered.common.internal.exceptions.InvalidClientIdException;
import org.ehcache.clustered.common.internal.messages.ClusterTierManagerReconnectMessage;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityMessage;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse.Failure;
import org.ehcache.clustered.common.internal.messages.EhcacheEntityResponse.PrepareForDestroy;
import org.ehcache.clustered.common.internal.messages.EhcacheResponseType;
import org.ehcache.clustered.common.internal.messages.LifeCycleMessageFactory;
import org.ehcache.clustered.common.internal.messages.ReconnectMessageCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terracotta.connection.entity.Entity;
import org.terracotta.entity.EndpointDelegate;
import org.terracotta.entity.EntityClientEndpoint;
import org.terracotta.entity.EntityResponse;
import org.terracotta.entity.InvokeFuture;
import org.terracotta.entity.MessageCodecException;
import org.terracotta.exception.EntityException;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
/**
* The client-side {@link Entity} through which clustered cache operations are performed.
* An instance of this class is created by the {@link ClusterTierManagerClientEntityService}.
* The server-side partner is the {@code EhcacheActiveEntity}.
*/
public class SimpleClusterTierManagerClientEntity implements InternalClusterTierManagerClientEntity {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleClusterTierManagerClientEntity.class);
private final AtomicLong sequenceGenerator = new AtomicLong(0L);
private final ReconnectMessageCodec reconnectMessageCodec = new ReconnectMessageCodec();
private final EntityClientEndpoint<EhcacheEntityMessage, EhcacheEntityResponse> endpoint;
private final LifeCycleMessageFactory messageFactory;
private volatile UUID clientId;
private Timeouts timeouts = Timeouts.builder().build();
public SimpleClusterTierManagerClientEntity(EntityClientEndpoint<EhcacheEntityMessage, EhcacheEntityResponse> endpoint) {
this.endpoint = endpoint;
this.messageFactory = new LifeCycleMessageFactory();
endpoint.setDelegate(new EndpointDelegate() {
@Override
public void handleMessage(EntityResponse messageFromServer) {
// Nothing to do
}
@Override
public byte[] createExtendedReconnectData() {
ClusterTierManagerReconnectMessage reconnectMessage = new ClusterTierManagerReconnectMessage(clientId);
return reconnectMessageCodec.encode(reconnectMessage);
}
@Override
public void didDisconnectUnexpectedly() {
// Nothing to do
}
});
}
@Override
public void setTimeouts(Timeouts timeouts) {
this.timeouts = timeouts;
}
@Override
public UUID getClientId() {
if (clientId == null) {
throw new IllegalStateException("Client Id cannot be null");
}
return this.clientId;
}
@Override
public void setClientId(UUID clientId) {
this.clientId = clientId;
}
@Override
public void close() {
endpoint.close();
}
@Override
public void validate(ServerSideConfiguration config) throws ClusterException, TimeoutException {
boolean clientIdGenerated = false;
while (true) {
try {
if (clientIdGenerated || clientId == null) {
clientId = UUID.randomUUID();
clientIdGenerated = true;
}
this.messageFactory.setClientId(clientId);
invokeInternal(timeouts.getLifecycleOperationTimeout(), messageFactory.validateStoreManager(config), false);
return;
} catch (InvalidClientIdException e) {
if (!clientIdGenerated) {
throw new AssertionError("Injected ClientID refused by server - " + clientId);
}
}
}
}
@Override
public Set<String> prepareForDestroy() {
try {
PrepareForDestroy response = (PrepareForDestroy) invokeInternal(timeouts.getLifecycleOperationTimeout(), messageFactory
.prepareForDestroy(), true);
return response.getStores();
} catch (ClusterException e) {
// TODO handle this
} catch (TimeoutException e) {
// TODO handle this
}
return null;
}
private EhcacheEntityResponse invokeInternal(TimeoutDuration timeLimit, EhcacheEntityMessage message, boolean replicate)
throws ClusterException, TimeoutException {
try {
EhcacheEntityResponse response = waitFor(timeLimit, invokeAsync(message, replicate));
if (EhcacheResponseType.FAILURE.equals(response.getResponseType())) {
throw ((Failure)response).getCause();
} else {
return response;
}
} catch (EntityException e) {
throw new RuntimeException(message + " error: " + e.toString(), e);
} catch (MessageCodecException e) {
throw new RuntimeException(message + " error: " + e.toString(), e);
} catch (TimeoutException e) {
String msg = "Timeout exceeded for " + message + " message; " + timeLimit;
TimeoutException timeoutException = new TimeoutException(msg);
timeoutException.initCause(e);
LOGGER.info(msg, timeoutException);
throw timeoutException;
}
}
private InvokeFuture<EhcacheEntityResponse> invokeAsync(EhcacheEntityMessage message, boolean replicate)
throws MessageCodecException {
getClientId();
if (replicate) {
message.setId(sequenceGenerator.getAndIncrement());
}
return endpoint.beginInvoke().message(message).replicate(replicate).invoke();
}
private static <T> T waitFor(TimeoutDuration timeLimit, InvokeFuture<T> future)
throws EntityException, TimeoutException {
boolean interrupted = false;
long deadlineTimeout = System.nanoTime() + timeLimit.toNanos();
try {
while (true) {
try {
long timeRemaining = deadlineTimeout - System.nanoTime();
return future.getWithTimeout(timeRemaining, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
interrupted = true;
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
}