/* * Copyright 2008-2009 LinkedIn, 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 voldemort.client; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Callable; import org.apache.log4j.Logger; import voldemort.VoldemortException; import voldemort.annotations.concurrency.Threadsafe; import voldemort.annotations.jmx.JmxManaged; import voldemort.annotations.jmx.JmxOperation; import voldemort.cluster.Node; import voldemort.routing.RoutingStrategy; import voldemort.serialization.Serializer; import voldemort.store.InvalidMetadataException; import voldemort.store.Store; import voldemort.store.StoreCapabilityType; import voldemort.utils.JmxUtils; import voldemort.utils.Utils; import voldemort.versioning.InconsistencyResolver; import voldemort.versioning.InconsistentDataException; import voldemort.versioning.ObsoleteVersionException; import voldemort.versioning.VectorClock; import voldemort.versioning.Version; import voldemort.versioning.Versioned; import com.google.common.collect.Maps; /** * The default {@link voldemort.client.StoreClient StoreClient} implementation * you get back from a {@link voldemort.client.StoreClientFactory * StoreClientFactory} * * * @param <K> The key type * @param <V> The value type */ @Threadsafe @JmxManaged(description = "A voldemort client") public class DefaultStoreClient<K, V> implements StoreClient<K, V> { private final Logger logger = Logger.getLogger(DefaultStoreClient.class); protected Callable<Object> beforeRebootstrapCallback = null; protected StoreClientFactory storeFactory; protected int metadataRefreshAttempts; protected String storeName; protected InconsistencyResolver<Versioned<V>> resolver; protected volatile Store<K, V, Object> store; public DefaultStoreClient(String storeName, InconsistencyResolver<Versioned<V>> resolver, StoreClientFactory storeFactory, int maxMetadataRefreshAttempts) { this.storeName = Utils.notNull(storeName); this.resolver = resolver; this.storeFactory = Utils.notNull(storeFactory); this.metadataRefreshAttempts = maxMetadataRefreshAttempts; // Registering self to be able to bootstrap client dynamically via JMX JmxUtils.registerMbean(this, JmxUtils.createObjectName(JmxUtils.getPackageName(this.getClass()), JmxUtils.getClassName(this.getClass()) + "." + storeName)); bootStrap(); } // Default constructor invoked from child class public DefaultStoreClient() {} @JmxOperation(description = "bootstrap metadata from the cluster.") public void bootStrap() { if(beforeRebootstrapCallback != null) { try { beforeRebootstrapCallback.call(); } catch(Exception e) { logger.warn("Exception caught when running callback before bootstrap", e); } } logger.info("Bootstrapping metadata for store " + this.storeName); this.store = storeFactory.getRawStore(storeName, resolver); } public boolean delete(K key) { Version version = getVersionWithResolution(key); if(version == null) return false; return delete(key, version); } public boolean delete(K key, Version version) { for(int attempts = 0; attempts < this.metadataRefreshAttempts; attempts++) { try { return store.delete(key, version); } catch(InvalidMetadataException e) { logger.info("Received invalid metadata exception during delete [ " + e.getMessage() + " ] on store '" + storeName + "'. Rebootstrapping"); bootStrap(); } } throw new VoldemortException(this.metadataRefreshAttempts + " metadata refresh attempts failed."); } public V getValue(K key, V defaultValue) { Versioned<V> versioned = get(key); if(versioned == null) return defaultValue; else return versioned.getValue(); } public V getValue(K key) { Versioned<V> returned = get(key, null); if(returned == null) return null; else return returned.getValue(); } public Versioned<V> get(K key, Versioned<V> defaultValue) { for(int attempts = 0; attempts < this.metadataRefreshAttempts; attempts++) { try { List<Versioned<V>> items = store.get(key, null); return getItemOrThrow(key, defaultValue, items); } catch(InvalidMetadataException e) { logger.info("Received invalid metadata exception during get [ " + e.getMessage() + " ] on store '" + storeName + "'. Rebootstrapping"); bootStrap(); } } throw new VoldemortException(this.metadataRefreshAttempts + " metadata refresh attempts failed."); } public Versioned<V> get(K key, Versioned<V> defaultValue, Object transform) { for(int attempts = 0; attempts < this.metadataRefreshAttempts; attempts++) { try { List<Versioned<V>> items = store.get(key, transform); return getItemOrThrow(key, defaultValue, items); } catch(InvalidMetadataException e) { logger.info("Received invalid metadata exception during get [ " + e.getMessage() + " ] on store '" + storeName + "'. Rebootstrapping"); bootStrap(); } } throw new VoldemortException(this.metadataRefreshAttempts + " metadata refresh attempts failed."); } protected List<Version> getVersions(K key) { for(int attempts = 0; attempts < this.metadataRefreshAttempts; attempts++) { try { return store.getVersions(key); } catch(InvalidMetadataException e) { logger.info("Received invalid metadata exception during getVersions [ " + e.getMessage() + " ] on store '" + storeName + "'. Rebootstrapping"); bootStrap(); } } throw new VoldemortException(this.metadataRefreshAttempts + " metadata refresh attempts failed."); } protected Versioned<V> getItemOrThrow(K key, Versioned<V> defaultValue, List<Versioned<V>> items) { if(items.size() == 0) return defaultValue; else if(items.size() == 1) return items.get(0); else throw new InconsistentDataException("Unresolved versions returned from get(" + key + ") = " + items, items); } public Versioned<V> get(K key) { return get(key, null); } public Map<K, Versioned<V>> getAll(Iterable<K> keys) { Map<K, List<Versioned<V>>> items = null; for(int attempts = 0;; attempts++) { if(attempts >= this.metadataRefreshAttempts) throw new VoldemortException(this.metadataRefreshAttempts + " metadata refresh attempts failed."); try { items = store.getAll(keys, null); break; } catch(InvalidMetadataException e) { logger.info("Received invalid metadata exception during getAll [ " + e.getMessage() + " ] on store '" + storeName + "'. Rebootstrapping"); bootStrap(); } } Map<K, Versioned<V>> result = Maps.newHashMapWithExpectedSize(items.size()); for(Entry<K, List<Versioned<V>>> mapEntry: items.entrySet()) { Versioned<V> value = getItemOrThrow(mapEntry.getKey(), null, mapEntry.getValue()); result.put(mapEntry.getKey(), value); } return result; } public Version put(K key, V value) { Version version = getVersionForPut(key); Versioned<V> versioned = Versioned.value(value, version); return put(key, versioned); } public Version put(K key, Versioned<V> versioned, Object transform) throws ObsoleteVersionException { for(int attempts = 0; attempts < this.metadataRefreshAttempts; attempts++) { try { store.put(key, versioned, transform); return versioned.getVersion(); } catch(InvalidMetadataException e) { logger.info("Received invalid metadata exception during put [ " + e.getMessage() + " ] on store '" + storeName + "'. Rebootstrapping"); bootStrap(); } } throw new VoldemortException(this.metadataRefreshAttempts + " metadata refresh attempts failed."); } public boolean putIfNotObsolete(K key, Versioned<V> versioned) { try { put(key, versioned); return true; } catch(ObsoleteVersionException e) { return false; } } public Version put(K key, Versioned<V> versioned) throws ObsoleteVersionException { for(int attempts = 0; attempts < this.metadataRefreshAttempts; attempts++) { try { store.put(key, versioned, null); return versioned.getVersion(); } catch(InvalidMetadataException e) { logger.info("Received invalid metadata exception during put [ " + e.getMessage() + " ] on store '" + storeName + "'. Rebootstrapping"); bootStrap(); } } throw new VoldemortException(this.metadataRefreshAttempts + " metadata refresh attempts failed."); } public boolean applyUpdate(UpdateAction<K, V> action) { return applyUpdate(action, 3); } public boolean applyUpdate(UpdateAction<K, V> action, int maxTries) { boolean success = false; try { for(int i = 0; i < maxTries; i++) { try { action.update(this); success = true; return success; } catch(ObsoleteVersionException e) { // ignore for now } } } finally { if(!success) action.rollback(); } // if we got here we have seen too many ObsoleteVersionExceptions // and have rolled back the updates return false; } public List<Node> getResponsibleNodes(K key) { RoutingStrategy strategy = (RoutingStrategy) store.getCapability(StoreCapabilityType.ROUTING_STRATEGY); @SuppressWarnings("unchecked") Serializer<K> keySerializer = (Serializer<K>) store.getCapability(StoreCapabilityType.KEY_SERIALIZER); return strategy.routeRequest(keySerializer.toBytes(key)); } @SuppressWarnings("unused") protected Version getVersion(K key) { List<Version> versions = getVersions(key); if(versions.size() == 0) return null; else if(versions.size() == 1) return versions.get(0); else throw new InconsistentDataException("Unresolved versions returned from get(" + key + ") = " + versions, versions); } public Versioned<V> get(K key, Object transforms) { return get(key, null, transforms); } public Map<K, Versioned<V>> getAll(Iterable<K> keys, Map<K, Object> transforms) { Map<K, List<Versioned<V>>> items = null; for(int attempts = 0;; attempts++) { if(attempts >= this.metadataRefreshAttempts) throw new VoldemortException(this.metadataRefreshAttempts + " metadata refresh attempts failed."); try { items = store.getAll(keys, transforms); break; } catch(InvalidMetadataException e) { logger.info("Received invalid metadata exception during getAll [ " + e.getMessage() + " ] on store '" + storeName + "'. Rebootstrapping"); bootStrap(); } } Map<K, Versioned<V>> result = Maps.newHashMapWithExpectedSize(items.size()); for(Entry<K, List<Versioned<V>>> mapEntry: items.entrySet()) { Versioned<V> value = getItemOrThrow(mapEntry.getKey(), null, mapEntry.getValue()); result.put(mapEntry.getKey(), value); } return result; } private Version getVersionWithResolution(K key) { List<Version> versions = getVersions(key); if(versions.isEmpty()) return null; else if(versions.size() == 1) return versions.get(0); else { Versioned<V> versioned = get(key, null); if(versioned == null) return null; else return versioned.getVersion(); } } private Version getVersionForPut(K key) { Version version = getVersionWithResolution(key); if(version == null) { version = new VectorClock(); } return version; } public Version put(K key, V value, Object transforms) { Version version = getVersionForPut(key); Versioned<V> versioned = Versioned.value(value, version); return put(key, versioned, transforms); } public void setBeforeRebootstrapCallback(Callable<Object> callback) { beforeRebootstrapCallback = callback; } }