public static void checkArgument(boolean condition, String msg) {
    if (!condition)
        throw new IllegalArgumentException(msg);
} public static Gson gson() {
    return builder.create();
} public static Gson gsonForObject() {
    return builder.serializeNulls().create();
} public static String jsonFromMap(Map<String, String> map) {
    try {
        mapper.getFactory()
            .configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
        return mapper.writeValueAsString(map);
    } catch (JsonProcessingException e) {
        throw new IllegalArgumentException(e);
    }
} public static final String ACCOUNT_HEADER = "X-oio-container-meta-sys-account"; public static final String ACTION_MODE_HEADER = "X-oio-action-mode"; public static final String CONTAINER_DEL_PROP = "%s/v3.0/%s/container/del_properties?acct=%s&ref=%s"; public static final String CONTAINER_GET_PROP = "%s/v3.0/%s/container/get_properties?acct=%s&ref=%s"; public static final String CONTAINER_SET_PROP = "%s/v3.0/%s/container/set_properties?acct=%s&ref=%s"; public static final String CONTAINER_SYS_NAME_HEADER = "X-oio-container-meta-sys-name"; public static final String CONTENT_META_CHUNK_METHOD_HEADER = "X-oio-content-meta-chunk-method"; public static final String CONTENT_META_CTIME_HEADER = "X-oio-content-meta-ctime"; public static final String CONTENT_META_HASH_HEADER = "X-oio-content-meta-hash"; public static final String CONTENT_META_HASH_METHOD_HEADER = "X-oio-content-meta-hash-method"; public static final String CONTENT_META_ID_HEADER = "X-oio-content-meta-id"; public static final String CONTENT_META_LENGTH_HEADER = "X-oio-content-meta-length"; public static final String CONTENT_META_MIME_TYPE_HEADER = "X-oio-content-meta-mime-type"; public static final String CONTENT_META_POLICY_HEADER = "X-oio-content-meta-policy"; public static final String CONTENT_META_VERSION_HEADER = "X-oio-content-meta-version"; public static final String CREATE_CONTAINER_FORMAT = "%s/v3.0/%s/container/create?acct=%s&ref=%s"; public static final String CS_GETSRV_FORMAT = "%s/v3.0/%s/conscience/list?type=%s"; public static final String CS_NSINFO_FORMAT = "%s/v3.0/%s/conscience/info"; public static final String DELETE_CONTAINER_FORMAT = "%s/v3.0/%s/container/destroy?acct=%s&ref=%s"; public static final String DELETE_OBJECT_FORMAT = "%s/v3.0/%s/content/delete?acct=%s&ref=%s&path=%s"; public static final String DELETE_MARKER_PARAM = "delete_marker"; public static final String DELIMITER_PARAM = "delimiter"; public static final String DIR_LINK_SRV_FORMAT = "%s/v3.0/%s/reference/link?acct=%s&ref=%s&type=%s"; public static final String DIR_LIST_SRV_FORMAT = "%s/v3.0/%s/reference/show?acct=%s&ref=%s&type=%s"; public static final String DIR_REF_CREATE_FORMAT = "%s/v3.0/%s/reference/create?acct=%s&ref=%s"; public static final String DIR_REF_DELETE_FORMAT = "%s/v3.0/%s/reference/destroy?acct=%s&ref=%s"; public static final String DIR_REF_SHOW_FORMAT = "%s/v3.0/%s/reference/show?acct=%s&ref=%s"; public static final String DIR_UNLINK_SRV_FORMAT = "%s/v3.0/%s/reference/unlink?acct=%s&ref=%s&type=%s"; public static final String FLUSH_PARAM = "flush"; public static final String GET_BEANS_FORMAT = "%s/v3.0/%s/content/prepare?acct=%s&ref=%s&path=%s"; public static final String GET_CONTAINER_INFO_FORMAT = "%s/v3.0/%s/container/show?acct=%s&ref=%s"; public static final String GET_OBJECT_FORMAT = "%s/v3.0/%s/content/show?acct=%s&ref=%s&path=%s"; public static final String INVALID_OBJECT_INFO_MSG = "Invalid object info"; public static final String INVALID_OPTIONS_MSG = "Invalid options"; public static final String INVALID_URL_MSG = "Invalid url"; public static final String LIST_MARKER_HEADER = "X-oio-list-marker"; public static final String LIST_OBJECTS_FORMAT = "%s/v3.0/%s/container/list?acct=%s&ref=%s"; public static final String LIST_TRUNCATED_HEADER = "X-oio-list-truncated"; public static final String M2_CTIME_HEADER = "X-oio-container-meta-sys-m2-ctime"; public static final String M2_INIT_HEADER = "X-oio-container-meta-sys-m2-init"; public static final String M2_USAGE_HEADER = "X-oio-container-meta-sys-m2-usage"; public static final String M2_VERSION_HEADER = "X-oio-container-meta-sys-m2-version"; public static final String MARKER_PARAM = "marker"; public static final String MAX_PARAM = "max"; public static final String NS_HEADER = "X-oio-container-meta-sys-ns"; public static final String OBJECT_DEL_PROP = "%s/v3.0/%s/content/del_properties?acct=%s&ref=%s&path=%s"; public static final String OBJECT_GET_PROP = "%s/v3.0/%s/content/get_properties?acct=%s&ref=%s&path=%s"; public static final String OBJECT_SET_PROP = "%s/v3.0/%s/content/set_properties?acct=%s&ref=%s&path=%s"; public static final String OIO_ACTION_MODE_HEADER = "X-oio-action-mode"; public static final Charset OIO_CHARSET = Charset.forName("UTF-8"); public static final String PREFIX_PARAM = "prefix"; public static final String PUT_OBJECT_FORMAT = "%s/v3.0/%s/content/create?acct=%s&ref=%s&path=%s"; public static final String SCHEMA_VERSION_HEADER = "X-oio-container-meta-x-schema-version"; public static final String SIMULATE_VERSIONING_HEADER = "X-oio-simulate-versioning"; public static final String TYPE_HEADER = "X-oio-container-meta-sys-type"; public static final String USER_NAME_HEADER = "X-oio-container-meta-sys-user-name"; public static final String VERSION_MAIN_ADMIN_HEADER = "X-oio-container-meta-x-version-main-admin"; public static final String VERSION_MAIN_ALIASES_HEADER = "X-oio-container-meta-x-version-main-aliases"; public static final String VERSION_MAIN_CHUNKS_HEADER = "X-oio-container-meta-x-version-main-chunks"; public static final String VERSION_MAIN_CONTENTS_HEADER = "X-oio-container-meta-x-version-main-contents"; public static final String VERSION_MAIN_PROPERTIES_HEADER = "X-oio-container-meta-x-version-main-properties"; public static final String VERSION_PARAM = "version"; public static boolean nullOrEmpty(String string) {
    return null == string || 0 == string.length();
} public static Long longHeader(OioHttpResponse r, String header) {
    try {
        return Long.parseLong(r.header(header));
    } catch (NumberFormatException e) {
        return -1L;
    }
} public static final OioHttpResponseVerifier CONTAINER_VERIFIER = new OioHttpResponseVerifier() {

    @Override
    public void verify(OioHttpResponse resp) throws OioException {
        if (200 == resp.code() || 201 == resp.code()
                || 204 == resp.code())
            return;
        ProxyError err = extractError(resp);
        switch (err.status()) {
        case 400:
            throw new BadRequestException(err.toString());
        case 406:
        case 431:
            throw new ContainerNotFoundException(err.toString());
        case 433:
            throw new ContainerExistException(err.toString());
        case 438:
            throw new ContainerNotEmptyException(err.toString());
        case 500:
            throw new OioSystemException(err.toString());
        default:
            throw new OioSystemException(err.toString());
        }
    }
}; public static final OioHttpResponseVerifier OBJECT_VERIFIER = new OioHttpResponseVerifier() {

    @Override
    public void verify(OioHttpResponse resp) throws OioException {
        if (200 == resp.code() || 201 == resp.code()
                || 204 == resp.code())
            return;
        ProxyError err = extractError(resp);
        switch (err.status()) {
        case 400:
            throw new BadRequestException(err.toString());
        case 406:
        case 431:
            throw new ContainerNotFoundException(err.toString());
        case 420:
            throw new ObjectNotFoundException(err.toString());
        case 421:
            throw new ObjectExistException(err.toString());
        case 426:
            throw new AliasDeletedException(err.toString());
        case 500:
            throw new OioSystemException(err.toString());
        default:
            throw new OioSystemException(err.toString());
        }
    }
}; public static final OioHttpResponseVerifier REFERENCE_VERIFIER = new OioHttpResponseVerifier() {

    @Override
    public void verify(OioHttpResponse resp) throws OioException {
        if (200 == resp.code() || 201 == resp.code()
                || 204 == resp.code())
            return;
        ProxyError err = extractError(resp);
        switch (err.status()) {
        case 406:
            throw new ReferenceNotFoundException(err.toString());
        case 400:
            throw new BadRequestException(err.toString());
        case 404:
            throw new ReferenceNotFoundException(err.toString());
        case 500:
            throw new OioSystemException(err.toString());
        default:
            throw new OioSystemException(err.toString());
        }
    }
}; public static final OioHttpResponseVerifier STANDALONE_VERIFIER = new OioHttpResponseVerifier() {

    @Override
    public void verify(OioHttpResponse resp) throws OioException {
        if (200 == resp.code() || 201 == resp.code()
                || 204 == resp.code())
            return;
        ProxyError err = extractError(resp);
        switch (err.status()) {
        case 400:
            throw new BadRequestException(err.toString());
        case 404:
            throw new OioException(err.toString());
        case 500:
            throw new OioSystemException(err.toString());
        default:
            throw new OioSystemException(err.toString());
        }
    }
}; public class RequestContext {

    private DeadlineManager dm;

    private String reqId = null;
    private int reqStart = -1;
    private int rawTimeout = -1;
    private int deadline = -1;

    /**
     * Build a new {@link RequestContext} with a default 30s timeout.
     */
    public RequestContext() {
        this.dm = DeadlineManager.instance();
    }

    /**
     * Copy constructor. Build a new {@link RequestContext} from another one.
     * This will keep the request ID, the timeout and the deadline (if there is one).
     *
     * @param src The {@link RequestContext} to copy.
     */
    public RequestContext(RequestContext src) {
        this.withRequestId(src.requestId());
        this.deadline = src.deadline;
        this.rawTimeout = src.rawTimeout;
    }

    /* -- Request IDs ----------------------------------------------------- */

    /**
     * Ensure this request has an ID and it is at least 8 characters.
     *
     * @return this
     */
    public RequestContext ensureRequestId() {
        if (this.reqId == null)
            this.reqId = IdGen.requestId();
        else if (this.reqId.length() < 8)
            this.reqId += IdGen.requestId().substring(8 - this.reqId.length());
        return this;
    }

    /**
     * Get the request ID.
     *
     * @return the request ID (auto-generated if not set)
     */
    public String requestId() {
        return this.ensureRequestId().reqId;
    }

    /**
     * Set a request ID.
     *
     * @param requestId
     *            a request ID string. If is null, it will be auto-generated. If
     *            it is less than 8 characters, it will be suffixed.
     * @return this
     */
    public RequestContext withRequestId(String requestId) {
        this.reqId = requestId;
        return this;
    }

    /* -- Deadlines and timeouts ------------------------------------------ */

    /**
     * Compute a deadline from the {@link #timeout()}. This also means that the
     * timeout will be recomputed for each unique sub-request resulting from an
     * API call. If this request has started, compute the timeout from the start,
     * otherwise compute it from now.
     *
     * @return {@code this}
     */
    public RequestContext computeDeadline() {
        if (!this.hasTimeout())
            throw new IllegalStateException("No timeout has been set, cannot compute deadline");
        if (this.hasStarted())
            this.deadline = dm.timeoutToDeadline(this.rawTimeout, this.reqStart);
        else
            this.deadline = dm.timeoutToDeadline(this.rawTimeout);
        return this;
    }

    /**
     * Get the deadline for this request.
     *
     * If a timeout has been set ({@link #withTimeout(int)}) but no
     * deadline, {@link #computeDeadline()} will be called.
     *
     * @return the overall deadline for this request, in milliseconds
     */
    public int deadline() {
        if (!hasDeadline()) {
            startTiming();
            computeDeadline();
        }
        return this.deadline;
    }

    /**
     * Tell the time elapsed since this request has started.
     *
     * @return the duration since this request has started, in milliseconds
     */
    public int elapsed() {
        if (!this.hasStarted())
            return 0;
        return this.dm.now() - this.reqStart;
    }

    /**
     * Tell whether this request has a deadline or not. This will always return
     * {@code true} after {@link #computeDeadline()} or {@link #deadline()} has been
     * called.
     *
     * @return {@code true} if this request has a deadline.
     */
    public boolean hasDeadline() {
        return this.deadline >= 0;
    }

    /**
     * Tell if this request has started.
     *
     * @return {@code true} if this request has started.
     */
    public boolean hasStarted() {
        return this.reqStart >= 0;
    }

    /**
     * Tell whether this request has a timeout or not.
     *
     * @return {@code true} if this request has timeout.
     */
    public boolean hasTimeout() {
        return this.rawTimeout >= 0;
    }

    /**
     * Reset the deadline set on this request. This must be called when reusing
     * a context only for its timeout and request ID.
     *
     * @return {@code this}
     */
    public RequestContext resetDeadline() {
        this.deadline = -1;
        return this;
    }

    /**
     * Start timing this request.
     *
     * @return this
     */
    public RequestContext startTiming() {
        this.reqStart = dm.now();
        return this;
    }

    /**
     * Get the timeout for the request.
     *
     * If {@link #hasDeadline()} returns {@code true}, successive calls to this
     * method will return decreasing values, and negative values when the
     * deadline has been exceeded.
     *
     * @return the timeout for this request, in milliseconds
     */
    public int timeout() {
        if (this.hasDeadline())
            return this.dm.deadlineToTimeout(this.deadline);
        return this.rawTimeout;
    }

    /**
     * Set a deadline on the whole request.
     *
     * This will reset any previous timeout set with {@link #withTimeout(int)} to the duration
     * from now to the deadline.
     *
     * @param deadline the deadline in milliseconds
     * @return this
     */
    public RequestContext withDeadline(int deadline) {
        checkArgument(deadline >= 0, "deadline cannot be negative");
        this.deadline = deadline;
        this.rawTimeout = this.dm.deadlineToTimeout(deadline);
        return this;
    }

    /**
     * Set a timeout for each unique sub-request resulting from an API call.
     *
     * This will reset any previous deadline set with {@link #withDeadline(int)}.
     *
     * The timeout will be used to compute the deadline when
     * {@link #startTiming()} or {@link #deadline()} is called.
     *
     * @param timeout
     *            a timeout in milliseconds
     * @return this
     */
    public RequestContext withTimeout(int timeout) {
        checkArgument(timeout > 0, "timeout cannot be negative");
        this.rawTimeout = timeout;
        this.resetDeadline();
        return this;
    }
} public class JsonUtils {

    private static final GsonBuilder builder = new GsonBuilder()
            .registerTypeAdapter(Position.class, new PositionAdapter());
    private static final ObjectMapper mapper = new ObjectMapper();

    private static final Type MAP_TYPE = new TypeToken<Map<String, String>>() {
    }.getType();
    private static final Type MAP_MAP_TYPE = new TypeToken<Map<String, Map<String, String>>>() {
    }.getType();

    /**
     * Returns a new {@code Gson} instance with OpenIO adapters
     *
     * @return the gson instance
     */
    public static Gson gson() {
        return builder.create();
    }

    public static Gson gsonForObject() {
        return builder.serializeNulls().create();
    }

    public static Map<String, String> jsonToMap(String map) {
        return gson().fromJson(map, MAP_TYPE);
    }

    public static Map<String, String> jsonToMap(InputStream in) {
        return gson().fromJson(
                new JsonReader(new InputStreamReader(in, OIO_CHARSET)),
                MAP_TYPE);
    }

    public static Map<String, Map<String, String>> jsonToMapMap(InputStream in) {
        return gson().fromJson(
                new JsonReader(new InputStreamReader(in, OIO_CHARSET)),
                MAP_MAP_TYPE);
    }

    public static String jsonFromMap(Map<String, String> map) {
        try {
            mapper.getFactory()
                .configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
            return mapper.writeValueAsString(map);
        } catch (JsonProcessingException e) {
            throw new IllegalArgumentException(e);
        }
    }

    private static final class PositionAdapter implements
            JsonSerializer<Position>, JsonDeserializer<Position> {

        @Override
        public Position deserialize(JsonElement json, Type typeOfT,
                JsonDeserializationContext context) throws JsonParseException {
            return Position.parse(json.getAsString());
        }

        @Override
        public JsonElement serialize(Position src, Type typeOfSrc,
                JsonSerializationContext context) {
            return new JsonPrimitive(src.toString());
        }
    }

} public class OioConstants {

    public static final Charset OIO_CHARSET = Charset.forName("UTF-8");

    /* -- http methods names -- */

    public static final String PUT_METHOD = "PUT";
    public static final String GET_METHOD = "GET";
    public static final String POST_METHOD = "POST";
    public static final String DELETE_METHOD = "DELETE";

    /* -- namespace info options name */
    public static final String OPT_META2_MAX_VERSION = "meta2_max_versions";
    public static final String OPT_META2_CHECK_PUT_DISTANCE = "meta2_check.put.DISTANCE";
    public static final String OPT_META2_CHECK_PUT_GAPS = "meta2_check.put.GAPS";
    public static final String OPT_META2_CHECK_PUT_SRVINFO = "meta2_check.put.SRVINFO";
    public static final String OPT_META2_CHECK_PUT_STGCLASS = "meta2_check.put.STGCLASS";
    public static final String OPT_AUTOMATIC_OPEN = "automatic_open";
    public static final String OPT_NS_STATUS = "ns_status";
    public static final String OPT_SRV_UPDATE_POLICY = "service_update_policy";
    public static final String OPT_STORAGE_POLICY = "storage_policy";
    public static final String OPT_WORM = "WORM";

    /* -- proxyd common headers -- */

    public static final String PROP_HEADER_PREFIX = "X-oio-content-meta-x-";
    public static final int PROP_HEADER_PREFIX_LEN = PROP_HEADER_PREFIX.length();

    public static final String CONTENT_LENGTH_HEADER = "Content-Length";
    public static final String CONTENT_TYPE_HEADER = "Content-Type";

    public static final String ACCOUNT_HEADER = "X-oio-container-meta-sys-account";
    public static final String ACTION_MODE_HEADER = "X-oio-action-mode";
    public static final String M2_CTIME_HEADER = "X-oio-container-meta-sys-m2-ctime";
    public static final String M2_INIT_HEADER = "X-oio-container-meta-sys-m2-init";
    public static final String M2_USAGE_HEADER = "X-oio-container-meta-sys-m2-usage";
    public static final String M2_VERSION_HEADER = "X-oio-container-meta-sys-m2-version";
    public static final String CONTAINER_SYS_NAME_HEADER = "X-oio-container-meta-sys-name";
    public static final String NS_HEADER = "X-oio-container-meta-sys-ns";
    public static final String TYPE_HEADER = "X-oio-container-meta-sys-type";
    public static final String USER_NAME_HEADER = "X-oio-container-meta-sys-user-name";
    public static final String SCHEMA_VERSION_HEADER = "X-oio-container-meta-x-schema-version";
    public static final String VERSION_MAIN_ADMIN_HEADER = "X-oio-container-meta-x-version-main-admin";
    public static final String VERSION_MAIN_ALIASES_HEADER = "X-oio-container-meta-x-version-main-aliases";
    public static final String VERSION_MAIN_CHUNKS_HEADER = "X-oio-container-meta-x-version-main-chunks";
    public static final String VERSION_MAIN_CONTENTS_HEADER = "X-oio-container-meta-x-version-main-contents";
    public static final String VERSION_MAIN_PROPERTIES_HEADER = "X-oio-container-meta-x-version-main-properties";

    public static final String CONTENT_META_ID_HEADER = "X-oio-content-meta-id";
    public static final String CONTENT_META_CHUNK_METHOD_HEADER = "X-oio-content-meta-chunk-method";
    public static final String CONTENT_META_CTIME_HEADER = "X-oio-content-meta-ctime";
    public static final String CONTENT_META_DELETED_HEADER = "X-oio-content-meta-deleted";
    public static final String CONTENT_META_HASH_HEADER = "X-oio-content-meta-hash";
    public static final String CONTENT_META_HASH_METHOD_HEADER = "X-oio-content-meta-hash-method";
    public static final String CONTENT_META_LENGTH_HEADER = "X-oio-content-meta-length";
    public static final String CONTENT_META_MIME_TYPE_HEADER = "X-oio-content-meta-mime-type";
    public static final String CONTENT_META_NAME_HEADER = "X-oio-content-meta-name";
    public static final String CONTENT_META_POLICY_HEADER = "X-oio-content-meta-policy";
    public static final String CONTENT_META_VERSION_HEADER = "X-oio-content-meta-version";

    public static final String NS_CHUNK_SIZE_HEADER = "X-oio-ns-chunk-size";
    public static final String LIST_MARKER_HEADER = "X-oio-list-marker";
    public static final String LIST_TRUNCATED_HEADER = "X-oio-list-truncated";

    public static final String OIO_ACTION_MODE_HEADER = "X-oio-action-mode";
    public static final String OIO_REQUEST_ID_HEADER = "X-oio-req-id";
    public static final String SIMULATE_VERSIONING_HEADER = "X-oio-simulate-versioning";

    public static final String RANGE_HEADER = "Range";
    public static final String OIO_TIMEOUT_HEADER = "X-oio-timeout";


    /* -- RAWX common headers -- */

    public static final String CHUNK_META_CONTAINER_ID = "X-oio-chunk-meta-container-id";
    public static final String CHUNK_META_CONTENT_ID = "X-oio-chunk-meta-content-id";
    public static final String CHUNK_META_CONTENT_VERSION = "X-oio-chunk-meta-content-version";
    public static final String CHUNK_META_CONTENT_CHUNKSNB = "x-oio-chunk-meta-content-chunksnb";
    public static final String CHUNK_META_CONTENT_SIZE = "X-oio-chunk-meta-content-size";
    public static final String CHUNK_META_CONTENT_PATH = "X-oio-chunk-meta-content-path";
    public static final String CHUNK_META_CONTENT_POLICY = "X-oio-chunk-meta-content-storage-policy";
    public static final String CHUNK_META_CONTENT_CHUNK_METHOD = "X-oio-chunk-meta-content-chunk-method";
    public static final String CHUNK_META_CONTENT_MIME_TYPE = "X-oio-chunk-meta-content-mime-type";
    public static final String CHUNK_META_CHUNK_ID = "X-oio-chunk-meta-chunk-id";
    public static final String CHUNK_META_CHUNK_POS = "X-oio-chunk-meta-chunk-pos";
    public static final String CHUNK_META_CHUNK_HASH = "X-oio-chunk-meta-chunk-hash";

    /* -- ECD common headers -- */

    public static final String CHUNK_META_CHUNK_PREFIX = "X-oio-chunk-meta-chunk-";
    public static final String CHUNK_META_CHUNKS_NB = "X-oio-chunk-meta-chunks-nb";
    public static final String CHUNK_META_CHUNK_SIZE = "X-oio-chunk-meta-chunk-size";
    public static final String CHUNK_META_FULL_PATH = "X-oio-chunk-meta-full-path";
    public static final String CHUNK_META_OIO_VERSION = "X-oio-chunk-meta-oio-version";

    /* -- Common parameters names -- */

    public static final String VERSION_PARAM = "version";
    public static final String MAX_PARAM = "max";
    public static final String PREFIX_PARAM = "prefix";
    public static final String DELIMITER_PARAM = "delimiter";
    public static final String MARKER_PARAM = "marker";
    public static final String FLUSH_PARAM = "flush";
    public static final String DELETE_MARKER_PARAM = "delete_marker";

    /* -- URL String format -- */

    /* -- CS -- */
    public static final String CS_NSINFO_FORMAT = "%s/v3.0/%s/conscience/info";
    public static final String CS_GETSRV_FORMAT = "%s/v3.0/%s/conscience/list?type=%s";

    /* -- DIR -- */
    public static final String DIR_REF_CREATE_FORMAT = "%s/v3.0/%s/reference/create?acct=%s&ref=%s";
    public static final String DIR_REF_DELETE_FORMAT = "%s/v3.0/%s/reference/destroy?acct=%s&ref=%s";
    public static final String DIR_REF_SHOW_FORMAT = "%s/v3.0/%s/reference/show?acct=%s&ref=%s";
    public static final String DIR_LINK_SRV_FORMAT = "%s/v3.0/%s/reference/link?acct=%s&ref=%s&type=%s";
    public static final String DIR_LIST_SRV_FORMAT = "%s/v3.0/%s/reference/show?acct=%s&ref=%s&type=%s";
    public static final String DIR_UNLINK_SRV_FORMAT = "%s/v3.0/%s/reference/unlink?acct=%s&ref=%s&type=%s";
    public static final String DIR_FORCE_SRV_FORMAT = "%s/v3.0/%s/reference/link?acct=%s&ref=%s";
    public static final String DIR_RENEW_SRV_FORMAT = "%s/v3.0/%s/reference/renew?acct=%s&ref=%s";

    /* -- STG -- */
    public static final String CREATE_CONTAINER_FORMAT = "%s/v3.0/%s/container/create?acct=%s&ref=%s";
    public static final String GET_CONTAINER_INFO_FORMAT = "%s/v3.0/%s/container/show?acct=%s&ref=%s";
    public static final String LIST_OBJECTS_FORMAT = "%s/v3.0/%s/container/list?acct=%s&ref=%s";
    public static final String DELETE_CONTAINER_FORMAT = "%s/v3.0/%s/container/destroy?acct=%s&ref=%s";
    public static final String GET_BEANS_FORMAT = "%s/v3.0/%s/content/prepare?acct=%s&ref=%s&path=%s";
    public static final String PUT_OBJECT_FORMAT = "%s/v3.0/%s/content/create?acct=%s&ref=%s&path=%s";
    public static final String GET_OBJECT_FORMAT = "%s/v3.0/%s/content/show?acct=%s&ref=%s&path=%s";
    public static final String DELETE_OBJECT_FORMAT = "%s/v3.0/%s/content/delete?acct=%s&ref=%s&path=%s";

    /* -- PROPS -- */
    public static final String CONTAINER_SET_PROP = "%s/v3.0/%s/container/set_properties?acct=%s&ref=%s";
    public static final String CONTAINER_GET_PROP = "%s/v3.0/%s/container/get_properties?acct=%s&ref=%s";
    public static final String CONTAINER_DEL_PROP = "%s/v3.0/%s/container/del_properties?acct=%s&ref=%s";
    public static final String OBJECT_SET_PROP = "%s/v3.0/%s/content/set_properties?acct=%s&ref=%s&path=%s";
    public static final String OBJECT_GET_PROP = "%s/v3.0/%s/content/get_properties?acct=%s&ref=%s&path=%s";
    public static final String OBJECT_DEL_PROP = "%s/v3.0/%s/content/del_properties?acct=%s&ref=%s&path=%s";

    /* -- Common String format -- */
    public static final String PROXY_ERROR_FORMAT = "(%d) %s";

    /* -- Common error messages -- */
    public static final String INVALID_OBJECT_INFO_MSG = "Invalid object info";
    public static final String INVALID_OPTIONS_MSG = "Invalid options";
    public static final String INVALID_URL_MSG = "Invalid url";

    public static final String USER_PROP_PREFIX = "user.";

    public static final String CHUNK_METHOD_PLAIN = "plain";
    public static final String AUTOCREATE_ACTION_MODE = "autocreate";

    public static final String EC_PREFIX = "ec/";

} public class Strings {

    private static final SdsLogger LOGGER = SdsLoggerFactory.getLogger(Strings.class);
    public static final String[] EMPTY_ARRAY = new String[0];

    public static boolean nullOrEmpty(String string) {
        return null == string || 0 == string.length();
    }

    public static HashMap<String, String> toMap(String src, String sep,
            String kvsep) {
        HashMap<String, String> res = new HashMap<String, String>();
        if (nullOrEmpty(src) || nullOrEmpty(sep) || nullOrEmpty(kvsep))
            return res;
        String[] pairs = src.split(sep);
        for (String pair : pairs) {
            if (nullOrEmpty(pair))
                continue;
            String[] tok = pair.split(kvsep, 2);
            if (tok.length != 2 || nullOrEmpty(tok[0]) || nullOrEmpty(tok[1]))
                continue;
            res.put(tok[0], tok[1]);
        }
        return res;
    }

    public static List<String> toList(String src, String sep) {
        ArrayList<String> res = new ArrayList<String>();
        if (nullOrEmpty(src) || nullOrEmpty(sep))
            return res;
        String[] toks = src.split(sep);
        for (String tok : toks)
            if (!nullOrEmpty(tok))
                res.add(tok);
        return res;
    }

    public static String urlEncode(String src) {
        try {
            return URLEncoder.encode(src, OioConstants.OIO_CHARSET.name());
        } catch (UnsupportedEncodingException e) {
            LOGGER.warn("Url encoding fail, may have some problems", e);
            return src;
        }
    }

    public static String quote(String src) {
        return Strings.urlEncode(src).replace("+", "%20");
    }

    public static String quote(Long src) {
        return Strings.quote(String.valueOf(src));
    }
} public class ContainerExistException extends OioException {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public ContainerExistException(String message) {
        super(message);
    }
} public class ContainerNotFoundException extends OioException {

    /**
     * 
     */
    private static final long serialVersionUID = -4662162990209727136L;
    
    public ContainerNotFoundException() {
        super(null);
    }
    public ContainerNotFoundException(String message) {
        super(message);
    }
} public class ObjectNotFoundException extends OioException {

    /**
     * 
     */
    private static final long serialVersionUID = -2995680181164449256L;

    public ObjectNotFoundException() {
        super(null);
    }

    public ObjectNotFoundException(String message) {
        super(message);
    }
} @SuppressWarnings("deprecation")
public class OioException extends SdsException {

