Java tutorial
/* * Copyright 2013 Basho Technologies 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 com.basho.riak.client.api.commands.kv; import com.basho.riak.client.core.RiakCluster; import com.basho.riak.client.api.RiakCommand; import com.basho.riak.client.api.cap.UnresolvedConflictException; import com.basho.riak.client.api.cap.VClock; import com.basho.riak.client.api.convert.ConversionException; import com.basho.riak.client.api.convert.reflection.AnnotationUtil; import com.basho.riak.client.core.RiakFuture; import com.basho.riak.client.core.RiakFutureListener; import com.basho.riak.client.api.commands.ListenableFuture; import com.basho.riak.client.core.query.Location; import com.basho.riak.client.core.query.RiakObject; import com.fasterxml.jackson.core.type.TypeReference; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * Perform an full cycle update of a Riak value: fetch, resolve, modify, store. * <script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js"></script> * <p> * The UpdateValue command completely encapsulates the typical read/modify/write * cycle used with data in Riak. * </p> * <p>The object specified by the given {@link com.basho.riak.client.core.query.Location} * will be fetched from Riak and have the {@link com.basho.riak.client.api.cap.ConflictResolver} stored in * the {@link com.basho.riak.client.api.cap.ConflictResolverFactory} applied. The resolved * object is then passed to your {@link com.basho.riak.client.api.commands.kv.UpdateValue.Update} * and the result stored back into Riak. * </p> * <p> * To create the mutation you wish to perform, you extend the * {@link com.basho.riak.client.api.commands.kv.UpdateValue.Update} class: * </p> * <pre class="prettyprint"> * {@code * class AppendUpdate extends UpdateValue.Update<MyPojo> * { * private final String update; * * public AppendUpdate(String update) * { * this.update = update; * } * * {@literal @Override} * public MyPojo apply(MyPojo original) * { * if (original == null) * { * original = new MyPojo(); * } * * original.value += update; * return original; * } * } * * Namespace ns = new Namespace("my_type", "my_bucket"); * Location loc = new Location(ns, "my_key"); * AppendUpdate update = new AppendUpdate("append this string"); * * UpdateValue uv = * new UpdateValue.Builder(loc).withUpdate(update).build(); * * UpdateValue.Response response = client.execute(uv);}</pre> * @author Dave Rusek <drusek at basho dot com> * @since 2.0 */ public final class UpdateValue extends RiakCommand<UpdateValue.Response, Location> { private final Location location; private final Update<?> update; private final TypeReference<?> typeReference; private final Map<FetchValue.Option<?>, Object> fetchOptions = new HashMap<FetchValue.Option<?>, Object>(); private final Map<StoreValue.Option<?>, Object> storeOptions = new HashMap<StoreValue.Option<?>, Object>(); UpdateValue(Builder builder) { this.location = builder.location; this.update = builder.update; this.typeReference = builder.typeReference; this.fetchOptions.putAll(builder.fetchOptions); this.storeOptions.putAll(builder.storeOptions); } @SuppressWarnings("unchecked") @Override protected RiakFuture<Response, Location> executeAsync(final RiakCluster cluster) { final UpdateValueFuture updateFuture = new UpdateValueFuture(location); FetchValue.Builder fetchBuilder = new FetchValue.Builder(location); for (Map.Entry<FetchValue.Option<?>, Object> optPair : fetchOptions.entrySet()) { fetchBuilder.withOption((FetchValue.Option<Object>) optPair.getKey(), optPair.getValue()); } RiakFuture<FetchValue.Response, Location> fetchFuture = fetchBuilder.build().executeAsync(cluster); // Anonymous listener that will do the work RiakFutureListener<FetchValue.Response, Location> fetchListener = new RiakFutureListener<FetchValue.Response, Location>() { @Override public void handle(RiakFuture<FetchValue.Response, Location> f) { if (f.isSuccess()) { FetchValue.Response fetchResponse; try { fetchResponse = f.get(); Object resolved = null; VClock vclock = null; if (!fetchResponse.isNotFound()) { if (typeReference == null) { // Steal the type from the Update. Yes, Really. ParameterizedType pType = (ParameterizedType) update.getClass() .getGenericSuperclass(); Type t = pType.getActualTypeArguments()[0]; if (t instanceof ParameterizedType) { t = ((ParameterizedType) t).getRawType(); } resolved = fetchResponse.getValue((Class<?>) t); } else { resolved = fetchResponse.getValue(typeReference); } // We get the vclock so we can inject it into the updated object. // This is so the end user doesn't have to worry about vclocks // in the Update. vclock = fetchResponse.getVectorClock(); } Object updated = ((Update<Object>) update).apply(resolved); if (update.isModified()) { AnnotationUtil.setVClock(updated, vclock); StoreValue.Builder store = new StoreValue.Builder(updated, typeReference) .withLocation(location).withVectorClock(vclock); for (Map.Entry<StoreValue.Option<?>, Object> optPair : storeOptions.entrySet()) { store.withOption((StoreValue.Option<Object>) optPair.getKey(), optPair.getValue()); } RiakFuture<StoreValue.Response, Location> storeFuture = store.build() .executeAsync(cluster); storeFuture.addListener(updateFuture); } else { Response updateResponse = new Response.Builder().withLocation(f.getQueryInfo()) .withUpdated(false).build(); updateFuture.setResponse(updateResponse); } } catch (InterruptedException ex) { updateFuture.setException(ex); } catch (UnresolvedConflictException ex) { updateFuture.setException(ex); } catch (ConversionException ex) { updateFuture.setException(ex); } catch (ExecutionException ex) { updateFuture.setException(ex); } } else { updateFuture.setException(f.cause().getCause()); } } }; fetchFuture.addListener(fetchListener); return updateFuture; } /** * */ public static class Response extends KvResponseBase { private final boolean wasUpdated; Response(Init<?> builder) { super(builder); this.wasUpdated = builder.wasUpdated; } /** * Determine if an update occurred. * <p> * The supplied {@code Update} indicates if a modification was made. If * no modification was made, no store operation is performed and this * will return false. * <p> * @return true if the supplied {@code Update} modified the retrieved object, * false otherwise. */ public boolean wasUpdated() { return wasUpdated; } /** * @ExcludeFromJavadoc */ protected static abstract class Init<T extends Init<T>> extends KvResponseBase.Init<T> { private boolean wasUpdated; T withUpdated(boolean updated) { this.wasUpdated = updated; return self(); } } static class Builder extends Init<Builder> { @Override protected Builder self() { return this; } @Override Response build() { return new Response(this); } } } /** * An update on a Riak object * * @param <T> */ public abstract static class Update<T> { private boolean modified = true; /** * Modify the input value and return the modification. It is OK to * modify the input value in-place and return it. * * @param original the resolved value * @return a modified value */ public abstract T apply(T original); /** * Set the modification status of this update, defaults to {@code true} * * @param modified true if modified */ protected void setModified(boolean modified) { this.modified = modified; } /** * true if this Update has modified the input value and requires a store, * defaults to {@code true} * * @return true if modified */ public boolean isModified() { return modified; } } /** * Used to construct an UpdateValue command. */ public static class Builder { private final Location location; private Update<?> update; private TypeReference<?> typeReference; private final Map<FetchValue.Option<?>, Object> fetchOptions = new HashMap<FetchValue.Option<?>, Object>(); private final Map<StoreValue.Option<?>, Object> storeOptions = new HashMap<StoreValue.Option<?>, Object>(); /** * Construct a Builder for an UpdateValue command. * @param location the location of the object in Riak to update. */ public Builder(Location location) { this.location = location; } /** * Add an option for the fetch phase of the update. * * @param option the option * @param value the option's value * @param <U> the type of the option's value * @return this */ public <U> Builder withFetchOption(FetchValue.Option<U> option, U value) { fetchOptions.put(option, value); return this; } /** * Add an option for the store phase of the update. * * @param option the option * @param value the option's value * @param <U> the type of the option's value * @return this */ public <U> Builder withStoreOption(StoreValue.Option<U> option, U value) { storeOptions.put(option, value); return this; } /** * Supply the Update. * <p> * During the update operation, the fetched value needs to be converted * before being passed to the {@code ConflictResolver} and the {@code Update} * method. * <p> * <p> * Supplying only an {@code Update<T>} means the raw type of {@code T} * will be used to retrieve the {@code Converter} and {@code ConflictResolver} * to be used. * </p> * @param update The {@code Update} instance * @return a reference to this object. * @see com.basho.riak.client.api.convert.Converter * @see com.basho.riak.client.api.convert.ConverterFactory * @see com.basho.riak.client.api.cap.ConflictResolver * @see com.basho.riak.client.api.cap.ConflictResolverFactory */ public Builder withUpdate(Update<?> update) { this.update = update; return this; } /** * Supply the Update with a TypeReference. * <p> * During the update operation, the fetched value needs to be converted * before being passed to the {@code ConflictResolver} and the {@code Update} * method. If your domain object is a parameterized type you will need to supply * a {@code TypeReference} so the appropriate {@code ConflictResolver} * and {@code Converter} can be found. * <p> * @param update The {@code Update} instance * @param typeReference the {@code TypeReference} for the class used for conversion. * @return a reference to this object. * @see com.basho.riak.client.api.convert.Converter * @see com.basho.riak.client.api.convert.ConverterFactory * @see com.basho.riak.client.api.cap.ConflictResolver * @see com.basho.riak.client.api.cap.ConflictResolverFactory */ public <T> Builder withUpdate(Update<T> update, TypeReference<T> typeReference) { this.update = update; this.typeReference = typeReference; return this; } /** * Set the Riak-side timeout value. * <p> * By default, riak has a 60s timeout for operations. Setting * this value will override that default for both the * fetch and store operation. * </p> * @param timeout the timeout in milliseconds to be sent to riak. * @return a reference to this object. */ public Builder withTimeout(int timeout) { withFetchOption(FetchValue.Option.TIMEOUT, timeout); withStoreOption(StoreValue.Option.TIMEOUT, timeout); return this; } /** * Construct the UpdateValue command. * @return a new UpdateValue command. */ public UpdateValue build() { return new UpdateValue(this); } } private class UpdateValueFuture extends ListenableFuture<UpdateValue.Response, Location> implements RiakFutureListener<StoreValue.Response, Location> { private final Location location; private final CountDownLatch latch = new CountDownLatch(1); private volatile Throwable exception; private volatile Response updateResponse; private UpdateValueFuture(Location location) { this.location = location; } @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public Response get() throws InterruptedException, ExecutionException { latch.await(); if (exception != null) { throw new ExecutionException(exception); } else { return updateResponse; } } @Override public Response get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { boolean succeed = latch.await(timeout, unit); if (!succeed) { throw new TimeoutException(); } else if (exception != null) { throw new ExecutionException(exception); } else { return updateResponse; } } @Override public Response getNow() { return updateResponse; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return latch.getCount() != 1; } @Override public void await() throws InterruptedException { latch.await(); } @Override public void await(long timeout, TimeUnit unit) throws InterruptedException { latch.await(timeout, unit); } @Override public boolean isSuccess() { return isDone() && exception == null; } @Override public Throwable cause() { return exception; } private void setResponse(Response response) { this.updateResponse = response; latch.countDown(); notifyListeners(); } private void setException(Throwable t) { this.exception = t; latch.countDown(); notifyListeners(); } @Override public void handle(RiakFuture<StoreValue.Response, Location> f) { if (f.isSuccess()) { StoreValue.Response storeResponse; try { storeResponse = f.get(); Response response = new Response.Builder().withValues(storeResponse.getValues(RiakObject.class)) .withLocation(f.getQueryInfo()).withUpdated(true).build(); setResponse(response); } catch (InterruptedException ex) { setException(ex); } catch (ExecutionException ex) { setException(ex); } } else { setException(f.cause().getCause()); } } @Override public Location getQueryInfo() { return location; } } }