Package voldemort.restclient

Source Code of voldemort.restclient.R2Store

/*
* Copyright 2008-2013 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.restclient;

import static org.jboss.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.PRECONDITION_FAILED;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.REQUEST_TIMEOUT;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.mail.MessagingException;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

import voldemort.VoldemortException;
import voldemort.common.VoldemortOpCode;
import voldemort.rest.RestMessageHeaders;
import voldemort.rest.RestUtils;
import voldemort.rest.VectorClockWrapper;
import voldemort.store.AbstractStore;
import voldemort.store.InsufficientOperationalNodesException;
import voldemort.utils.ByteArray;
import voldemort.versioning.ObsoleteVersionException;
import voldemort.versioning.VectorClock;
import voldemort.versioning.Version;
import voldemort.versioning.Versioned;

import com.linkedin.common.callback.FutureCallback;
import com.linkedin.common.util.None;
import com.linkedin.data.ByteString;
import com.linkedin.r2.message.rest.RestException;
import com.linkedin.r2.message.rest.RestRequest;
import com.linkedin.r2.message.rest.RestRequestBuilder;
import com.linkedin.r2.message.rest.RestResponse;
import com.linkedin.r2.transport.common.Client;
import com.linkedin.r2.transport.common.bridge.client.TransportClient;
import com.linkedin.r2.transport.common.bridge.client.TransportClientAdapter;

/**
* A class that implements the Store interface for interacting with the RESTful
* Coordinator. It leverages the R2 library for doing this.
*
*/
public class R2Store extends AbstractStore<ByteArray, byte[], byte[]> {

    private static final String GET = "GET";
    private static final String POST = "POST";
    private static final String DELETE = "DELETE";
    public static final String CONTENT_TYPE = "Content-Type";
    public static final String CONTENT_LENGTH = "Content-Length";
    public static final String CUSTOM_RESOLVING_STRATEGY = "custom";
    public static final String DEFAULT_RESOLVING_STRATEGY = "timestamp";
    public static final String SCHEMATA_STORE_NAME = "schemata";
    private static final String MULTIPART_CONTENT_TYPE = "multipart/binary";
    private static final String FETCH_SCHEMA_TIMEOUT_MS = "50000";
    private static final int INVALID_ZONE_ID = -1;
    private final Logger logger = Logger.getLogger(R2Store.class);

    private Client client = null;
    private String restBootstrapURL;
    private ObjectMapper mapper;
    private RESTClientConfig config;
    private String routingTypeCode = null;
    private int zoneId;
    private AtomicBoolean isActive;

    public R2Store(String storeName,
                   String restBootstrapURL,
                   final TransportClient transportClient,
                   final RESTClientConfig config) {
        this(storeName, restBootstrapURL, null, transportClient, null, config, INVALID_ZONE_ID);
    }

    public R2Store(String storeName,
                   String restBootstrapURL,
                   String routingCodeStr,
                   final TransportClient transportClient,
                   final RESTClientConfig config,
                   int zoneId) {
        this(storeName, restBootstrapURL, routingCodeStr, transportClient, null, config, zoneId);
    }

    public R2Store(String storeName,
                   String restBootstrapURL,
                   Client d2Client,
                   RESTClientConfig config) {
        this(storeName, restBootstrapURL, null, null, d2Client, config, INVALID_ZONE_ID);
    }

    public R2Store(String storeName,
                   String restBootstrapURL,
                   String routingCodeStr,
                   final TransportClient transportClient,
                   Client d2Client,
                   RESTClientConfig config,
                   int zoneId) {
        super(storeName);
        if(transportClient == null) {
            this.client = d2Client;
        } else {
            this.client = new TransportClientAdapter(transportClient);
        }
        this.config = config;
        this.restBootstrapURL = restBootstrapURL;
        this.mapper = new ObjectMapper();
        this.routingTypeCode = routingCodeStr;
        this.zoneId = zoneId;
        this.isActive = new AtomicBoolean(true);
    }