    /**
     * 
     */
    private static final long serialVersionUID = 3270900242235005338L;

    public OioException(String message) {
        super(message);
    }

    public OioException(String message, Throwable t) {
        super(message, t);
    }
} public class OioSystemException extends OioException {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public OioSystemException(String message) {
        super(message);
    }

    public OioSystemException(String message, Throwable t) {
        super(message, t);
    }
} public class OioHttp {

    private static final SdsLogger logger = SdsLoggerFactory.getLogger(SdsLogger.class);

    private static final String CRLF = "\r\n";

    private static final byte[] CRLF_BYTES = { '\r', '\n' };

    private OioHttpSettings settings;

    private SocketProvider socketProvider;

    private OioHttp(OioHttpSettings settings, SocketProvider socketProvider) {
        this.settings = settings;
        this.socketProvider = socketProvider;
    }

    public static OioHttp http(OioHttpSettings settings, SocketProvider socketProvider) {
        Check.checkArgument(null != settings);
        Check.checkArgument(null != socketProvider);
        return new OioHttp(settings, socketProvider);
    }

    public RequestBuilder post(String uri) {
        Check.checkArgument(!nullOrEmpty(uri));
        return new RequestBuilder().req(POST_METHOD, uri);
    }

    public RequestBuilder put(String uri) {
        Check.checkArgument(!nullOrEmpty(uri));
        return new RequestBuilder().req(PUT_METHOD, uri);
    }

    public RequestBuilder get(String uri) {
        Check.checkArgument(!nullOrEmpty(uri));
        return new RequestBuilder().req(GET_METHOD, uri);
    }

    public RequestBuilder delete(String uri) {
        Check.checkArgument(!nullOrEmpty(uri));
        return new RequestBuilder().req(DELETE_METHOD, uri);
    }


    public static String timeoutMillisToStringMicros(int timeout) {
        // oio-proxy wants microseconds, and we remove 1% for the parsing overhead.
        return String.valueOf(timeout * 990);
    }

    public class RequestBuilder {

        private String method;
        private HashMap<String, String> headers = new HashMap<String, String>();
        private HashMap<String, String> query = new HashMap<String, String>();
        private String body;
        private InputStream data;
        private Long len;
        private URI uri;
        private OioHttpResponseVerifier verifier = null;
        private boolean chunked;
        private List<InetSocketAddress> hosts = null;
        private RequestContext reqCtx = null;

        public RequestBuilder req(String method, String url) {
            this.method = method;
            this.uri = URI.create(url);
            return this;
        }

        public RequestBuilder header(String name, String value) {
            if (!nullOrEmpty(name) && !nullOrEmpty(value))
                headers.put(name, value);
            return this;
        }

        public RequestBuilder headers(Map<String, String> headers) {
            if (null != headers)
                this.headers.putAll(headers);
            return this;
        }

        public RequestBuilder query(String name, String value) {
            if (!nullOrEmpty(name) && !nullOrEmpty(value))
                this.query.put(name, value);
            return this;
        }

        public RequestBuilder chunked() {
            this.headers.put("Transfer-Encoding", "chunked");
            this.chunked = true;
            return this;
        }

        public RequestBuilder body(String body) {
            if (nullOrEmpty(body))
                return this;
            headers.put(CONTENT_LENGTH_HEADER, String.valueOf(body.length()));
            headers.put(CONTENT_TYPE_HEADER, "application/json");
            this.body = body;
            return this;
        }

        public RequestBuilder body(InputStream data, Long size) {
            if (null == data)
                return this;
            headers.put(CONTENT_LENGTH_HEADER, String.valueOf(size));
            if (!headers.containsKey(CONTENT_TYPE_HEADER)) {
                headers.put(CONTENT_TYPE_HEADER, "application/octet-stream");
            }
            this.data = data;
            this.len = size;
            return this;
        }

        public RequestBuilder verifier(OioHttpResponseVerifier verifier) {
            this.verifier = verifier;
            return this;
        }

        /**
         * Set the context associated to the current request.
         *
         * @param reqCtx
         *            the context to get deadline and request ID from
         * @return {@code this}
         */
        public RequestBuilder withRequestContext(RequestContext reqCtx) {
            this.reqCtx = reqCtx;
            if (reqCtx != null)
                headers.put(OIO_REQUEST_ID_HEADER, reqCtx.requestId());
            return this;
        }

        /**
         * @param hosts
         *            An optional list of hosts to connect to.
         * @return this
         */
        public RequestBuilder hosts(List<InetSocketAddress> hosts) {
            this.hosts = hosts;
            return this;
        }

        public OioHttpResponse execute() throws OioException {
            if (this.hosts == null || this.hosts.isEmpty()) {
                return execute(new InetSocketAddress(uri.getHost(), uri.getPort()));
            } else {
                OioException lastExc = null;
                // TODO: implement better fallback mechanism, with randomization
                for (InetSocketAddress addr : this.hosts) {
                    try {
                        if (lastExc != null)
                            logger.info("Retrying on " + addr.toString());
                        return execute(addr);
                    } catch (OioException oioe) {
                        // Retry only if the cause is network
                        if (oioe.getCause() instanceof IOException) {
                            logger.warn("Failed to perform request", oioe);
                            lastExc = oioe;
                        } else {
                            throw oioe;
                        }
                    }
                }
                throw new OioSystemException("HTTP request execution error", lastExc);
            }
        }

        /**
         * If a deadline has been set on the request:
         * - check it;
         * - set the socket timeout accordingly;
         * Whether a deadline has been set or not, add a timeout header
         * so the server will limit itself (99% of the timeout set on the socket).
         */
        private void applyDeadline(Socket sock) throws SocketException {
            int timeout;
            if (this.reqCtx != null) {
                if (this.reqCtx.hasDeadline())
                    DeadlineManager.instance().checkDeadline(this.reqCtx.deadline());
                if (this.reqCtx.hasDeadline() || this.reqCtx.hasTimeout()) {
                    timeout = this.reqCtx.timeout();
                    sock.setSoTimeout(timeout);
                } else {
                    // Do not change the timeout, but still set the header.
                    timeout = sock.getSoTimeout();
                }
            } else {
                timeout = sock.getSoTimeout();
            }
            headers.put(OIO_TIMEOUT_HEADER, timeoutMillisToStringMicros(timeout));
        }

        private OioHttpResponse execute(InetSocketAddress addr) throws OioException {
            Socket sock = null;
            try {
                sock = socketProvider.getSocket(addr);
                applyDeadline(sock);
                if (chunked)
                    sendRequestChunked(sock);
                else
                    sendRequest(sock);
                OioHttpResponse resp = readResponse(sock);
                try {
                    if (null != verifier)
                        verifier.verify(resp);
                    return resp;
                } catch (OioException e) {
                    resp.close(true);
                    throw e;
                }
            } catch (IOException e) {
                if (null != sock)
                    try {
                        try {
                            sock.shutdownInput();
                        } catch (SocketException se) {
                            logger.debug("Socket input already shutdown");
                        }
                        sock.close();
                    } catch (IOException ioe) {
                        logger.warn("Failed to close socket, possible leak", ioe);
                    }
                throw new OioSystemException("HTTP request execution error", e);
            }
        }

        public <T> T execute(Class<T> c) {
            OioHttpResponse resp = execute();
            boolean success = false;
            try {
                T t = gson().fromJson(
                        new JsonReader(new InputStreamReader(resp.body(), OIO_CHARSET)), c);
                success = true;
                return t;
            } finally {
                resp.close(success);
            }
        }

        private OioHttpResponse readResponse(Socket sock) throws IOException {
            return OioHttpResponse.build(sock, this.reqCtx);
        }

        private void sendRequest(Socket sock) throws IOException {
            headers.put("Host", uri.getHost() + ":" + uri.getPort());
            headers.put("Connection", socketProvider.reusableSocket() ? "keep-alive" : "close");
            headers.put("Accept", "*/*");
            headers.put("Accept-Encoding", "gzip, deflate");
            headers.put("User-Agent", settings.userAgent());

            if (!headers.containsKey("Content-Length"))
                headers.put(CONTENT_LENGTH_HEADER, "0");

            BufferedOutputStream bos = new BufferedOutputStream(sock.getOutputStream(),
                    settings.sendBufferSize());
            bos.write(requestHead());
            if (null != data) {
                stream(bos);
            } else if (null != body) {
                bos.write(body.getBytes(OIO_CHARSET));
            }
            bos.flush();
        }

        private void sendRequestChunked(Socket sock) throws IOException {
            headers.put("Host", uri.getHost() + ":" + uri.getPort());
            headers.put("Connection", socketProvider.reusableSocket() ? "keep-alive" : "close");
            headers.put("Accept", "*/*");
            headers.put("Accept-Encoding", "gzip, deflate");
            headers.put("User-Agent", settings.userAgent());

            // ensure no content-length
            headers.remove("Content-Length");
            BufferedOutputStream bos = new BufferedOutputStream(sock.getOutputStream(),
                    settings.sendBufferSize());
            byte[] requestHead = requestHead();
            bos.write(requestHead);
            streamChunked(bos);
            bos.flush();
        }

        private void streamChunked(OutputStream os) throws IOException {
            byte[] b = new byte[settings.sendBufferSize()];
            int remaining = len.intValue();
            while (remaining > 0) {
                int read = data.read(b, 0, Math.min(remaining, b.length));
                if (-1 == read)
                    throw new EOFException("Unexpected end of source stream");
                remaining -= read;
                if (read > 0) {
                    os.write((Integer.toHexString(read) + CRLF).getBytes(OIO_CHARSET));
                    os.write(b, 0, read);
                    os.write(CRLF_BYTES);
                }
            }
            os.write(("0" + CRLF + CRLF).getBytes(OIO_CHARSET));
        }

        private void stream(OutputStream sos) throws IOException {
            byte[] b = new byte[settings.sendBufferSize()];
            int remaining = len.intValue();

            while (remaining > 0) {
                int read = data.read(b, 0, Math.min(remaining, b.length));
                if (-1 == read)
                    throw new EOFException("Unexpected end of source stream");
                remaining -= read;
                sos.write(b, 0, read);
            }
        }

        private byte[] requestHead() throws UnsupportedEncodingException {
            StringBuilder qbuilder = new StringBuilder(null == uri.getRawQuery() ? ""
                    : uri.getRawQuery());
            boolean removeTrailindAnd = 0 == qbuilder.length();
            for (Entry<String, String> h : query.entrySet()) {
                qbuilder.append("&").append(URLEncoder.encode(h.getKey(), "utf-8")).append("=")
                        .append(URLEncoder.encode(h.getValue(), "utf-8"));
            }
            String uriPath = uri.getRawPath();
            StringBuilder req = new StringBuilder(method)
                    .append(" ")
                    .append(uriPath != null && !uriPath.isEmpty() ? uriPath : "/")
                    .append(qbuilder.length() > 0 ? "?" : "")
                    .append(qbuilder.length() > 0 ? (removeTrailindAnd ? qbuilder.substring(1)
                            : qbuilder.toString()) : "").append(" ").append("HTTP/1.1")
                    .append(CRLF);
            for (Entry<String, String> h : headers.entrySet()) {
                req.append(h.getKey()).append(": ").append(h.getValue()).append(CRLF);
            }
            byte[] res = req.append(CRLF).toString().getBytes(OIO_CHARSET);
            return res;
        }
    }
} public class OioHttpResponse {

    private static final SdsLogger logger = SdsLoggerFactory.getLogger(OioHttpResponse.class);

    private static final int R = 1;
    private static final int RN = 2;
    private static final int RNR = 3;
    private static final int RNRN = 3;

    private static byte BS_R = '\r';
    private static byte BS_N = '\n';

    private ResponseHead head;

    private RequestContext reqCtx;
    private Socket sock;

    private InputStream sis;

    private OioHttpResponse(Socket sock, RequestContext reqCtx) {
        this.reqCtx = reqCtx;
        this.sock = sock;
    }

    public static OioHttpResponse build(Socket sock, RequestContext reqCtx) throws IOException {
        return new OioHttpResponse(sock, reqCtx).responseHead();
    }

    public HashMap<String, String> headers() {
        return head.headers();
    }

    public String header(String key) {
        return head.header(key);
    }

    public int code() {
        return head.code();
    }

    public String msg() {
        return head.msg();
    }

    public InputStream body() {
        return sis;
    }

    public Long length() {
        String str = header("Content-Length");
        return null == str ? 0L : Long.parseLong(str);
    }

    public OioHttpResponse close(boolean reuse) {
        try {
            if (!reuse)
                sock.shutdownInput();
            sock.close();
        } catch (Exception e) {
            logger.warn("Failed to close socket, possible leak", e);
        }
        return this;
    }

    public OioHttpResponse close() {
        return close(true);
    }

    public RequestContext requestContext() {
        return this.reqCtx;
    }

    private OioHttpResponse responseHead() throws IOException {
        sis = new BufferedInputStream(sock.getInputStream());
        this.head = ResponseHead.parse(readHeaders());
        if (head.chunked()) {
            sis = new ChunkedStream(sis);
        } else {
            sis = new Stream(sis, length());
        }
        return this;
    }

    private String readHeaders() throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        int state = 0;
        while (state < RNRN)
            state = next(bos, state);
        read(); // read trailing \r
        return new String(bos.toByteArray(), OIO_CHARSET);
    }

    private int next(ByteArrayOutputStream bos, int state) throws IOException {
        int b = read();
        bos.write(b);
        switch (state) {
        case R:
            return b == BS_N ? RN : b == BS_R ? R : 0;
        case RN:
            return b == BS_R ? RNR : 0;
        case RNR:
            return b == BS_N ? RNRN : b == BS_R ? R : 0;
        default:
            return b == BS_R ? R : 0;
        }
    }

    private int read() throws IOException {
        int b = sis.read();
        if (-1 == b)
            throw new IOException("Unexpected end of stream");
        return b;
    }

    public static class ResponseHead {

        private BufferedReader reader;
        private StatusLine statusLine;
        private HashMap<String, String> headers = new HashMap<String, String>();

        private ResponseHead(BufferedReader reader) {
            this.reader = reader;
        }

        public static ResponseHead parse(String head) throws IOException {
            return new ResponseHead(new BufferedReader(new StringReader(head))).parseStatusLine()
                    .parseHeaders();
        }

        private ResponseHead parseStatusLine() throws IOException {
            this.statusLine = StatusLine.parse(reader.readLine());
            return this;
        }

        private ResponseHead parseHeaders() throws IOException {
            String line;
            while (null != (line = reader.readLine())) {
                if (nullOrEmpty(line))
                    continue;
                String[] tok = line.trim().split(":", 2);
                if (2 != tok.length)
                    continue;
                headers.put(tok[0].trim().toLowerCase(), tok[1].trim());
            }
            return this;
        }

        public HashMap<String, String> headers() {
            return this.headers;
        }

        public String header(String key) {
            return this.headers.get(key.toLowerCase());
        }

        public int code() {
            return statusLine.code();
        }

        public String msg() {
            return statusLine.msg();
        }

        public boolean chunked() {
            String chunked = this.header("transfer-encoding");
            return null != chunked && "chunked".equals(chunked);
        }

    }

    public static class StatusLine {

        private String proto;
        private int code;
        private String msg;

        private StatusLine(String proto, int code, String msg) {
            this.proto = proto;
            this.code = code;
            this.msg = msg;
        }

        public static StatusLine parse(String line) throws IOException {
            String[] tok = line.trim().split(" ", 3);
            if (3 != tok.length)
                throw new IOException(format("Invalid HTTP status line (%s)", line));
            return new StatusLine(tok[0], Integer.parseInt(tok[1].trim()), tok[2].trim());
        }

        public String proto() {
            return this.proto;
        }

        public String msg() {
            return this.msg;
        }

        public int code() {
            return this.code;
        }
    }
} public class RequestBuilder {

    private String method;
    private HashMap<String, String> headers = new HashMap<String, String>();
    private HashMap<String, String> query = new HashMap<String, String>();
    private String body;
    private InputStream data;
    private Long len;
    private URI uri;
    private OioHttpResponseVerifier verifier = null;
    private boolean chunked;
    private List<InetSocketAddress> hosts = null;
    private RequestContext reqCtx = null;

    public RequestBuilder req(String method, String url) {
        this.method = method;
        this.uri = URI.create(url);
        return this;
    }

    public RequestBuilder header(String name, String value) {
        if (!nullOrEmpty(name) && !nullOrEmpty(value))
            headers.put(name, value);
        return this;
    }

    public RequestBuilder headers(Map<String, String> headers) {
        if (null != headers)
            this.headers.putAll(headers);
        return this;
    }

    public RequestBuilder query(String name, String value) {
        if (!nullOrEmpty(name) && !nullOrEmpty(value))
            this.query.put(name, value);
        return this;
    }

    public RequestBuilder chunked() {
        this.headers.put("Transfer-Encoding", "chunked");
        this.chunked = true;
        return this;
    }

    public RequestBuilder body(String body) {
        if (nullOrEmpty(body))
            return this;
        headers.put(CONTENT_LENGTH_HEADER, String.valueOf(body.length()));
        headers.put(CONTENT_TYPE_HEADER, "application/json");
        this.body = body;
        return this;
    }

    public RequestBuilder body(InputStream data, Long size) {
        if (null == data)
            return this;
        headers.put(CONTENT_LENGTH_HEADER, String.valueOf(size));
        if (!headers.containsKey(CONTENT_TYPE_HEADER)) {
            headers.put(CONTENT_TYPE_HEADER, "application/octet-stream");
        }
        this.data = data;
        this.len = size;
        return this;
    }

    public RequestBuilder verifier(OioHttpResponseVerifier verifier) {
        this.verifier = verifier;
        return this;
    }

    /**
     * Set the context associated to the current request.
     *
     * @param reqCtx
     *            the context to get deadline and request ID from
     * @return {@code this}
     */
    public RequestBuilder withRequestContext(RequestContext reqCtx) {
        this.reqCtx = reqCtx;
        if (reqCtx != null)
            headers.put(OIO_REQUEST_ID_HEADER, reqCtx.requestId());
        return this;
    }

    /**
     * @param hosts
     *            An optional list of hosts to connect to.
     * @return this
     */
    public RequestBuilder hosts(List<InetSocketAddress> hosts) {
        this.hosts = hosts;
        return this;
    }

    public OioHttpResponse execute() throws OioException {
        if (this.hosts == null || this.hosts.isEmpty()) {
            return execute(new InetSocketAddress(uri.getHost(), uri.getPort()));
        } else {
            OioException lastExc = null;
            // TODO: implement better fallback mechanism, with randomization
            for (InetSocketAddress addr : this.hosts) {
                try {
                    if (lastExc != null)
                        logger.info("Retrying on " + addr.toString());
                    return execute(addr);
                } catch (OioException oioe) {
                    // Retry only if the cause is network
                    if (oioe.getCause() instanceof IOException) {
                        logger.warn("Failed to perform request", oioe);
                        lastExc = oioe;
                    } else {
                        throw oioe;
                    }
                }
            }
            throw new OioSystemException("HTTP request execution error", lastExc);
        }
    }

    /**
     * If a deadline has been set on the request:
     * - check it;
     * - set the socket timeout accordingly;
     * Whether a deadline has been set or not, add a timeout header
     * so the server will limit itself (99% of the timeout set on the socket).
     */
    private void applyDeadline(Socket sock) throws SocketException {
        int timeout;
        if (this.reqCtx != null) {
            if (this.reqCtx.hasDeadline())
                DeadlineManager.instance().checkDeadline(this.reqCtx.deadline());
            if (this.reqCtx.hasDeadline() || this.reqCtx.hasTimeout()) {
                timeout = this.reqCtx.timeout();
                sock.setSoTimeout(timeout);
            } else {
                // Do not change the timeout, but still set the header.
                timeout = sock.getSoTimeout();
            }
        } else {
            timeout = sock.getSoTimeout();
        }
        headers.put(OIO_TIMEOUT_HEADER, timeoutMillisToStringMicros(timeout));
    }

    private OioHttpResponse execute(InetSocketAddress addr) throws OioException {
        Socket sock = null;
        try {
            sock = socketProvider.getSocket(addr);
            applyDeadline(sock);
            if (chunked)
                sendRequestChunked(sock);
            else
                sendRequest(sock);
            OioHttpResponse resp = readResponse(sock);
            try {
                if (null != verifier)
                    verifier.verify(resp);
                return resp;
            } catch (OioException e) {
                resp.close(true);
                throw e;
            }
        } catch (IOException e) {
            if (null != sock)
                try {
                    try {
                        sock.shutdownInput();
                    } catch (SocketException se) {
                        logger.debug("Socket input already shutdown");
                    }
                    sock.close();
                } catch (IOException ioe) {
                    logger.warn("Failed to close socket, possible leak", ioe);
                }
            throw new OioSystemException("HTTP request execution error", e);
        }
    }

    public <T> T execute(Class<T> c) {
        OioHttpResponse resp = execute();
        boolean success = false;
        try {
            T t = gson().fromJson(
                    new JsonReader(new InputStreamReader(resp.body(), OIO_CHARSET)), c);
            success = true;
            return t;
        } finally {
            resp.close(success);
        }
    }

    private OioHttpResponse readResponse(Socket sock) throws IOException {
        return OioHttpResponse.build(sock, this.reqCtx);
    }

    private void sendRequest(Socket sock) throws IOException {
        headers.put("Host", uri.getHost() + ":" + uri.getPort());
        headers.put("Connection", socketProvider.reusableSocket() ? "keep-alive" : "close");
        headers.put("Accept", "*/*");
        headers.put("Accept-Encoding", "gzip, deflate");
        headers.put("User-Agent", settings.userAgent());

        if (!headers.containsKey("Content-Length"))
            headers.put(CONTENT_LENGTH_HEADER, "0");

        BufferedOutputStream bos = new BufferedOutputStream(sock.getOutputStream(),
                settings.sendBufferSize());
        bos.write(requestHead());
        if (null != data) {
            stream(bos);
        } else if (null != body) {
            bos.write(body.getBytes(OIO_CHARSET));
        }
        bos.flush();
    }

    private void sendRequestChunked(Socket sock) throws IOException {
        headers.put("Host", uri.getHost() + ":" + uri.getPort());
        headers.put("Connection", socketProvider.reusableSocket() ? "keep-alive" : "close");
        headers.put("Accept", "*/*");
        headers.put("Accept-Encoding", "gzip, deflate");
        headers.put("User-Agent", settings.userAgent());

        // ensure no content-length
        headers.remove("Content-Length");
        BufferedOutputStream bos = new BufferedOutputStream(sock.getOutputStream(),
                settings.sendBufferSize());
        byte[] requestHead = requestHead();
        bos.write(requestHead);
        streamChunked(bos);
        bos.flush();
    }

    private void streamChunked(OutputStream os) throws IOException {
        byte[] b = new byte[settings.sendBufferSize()];
        int remaining = len.intValue();
        while (remaining > 0) {
            int read = data.read(b, 0, Math.min(remaining, b.length));
            if (-1 == read)
                throw new EOFException("Unexpected end of source stream");
            remaining -= read;
            if (read > 0) {
                os.write((Integer.toHexString(read) + CRLF).getBytes(OIO_CHARSET));
                os.write(b, 0, read);
                os.write(CRLF_BYTES);
            }
        }
        os.write(("0" + CRLF + CRLF).getBytes(OIO_CHARSET));
    }

    private void stream(OutputStream sos) throws IOException {
        byte[] b = new byte[settings.sendBufferSize()];
        int remaining = len.intValue();

        while (remaining > 0) {
            int read = data.read(b, 0, Math.min(remaining, b.length));
            if (-1 == read)
                throw new EOFException("Unexpected end of source stream");
            remaining -= read;
            sos.write(b, 0, read);
        }
    }

    private byte[] requestHead() throws UnsupportedEncodingException {
        StringBuilder qbuilder = new StringBuilder(null == uri.getRawQuery() ? ""
                : uri.getRawQuery());
        boolean removeTrailindAnd = 0 == qbuilder.length();
        for (Entry<String, String> h : query.entrySet()) {
            qbuilder.append("&").append(URLEncoder.encode(h.getKey(), "utf-8")).append("=")
                    .append(URLEncoder.encode(h.getValue(), "utf-8"));
        }
        String uriPath = uri.getRawPath();
        StringBuilder req = new StringBuilder(method)
                .append(" ")
                .append(uriPath != null && !uriPath.isEmpty() ? uriPath : "/")
                .append(qbuilder.length() > 0 ? "?" : "")
                .append(qbuilder.length() > 0 ? (removeTrailindAnd ? qbuilder.substring(1)
                        : qbuilder.toString()) : "").append(" ").append("HTTP/1.1")
                .append(CRLF);
        for (Entry<String, String> h : headers.entrySet()) {
            req.append(h.getKey()).append(": ").append(h.getValue()).append(CRLF);
        }
        byte[] res = req.append(CRLF).toString().getBytes(OIO_CHARSET);
        return res;
    }
} public class BeansRequest {

    private long size;
    private String policy;

    public BeansRequest() {

    }

    public long size() {
        return size;
    }

    public BeansRequest size(long size) {
        this.size = size;
        return this;
    }

    public String policy() {
        return policy;
    }

    public BeansRequest policy(String policy) {
        this.policy = policy;
        return this;
    }

} public class ChunkInfo {

    public ChunkInfo() {
    }

    private String url;
    private String real_url;
    private Long size;
    private String hash;
    private Position pos;

    public String url() {
        return url;
    }

    public String hash() {
        return hash;
    }

    public Position pos() {
        return pos;
    }

    public Long size() {
        return size;
    }

    public ChunkInfo url(String url) {
        this.url = url;
        return this;
    }

    public ChunkInfo real_url(String real_url) {
        this.real_url = real_url;
        return this;
    }

    public ChunkInfo size(Long size) {
        this.size = size;
        return this;
    }

    public ChunkInfo hash(String hash) {
        this.hash = hash;
        return this;
    }

    public ChunkInfo pos(Position pos) {
        this.pos = pos;
        return this;
    }

    public String id(){
        return url.substring(url.lastIndexOf("/") + 1);
    }

    public String finalUrl() {
        if (real_url != null && real_url.length() > 0) {
            return real_url;
        }
        return url;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .omitNullValues()
                .add("url", url)
                .add("real_url", real_url)
                .add("size", size)
                .add("hash", hash)
                .add("pos", pos)
                .toString();
    }
} public class ContainerInfo {

    private String name;
    private String account;
    private Long ctime;
    private Long init;
    private Long usage;
    private Long version;
    private String id;
    private String ns;
    private String type;
    private String user;
    private String schemavers;
    private String versionMainAdmin;
    private String versionMainAliases;
    private String versionMainChunks;
    private String versionMainContents;
    private String versionMainProperties;

    public ContainerInfo(String name) {
        this.name = name;
    }

    public String name() {
        return name;
    }

    public ContainerInfo name(String name) {
        this.name = name;
        return this;
    }

    public String account() {
        return account;
    }

    public ContainerInfo account(String account) {
        this.account = account;
        return this;
    }

    public Long ctime() {
        return ctime;
    }

    public ContainerInfo ctime(Long ctime) {
        this.ctime = ctime;
        return this;
    }

    public Long init() {
        return init;
    }

    public ContainerInfo init(Long init) {
        this.init = init;
        return this;
    }

    public Long usage() {
        return usage;
    }

    public ContainerInfo usage(Long usage) {
        this.usage = usage;
        return this;
    }

    public Long version() {
        return version;
    }

    public ContainerInfo version(Long version) {
        this.version = version;
        return this;
    }

    public String id() {
        return id;
    }

    public ContainerInfo id(String id) {
        this.id = id;
        return this;
    }

    public String ns() {
        return ns;
    }

    public ContainerInfo ns(String ns) {
        this.ns = ns;
        return this;
    }

    public String type() {
        return type;
    }

    public ContainerInfo type(String type) {
        this.type = type;
        return this;
    }

    public String user() {
        return user;
    }

    public ContainerInfo user(String user) {
        this.user = user;
        return this;
    }

    public String schemavers() {
        return schemavers;
    }

    public ContainerInfo schemavers(String schemavers) {
        this.schemavers = schemavers;
        return this;
    }

    public String versionMainAdmin() {
        return versionMainAdmin;
    }

    public ContainerInfo versionMainAdmin(String versionMainAdmin) {
        this.versionMainAdmin = versionMainAdmin;
        return this;
    }

    public String versionMainAliases() {
        return versionMainAliases;
    }

    public ContainerInfo versionMainAliases(String versionMainAliases) {
        this.versionMainAliases = versionMainAliases;
        return this;
    }

    public String versionMainChunks() {
        return versionMainChunks;
    }

    public ContainerInfo versionMainChunks(String versionMainChunks) {
        this.versionMainChunks = versionMainChunks;
        return this;
    }

    public String versionMainContents() {
        return versionMainContents;
    }

    public ContainerInfo versionMainContents(String versionMainContents) {
        this.versionMainContents = versionMainContents;
        return this;
    }

    public String versionMainProperties() {
        return versionMainProperties;
    }

    public ContainerInfo versionMainProperties(
            String versionMainProperties) {
        this.versionMainProperties = versionMainProperties;
        return this;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .omitNullValues()
                .add("name", name)
                .add("account", account)
                .add("m2-ctime", ctime)
                .add("m2-init", init)
                .add("m2-usage", usage)
                .add("id", id)
                .add("ns", ns)
                .add("type", type)
                .add("user-name", user)
                .toString();
    }
} public class LinkedServiceInfo {

    private Integer seq;
    private String type;
    private String host;
    private String args;

    public LinkedServiceInfo() {
    }

    public Integer seq() {
        return seq;
    }

    public LinkedServiceInfo seq(Integer seq) {
        this.seq = seq;
        return this;
    }

    public String type() {
        return type;
    }

    public LinkedServiceInfo type(String type) {
        this.type = type;
        return this;
    }

    public String host() {
        return host;
    }

    public LinkedServiceInfo host(String host) {
        this.host = host;
        return this;
    }

    public String args() {
        return args;
    }

    public LinkedServiceInfo args(String args) {
        this.args = args;
        return this;
    }
    
    @Override
    public String toString(){
        return MoreObjects.toStringHelper(this)
                .add("seq", seq)
                .add("type", type)
                .add("host", host)
                .add("args", args)
                .toString();
    }

} public class ListOptions {

    private int limit = -1;
    private String delimiter;
    private String prefix;
    private String marker;

    public ListOptions() {
    }

    public Integer limit() {
        return limit;
    }

    public ListOptions limit(int limit) {
        this.limit = limit;
        return this;
    }

    public String delimiter() {
        return delimiter;
    }

    public ListOptions delimiter(String delimiter) {
        this.delimiter = delimiter;
        return this;
    }

    public String prefix() {
        return prefix;
    }

    public ListOptions prefix(String prefix) {
        this.prefix = prefix;
        return this;
    }

    public String marker() {
        return marker;
    }

    public ListOptions marker(String marker) {
        this.marker = marker;
        return this;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("prefix", prefix)
                .add("delimiter", delimiter)
                .add("marker", marker)
                .add("limit", limit)
                .toString();
    }
} public class NamespaceInfo {

    private String ns;
    private Long chunksize;
    private Map<String, String> options;
    @SerializedName("storage_policy")
    private Map<String, String> storagePolicies;
    @SerializedName("storage_class")
    private Map<String, String> storageClasses;
    @SerializedName("data_security")
    private Map<String, String> dataSecurities;
    @SerializedName("data_treatments")
    private Map<String, String> dataTreatments;

    public NamespaceInfo() {
    }

    public String ns() {
        return ns;
    }

    public NamespaceInfo ns(String ns) {
        this.ns = ns;
        return this;
    }

    public Long chunksize() {
        return chunksize;
    }

    public NamespaceInfo chunksize(Long chunksize) {
        this.chunksize = chunksize;
        return this;
    }

    public Map<String, String> options() {
        return options;
    }

    public NamespaceInfo options(Map<String, String> options) {
        this.options = options;
        return this;
    }

    public Map<String, String> storagePolicies() {
        return storagePolicies;
    }

    public NamespaceInfo storagePolicies(Map<String, String> storagePolicies) {
        this.storagePolicies = storagePolicies;
        return this;
    }

    public Map<String, String> storageClasses() {
        return storageClasses;
    }

    public NamespaceInfo storageClasses(Map<String, String> storageClasses) {
        this.storageClasses = storageClasses;
        return this;
    }

    public Map<String, String> dataSecurities() {
        return dataSecurities;
    }

    public NamespaceInfo dataSecurities(Map<String, String> dataSecurities) {
        this.dataSecurities = dataSecurities;
        return this;
    }

    public Map<String, String> dataTreatments() {
        return dataTreatments;
    }

    public NamespaceInfo dataTreatments(Map<String, String> dataTreatments) {
        this.dataTreatments = dataTreatments;
        return this;
    }

} public class ObjectCreationOptions {

    private Long version;
    private Map<String, String> properties;
    private String policy;
    private String mimeType;
    private boolean simulateVersioning = false;

    public Long version() {
        return version;
    }

    public ObjectCreationOptions version(Long version) {
        this.version = version;
        return this;
    }

    public Map<String, String> properties() {
        return properties;
    }

    public ObjectCreationOptions properties(Map<String, String> properties) {
        this.properties = properties;
        return this;
    }

    public String policy() {
        return policy;
    }

    public ObjectCreationOptions policy(String policy) {
        this.policy = policy;
        return this;
    }

    public String mimeType() {
        return mimeType;
    }

    public ObjectCreationOptions mimeType(String mimeType) {
        this.mimeType = mimeType;
        return this;
    }

    public boolean simulateVersioning() {
        return simulateVersioning;
    }

    public ObjectCreationOptions simulateVersioning(boolean simulateVersioning) {
        this.simulateVersioning = simulateVersioning;
        return this;
    }

} public class ObjectDeletionOptions {

    private Long version;
    private boolean simulateVersioning = false;
    private boolean deleteMarker = false;

    public Long version() {
        return version;
    }

    public ObjectDeletionOptions version(Long version) {
        this.version = version;
        return this;
    }

    public boolean simulateVersioning() {
        return simulateVersioning;
    }

    public ObjectDeletionOptions simulateVersioning(boolean simulateVersioning) {
        this.simulateVersioning = simulateVersioning;
        return this;
    }

    public boolean deleteMarker() {
        return deleteMarker;
    }

    public ObjectDeletionOptions deleteMarker(boolean deleteMarker) {
        this.deleteMarker = deleteMarker;
        return this;
    }

} public class ObjectInfo {

    private OioUrl url;
    private String oid;
    private Long ctime;
    private Boolean deleted = false;
    private String policy;
    private String hash;
    private String hashMethod;
    private String chunkMethod;
    private Long size;
    private Long version;
    private String mimeType;
    private Map<String, String> properties;
    private List<ChunkInfo> chunks;
    private ECInfo ecinfo;
    private RequestContext reqCtx;

    private transient Map<Integer, List<ChunkInfo>> sortedChunks;

    private static final Comparator<ChunkInfo> comparator = new Comparator<ChunkInfo>() {

        @Override
        public int compare(ChunkInfo c1, ChunkInfo c2) {
            return c1.pos()
                    .compare(c2.pos());
        }
    };

    public ObjectInfo() {
        // TODO: content hash
        this.properties = new HashMap<String, String>();
        this.hash = Hash.md5()
                .hashBytes("".getBytes(OIO_CHARSET))
                .toString();
    }

    public List<ChunkInfo> chunks() {
        return chunks;
    }

    public Map<Integer, List<ChunkInfo>> sortedChunks() {
        return sortedChunks;
    }

    public ObjectInfo chunks(List<ChunkInfo> chunks) {
        this.sortedChunks = sortChunks(chunks);
        this.chunks = chunks;
        return this;
    }

    public ObjectInfo size(Long size) {
        this.size = size;
        return this;
    }

    public ObjectInfo hash(String hash) {
        this.hash = hash;
        return this;
    }

    public ObjectInfo url(OioUrl url) {
        this.url = url;
        return this;
    }

    public OioUrl url() {
        return url;
    }

    public String oid() {
        return oid;
    }

    public ObjectInfo oid(String oid) {
        this.oid = oid;
        return this;
    }

    public String hash() {
        return hash;
    }

    public Long size() {
        return size;
    }

    public String policy() {
        return policy;
    }

    public ObjectInfo policy(String policy) {
        this.policy = policy;
        return this;
    }

    public boolean deleted() {
        return deleted;
    }

    public Long ctime() {
        return ctime;
    }

    public ObjectInfo ctime(Long ctime) {
        this.ctime = ctime;
        return this;
    }

    public String hashMethod() {
        return hashMethod;
    }

    public ObjectInfo hashMethod(String hashMethod) {
        this.hashMethod = hashMethod;
        return this;
    }

    public String chunkMethod() {
        return chunkMethod;
    }

    public ObjectInfo chunkMethod(String chunkMethod) {
        this.chunkMethod = chunkMethod;
        this.ecinfo = ECInfo.fromString(chunkMethod);
        return this;
    }

    public Long version() {
        return version;
    }

    public ObjectInfo version(Long version) {
        this.version = version;
        return this;
    }

    @Deprecated
    public String mtype() {
        return mimeType();
    }

    @Deprecated
    public ObjectInfo mtype(String mimeType) {
        return mimeType(mimeType);
    }

    public String mimeType() {
        return mimeType;
    }

    public ObjectInfo mimeType(String mimeType) {
        this.mimeType = mimeType;
        return this;
    }

    public Integer nbchunks() {
        return isEC() ? chunks.size() : sortedChunks.size();
    }

    public Long chunksize(Integer pos) {
        return sortedChunks.get(pos).get(0).size();
    }

    public Map<String, String> properties() {
        return properties;
    }

    public ObjectInfo properties(Map<String, String> properties) {
        this.properties = properties;
        return this;
    }

    public ECInfo ecinfo(){
    	return ecinfo;
    }

    public boolean isEC(){
    	return null != ecinfo;
    }

    public long metachunksize(int pos) {
        if (!isEC())
            return -1;
        long maxmcsize = ecinfo.k() * sortedChunks.get(pos).get(0).size();
        long remaining = size - (pos * maxmcsize);
        return Math.min(maxmcsize, remaining);
    }

    /**
     * @return a string suitable to be sent as {@code CHUNK_META_FULL_PATH} header value.
     */
    public String fullpath() {
        if (this.url.account() == null || this.url.account().isEmpty()
                || this.url.container() == null || this.url.container().isEmpty()
                || this.url.object() == null || this.url.object().isEmpty()
                || this.version() == null || this.version() == 0
                || this.oid() == null || this.oid().isEmpty()) {
            throw new IllegalArgumentException("Can't encode fullpath");
        }
        StringBuilder sb = new StringBuilder();
        sb.append(Strings.quote(this.url.account()));
        sb.append('/');
        sb.append(Strings.quote(this.url.container()));
        sb.append('/');
        sb.append(Strings.quote(this.url.object()));
        sb.append('/');
        sb.append(Strings.quote(this.version()));
        sb.append('/');
        sb.append(Strings.quote(this.oid()));
        return sb.toString();
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .omitNullValues()
                .add("url", url)
                .add("ctime", ctime)
                .add("mime-type", mimeType)
                .add("deleted", deleted)
                .add("policy", policy)
                .add("hash", hash)
                .add("hash-method", hashMethod)
                .add("chunk-method", chunkMethod)
                .add("size", size)
                .add("version", version)
                .add("props", properties)
                .add("chunks", chunks)
                .toString();
    }

    public RequestContext requestContext() {
        return this.reqCtx;
    }

    /**
     * Set the context of the request that lead to this object's creation.
     *
     * @param reqCtx
     *            the request context
     * @return {@code this}
     */
    public ObjectInfo withRequestContext(RequestContext reqCtx) {
        this.reqCtx = reqCtx;
        return this;
    }

    /* -- INTERNAL -- */

    private Map<Integer, List<ChunkInfo>> sortChunks(
            List<ChunkInfo> chunks) {

        Map<Integer, List<ChunkInfo>> res = new HashMap<Integer, List<ChunkInfo>>();
        for (ChunkInfo ci : chunks) {
            List<ChunkInfo> l = res.get(ci.pos().meta());
            if (null == l) {
                l = new ArrayList<ChunkInfo>();
                res.put(ci.pos().meta(), l);
            }
            l.add(ci);
        }
        for (List<ChunkInfo> l : res.values())
            Collections.sort(l, comparator);

        return res;
    }

} public class ObjectList {

    private List<ObjectView> objects;
    private List<String> prefixes;
    private ListOptions listOptions;
    private boolean truncated;
    private String nextMarker;

    /**
     * 
     */
    public ObjectList() {
    }

    /**
     * List of {@link ObjectView} found in the container
     * 
     * @return a list of {@link ObjectView}
     */
    public List<ObjectView> objects() {
        return objects;
    }

    /**
     * Specifies the listing objects
     *
     * @param objects
     *            the value to set
     * @return this
     */
    public ObjectList objects(List<ObjectView> objects) {
        this.objects = objects;
        return this;
    }

    /**
     * Returns {@code true} if this listing does not contains all objects
     * matching the specified {@code ListOptions}, {@code false} otherwise
     * 
     * @return {@code true} if this listing does not contains all objects
     *         matching the specified {@code ListOptions}, {@code false}
     *         otherwise
     */
    public boolean truncated() {
        return truncated;
    }

    /**
     * Returns the prefixes
     * 
     * @return the prefixes
     */
    public List<String> prefixes() {
        return prefixes;
    }

    /**
     * Returns the options
     * 
     * @return the options
     */
    public ListOptions listOptions() {
        return listOptions;
    }

    /**
     * When a listing is {@link #truncated()}, to get the rest of the list, you
     * have to run the request again with a marker. This method returns this
     * marker (usually the last element of the previous listing).
     *
     * @return the {@link String} to use as {@link ListOptions#marker(String)}
     *         input to get the next page of results
     * @since 1.0.0
     */
    public String nextMarker() {
        return this.nextMarker;
    }

    /**
     * Specifies the listing prefixes
     * 
     * @param prefixes
     *            the value to set
     * @return this
     */
    public ObjectList prefixes(List<String> prefixes) {
        this.prefixes = prefixes;
        return this;
    }

    /**
     * Specifies the listing options
     * 
     * @param listOptions
     *            the value to set
     * @return this
     */
    public ObjectList listOptions(ListOptions listOptions) {
        this.listOptions = listOptions;
        return this;
    }

    /**
     * Specify the next marker.
     *
     * @param nextMarker
     *            the value to set
     * @return this
     * @since 1.0.0
     */
    public ObjectList nextMarker(String nextMarker) {
        this.nextMarker = nextMarker;
        return this;
    }

    /**
     * Specifies if the listing is truncated
     * 
     * @param truncated
     *            the value to set
     * @return this
     */
    public ObjectList truncated(boolean truncated) {
        this.truncated = truncated;
        return this;
    }

    /**
     * Representation of an object inside a container.
     * 
     * Here is a description of all fields meaning.
     * <ul>
     * <li>Name: the name of the object</li>
     * <li>Version: the current object version</li>
     * <li>Creation time: Timestamp where the object was created</li>
     * <li>System metadata: Meta informations about the object</li>
     * <li>deleted: {@code true} if the object is deleted, {@code false}
     * otherwise</li>
     * <li>policy: the storage policy used to store the object</li>
     * <li>hash: the object data md5sum</li>
     * <li>size: the total size of the object</li>
     * </ul>
     */
    public static class ObjectView {

        private String name;
        private String ver;
        private String ctime;
        // @SerializedName("system_metadata")
        private String sysmd;
        private Boolean deleted;
        private String policy;
        private String hash;
        private Long size;

        public ObjectView() {
        }

        /**
         * Returns the name of the object
         * 
         * @return the name of the object
         */
        public String name() {
            return name;
        }

        /**
         * Specifies the name of the object
         * 
         * @param name
         *            the value to set
         * @return this
         */
        public ObjectView name(String name) {
            this.name = name;
            return this;
        }

        /**
         * Returns the object version
         * 
         * @return the object version
         */
        public String version() {
            return ver;
        }

        /**
         * Specifies the object version
         * 
         * @param version
         *            the version to set
         * @return this
         */
        public ObjectView version(String version) {
            this.ver = version;
            return this;
        }

        /**
         * Returns the object creation time
         * 
         * @return the object creation time
         */
        public String ctime() {
            return ctime;
        }

        /**
         * Specifies the object creation time
         * 
         * @param ctime
         *            the value to set
         * @return this
         */
        public ObjectView ctime(String ctime) {
            this.ctime = ctime;
            return this;
        }

        /**
         * Returns the system metadata
         * 
         * @return the system metadata
         */
        public String sysmd() {
            return sysmd;
        }

        /**
         * Specifies the object system metadata
         * 
         * @param sysmd
         *            the metadata to set
         * @return this
         */
        public ObjectView sysmd(String sysmd) {
            this.sysmd = sysmd;
            return this;
        }

        /**
         * Returns {@code true} if the object is deleted, {@code false}
         * otherwise
         * 
         * @return {@code true} if the object is deleted, {@code false}
         *         otherwise
         */
        public Boolean deleted() {
            return deleted;
        }

        /**
         * Specifies if content is deleted or not
         * 
         * @param deleted
         *            the value to set
         * @return this
         */
        public ObjectView deleted(Boolean deleted) {
            this.deleted = deleted;
            return this;
        }

        /**
         * Returns the policy
         * 
         * @return the policy
         */
        public String policy() {
            return policy;
        }

        /**
         * Specifies the policy
         * 
         * @param policy
         *            the policy to set
         * @return this
         */
        public ObjectView policy(String policy) {
            this.policy = policy;
            return this;
        }

        /**
         * Returns the hash
         * 
         * @return the hash
         */
        public String hash() {
            return hash;
        }

        /**
         * Specifies the hash
         * 
         * @param hash
         *            the hash to set
         * @return this
         */
        public ObjectView hash(String hash) {
            this.hash = hash;
            return this;
        }

        /**
         * Returns the size
         * 
         * @return the size
         */
        public Long size() {
            return size;
        }

        /**
         * Specifies the size
         * 
         * @param size
         *            the size to set
         * @return this
         */
        public ObjectView size(Long size) {
            this.size = size;
            return this;
        }

    }
} public class OioUrl {

    private static final byte[] BACK_ZERO = { '\0' };

    private String ns;
    private String account;
    private String container;
    private String cid;
    private String object;

    private OioUrl(String ns, String account, String container, String cid,
            String object) {
        this.ns = ns;
        this.account = account;
        this.container = container;
        this.cid = cid;
        this.object = object;
    }
    
    
    
    public static OioUrl url(String account, String container) {
        return url(account, container, null);
    }

    public static OioUrl url(String account, String container,
            String object) {
        checkArgument(!nullOrEmpty(account),
                "account cannot be null or empty");
        checkArgument(!nullOrEmpty(container),
                "container cannot be null or empty");
        return new OioUrl(null,
                account,
                container,
                cid(account, container),
                object);
    }
    
    public static OioUrl url(String ns, String account, String container,
            String object) {
        checkArgument(!nullOrEmpty(account),
                "account cannot be null or empty");
        checkArgument(!nullOrEmpty(container),
                "container cannot be null or empty");
        return new OioUrl(ns, 
                account,
                container,
                cid(account, container),
                object);
    }

    public String namespace() {
        return ns;
    }

    public OioUrl namespace(String namespace) {
        this.ns = namespace;
        return this;
    }

    public String account() {
        return account;
    }

    public OioUrl account(String account) {
        this.account = account;
        return this;
    }

    public String container() {
        return container;
    }

    public OioUrl container(String container) {
        this.container = container;
        return this;
    }

    public String object() {
        return object;
    }

    public OioUrl object(String object) {
        this.object = object;
        return this;
    }

    public String cid() {
        return cid;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .omitNullValues()
                .add("namespace", ns)
                .add("account", account)
                .add("container", container)
                .add("object", object)
                .toString();
    }

    /**
     * Generates the container id from the specified account and container name
     * 
     * @param account
     *            the name of the account
     * @param container
     *            the name of the container
     * @return the generated id
     */
    public static String cid(String account, String container) {
        return Hash.sha256()
                .putBytes(account.getBytes(OIO_CHARSET))
                .putBytes(BACK_ZERO)
                .putBytes(container.getBytes(OIO_CHARSET))
                .hash().toString();
    }

} public class ReferenceInfo {

    private List<LinkedServiceInfo> dir;
    private List<LinkedServiceInfo> srv;

    public ReferenceInfo() {
    }

    public List<LinkedServiceInfo> dir() {
        return dir;
    }

    public ReferenceInfo dir(List<LinkedServiceInfo> dir) {
        this.dir = dir;
        return this;
    }

    public List<LinkedServiceInfo> srv() {
        return srv;
    }

    public ReferenceInfo srv(List<LinkedServiceInfo> srv) {
        this.srv = srv;
        return this;
    }
    
    @Override
    public String toString(){
        return MoreObjects.toStringHelper(this)
                .add("dir", dir)
                .add("srv", srv)
                .toString();
    }

} public class ServiceInfo {

    private String addr;
    private Integer score;
    private Map<String, String> tags;
    
    public ServiceInfo() {
    }

    public String addr() {
        return addr;
    }

    public ServiceInfo addr(String addr) {
        this.addr = addr;
        return this;
    }

    public Integer score() {
        return score;
    }

    public ServiceInfo score(Integer score) {
        this.score = score;
        return this;
    }

    public Map<String, String> tags() {
        return tags;
    }

    public ServiceInfo tags(Map<String, String> tags) {
        this.tags = tags;
        return this;
    }
    
    @Override
    public String toString(){
        return MoreObjects.toStringHelper(this)
                .add("addr", addr)
                .add("score", score)
                .add("tags", tags)
                .toString();
    }
    
}
import static io.openio.sds.common.Check.checkArgument;
import static io.openio.sds.common.JsonUtils.gson;
import static io.openio.sds.common.JsonUtils.gsonForObject;
import static io.openio.sds.common.JsonUtils.jsonFromMap;
import static io.openio.sds.common.OioConstants.ACCOUNT_HEADER;
import static io.openio.sds.common.OioConstants.ACTION_MODE_HEADER;
import static io.openio.sds.common.OioConstants.CONTAINER_DEL_PROP;
import static io.openio.sds.common.OioConstants.CONTAINER_GET_PROP;
import static io.openio.sds.common.OioConstants.CONTAINER_SET_PROP;
import static io.openio.sds.common.OioConstants.CONTAINER_SYS_NAME_HEADER;
import static io.openio.sds.common.OioConstants.CONTENT_META_CHUNK_METHOD_HEADER;
import static io.openio.sds.common.OioConstants.CONTENT_META_CTIME_HEADER;
import static io.openio.sds.common.OioConstants.CONTENT_META_HASH_HEADER;
import static io.openio.sds.common.OioConstants.CONTENT_META_HASH_METHOD_HEADER;
import static io.openio.sds.common.OioConstants.CONTENT_META_ID_HEADER;
import static io.openio.sds.common.OioConstants.CONTENT_META_LENGTH_HEADER;
import static io.openio.sds.common.OioConstants.CONTENT_META_MIME_TYPE_HEADER;
import static io.openio.sds.common.OioConstants.CONTENT_META_POLICY_HEADER;
import static io.openio.sds.common.OioConstants.CONTENT_META_VERSION_HEADER;
import static io.openio.sds.common.OioConstants.CREATE_CONTAINER_FORMAT;
import static io.openio.sds.common.OioConstants.CS_GETSRV_FORMAT;
import static io.openio.sds.common.OioConstants.CS_NSINFO_FORMAT;
import static io.openio.sds.common.OioConstants.DELETE_CONTAINER_FORMAT;
import static io.openio.sds.common.OioConstants.DELETE_OBJECT_FORMAT;
import static io.openio.sds.common.OioConstants.DELETE_MARKER_PARAM;
import static io.openio.sds.common.OioConstants.DELIMITER_PARAM;
import static io.openio.sds.common.OioConstants.DIR_LINK_SRV_FORMAT;
import static io.openio.sds.common.OioConstants.DIR_LIST_SRV_FORMAT;
import static io.openio.sds.common.OioConstants.DIR_REF_CREATE_FORMAT;
import static io.openio.sds.common.OioConstants.DIR_REF_DELETE_FORMAT;
import static io.openio.sds.common.OioConstants.DIR_REF_SHOW_FORMAT;
import static io.openio.sds.common.OioConstants.DIR_UNLINK_SRV_FORMAT;
import static io.openio.sds.common.OioConstants.FLUSH_PARAM;
import static io.openio.sds.common.OioConstants.GET_BEANS_FORMAT;
import static io.openio.sds.common.OioConstants.GET_CONTAINER_INFO_FORMAT;
import static io.openio.sds.common.OioConstants.GET_OBJECT_FORMAT;
import static io.openio.sds.common.OioConstants.INVALID_OBJECT_INFO_MSG;
import static io.openio.sds.common.OioConstants.INVALID_OPTIONS_MSG;
import static io.openio.sds.common.OioConstants.INVALID_URL_MSG;
import static io.openio.sds.common.OioConstants.LIST_MARKER_HEADER;
import static io.openio.sds.common.OioConstants.LIST_OBJECTS_FORMAT;
import static io.openio.sds.common.OioConstants.LIST_TRUNCATED_HEADER;
import static io.openio.sds.common.OioConstants.M2_CTIME_HEADER;
import static io.openio.sds.common.OioConstants.M2_INIT_HEADER;
import static io.openio.sds.common.OioConstants.M2_USAGE_HEADER;
import static io.openio.sds.common.OioConstants.M2_VERSION_HEADER;
import static io.openio.sds.common.OioConstants.MARKER_PARAM;
import static io.openio.sds.common.OioConstants.MAX_PARAM;
import static io.openio.sds.common.OioConstants.NS_HEADER;
import static io.openio.sds.common.OioConstants.OBJECT_DEL_PROP;
import static io.openio.sds.common.OioConstants.OBJECT_GET_PROP;
import static io.openio.sds.common.OioConstants.OBJECT_SET_PROP;
import static io.openio.sds.common.OioConstants.OIO_ACTION_MODE_HEADER;
import static io.openio.sds.common.OioConstants.OIO_CHARSET;
import static io.openio.sds.common.OioConstants.PREFIX_PARAM;
import static io.openio.sds.common.OioConstants.PUT_OBJECT_FORMAT;
import static io.openio.sds.common.OioConstants.SCHEMA_VERSION_HEADER;
import static io.openio.sds.common.OioConstants.SIMULATE_VERSIONING_HEADER;
import static io.openio.sds.common.OioConstants.TYPE_HEADER;
import static io.openio.sds.common.OioConstants.USER_NAME_HEADER;
import static io.openio.sds.common.OioConstants.VERSION_MAIN_ADMIN_HEADER;
import static io.openio.sds.common.OioConstants.VERSION_MAIN_ALIASES_HEADER;
import static io.openio.sds.common.OioConstants.VERSION_MAIN_CHUNKS_HEADER;
import static io.openio.sds.common.OioConstants.VERSION_MAIN_CONTENTS_HEADER;
import static io.openio.sds.common.OioConstants.VERSION_MAIN_PROPERTIES_HEADER;
import static io.openio.sds.common.OioConstants.VERSION_PARAM;
import static io.openio.sds.common.Strings.nullOrEmpty;
import static io.openio.sds.http.OioHttpHelper.longHeader;
import static io.openio.sds.http.Verifiers.CONTAINER_VERIFIER;
import static io.openio.sds.http.Verifiers.OBJECT_VERIFIER;
import static io.openio.sds.http.Verifiers.REFERENCE_VERIFIER;
import static io.openio.sds.http.Verifiers.STANDALONE_VERIFIER;
import static java.lang.String.format;
import io.openio.sds.RequestContext;
import io.openio.sds.common.JsonUtils;
import io.openio.sds.common.OioConstants;
import io.openio.sds.common.Strings;
import io.openio.sds.exceptions.ContainerExistException;
import io.openio.sds.exceptions.ContainerNotFoundException;
import io.openio.sds.exceptions.ObjectNotFoundException;
import io.openio.sds.exceptions.OioException;
import io.openio.sds.exceptions.OioSystemException;
import io.openio.sds.http.OioHttp;
import io.openio.sds.http.OioHttpResponse;
import io.openio.sds.http.OioHttp.RequestBuilder;
import io.openio.sds.models.BeansRequest;
import io.openio.sds.models.ChunkInfo;
import io.openio.sds.models.ContainerInfo;
import io.openio.sds.models.LinkedServiceInfo;
import io.openio.sds.models.ListOptions;
import io.openio.sds.models.NamespaceInfo;
import io.openio.sds.models.ObjectCreationOptions;
import io.openio.sds.models.ObjectDeletionOptions;
import io.openio.sds.models.ObjectInfo;
import io.openio.sds.models.ObjectList;
import io.openio.sds.models.OioUrl;
import io.openio.sds.models.ReferenceInfo;
import io.openio.sds.models.ServiceInfo;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
package io.openio.sds.proxy;




