com.basho.riak.client.raw.http.ConversionUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.basho.riak.client.raw.http.ConversionUtil.java

Source

/*
 * 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.raw.http;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.http.impl.cookie.DateUtils;
import org.json.JSONException;
import org.json.JSONObject;

import com.basho.riak.client.IRiakObject;
import com.basho.riak.client.RiakLink;
import com.basho.riak.client.bucket.BucketProperties;
import com.basho.riak.client.builders.BucketPropertiesBuilder;
import com.basho.riak.client.builders.RiakObjectBuilder;
import com.basho.riak.client.cap.Quorum;
import com.basho.riak.client.convert.ConversionException;
import com.basho.riak.client.http.RiakBucketInfo;
import com.basho.riak.client.http.RiakClient;
import com.basho.riak.client.http.RiakObject;
import com.basho.riak.client.http.request.RequestMeta;
import com.basho.riak.client.http.request.RiakWalkSpec;
import com.basho.riak.client.http.response.BucketResponse;
import com.basho.riak.client.http.response.IndexResponse;
import com.basho.riak.client.http.response.MapReduceResponse;
import com.basho.riak.client.http.response.WalkResponse;
import com.basho.riak.client.http.util.Constants;
import com.basho.riak.client.query.LinkWalkStep;
import com.basho.riak.client.query.MapReduceResult;
import com.basho.riak.client.query.WalkResult;
import com.basho.riak.client.query.functions.NamedErlangFunction;
import com.basho.riak.client.query.functions.NamedFunction;
import com.basho.riak.client.query.functions.NamedJSFunction;
import com.basho.riak.client.query.indexes.BinIndex;
import com.basho.riak.client.query.indexes.IntIndex;
import com.basho.riak.client.raw.DeleteMeta;
import com.basho.riak.client.raw.FetchMeta;
import com.basho.riak.client.raw.JSONErrorParser;
import com.basho.riak.client.raw.RawClient;
import com.basho.riak.client.raw.StoreMeta;
import com.basho.riak.client.raw.query.LinkWalkSpec;
import com.basho.riak.client.raw.query.MapReduceTimeoutException;
import com.basho.riak.client.util.CharsetUtils;
import com.basho.riak.client.util.UnmodifiableIterator;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.TypeFactory;

/**
 * Static methods used internally by {@link HTTPClientAdapter} for converting
 * between http.{@link RiakClient} value classes and {@link RawClient} value
 * classes
 * 
 * @author russell
 * 
 */
public final class ConversionUtil {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    static {
        OBJECT_MAPPER.registerModule(new SimpleModule("http.ConversionUtil", Version.unknownVersion())
                .addDeserializer(Quorum.class, new QuorumDeserializer())
                .addDeserializer(NamedErlangFunction.class, new NamedErlangFunctionDeserializer())
                .addDeserializer(NamedJSFunction.class, new NamedJSFunctionDeserializer()));
    }

    /**
     * All static methods so we don't want any instances created
     */
    private ConversionUtil() {
    }

    /**
     * Converts a Collection from legacy http {@link RiakObject} to
     * {@link IRiakObject}
     * 
     * @param siblings
     *            the siblings from the http client
     * @return an array of {@link IRiakObject}, one for each member of
     *         <code>siblings</code>
     */
    static IRiakObject[] convert(Collection<com.basho.riak.client.http.RiakObject> siblings) {
        final Collection<IRiakObject> results = new ArrayList<IRiakObject>();

        for (com.basho.riak.client.http.RiakObject object : siblings) {
            results.add(convert(object));
        }

        return results.toArray(new IRiakObject[results.size()]);
    }