    @Override
    public void close() throws VoldemortException {
        final FutureCallback<None> clientShutdownCallback = new FutureCallback<None>();
        if(!this.isActive.get()) {
            return;
        }

        if(client != null) {
            client.shutdown(clientShutdownCallback);
            try {
                clientShutdownCallback.get();
                this.isActive.compareAndSet(true, false);
            } catch(InterruptedException e) {
                logger.error("Interrupted while shutting down the HttpClientFactory: "
                                     + e.getMessage(),
                             e);
            } catch(ExecutionException e) {
                logger.error("Execution exception occurred while shutting down the HttpClientFactory: "
                                     + e.getMessage(),
                             e);
            }
        }
    }

    @Override
    public boolean delete(ByteArray key, Version version) throws VoldemortException {
        try {
            // Create the REST request with this byte array
            String base64Key = RestUtils.encodeVoldemortKey(key.get());
            RestRequestBuilder rb = new RestRequestBuilder(new URI(this.restBootstrapURL + "/"
                                                                   + getName() + "/" + base64Key));
            // Create a HTTP POST request
            rb.setMethod(DELETE);
            rb.setHeader(CONTENT_LENGTH, "0");
            String timeoutStr = Long.toString(this.config.getTimeoutConfig()
                                                         .getOperationTimeout(VoldemortOpCode.DELETE_OP_CODE));
            rb.setHeader(RestMessageHeaders.X_VOLD_REQUEST_TIMEOUT_MS, timeoutStr);
            rb.setHeader(RestMessageHeaders.X_VOLD_REQUEST_ORIGIN_TIME_MS,
                         String.valueOf(System.currentTimeMillis()));
            if(this.routingTypeCode != null) {
                rb.setHeader(RestMessageHeaders.X_VOLD_ROUTING_TYPE_CODE, this.routingTypeCode);
            }
            if(this.zoneId != INVALID_ZONE_ID) {
                rb.setHeader(RestMessageHeaders.X_VOLD_ZONE_ID, String.valueOf(this.zoneId));
            }
            // Serialize the Vector clock
            VectorClock vc = (VectorClock) version;

            // If the given Vector clock is empty, we'll let the receiver of
            // this request fetch the existing vector clock and increment
            // before
            // doing the put.
            if(vc != null && vc.getEntries().size() != 0) {
                String serializedVC = null;
                if(!vc.getEntries().isEmpty()) {
                    serializedVC = RestUtils.getSerializedVectorClock(vc);
                }

                if(serializedVC != null && serializedVC.length() > 0) {
                    rb.setHeader(RestMessageHeaders.X_VOLD_VECTOR_CLOCK, serializedVC);
                }
            }

            RestRequest request = rb.build();
            Future<RestResponse> f = client.restRequest(request);

            // This will block
            RestResponse response = f.get();
            final ByteString entity = response.getEntity();
            if(entity == null) {
                if(logger.isDebugEnabled()) {
                    logger.debug("Empty response !");
                }
            }

        } catch(ExecutionException e) {
            if(e.getCause() instanceof RestException) {
                RestException exception = (RestException) e.getCause();
                if(logger.isDebugEnabled()) {
                    logger.debug("REST EXCEPTION STATUS : " + exception.getResponse().getStatus());
                }
                if(exception.getResponse().getStatus() == NOT_FOUND.getCode()) {
                    return false;
                }
            } else {
                throw new VoldemortException("Unknown HTTP request execution exception: "
                                             + e.getMessage(), e);
            }
        } catch(InterruptedException e) {
            if(logger.isDebugEnabled()) {
                logger.debug("Operation interrupted : " + e.getMessage());
            }
            throw new VoldemortException("Operation Interrupted: " + e.getMessage(), e);
        } catch(URISyntaxException e) {
            throw new VoldemortException("Illegal HTTP URL" + e.getMessage(), e);
        }
        return true;
    }

    private RestResponse fetchGetResponse(RestRequestBuilder requestBuilder, String timeoutStr)
            throws InterruptedException, ExecutionException {
        requestBuilder.setMethod(GET);
        requestBuilder.setHeader(RestMessageHeaders.X_VOLD_REQUEST_TIMEOUT_MS, timeoutStr);
        requestBuilder.setHeader(RestMessageHeaders.X_VOLD_REQUEST_ORIGIN_TIME_MS,
                                 String.valueOf(System.currentTimeMillis()));
        if(this.routingTypeCode != null) {
            requestBuilder.setHeader(RestMessageHeaders.X_VOLD_ROUTING_TYPE_CODE,
                                     this.routingTypeCode);
        }
        if(this.zoneId != INVALID_ZONE_ID) {
            requestBuilder.setHeader(RestMessageHeaders.X_VOLD_ZONE_ID, String.valueOf(this.zoneId));
        }
        RestRequest request = requestBuilder.build();
        Future<RestResponse> f = client.restRequest(request);
        // This will block
        return f.get();
    }

