All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.basho.riak.client.operations.StoreObject Maven / Gradle / Ivy

There is a newer version: 2.1.1
Show newest version
/*
 * This file is provided to you 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.operations;

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Callable;

import com.basho.riak.client.IRiakObject;
import com.basho.riak.client.RiakException;
import com.basho.riak.client.RiakRetryFailedException;
import com.basho.riak.client.bucket.Bucket;
import com.basho.riak.client.bucket.DomainBucket;
import com.basho.riak.client.cap.*;
import com.basho.riak.client.convert.ConversionException;
import com.basho.riak.client.convert.Converter;
import com.basho.riak.client.convert.VClockUtil;
import com.basho.riak.client.raw.RawClient;
import com.basho.riak.client.raw.RiakResponse;
import com.basho.riak.client.raw.StoreMeta;

/**
 * Stores a given object into riak always fetches first.
 * 
 * 

* Use {@link Bucket#store(Object)} methods to create a store operation. Also * look at {@link DomainBucket#store(Object)}. *

* * TODO Should fetch first be optional? What about the vclock if not? * * @author russell * @see Bucket * @see DomainBucket */ public class StoreObject implements RiakOperation { private final RawClient client; private final FetchObject fetchObject; private Retrier retrier; private final StoreMeta.Builder storeMetaBuilder = new StoreMeta.Builder(); private final boolean hasKey; private boolean returnBody = false; private boolean doNotFetch = false; private boolean deletedVClockWithReturnbody = false; private Mutation mutation; private ConflictResolver resolver; private Converter converter; /** * Create a new StoreObject operation for the object in bucket * at key. *

* Use {@link Bucket} to create a store operation. *

* * @param client * the RawClient to use * @param bucket * location of data to store * @param key * location of data to store * @param retrier * the Retrier to use for this operation */ public StoreObject(final RawClient client, String bucket, String key, final Retrier retrier) { this.client = client; this.retrier = retrier; fetchObject = new FetchObject(client, bucket, key, retrier); hasKey = key != null; } /** * Fetches data from bucket/key, if item exists it is converted * with {@link Converter} and any siblings resolved with * {@link ConflictResolver}. {@link Mutation} is applied to the result which * is then converted back to {@link IRiakObject} and stored with the * {@link RawClient}. If returnBody is true then the returned * result is treated like a fetch (converted, conflict resolved) and the * resultant object returned. * * If you wish to eliminate the fetch and conflict resolution, calling * withoutFetch() prior to this will do so. * * * @return the result of the store if returnBody is * true, null if returnBody * is false * @throws RiakException * @throws {@link MatchFoundException} if a 'ifNoneMatch' conditional store * fails because a match exists */ public T execute() throws RiakRetryFailedException, UnresolvedConflictException, ConversionException { if (!hasKey && !doNotFetch) { throw new IllegalArgumentException("Can not store object will null key without calling withoutFetch()"); } T resolved = null; VClock vclock = null; if (!doNotFetch) { resolved = fetchObject.execute(); vclock = fetchObject.getVClock(); } final T mutated = mutation.apply(resolved); if (doNotFetch) { vclock = VClockUtil.getVClock(mutated); } final IRiakObject o = converter.fromDomain(mutated, vclock); final StoreMeta storeMeta = storeMetaBuilder.returnBody(returnBody).build(); // if non match and if not modified require extra data for the HTTP API // pull that from the riak object if possible if(storeMeta.hasIfNoneMatch() && storeMeta.getIfNoneMatch() && o != null) { storeMeta.etags(new String[] {o.getVtag()}); } if(storeMeta.hasIfNotModified() && storeMeta.getIfNotModified() && o != null) { storeMeta.lastModified(o.getLastModified()); } final boolean hasMutated = mutation instanceof ConditionalStoreMutation ? ((ConditionalStoreMutation)mutation).hasMutated() : true; if (hasMutated) { final RiakResponse stored = retrier.attempt(new Callable() { public RiakResponse call() throws Exception { return client.store(o, storeMeta); } }); final Collection storedSiblings = new ArrayList(stored.numberOfValues()); // both HTTP and Protocol buffers will return tombstone siblings on a // returnbody=true. There is no 'deletedvclock' option in RpbPutReq and // HTTP just always returns them. This makes returnbody=true consistent // with a fetch operation. See: returnDeletedVClock() below for (IRiakObject s : stored) { if (s.isDeleted() && !deletedVClockWithReturnbody) { continue; } storedSiblings.add(converter.toDomain(s)); } return resolver.resolve(storedSiblings); } else { return mutated; } } /** * A store performs a fetch first (to get a vclock and resolve any conflicts), set the read quorum for the fetch * * @param r the read quorum for the pre-store fetch * @return this */ public StoreObject r(int r) { this.fetchObject.r(r); return this; } /** * A store performs a fetch first (to get a vclock and resolve any conflicts), set the read quorum for the fetch * * @param r the read quorum for the pre-store fetch * @return this */ public StoreObject r(Quora r) { this.fetchObject.r(r); return this; } /** * A store performs a fetch first (to get a vclock and resolve any conflicts), set the read quorum for the fetch * * @param r the read quorum for the pre-store fetch * @return this */ public StoreObject r(Quorum r) { this.fetchObject.r(r); return this; } /** * The pr for the pre-store fetch * @param pr * @return * @see com.basho.riak.client.operations.FetchObject#pr(int) */ public StoreObject pr(int pr) { this.fetchObject.pr(pr); return this; } /** * The pr for the pre-store fetch * @param pr * @return * @see com.basho.riak.client.operations.FetchObject#pr(Quora) */ public StoreObject pr(Quora pr) { this.fetchObject.pr(pr); return this; } /** * The pr for the pre-store fetch * @param pr * @return * @see com.basho.riak.client.operations.FetchObject#pr(Quorum) */ public StoreObject pr(Quorum pr) { this.fetchObject.pr(pr); return this; } /** * if notfound_ok counts towards r count (for the pre-store fetch) * * @param notFoundOK * @return * @see com.basho.riak.client.operations.FetchObject#notFoundOK(boolean) */ public StoreObject notFoundOK(boolean notFoundOK) { this.fetchObject.notFoundOK(notFoundOK); return this; } /** * fail early if a quorum of error/notfounds are reached before a successful * read (for the pre-store fetch) * * @param basicQuorum * @return * @see com.basho.riak.client.operations.FetchObject#basicQuorum(boolean) */ public StoreObject basicQuorum(boolean basicQuorum) { this.fetchObject.basicQuorum(basicQuorum); return this; } /** * Set an operation timeout in milliseconds to be sent to Riak * * As of 1.4 Riak allows a timeout to be sent for get, put, and delete operations. * The client will receive a timeout error if the operation is not completed * within the specified time * * @param timeout * @return this * @see com.basho.riak.client.raw.FetchMeta.Builder#timeout */ public StoreObject timeout(int timeout) { fetchObject.timeout(timeout); storeMetaBuilder.timeout(timeout); return this; } /** * If the object has just been deleted, there maybe a tombstone value * vclock, set to true to have this returned in the pre-store fetch. * * @param returnDeletedVClock * @return * @see com.basho.riak.client.operations.FetchObject#returnDeletedVClock(boolean) */ public StoreObject returnDeletedVClock(boolean returnDeletedVClock) { this.fetchObject.returnDeletedVClock(returnDeletedVClock); deletedVClockWithReturnbody = true; return this; } /** * Set the primary write quorum for the store operation, takes precedence * over w. * * @param pw * @return this */ public StoreObject pw(int pw) { storeMetaBuilder.pw(pw); return this; } /** * Set the primary write quorum for the store operation, takes precedence * over w. * * @param pw * @return this */ public StoreObject pw(Quora pw) { storeMetaBuilder.pw(pw); return this; } /** * Set the primary write quorum for the store operation, takes precedence * over w. * * @param pw * @return this */ public StoreObject pw(Quorum pw) { storeMetaBuilder.pw(pw); return this; } /** * Set the write quorum for the store operation * @param w * @return this */ public StoreObject w(int w) { storeMetaBuilder.w(w); return this; } /** * Set the write quorum for the store operation * @param w * @return this */ public StoreObject w(Quora w) { storeMetaBuilder.w(w); return this; } /** * Set the write quorum for the store operation * @param w * @return this */ public StoreObject w(Quorum w) { storeMetaBuilder.w(w); return this; } /** * The durable write quorum for this store operation * @param dw * @return this */ public StoreObject dw(int dw) { storeMetaBuilder.dw(dw); return this; } /** * The durable write quorum for this store operation * @param dw * @return this */ public StoreObject dw(Quora dw) { storeMetaBuilder.dw(dw); return this; } /** * The durable write quorum for this store operation * @param dw * @return this */ public StoreObject dw(Quorum dw) { storeMetaBuilder.dw(dw); return this; } /** * Should the store operation return a response body? * @param returnBody * @return this */ public StoreObject returnBody(boolean returnBody) { this.returnBody = returnBody; return this; } /** * If you don't know what this is or what it does, you should not * be using it. * @param asis * @return this */ public StoreObject asis(boolean asis) { storeMetaBuilder.asis(asis); return this; } /** * Default is false (i.e. NOT a conditional store). *

* NOTE: This has different meanings depending on the underlying transport. *

*

* In the case of the PB interface it means: Only store if there is no * bucket/key entry for this object in the database already. *

*

* For the HTTP API it means: Only store if there is no entity that matches * some etags I provide you *

*

* To make this transparent the StoreOperation will pull the etag from the * object returned in the pre-store fetch, and use that as supplementary * data to the HTTP Store request. *

*

* If there is match (b/k or etag) then the operation is *not* retried by * the retrier, to override this, provide a custom retrier. *

* * @param ifNoneMatch * true if you want a conditional store, false otherwise, * defaults to false. * @return this */ public StoreObject ifNoneMatch(boolean ifNoneMatch) { storeMetaBuilder.ifNoneMatch(ifNoneMatch); return this; } /** * Default is false (i.e. NOT a conditional store). *

* NOTE: This has different meanings depending on the underlying transport. *

*

* In the case of the PB interface it means: Only store if the vclock * provided with the store is the same as the one in Riak for this object * (i.e. the object has not been modified since you last got it), of course, * since this StoreObject does a fetch before a store the window for * concurrent modification is minimized, but this is an extra guard, still. *

*

* For the HTTP API it means: Only store if there has been no modification * since the provided timestamp. *

*

* To make this transparent the StoreOperation will pull the last * modified date from the object returned in the pre-store fetch, and use * that as supplementary data to the HTTP Store request. *

* * @param ifNotModified * true if you want a conditional store, false otherwise, * defaults to false. * @return this */ public StoreObject ifNotModified(boolean ifNotModified) { storeMetaBuilder.ifNotModified(ifNotModified); return this; } /** * The {@link Retrier} to use for the fetch and store operations. * @param retrier a {@link Retrier} * @return this */ public StoreObject withRetrier(final Retrier retrier) { this.retrier = retrier; this.fetchObject.withRetrier(retrier); return this; } /** * The {@link Mutation} to apply to the value retrieved from the fetch operation * @param mutation a {@link Mutation} * @return this */ public StoreObject withMutator(Mutation mutation) { this.mutation = mutation; return this; } /** * The {@link ConflictResolver} to use on any sibling results returned from the fetch (and store if returnBody is true) * NOTE: since it is used for fetch and after store must be reusable. * @param resolver a {@link ConflictResolver} * @return this */ public StoreObject withResolver(ConflictResolver resolver) { this.resolver = resolver; this.fetchObject.withResolver(resolver); return this; } /** * The {@link Converter} to use * @param converter a {@link Converter} * @return this */ public StoreObject withConverter(Converter converter) { this.converter = converter; this.fetchObject.withConverter(converter); return this; } /** * Creates a {@link ClobberMutation} that applies value * * @param value * new value * @return this StoreObject */ public StoreObject withValue(final T value) { this.mutation = new ClobberMutation(value); return this; } /** * Eliminates fetching the existing value before storing the current one. * * The original client design was based around the StoreObject retrieving * an existing value from Riak, resolving siblings, applying a Mutation, then * finally storing the modified object to Riak. In several use cases this is * not optimal. * * By calling this method prior to execute()
the fetch and * conflict resolution will not occur. * * Care must be taken when using this option: * * 1) null will be passed to the {@link Mutation} object (if * you are using the default {@link ClobberMutation} this is fine). * 2) A vector clock should be provided by the object returned from your * {@link Mutation}. * */ public StoreObject withoutFetch() { this.doNotFetch = true; return this; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy