/* * 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.ValueSupplier; import org.ehcache.clustered.client.internal.store.ChainBuilder; import org.ehcache.clustered.client.internal.store.ResolvedChain; import org.ehcache.clustered.client.internal.store.operations.codecs.OperationsCodec; import org.ehcache.clustered.common.internal.store.Chain; import org.ehcache.clustered.common.internal.store.Element; import org.ehcache.expiry.Duration; import org.ehcache.expiry.Expirations; import org.ehcache.expiry.Expiry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; public class ChainResolver<K, V> { private static final Logger LOG = LoggerFactory.getLogger(ChainResolver.class); private static final TimeUnit TIME_UNIT = TimeUnit.MILLISECONDS; private final OperationsCodec<K, V> codec; private final Expiry<? super K, ? super V> expiry; public ChainResolver(final OperationsCodec<K, V> codec, Expiry<? super K, ? super V> expiry) { if(expiry == null) { throw new NullPointerException("Expiry can not be null"); } this.codec = codec; this.expiry = expiry; } /** * Extract the {@code Element}s from the provided {@code Chain} that are not associated with the provided key * and create a new {@code Chain} * * Separate the {@code Element}s from the provided {@code Chain} that are associated and not associated with * the provided key. Create a new chain with the unassociated {@code Element}s. Resolve the associated elements * and append the resolved {@code Element} to the newly created chain. * * @param chain a heterogeneous {@code Chain} * @param key a key * @param now time when the chain is being resolved * @return a resolved chain, result of resolution of chain provided */ public ResolvedChain<K, V> resolve(Chain chain, K key, long now) { Result<V> result = null; ChainBuilder chainBuilder = new ChainBuilder(); long expirationTime = Long.MIN_VALUE; int keyMatch = 0; boolean compacted = false; for (Element element : chain) { ByteBuffer payload = element.getPayload(); Operation<K, V> operation = codec.decode(payload); final Result<V> previousResult = result; if(key.equals(operation.getKey())) { keyMatch++; result = operation.apply(result); if(result == null) { continue; } if (expiry != Expirations.noExpiration()) { if(operation.isExpiryAvailable()) { expirationTime = operation.expirationTime(); if (expirationTime == Long.MIN_VALUE) { continue; } if (now >= expirationTime) { result = null; } } else { Duration duration; try { if(previousResult == null) { duration = expiry.getExpiryForCreation(key, result.getValue()); if (duration == null) { result = null; continue; } } else { duration = expiry.getExpiryForUpdate(key, new ValueSupplier<V>() { @Override public V value() { return previousResult.getValue(); } }, result.getValue()); if (duration == null) { continue; } } } catch (Exception ex) { LOG.error("Expiry computation caused an exception - Expiry duration will be 0 ", ex); duration = Duration.ZERO; } compacted = true; if(duration.isInfinite()) { expirationTime = Long.MIN_VALUE; continue; } long time = TIME_UNIT.convert(duration.getLength(), duration.getTimeUnit()); expirationTime = time + operation.timeStamp(); if(now >= expirationTime) { result = null; } } } } else { payload.rewind(); chainBuilder = chainBuilder.add(payload); } } compacted = (result == null) ? (keyMatch > 0) : (compacted || keyMatch > 1); if(compacted) { if(result != null) { Operation<K, V> resolvedOperation = new PutOperation<K, V>(key, result.getValue(), -expirationTime); ByteBuffer payload = codec.encode(resolvedOperation); chainBuilder = chainBuilder.add(payload); } return new ResolvedChain.Impl<K, V>(chainBuilder.build(), key, result, keyMatch); } else { return new ResolvedChain.Impl<K, V>(chain, key, result, 0); } } }