/**
 * Simple OpenIO proxyd http client based on API reference available at
 * https://github.com/open-io/oio-sds/wiki/OpenIO-Proxyd-API-Reference
 */
public class ProxyClient {

    private final static String CONTENT_HEADER_PREFIX = "x-oio-content-meta-";
    private final static List<String> SYSMETA_KEYS = Arrays.asList(
            "chunk-method", "ctime", "mtime", "deleted", "hash",
            "hash-method", "id", "length", "mime-type", "name", "policy",
            "size", "version");

    private OioHttp http;
    private ProxySettings settings;
    private List<InetSocketAddress> hosts = null;

    public ProxyClient(OioHttp http, ProxySettings settings) {
        this.http = http;
        this.settings = settings;
        this.hosts = this.settings.allHosts();
    }

    /* -- CS -- */

    /**
     * Retrieves technical informations relative to the proxyd served namespace
     *
     * @param reqCtx
     *            common parameters to all requests
     * @return the matching {@code NamespaceInfo}
     */
    public NamespaceInfo getNamespaceInfo(RequestContext reqCtx) throws OioException {
        return http.get(format(CS_NSINFO_FORMAT, settings.url(), settings.ns()))
                .hosts(hosts).verifier(STANDALONE_VERIFIER)
                .withRequestContext(reqCtx).execute(NamespaceInfo.class);
    }