    /**
     * Convert a {@link RiakObject} to an {@link IRiakObject}
     * 
     * @param object
     *            the {@link RiakObject} to convert
     * @return
     */
    static IRiakObject convert(final com.basho.riak.client.http.RiakObject o) {

        RiakObjectBuilder builder = RiakObjectBuilder.newBuilder(o.getBucket(), o.getKey());

        builder.withValue(o.getValueAsBytes());
        builder.withVClock(nullSafeGetBytes(o.getVclock()));
        builder.withVtag(o.getVtag());
        builder.withDeleted(o.isDeleted());

        String lastModified = o.getLastmod();

        if (lastModified != null) {
            Date lastModDate = o.getLastmodAsDate();
            builder.withLastModified(lastModDate.getTime());
        }

        final Collection<RiakLink> links = new ArrayList<RiakLink>();

        for (com.basho.riak.client.http.RiakLink link : o.iterableLinks()) {
            links.add(convert(link));
        }

        builder.withLinks(links);

        @SuppressWarnings("rawtypes")
        final Collection<com.basho.riak.client.http.RiakIndex> indexes = o.getIndexes();

        for (@SuppressWarnings("rawtypes")
        com.basho.riak.client.http.RiakIndex i : indexes) {
            if (i instanceof com.basho.riak.client.http.IntIndex) {
                builder.addIndex(i.getName(), (Long) i.getValue());
            }
            if (i instanceof com.basho.riak.client.http.BinIndex) {
                builder.addIndex(i.getName(), (String) i.getValue());
            }
        }

        builder.withContentType(o.getContentType());

        final Map<String, String> userMetaData = new HashMap<String, String>();

        for (String key : o.usermetaKeys()) {
            userMetaData.put(key, o.getUsermetaItem(key));
        }

        builder.withUsermeta(userMetaData);

        return builder.build();
    }

    /**
     * Convert a {@link com.basho.riak.client.http.RiakLink} to a
     * {@link RiakLink}
     * 
     * @param link
     *            the {@link com.basho.riak.client.http.RiakLink} to convert
     * @return a {@link RiakLink} with the same bucket/key/tag values
     */
    static RiakLink convert(com.basho.riak.client.http.RiakLink link) {
        return new RiakLink(link.getBucket(), link.getKey(), link.getTag());
    }

    /**
     * Get the <code>byte[]</code> of a vector clock string, in a null safe way.
     * @param vclock the String representation of a vector clock
     * @return <code>vclock</code> as an array of bytes or null (if <code>vclock</code> was null)
     */
    static byte[] nullSafeGetBytes(String vclock) {
        return vclock == null ? null : CharsetUtils.utf8StringToBytes(vclock);
    }

    /**
     * Convert a {@link StoreMeta} into a {@link RequestMeta} for use with the
     * legacy http client
     * 
     * @param storeMeta
     *            the {@link StoreMeta} to convert
     * @return a {@link RequestMeta} populated with <code>w/dw/returnBody</code>
     *         params from <code>storeMeta</code>
     */
    static RequestMeta convert(StoreMeta storeMeta) {
        RequestMeta requestMeta = RequestMeta.writeParams(storeMeta.getW(), storeMeta.getDw());

        if (storeMeta.hasReturnBody() && storeMeta.getReturnBody()) {
            requestMeta.setQueryParam(Constants.QP_RETURN_BODY, Boolean.toString(true));
        } else {
            requestMeta.setQueryParam(Constants.QP_RETURN_BODY, Boolean.toString(false));
        }

        if (storeMeta.hasPw()) {
            if (storeMeta.getPw().isSymbolic()) {
                requestMeta.setQueryParam(Constants.QP_PW, storeMeta.getPw().getName());
            } else {
                requestMeta.setQueryParam(Constants.QP_PW, Integer.toString(storeMeta.getPw().getIntValue()));
            }
        }

        if (storeMeta.hasIfNoneMatch() && storeMeta.getIfNoneMatch()) {
            requestMeta.setIfNoneMatch(storeMeta.getEtags());
        }

        if (storeMeta.hasIfNotModified() && storeMeta.getIfNotModified()) {
            requestMeta.setIfUnmodifiedSince(storeMeta.getLastModified());
        }

        return requestMeta;
    }

    /**
     * Convert an {@link IRiakObject} to an http.{@link RiakObject}, requires a {@link RiakClient}
     * @param object the {@link IRiakObject} to convert
     * @return a {@link RiakObject} populate with {@link IRiakObject}'s data
     */
    static com.basho.riak.client.http.RiakObject convert(IRiakObject object, final RiakClient client) {
        final List<com.basho.riak.client.http.RiakIndex<Long>> intIndexes = convertIntIndexes(
                object.allIntIndexesV2());
        final List<com.basho.riak.client.http.RiakIndex<String>> binIndexes = convertBinIndexes(
                object.allBinIndexes());

        @SuppressWarnings("rawtypes")
        final List<com.basho.riak.client.http.RiakIndex> allIndexes = new ArrayList<com.basho.riak.client.http.RiakIndex>(
                intIndexes);
        allIndexes.addAll(binIndexes);

        com.basho.riak.client.http.RiakObject riakObject = new com.basho.riak.client.http.RiakObject(client,
                object.getBucket(), object.getKey(), object.getValue(), object.getContentType(), getLinks(object),
                getUserMetaData(object), object.getVClockAsString(), formatDate(object.getLastModified()),
                object.getVtag(), allIndexes, false);
        return riakObject;
    }

    /**
     * @param binIndexes
     * @return
     */
    private static List<com.basho.riak.client.http.RiakIndex<String>> convertBinIndexes(
            Map<BinIndex, Set<String>> binIndexes) {
        final List<com.basho.riak.client.http.RiakIndex<String>> converted = new ArrayList<com.basho.riak.client.http.RiakIndex<String>>();

        for (Map.Entry<BinIndex, Set<String>> index : binIndexes.entrySet()) {
            String name = index.getKey().getFullname();
            for (String v : index.getValue()) {
                converted.add(new com.basho.riak.client.http.BinIndex(name, v));
            }
        }
        return converted;
    }

    /**
     * @param allIntIndexes
     * @return
     */
    private static List<com.basho.riak.client.http.RiakIndex<Long>> convertIntIndexes(
            Map<IntIndex, Set<Long>> intIndexes) {
        final List<com.basho.riak.client.http.RiakIndex<Long>> converted = new ArrayList<com.basho.riak.client.http.RiakIndex<Long>>();

        for (Map.Entry<IntIndex, Set<Long>> index : intIndexes.entrySet()) {
            String name = index.getKey().getFullname();
            for (Long v : index.getValue()) {
                converted.add(new com.basho.riak.client.http.IntIndex(name, v));
            }
        }
        return converted;
    }

    /**
     * {@link RiakObject} expects the date as a string in a certain format
     * @param lastModified the date to format
     * @return null (if <code>lastModified</code> was null) or a String of the date
     */
    static String formatDate(Date lastModified) {
        if (lastModified == null) {
            return null;
        }
        return DateUtils.formatDate(lastModified);
    }

    /**
     * Copies the user meta data from an {@link IRiakObject} into a {@link Map}
     * @param object the {@link IRiakObject} whose meta data we want
     * @return the map of user meta (may be empty, won't be null)
     */
    static Map<String, String> getUserMetaData(IRiakObject object) {
        final Map<String, String> userMetaData = new HashMap<String, String>();

        for (Entry<String, String> entry : object.userMetaEntries()) {
            userMetaData.put(entry.getKey(), entry.getValue());
        }
        return userMetaData;
    }

    /**
     * Copy the {@link RiakLink}s from an {@link IRiakObject} into a
     * {@link List}
     * 
     * @param object
     *            the {@link IRiakObject}s whose links we want
     * @return a {@link List} of {@link com.basho.riak.client.http.RiakLink},
     *         maybe empty, won't be null
     */
    static List<com.basho.riak.client.http.RiakLink> getLinks(IRiakObject object) {

        final List<com.basho.riak.client.http.RiakLink> links = new ArrayList<com.basho.riak.client.http.RiakLink>();

        for (RiakLink link : object) {
            links.add(convert(link));
        }

        return links;
    }

    /**
     * Convert an http {@link com.basho.riak.client.http.RiakLink} to a
     * {@link RiakLink}
     * 
     * @param link
     *            the {@link com.basho.riak.client.http.RiakLink} to convert
     * @return a {@link RiakLink} with the same <code>bucket/key/tag</code>
     *         data.
     */
    static com.basho.riak.client.http.RiakLink convert(RiakLink link) {
        return new com.basho.riak.client.http.RiakLink(link.getBucket(), link.getKey(), link.getTag());
    }