    @Override
    public List<Versioned<byte[]>> get(ByteArray key, byte[] transforms) throws VoldemortException {
        List<Versioned<byte[]>> resultList = new ArrayList<Versioned<byte[]>>();
        String base64Key = RestUtils.encodeVoldemortKey(key.get());
        RestRequestBuilder rb = null;
        try {
            rb = new RestRequestBuilder(new URI(this.restBootstrapURL + "/" + getName() + "/"
                                                + base64Key));
            String timeoutStr = Long.toString(this.config.getTimeoutConfig()
                                                         .getOperationTimeout(VoldemortOpCode.GET_OP_CODE));
            rb.setHeader("Accept", MULTIPART_CONTENT_TYPE);

            RestResponse response = fetchGetResponse(rb, timeoutStr);
            final ByteString entity = response.getEntity();
            if(entity != null) {
                resultList = parseGetResponse(entity);
            } else {
                if(logger.isDebugEnabled()) {
                    logger.debug("Did not get any response!");
                }
            }
        } catch(ExecutionException e) {
            if(e.getCause() instanceof RestException) {
                RestException exception = (RestException) e.getCause();
                if(logger.isDebugEnabled()) {
                    logger.debug("REST EXCEPTION STATUS : " + exception.getResponse().getStatus());
                }

            } else {
                throw new VoldemortException("Unknown HTTP request execution exception: "
                                             + e.getMessage(), e);
            }
        } catch(InterruptedException e) {
            if(logger.isDebugEnabled()) {
                logger.debug("Operation interrupted : " + e.getMessage(), e);
            }
            throw new VoldemortException("Operation interrupted exception: " + e.getMessage(), e);
        } catch(URISyntaxException e) {
            throw new VoldemortException("Illegal HTTP URL" + e.getMessage(), e);
        }

        return resultList;
    }

    private List<Versioned<byte[]>> parseGetResponse(ByteString entity) {
        List<Versioned<byte[]>> results = new ArrayList<Versioned<byte[]>>();

        try {
            // Build the multipart object
            byte[] bytes = new byte[entity.length()];
            entity.copyBytes(bytes, 0);

            ByteArrayDataSource ds = new ByteArrayDataSource(bytes, "multipart/mixed");
            MimeMultipart mp = new MimeMultipart(ds);
            for(int i = 0; i < mp.getCount(); i++) {
                MimeBodyPart part = (MimeBodyPart) mp.getBodyPart(i);
                String serializedVC = part.getHeader(RestMessageHeaders.X_VOLD_VECTOR_CLOCK)[0];
                int contentLength = Integer.parseInt(part.getHeader(RestMessageHeaders.CONTENT_LENGTH)[0]);

                if(logger.isDebugEnabled()) {
                    logger.debug("Received VC : " + serializedVC);
                }
                VectorClockWrapper vcWrapper = mapper.readValue(serializedVC,
                                                                VectorClockWrapper.class);

                InputStream input = part.getInputStream();
                byte[] bodyPartBytes = new byte[contentLength];
                input.read(bodyPartBytes);

                VectorClock clock = new VectorClock(vcWrapper.getVersions(),
                                                    vcWrapper.getTimestamp());
                results.add(new Versioned<byte[]>(bodyPartBytes, clock));

            }

        } catch(MessagingException e) {
            throw new VoldemortException("Messaging exception while trying to parse GET response "
                                         + e.getMessage(), e);
        } catch(JsonParseException e) {
            throw new VoldemortException("JSON parsing exception while trying to parse GET response "
                                                 + e.getMessage(),
                                         e);
        } catch(JsonMappingException e) {
            throw new VoldemortException("JSON mapping exception while trying to parse GET response "
                                                 + e.getMessage(),
                                         e);
        } catch(IOException e) {
            throw new VoldemortException("IO exception while trying to parse GET response "
                                         + e.getMessage(), e);
        }
        return results;

    }

    public String getSerializerInfoXml() throws VoldemortException {
        RestRequestBuilder rb = null;
        try {
            String base64Key = new String(Base64.encodeBase64(getName().getBytes("UTF-8")));
            rb = new RestRequestBuilder(new URI(this.restBootstrapURL + "/" + SCHEMATA_STORE_NAME
                                                + "/" + base64Key));
            rb.setHeader("Accept", "binary");
            rb.setHeader(RestMessageHeaders.X_VOLD_REQUEST_ORIGIN_TIME_MS,
                         String.valueOf(System.currentTimeMillis()));
            if(this.routingTypeCode != null) {
                rb.setHeader(RestMessageHeaders.X_VOLD_ROUTING_TYPE_CODE, this.routingTypeCode);
            }
            if(this.zoneId != INVALID_ZONE_ID) {
                rb.setHeader(RestMessageHeaders.X_VOLD_ZONE_ID, String.valueOf(this.zoneId));
            }

            RestResponse response = fetchGetResponse(rb, FETCH_SCHEMA_TIMEOUT_MS);
            return response.getEntity().asString("UTF-8");
        } catch(ExecutionException e) {
            if(e.getCause() instanceof RestException) {
                RestException exception = (RestException) e.getCause();
                if(logger.isDebugEnabled()) {
                    logger.debug("REST EXCEPTION STATUS : " + exception.getResponse().getStatus());
                }
            } else {
                throw new VoldemortException("Unknown HTTP request execution exception: "
                                             + e.getMessage(), e);
            }
        } catch(InterruptedException e) {
            if(logger.isDebugEnabled()) {
                logger.debug("Operation interrupted : " + e.getMessage(), e);
            }
            throw new VoldemortException("Operation interrupted exception: " + e.getMessage(), e);
        } catch(URISyntaxException e) {
            throw new VoldemortException("Illegal HTTP URL" + e.getMessage(), e);
        } catch(UnsupportedEncodingException e) {
            throw new VoldemortException("Unsupported Encoding exception while encoding the key"
                                         + e.getMessage(), e);
        }
        return null;
    }

    @Override
    public void put(ByteArray key, Versioned<byte[]> value, byte[] transform)
            throws VoldemortException {
        RestResponse response = null;

        try {
            byte[] payload = value.getValue();

            // Create the REST request with this byte array
            String base64Key = RestUtils.encodeVoldemortKey(key.get());
            RestRequestBuilder rb = new RestRequestBuilder(new URI(this.restBootstrapURL + "/"
                                                                   + getName() + "/" + base64Key));

            // Create a HTTP POST request
            rb.setMethod(POST);
            rb.setEntity(payload);
            rb.setHeader(CONTENT_TYPE, "binary");
            rb.setHeader(CONTENT_LENGTH, "" + payload.length);
            String timeoutStr = Long.toString(this.config.getTimeoutConfig()
                                                         .getOperationTimeout(VoldemortOpCode.PUT_OP_CODE));
            rb.setHeader(RestMessageHeaders.X_VOLD_REQUEST_TIMEOUT_MS, timeoutStr);
            rb.setHeader(RestMessageHeaders.X_VOLD_REQUEST_ORIGIN_TIME_MS,
                         String.valueOf(System.currentTimeMillis()));
            if(this.routingTypeCode != null) {
                rb.setHeader(RestMessageHeaders.X_VOLD_ROUTING_TYPE_CODE, this.routingTypeCode);
            }
            if(this.zoneId != INVALID_ZONE_ID) {
                rb.setHeader(RestMessageHeaders.X_VOLD_ZONE_ID, String.valueOf(this.zoneId));
            }

            // Serialize the Vector clock
            VectorClock vc = (VectorClock) value.getVersion();

            // If the given Vector clock is empty, we'll let the receiver of
            // this request fetch the existing vector clock and increment before
            // doing the put.
            if(vc != null) {
                String serializedVC = null;
                if(!vc.getEntries().isEmpty()) {
                    serializedVC = RestUtils.getSerializedVectorClock(vc);
                }

                if(serializedVC != null && serializedVC.length() > 0) {
                    rb.setHeader(RestMessageHeaders.X_VOLD_VECTOR_CLOCK, serializedVC);
                }
            }

            RestRequest request = rb.build();
            Future<RestResponse> f = client.restRequest(request);

            // This will block
            response = f.get();

            String serializedUpdatedVC = response.getHeader(RestMessageHeaders.X_VOLD_VECTOR_CLOCK);
            if(serializedUpdatedVC == null || serializedUpdatedVC.length() == 0) {
                if(logger.isDebugEnabled()) {
                    logger.debug("Received empty vector clock in the response");
                }
            } else {
                VectorClock updatedVC = RestUtils.deserializeVectorClock(serializedUpdatedVC);
                VectorClock originalVC = (VectorClock) value.getVersion();
                originalVC.copyFromVectorClock(updatedVC);
            }

            final ByteString entity = response.getEntity();
            if(entity == null) {
                if(logger.isDebugEnabled()) {
                    logger.debug("Empty response !");
                }
            }
        } catch(ExecutionException e) {
            if(e.getCause() instanceof RestException) {
                RestException exception = (RestException) e.getCause();
                if(logger.isDebugEnabled()) {
                    logger.debug("REST EXCEPTION STATUS : " + exception.getResponse().getStatus());
                }

                int httpErrorStatus = exception.getResponse().getStatus();
                if(httpErrorStatus == BAD_REQUEST.getCode()) {
                    throw new VoldemortException("Bad request: " + e.getMessage(), e);
                } else if(httpErrorStatus == PRECONDITION_FAILED.getCode()) {
                    throw new ObsoleteVersionException(e.getMessage());
                } else if(httpErrorStatus == REQUEST_TIMEOUT.getCode()
                          || httpErrorStatus == INTERNAL_SERVER_ERROR.getCode()) {
                    throw new InsufficientOperationalNodesException(e.getMessage());
                }
            }
            throw new VoldemortException("Unknown HTTP request execution exception: "
                                         + e.getMessage(), e);

        } catch(InterruptedException e) {
            if(logger.isDebugEnabled()) {
                logger.debug("Operation interrupted : " + e.getMessage());
            }
            throw new VoldemortException("Unknown Voldemort exception: " + e.getMessage());
        } catch(URISyntaxException e) {
            throw new VoldemortException("Illegal HTTP URL" + e.getMessage());
        }
    }

    @Override
    public Map<ByteArray, List<Versioned<byte[]>>> getAll(Iterable<ByteArray> keys,
                                                          Map<ByteArray, byte[]> transforms)
            throws VoldemortException {

        Map<ByteArray, List<Versioned<byte[]>>> resultMap = new HashMap<ByteArray, List<Versioned<byte[]>>>();
        int numberOfKeys = 0;

        try {
            Iterator<ByteArray> it = keys.iterator();
            StringBuilder keyArgs = null;

            while(it.hasNext()) {
                ByteArray key = it.next();
                String base64Key = RestUtils.encodeVoldemortKey(key.get());
                if(keyArgs == null) {
                    keyArgs = new StringBuilder();
                    keyArgs.append(base64Key);
                } else {
                    keyArgs.append("," + base64Key);
                }
                numberOfKeys++;
            }

            // Rerouting getall() requests with single key to get(). This is a
            // temporary fix to handle the NPE when getAll requests are made
            // with single key.
            // TODO a common way to handle getAll with any number of keys
            if(numberOfKeys == 1) {
                List<Versioned<byte[]>> resultList = new ArrayList<Versioned<byte[]>>();
                it = keys.iterator();
                ByteArray key = it.next();
                byte[] singleKeyTransforms = null;
                if(transforms != null) {
                    singleKeyTransforms = transforms.get(key);
                }
                resultList = this.get(key, singleKeyTransforms);
                resultMap.put(key, resultList);
            } else {

                RestRequestBuilder rb = new RestRequestBuilder(new URI(this.restBootstrapURL + "/"
                                                                       + getName() + "/"
                                                                       + keyArgs.toString()));

                rb.setMethod(GET);
                rb.setHeader("Accept", MULTIPART_CONTENT_TYPE);
                String timeoutStr = Long.toString(this.config.getTimeoutConfig()
                                                             .getOperationTimeout(VoldemortOpCode.GET_ALL_OP_CODE));
                rb.setHeader(RestMessageHeaders.X_VOLD_REQUEST_TIMEOUT_MS, timeoutStr);
                rb.setHeader(RestMessageHeaders.X_VOLD_REQUEST_ORIGIN_TIME_MS,
                             String.valueOf(System.currentTimeMillis()));
                if(this.routingTypeCode != null) {
                    rb.setHeader(RestMessageHeaders.X_VOLD_ROUTING_TYPE_CODE, this.routingTypeCode);
                }
                if(this.zoneId != INVALID_ZONE_ID) {
                    rb.setHeader(RestMessageHeaders.X_VOLD_ZONE_ID, String.valueOf(this.zoneId));
                }

                RestRequest request = rb.build();
                Future<RestResponse> f = client.restRequest(request);

                // This will block
                RestResponse response = f.get();

                // Parse the response
                final ByteString entity = response.getEntity();

                String contentType = response.getHeader(CONTENT_TYPE);
                if(entity != null) {
                    if(contentType.equalsIgnoreCase(MULTIPART_CONTENT_TYPE)) {

                        resultMap = parseGetAllResults(entity);
                    } else {
                        if(logger.isDebugEnabled()) {
                            logger.debug("Did not receive a multipart response");
                        }
                    }

                } else {
                    if(logger.isDebugEnabled()) {
                        logger.debug("Did not get any response!");
                    }
                }
            }
        } catch(ExecutionException e) {
            if(e.getCause() instanceof RestException) {
                RestException exception = (RestException) e.getCause();
                if(logger.isDebugEnabled()) {
                    logger.debug("REST EXCEPTION STATUS : " + exception.getResponse().getStatus());
                }
            } else {
                throw new VoldemortException("Unknown HTTP request execution exception: "
                                             + e.getMessage(), e);
            }
        } catch(InterruptedException e) {
            if(logger.isDebugEnabled()) {
                logger.debug("Operation interrupted : " + e.getMessage(), e);
            }
            throw new VoldemortException("Operation interrupted exception: " + e.getMessage(), e);
        } catch(URISyntaxException e) {
            throw new VoldemortException("Illegal HTTP URL" + e.getMessage(), e);
        }

        return resultMap;
    }

    private Map<ByteArray, List<Versioned<byte[]>>> parseGetAllResults(ByteString entity) {
        Map<ByteArray, List<Versioned<byte[]>>> results = new HashMap<ByteArray, List<Versioned<byte[]>>>();

        try {
            // Build the multipart object
            byte[] bytes = new byte[entity.length()];
            entity.copyBytes(bytes, 0);

            // Get the outer multipart object
            ByteArrayDataSource ds = new ByteArrayDataSource(bytes, "multipart/mixed");
            MimeMultipart mp = new MimeMultipart(ds);
            for(int i = 0; i < mp.getCount(); i++) {

                // Get an individual part. This contains all the versioned
                // values for a particular key referenced by content-location
                MimeBodyPart part = (MimeBodyPart) mp.getBodyPart(i);

                // Get the key
                String contentLocation = part.getHeader("Content-Location")[0];
                String base64Key = contentLocation.split("/")[2];
                ByteArray key = new ByteArray(RestUtils.decodeVoldemortKey(base64Key));

                if(logger.isDebugEnabled()) {
                    logger.debug("Content-Location : " + contentLocation);
                    logger.debug("Base 64 key : " + base64Key);
                }

                // Create an array list for holding all the (versioned values)
                List<Versioned<byte[]>> valueResultList = new ArrayList<Versioned<byte[]>>();

                /*
                 * Get the nested Multi-part object. This contains one part for each unique versioned value.
                 *
                 * GetContent method can corrupt the embedded data, for example 0x8c be converted to 0xc2, 0x8c,
                 * hence use getInputStream.
                 *
                 * This thread tracks this question
                 * http://stackoverflow.com/questions/23023583/mimebodypart-getcontent-corrupts-binary-data
                 *
                 * getInputStream() : Return a decoded input stream for this Message's "content.
                 *
                 * getRawInputStream() : Return an InputStream to the raw data with any Content-Transfer-Encoding
                 * intact. This method is useful if the "Content-Transfer-Encoding" header is incorrect or corrupt,
                 * which would prevent the getInputStream method from returning the correct data. In such a case
                 * the application may use this method and attempt to decode the raw data itself.
                 *
                 */

                ByteArrayDataSource nestedDS = new ByteArrayDataSource(part.getInputStream(),
                                                                       "multipart/mixed");
                MimeMultipart valueParts = new MimeMultipart(nestedDS);

                for(int valueId = 0; valueId < valueParts.getCount(); valueId++) {

                    MimeBodyPart valuePart = (MimeBodyPart) valueParts.getBodyPart(valueId);
                    String serializedVC = valuePart.getHeader(RestMessageHeaders.X_VOLD_VECTOR_CLOCK)[0];
                    int contentLength = Integer.parseInt(valuePart.getHeader(RestMessageHeaders.CONTENT_LENGTH)[0]);

                    if(logger.isDebugEnabled()) {
                        logger.debug("Received serialized Vector Clock : " + serializedVC);
                    }

                    VectorClockWrapper vcWrapper = mapper.readValue(serializedVC,
                                                                    VectorClockWrapper.class);

                    // get the value bytes
                    InputStream input = valuePart.getInputStream();
                    byte[] bodyPartBytes = new byte[contentLength];
                    input.read(bodyPartBytes);

                    VectorClock clock = new VectorClock(vcWrapper.getVersions(),
                                                        vcWrapper.getTimestamp());
                    valueResultList.add(new Versioned<byte[]>(bodyPartBytes, clock));

                }
                results.put(key, valueResultList);
            }

        } catch(MessagingException e) {
            throw new VoldemortException("Messaging exception while trying to parse GET response "
                                         + e.getMessage(), e);
        } catch(JsonParseException e) {
            throw new VoldemortException("JSON parsing exception while trying to parse GET response "
                                                 + e.getMessage(),
                                         e);
        } catch(JsonMappingException e) {
            throw new VoldemortException("JSON mapping exception while trying to parse GET response "
                                                 + e.getMessage(),
                                         e);
        } catch(IOException e) {
            throw new VoldemortException("IO exception while trying to parse GET response "
                                         + e.getMessage(), e);
        }
        return results;

    }