    /**
     * Returns available services of the specified type
     * 
     * @param type
     *            the type of service to get
     * @param reqCtx
     *            common parameters to all requests
     * @return the list of matching services
     * @throws OioException
     *             if any error occurs during request execution
     */
    public List<ServiceInfo> getServices(String type, RequestContext reqCtx) throws OioException {
        OioHttpResponse resp = http.get(
                format(CS_GETSRV_FORMAT, settings.url(), settings.ns(), type))
                .hosts(hosts).verifier(STANDALONE_VERIFIER)
                .withRequestContext(reqCtx).execute();
        return serviceInfoListAndClose(resp);
    }

    /* -- DIRECTORY -- */

    /**
     * Creates a reference in Oio directory.
     * 
     * @param url
     *            the url of the reference to create
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void createReference(OioUrl url) throws OioException {
        createReference(url, new RequestContext());
    }

    /**
     * Creates a reference in Oio directory.
     * 
     * @param url
     *            the url of the reference
     * @param reqCtx
     *            common parameters to all requests
     * @throws OioException
     *             if any error occurs during request execution
     */
    public void createReference(OioUrl url, RequestContext reqCtx)
            throws OioException {
        checkArgument(null != url, INVALID_URL_MSG);
        http.post(
                format(DIR_REF_CREATE_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),