    /**
     * Copy the data from a {@link BucketResponse} into a
     * {@link BucketProperties}
     * 
     * @param response
     *            the {@link BucketResponse} to copy
     * @return a {@link BucketProperties} populated from <code>response</code>
     */
    static BucketProperties convert(BucketResponse response) throws IOException {
        String schema = response.getBodyAsString();
        JsonNode root = OBJECT_MAPPER.readValue(schema, JsonNode.class);

        BucketPropertiesBuilder builder = new BucketPropertiesBuilder();
        JsonNode props = root.path(Constants.FL_SCHEMA);

        if (props.isMissingNode()) {
            throw new JsonMappingException("no 'props' field found");
        }

        builder.allowSiblings(props.path(Constants.FL_SCHEMA_ALLOW_MULT).asBoolean());
        builder.lastWriteWins(props.path(Constants.FL_SCHEMA_LAST_WRITE_WINS).asBoolean());
        builder.nVal(props.path(Constants.FL_SCHEMA_NVAL).asInt());
        builder.backend(props.path(Constants.FL_SCHEMA_BACKEND).textValue());
        builder.smallVClock(props.path(Constants.FL_SCHEMA_SMALL_VCLOCK).asInt());
        builder.bigVClock(props.path(Constants.FL_SCHEMA_BIG_VCLOCK).asInt());
        builder.youngVClock(props.path(Constants.FL_SCHEMA_YOUNG_VCLOCK).asLong());
        builder.oldVClock(props.path(Constants.FL_SCHEMA_OLD_VCLOCK).asLong());

        for (JsonNode n : props.path(Constants.FL_SCHEMA_PRECOMMIT)) {
            if (n.path(Constants.FL_SCHEMA_FUN_NAME).isMissingNode()) {
                builder.addPrecommitHook(OBJECT_MAPPER.treeToValue(n, NamedErlangFunction.class));
            } else {
                builder.addPrecommitHook(OBJECT_MAPPER.treeToValue(n, NamedJSFunction.class));
            }
        }

        for (JsonNode n : props.path(Constants.FL_SCHEMA_POSTCOMMIT)) {
            builder.addPostcommitHook(OBJECT_MAPPER.treeToValue(n, NamedErlangFunction.class));
        }

        builder.r(OBJECT_MAPPER.treeToValue(props.path(Constants.FL_SCHEMA_R), Quorum.class));
        builder.w(OBJECT_MAPPER.treeToValue(props.path(Constants.FL_SCHEMA_W), Quorum.class));
        builder.dw(OBJECT_MAPPER.treeToValue(props.path(Constants.FL_SCHEMA_DW), Quorum.class));
        builder.rw(OBJECT_MAPPER.treeToValue(props.path(Constants.FL_SCHEMA_RW), Quorum.class));
        // TODO backwards compatibility - remove when riak goes 1.3
        if (!props.path(Constants.FL_SCHEMA_PR).isMissingNode()) {
            builder.pr(OBJECT_MAPPER.treeToValue(props.path(Constants.FL_SCHEMA_PR), Quorum.class));
        }
        if (!props.path(Constants.FL_SCHEMA_PW).isMissingNode()) {
            builder.pw(OBJECT_MAPPER.treeToValue(props.path(Constants.FL_SCHEMA_PW), Quorum.class));
        }
        if (!props.path(Constants.FL_SCHEMA_BASIC_QUORUM).isMissingNode()) {
            builder.basicQuorum(props.path(Constants.FL_SCHEMA_BASIC_QUORUM).asBoolean());
        }
        if (!props.path(Constants.FL_SCHEMA_NOT_FOUND_OK).isMissingNode()) {
            builder.notFoundOK(props.path(Constants.FL_SCHEMA_NOT_FOUND_OK).asBoolean());
        }

        builder.chashKeyFunction(
                OBJECT_MAPPER.treeToValue(props.path(Constants.FL_SCHEMA_CHASHFUN), NamedErlangFunction.class));
        builder.linkWalkFunction(
                OBJECT_MAPPER.treeToValue(props.path(Constants.FL_SCHEMA_LINKFUN), NamedErlangFunction.class));
        builder.search(props.path(Constants.FL_SCHEMA_SEARCH).asBoolean());

        return builder.build();
    }

    /**
     * Parse a http client chash_fun string into a {@link NamedErlangFunction}
     * 
     * @param funString
     *            a String of the format "mod:fun"
     * @return a {@link NamedErlangFunction} populated from
     *         <code>funString</code> or <code>null</code> if the string cannot
     *         be parsed.
     */
    static NamedErlangFunction convert(String funString) {
        if (funString == null) {
            return null;
        }
        String[] fun = funString.split(":");

        if (fun.length != 2) {
            return null;
        }

        return new NamedErlangFunction(fun[0], fun[1]);
    }