    @Override
    public List<Version> getVersions(ByteArray key) {
        List<Version> resultList = new ArrayList<Version>();
        String base64Key = RestUtils.encodeVoldemortKey(key.get());
        RestRequestBuilder rb = null;
        try {
            rb = new RestRequestBuilder(new URI(this.restBootstrapURL + "/" + getName() + "/"
                                                + base64Key));
            String timeoutStr = Long.toString(this.config.getTimeoutConfig()
                                                         .getOperationTimeout(VoldemortOpCode.GET_VERSION_OP_CODE));

            rb.setHeader(RestMessageHeaders.X_VOLD_GET_VERSION, "true");

            RestResponse response = fetchGetResponse(rb, timeoutStr);
            final ByteString entity = response.getEntity();
            if(entity != null) {
                resultList = parseGetVersionResponse(entity);
            } else {
                if(logger.isDebugEnabled()) {
                    logger.debug("Did not get any response!");
                }
            }
        } catch(ExecutionException e) {
            if(e.getCause() instanceof RestException) {
                RestException exception = (RestException) e.getCause();
                if(logger.isDebugEnabled()) {
                    logger.debug("REST EXCEPTION STATUS : " + exception.getResponse().getStatus());
                }

            } else {
                throw new VoldemortException("Unknown HTTP request execution exception: "
                                             + e.getMessage(), e);
            }
        } catch(InterruptedException e) {
            if(logger.isDebugEnabled()) {
                logger.debug("Operation interrupted : " + e.getMessage(), e);
            }
            throw new VoldemortException("Operation interrupted exception: " + e.getMessage(), e);
        } catch(URISyntaxException e) {
            throw new VoldemortException("Illegal HTTP URL" + e.getMessage(), e);
        }

        return resultList;
    }

    private List<Version> parseGetVersionResponse(ByteString entity) {
        byte[] bytes = new byte[entity.length()];
        entity.copyBytes(bytes, 0);
        String vectorClockListStr = new String(bytes);
        List<Version> vectorClockList = RestUtils.deserializeVectorClocks(vectorClockListStr);
        return vectorClockList;
    }

}
TOP

Related Classes of voldemort.restclient.R2Store

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.