    /**
     * Turn a {@link BucketProperties} into a {@link RiakBucketInfo} for
     * persisting.
     * 
     * @param bucketProperties
     *            the {@link BucketProperties} to convert
     * @return a {@link RiakBucketInfo} populated from the
     *         {@link BucketProperties}
     * @throws IOException
     */
    static RiakBucketInfo convert(BucketProperties bucketProperties) throws IOException {
        String bucketSchemaJson = toJSON(bucketProperties);
        RiakBucketInfo rbi;
        try {
            rbi = new RiakBucketInfo(new JSONObject(bucketSchemaJson), null);
        } catch (JSONException e) {
            throw new IOException("Failed to create bucket schema JSON from JSON string: " + bucketSchemaJson, e);
        }

        return rbi;
    }

    /**
     * Converts a {@link BucketProperties} to a JSON string
     * @param bp
     * @return a String of JSON that is acceptable to {@link RiakBucketInfo}
     * @throws IOException
     * TODO: move this to a custom serializer?
     */
    private static String toJSON(BucketProperties bp) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        JsonGenerator jg = new JsonFactory().createJsonGenerator(out, JsonEncoding.UTF8);

        jg.writeStartObject();
        writeIfNotNull(jg, bp.getAllowSiblings(), Constants.FL_SCHEMA_ALLOW_MULT);
        writeIfNotNull(jg, bp.getNVal(), Constants.FL_SCHEMA_NVAL);
        writeIfNotNull(jg, bp.getLastWriteWins(), Constants.FL_SCHEMA_LAST_WRITE_WINS);
        writeIfNotNull(jg, bp.getBackend(), Constants.FL_SCHEMA_BACKEND);
        writeIfNotNull(jg, bp.getSmallVClock(), Constants.FL_SCHEMA_SMALL_VCLOCK);
        writeIfNotNull(jg, bp.getBigVClock(), Constants.FL_SCHEMA_BIG_VCLOCK);
        writeIfNotNull(jg, bp.getYoungVClock(), Constants.FL_SCHEMA_YOUNG_VCLOCK);
        writeIfNotNull(jg, bp.getOldVClock(), Constants.FL_SCHEMA_OLD_VCLOCK);
        writeIfNotNull(jg, bp.getR(), Constants.FL_SCHEMA_R);
        writeIfNotNull(jg, bp.getRW(), Constants.FL_SCHEMA_RW);
        writeIfNotNull(jg, bp.getW(), Constants.FL_SCHEMA_W);
        writeIfNotNull(jg, bp.getDW(), Constants.FL_SCHEMA_DW);
        writeIfNotNull(jg, bp.getPR(), Constants.FL_SCHEMA_PR);
        writeIfNotNull(jg, bp.getPW(), Constants.FL_SCHEMA_PW);
        writeIfNotNull(jg, bp.getBasicQuorum(), Constants.FL_SCHEMA_BASIC_QUORUM);
        writeIfNotNull(jg, bp.getNotFoundOK(), Constants.FL_SCHEMA_NOT_FOUND_OK);
        writeIfNotNull(jg, bp.getChashKeyFunction(), Constants.FL_SCHEMA_CHASHFUN);
        writeIfNotNull(jg, bp.getLinkWalkFunction(), Constants.FL_SCHEMA_LINKFUN);
        writeIfNotNull(jg, bp.getPostcommitHooks(), Constants.FL_SCHEMA_POSTCOMMIT);
        writeIfNotNull(jg, bp.getPrecommitHooks(), Constants.FL_SCHEMA_PRECOMMIT);
        writeIfNotNull(jg, bp.getSearch(), Constants.FL_SCHEMA_SEARCH);

        jg.writeEndObject();

        jg.flush();
        return CharsetUtils.asUTF8String(out.toByteArray());
    }

    // TODO move to a serializer?

    private static void writeIfNotNull(JsonGenerator jg, Boolean value, String fieldName) throws IOException {
        if (value != null) {
            jg.writeBooleanField(fieldName, value);
        }
    }

    private static void writeIfNotNull(JsonGenerator jg, String value, String fieldName) throws IOException {
        if (value != null) {
            jg.writeStringField(fieldName, value);
        }
    }

    private static void writeIfNotNull(JsonGenerator jg, NamedErlangFunction value, String fieldName)
            throws IOException {
        if (value != null) {
            jg.writeFieldName(fieldName);
            writeNamedFun(jg, value);
        }
    }

    private static void writeNamedFun(JsonGenerator jg, NamedFunction nf) throws IOException {
        jg.writeStartObject();
        if (nf instanceof NamedErlangFunction) {
            NamedErlangFunction nef = (NamedErlangFunction) nf;
            jg.writeStringField(Constants.FL_SCHEMA_FUN_MOD, nef.getMod());
            jg.writeStringField(Constants.FL_SCHEMA_FUN_FUN, nef.getFun());
        } else {
            NamedJSFunction njsf = (NamedJSFunction) nf;
            jg.writeStringField(Constants.FL_SCHEMA_FUN_NAME, njsf.getFunction());
        }
        jg.writeEndObject();
    }

    private static void writeIfNotNull(JsonGenerator jg, Quorum q, String fieldName) throws IOException {
        if (q != null) {
            if (q.isSymbolic()) {
                jg.writeStringField(fieldName, q.getName());
            } else {
                jg.writeNumberField(fieldName, q.getIntValue());
            }
        }
    }

    private static void writeIfNotNull(JsonGenerator jg, Integer value, String fieldName) throws IOException {
        if (value != null) {
            jg.writeNumberField(fieldName, value);
        }
    }

    private static void writeIfNotNull(JsonGenerator jg, Long value, String fieldName) throws IOException {
        if (value != null) {
            jg.writeNumberField(fieldName, value);
        }
    }

    private static void writeIfNotNull(JsonGenerator jg, Collection<? extends NamedFunction> value,
            String fieldName) throws IOException {
        if (value != null) {
            jg.writeArrayFieldStart(fieldName);

            for (NamedFunction nf : value) {
                writeNamedFun(jg, nf);
            }

            jg.writeEndArray();
        }
    }

    /**
     * Turn a {@link MapReduceResponse} into a {@link MapReduceResult} Creates
     * an anonymous inner class implementation of {@link MapReduceResult} and
     * uses Jackson's ObjectMapper to convert the response payload.
     * 
     * @param resp
     *            the {@link MapReduceResponse}
     * @return a {@link MapReduceResult} view of the results in
     *         <code>resp</code>
     * @throws MapReduceTimeoutException
     */
    static MapReduceResult convert(final MapReduceResponse resp) throws IOException, MapReduceTimeoutException {
        if (resp.isError()) {
            if (JSONErrorParser.isTimeoutException(resp.getBodyAsString())) {
                throw new MapReduceTimeoutException();
            } else {
                throw new IOException(resp.getBodyAsString());
            }
        }
        final MapReduceResult result = new MapReduceResult() {

            public String getResultRaw() {
                return resp.getBodyAsString();
            }

            public <T> Collection<T> getResult(Class<T> resultType) throws ConversionException {
                try {
                    return OBJECT_MAPPER.readValue(getResultRaw(),
                            OBJECT_MAPPER.getTypeFactory().constructCollectionType(Collection.class, resultType));
                } catch (IOException e) {
                    throw new ConversionException(e);
                }
            }
        };
        return result;
    }

    /**
     * Convert a {@link LinkWalkSpec} to a String for execution by the http.
     * {@link RiakClient}
     * 
     * @param linkWalkSpec
     *            the {@link LinkWalkSpec}
     * @return a String representation of <code>linkWalkSpec</code> useful to
     *         the http.{@link RiakClient}
     */
    static String convert(LinkWalkSpec linkWalkSpec) {
        RiakWalkSpec riakWalkSpec = new RiakWalkSpec();
        for (LinkWalkStep step : linkWalkSpec) {
            riakWalkSpec.addStep(step.getBucket(), step.getTag(), step.getKeep().toString());
        }
        return riakWalkSpec.toString();
    }

    /**
     * Converts a {@link WalkResponse} -> {@link WalkResult}
     * 
     * Creates an anonymous implementation of {@link WalkResult} that exposes an
     * {@link UnmodifiableIterator} view of the results.
     * 
     * @param walkResponse
     *            An http.{@link RiakClient} {@link WalkResponse}
     * @return a new {@link WalkResult}
     */
    static WalkResult convert(WalkResponse walkResponse) {
        final Collection<Collection<IRiakObject>> convertedSteps = new LinkedList<Collection<IRiakObject>>();

        for (List<com.basho.riak.client.http.RiakObject> step : walkResponse.getSteps()) {
            final LinkedList<IRiakObject> objects = new LinkedList<IRiakObject>();
            for (com.basho.riak.client.http.RiakObject o : step) {
                objects.add(convert(o));
            }
            convertedSteps.add(objects);
        }

        return new WalkResult() {
            public Iterator<Collection<IRiakObject>> iterator() {
                return new UnmodifiableIterator<Collection<IRiakObject>>(convertedSteps.iterator());
            }
        };
    }

    /**
     * Convert an {@link IndexResponse} to a List of Strings
     * 
     * @param response an {@link IndexResponse}
     * @return a List<String> or the empty list
     * @throws IOException
     *             if {@link IndexResponse} isn't a success.
     */
    static List<String> convert(IndexResponse response) throws IOException {
        if (response.isSuccess()) {
            return response.getKeys();
        } else {
            throw new IOException(response.getBodyAsString());
        }
    }

    /**
     * Convert a {@link FetchMeta} to a {@link RequestMeta}
     * 
     * @param fetchMeta
     *            the {@link FetchMeta} to convert
     * @return the {@link RequestMeta}
     */
    static RequestMeta convert(FetchMeta fetchMeta) {
        RequestMeta rm = new RequestMeta();

        if (fetchMeta.getR() != null) {
            if (fetchMeta.getR().isSymbolic()) {
                rm.setQueryParam(Constants.QP_R, fetchMeta.getR().getName());
            } else {
                rm.setQueryParam(Constants.QP_R, Integer.toString(fetchMeta.getR().getIntValue()));
            }

        }

        if (fetchMeta.getPr() != null) {
            if (fetchMeta.getPr().isSymbolic()) {
                rm.setQueryParam(Constants.QP_PR, fetchMeta.getPr().getName());
            } else {
                rm.setQueryParam(Constants.QP_PR, Integer.toString(fetchMeta.getPr().getIntValue()));
            }
        }

        if (fetchMeta.getNotFoundOK() != null) {
            rm.setQueryParam(Constants.QP_NOT_FOUND_OK, fetchMeta.getNotFoundOK().toString());
        }

        if (fetchMeta.getBasicQuorum() != null) {
            rm.setQueryParam(Constants.QP_BASIC_QUORUM, fetchMeta.getBasicQuorum().toString());
        }

        if (fetchMeta.getIfModifiedSince() != null) {
            rm.setIfModifiedSince(fetchMeta.getIfModifiedSince());
        }

        return rm;
    }

    static RequestMeta convert(DeleteMeta deleteMeta) {
        if (deleteMeta == null) {
            return null;
        }
        RequestMeta rm = convert(
                new FetchMeta(deleteMeta.getR(), deleteMeta.getPr(), null, null, null, null, null, null));

        if (deleteMeta.hasW()) {
            if (deleteMeta.getW().isSymbolic())
                rm.setQueryParam(Constants.QP_W, deleteMeta.getW().getName());
            else {
                rm.setQueryParam(Constants.QP_W, String.valueOf(deleteMeta.getW().getIntValue()));
            }
        }

        if (deleteMeta.hasPw()) {
            if (deleteMeta.getPw().isSymbolic()) {
                rm.setQueryParam(Constants.QP_PW, deleteMeta.getPw().getName());
            } else {
                rm.setQueryParam(Constants.QP_PW, String.valueOf(deleteMeta.getPw().getIntValue()));
            }
        }

        if (deleteMeta.hasDw()) {
            if (deleteMeta.getDw().isSymbolic()) {
                rm.setQueryParam(Constants.QP_DW, deleteMeta.getDw().getName());
            } else {
                rm.setQueryParam(Constants.QP_DW, String.valueOf(deleteMeta.getDw().getIntValue()));
            }
        }

        if (deleteMeta.hasRw()) {
            if (deleteMeta.getRw().isSymbolic()) {
                rm.setQueryParam(Constants.QP_RW, deleteMeta.getRw().getName());
            } else {
                rm.setQueryParam(Constants.QP_RW, String.valueOf(deleteMeta.getRw().getIntValue()));
            }
        }

        if (deleteMeta.hasVclock()) {
            rm.setHeader(Constants.HDR_VCLOCK, deleteMeta.getVclock().asString());
        }

        return rm;
    }
}