public static OioUrl url(String account, String container) {
    return url(account, container, null);
} 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 TestHelper {

    private static final String TEST_ACCOUNT = "TEST";
    private static String defaultNs = "OPENIO";
    private static boolean isLoaded = false;
    private static Settings settings = null;

    public static ProxySettings proxySettings() {
        return settings().proxy();
    }

    public static Settings settings() {
        if (!isLoaded)
            loadConfiguration();
        return settings;
    }

    public static String testAccount() {
        return TEST_ACCOUNT;
    }

    // TODO provide actual test_file
    public static String test_file() {
        return "/test/file";
    }

    public static byte[] bytes(long size) {
        byte[] res = new byte[(int) size];
        if (size > 0)
            new Random().nextBytes(res);
        return res;
    }

    /**
     * Load configuration from "sds.conf" file found either in "$HOME/.oio/" or
     * "/etc/oio/".
     *
     * @param myNs
     *            name of the namespace to load
     * @throws FileNotFoundException
     *             if no configuration file could be found in default places
     */
    public static void loadConfiguration(String myNs) throws FileNotFoundException {
        settings = Settings.forNamespace(myNs);
        isLoaded = true;
    }

    /**
     * Load configuration from default file for the namespace defined in
     * "OIO_NS" environment variable.
     *
     * @throws RuntimeException
     *             if no configuration file could be found in default places
     */
    public static void loadConfiguration() {
        String myNs = System.getenv("OIO_NS");
        if (myNs == null || myNs.isEmpty())
            myNs = System.getProperty("OIO_NS");
        if (myNs == null || myNs.isEmpty())
            myNs = defaultNs;
        try {
            loadConfiguration(myNs);
        } catch (FileNotFoundException fnfe) {
            throw new RuntimeException(fnfe);
        }
    }

    public static ObjectInfo newTestObjectInfo(OioUrl url, long size) {
        ObjectInfo info = new ObjectInfo();
        info.url(url);
        info.size(size);
        info.version(Long.valueOf(1234567890));
        info.oid("0123456789ABCDEF");

        List<ChunkInfo> l = new ArrayList<ChunkInfo>();
        for (int i = 0; i < 3; i++) {
            String chunkId = Hash.sha256().hashBytes(Integer.toString(i).getBytes()).toString();
            String chunkUrl = format("http://127.0.0.1:601%d/%s", i, chunkId);
            ChunkInfo ci = new ChunkInfo().pos(Position.simple(0)).size(size).url(chunkUrl);
            l.add(ci);
        }
        info.chunks(l);

        return info;
    }

    public static byte[] toByteArray(InputStream is) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        int r;
        while ((r = is.read(buf, 0, buf.length)) != -1) {
            out.write(buf, 0, r);
        }
        return out.toByteArray();
    }

    public static boolean isSdsBranch(String sdsBranch) {
        String currentSdsBranch = null;
        try {
            currentSdsBranch = System.getenv("SDS_BRANCH");
        } catch (NullPointerException e) {
        }
        if (currentSdsBranch == null) {
            currentSdsBranch = "master";
        }
        return currentSdsBranch.equals(sdsBranch);
    }
} public interface SocketProvider {

    public Socket getSocket(String host, int port);
    public Socket getSocket(InetSocketAddress addr);

    public boolean reusableSocket();

} public class SocketProviders {

    /**
     * Socket provider that reuses socket instances if possible.
     *
     * @param poolSettings
     *            pooling settings to use
     * @param httpSettings
     *            http settings to use
     * @param target
     *            The address that will be requested most of the time. A request
     *            to another address will create a new socket instead of taking
     *            it from the pool.
     * @return new {@code SocketProvider} instance
     */
    public static SocketProvider pooledSocketProvider(PoolingSettings poolSettings,
            final OioHttpSettings httpSettings, final InetSocketAddress target) {

        final SocketPool pool = new SocketPool(httpSettings, poolSettings, target);
        return new AbstractSocketProvider() {

            @Override
            public boolean reusableSocket() {
                return true;
            }

            @Override
            public Socket getSocket(String host, int port) {
                return getSocket(new InetSocketAddress(host, port));
            }

            @Override
            public Socket getSocket(InetSocketAddress addr) {
                if (!addr.equals(target)) {
                    Socket sock = new Socket();
                    configureAndConnect(sock, addr, httpSettings);
                    return sock;
                } else {
                    return pool.lease();
                }
            }
        };
    }

    public static SocketProvider directSocketProvider(final OioHttpSettings http) {
        return new AbstractSocketProvider() {

            @Override
            public boolean reusableSocket() {
                return false;
            }

            @Override
            public Socket getSocket(String host, int port) {
                InetSocketAddress target = new InetSocketAddress(host, port);
                return getSocket(target);
            }

            @Override
            public Socket getSocket(InetSocketAddress target) {
                Socket sock = new Socket();
                configureAndConnect(sock, target, http);
                return sock;
            }
        };
    }
} public class ContainerNotFoundException extends OioException {

    /**
     * 
     */
    private static final long serialVersionUID = -4662162990209727136L;
    
    public ContainerNotFoundException() {
        super(null);
    }
    public ContainerNotFoundException(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 ReferenceNotFoundException extends OioException {

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

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

} 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 OioHttpSettings {

	private Integer sendBufferSize = 131072;
	private Integer receiveBufferSize = 131072;
	private Boolean setSocketBufferSize = false;
	private Integer connectTimeout = 30000;
	private Integer readTimeout = 60000;
	private String userAgent = "oio-http";

	public OioHttpSettings() {
	}

	public Integer sendBufferSize() {
		return sendBufferSize;
	}

	public OioHttpSettings sendBufferSize(Integer sendBufferSize) {
		this.sendBufferSize = sendBufferSize;
		return this;
	}

	public Integer receiveBufferSize() {
		return receiveBufferSize;
	}

	public OioHttpSettings receiveBufferSize(Integer receiveBufferSize) {
		this.receiveBufferSize = receiveBufferSize;
		return this;
	}

    /**
     * Should the size of the socket buffers be explicitly set?
     * When true, explicitly set the send buffer size (resp. receive buffer
     * size) to {@link #sendBufferSize} (resp. {@link #receiveBufferSize}).
     * When false, let the kernel adjust the size automatically.
     *
     * @return true when the API should set the socket buffer sizes,
     *  false when it should let the kernel decide.
     */
    public Boolean setSocketBufferSize() {
        return this.setSocketBufferSize;
    }

    /**
     * Should the size of the socket buffers be explicitly set?
     * When true, explicitly set the send buffer size (resp. receive buffer
     * size) to {@link #sendBufferSize} (resp. {@link #receiveBufferSize}).
     * When false, let the kernel adjust the size automatically.
     *
     * @param setSocketBufferSize
     * @return this
     */
    public OioHttpSettings setSocketBufferSize(Boolean setSocketBufferSize) {
        this.setSocketBufferSize = setSocketBufferSize;
        return this;
    }

	public Integer connectTimeout() {
		return connectTimeout;
	}

	public OioHttpSettings connectTimeout(Integer connectTimeout) {
		this.connectTimeout = connectTimeout;
		return this;
	}

	public Integer readTimeout() {
		return readTimeout;
	}

	public OioHttpSettings readTimeout(Integer readTimeout) {
		this.readTimeout = readTimeout;
		return this;
	}

    public String userAgent() {
        return userAgent;
    }

    public OioHttpSettings userAgent(String userAgent) {
        this.userAgent = userAgent;
        return this;
    }
} 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 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 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 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();
    }
    
} public class PoolingSettings {

    private Boolean enabled = true;
    private Long cleanDelay = 1L;
    private Long cleanRate = 5L;
    private Integer maxForEach = 1024;
    private Integer maxTotal = 8192;
    private Integer maxWait = 5000;
    private Integer idleTimeout = 3000;

    public PoolingSettings() {
    }

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

    /**
     * Specifies if the socket pooling is enabled
     * 
     * @param enabled
     *            the value to set
     * @return this
     */
    public PoolingSettings enabled(Boolean enabled) {
        this.enabled = enabled;
        return this;
    }

    /**
     * Returns the idle socket first cleanup delay in seconds
     * 
     * @return the idle socket first cleanup delay in seconds
     */
    public Long cleanDelay() {
        return cleanDelay;
    }

    /**
     * Specifies the idle socket first cleanup delay in seconds
     * 
     * @param cleanDelay
     *            the value to set
     * @return this
     */
    public PoolingSettings cleanDelay(Long cleanDelay) {
        this.cleanDelay = cleanDelay;
        return this;
    }

    /**
     * Returns the idle socket cleanup frequency is seconds
     * 
     * @return the idle socket cleanup frequency is seconds
     */
    public Long cleanRate() {
        return cleanRate;
    }

    /**
     * Specifies the idle socket cleanup frequency is seconds
     * 
     * @param cleanRate
     *            the value to set
     * @return this
     */
    public PoolingSettings cleanRate(Long cleanRate) {
        this.cleanRate = cleanRate;
        return this;
    }

    /**
     * Returns the socket idle timeout in milliseconds
     * 
     * @return the socket idle timeout in milliseconds
     */
    public Integer idleTimeout() {
        return idleTimeout;
    }

    /**
     * Specifies the idle timeout in milliseconds
     * 
     * @param idleTimeout
     *            the value to set
     * @return this
     */
    public PoolingSettings idleTimeout(Integer idleTimeout) {
        this.idleTimeout = idleTimeout;
        return this;
    }

    /**
     * Returns the max leased socket allowed per route
     * 
     * @return the max leased item allowed by pool
     */
    public Integer maxForEach() {
        return maxForEach;
    }

    /**
     * Specifies the max leased items in each pool in a pool group
     * 
     * @param maxForEach
     *            the value to set
     * @return this
     */
    public PoolingSettings maxForEach(Integer maxForEach) {
        this.maxForEach = maxForEach;
        return this;
    }

    /**
     * Returns the max leased socket allowed in total
     * 
     * @return the max leased socket allowed in total
     */
    public Integer maxTotal() {
        return maxTotal;
    }

    /**
     * Specifies the max leased socket allowed in total
     * 
     * @param maxTotal the value to set
     * @return this
     */
    public PoolingSettings maxTotal(Integer maxTotal) {
        this.maxTotal = maxTotal;
        return this;
    }

    /**
     * Returns the max time allowed to wait for a connection to become available
     * (in milliseconds)
     * 
     * @return the max time allowed to wait for a connection to become available
     *         (in milliseconds)
     */
    public Integer maxWait() {
        return maxWait;
    }

    /**
     * Specifies the max time allowed to wait for a connection to become
     * available
     * 
     * @param maxWait
     *            the value to set
     * @return this
     */
    public PoolingSettings maxWait(Integer maxWait) {
        this.maxWait = maxWait;
        return this;
    }

} 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()),
                        Strings.urlEncode(url.container())))
                .hosts(hosts).verifier(REFERENCE_VERIFIER)
                .withRequestContext(reqCtx).execute().close();
    }

    /**
     * Retrieves informations about the specified reference
     * 
     * @param url
     *            the url of the reference to look for.
     * @return informations about the reference
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public ReferenceInfo showReference(OioUrl url) throws OioException {
        return showReference(url, new RequestContext());
    }

    /**
     * Retrieves informations about the specified reference
     * 
     * @param url
     *            the url of the reference to look for.
     * @param reqCtx
     *            common parameters to all requests
     * @return informations about the reference
     * @throws OioException
     *             if any error occurs during request execution
     */
    public ReferenceInfo showReference(OioUrl url, RequestContext reqCtx)
            throws OioException {
        checkArgument(null != url, INVALID_URL_MSG);
        return http.get(
                format(DIR_REF_SHOW_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container())))
                .hosts(hosts).verifier(REFERENCE_VERIFIER)
                .withRequestContext(reqCtx).execute(ReferenceInfo.class);
    }

    /**
     * Deletes a reference from Oio directory. Reference should not be linked to
     * a any service to be dropped
     * 
     * @param url
     *            the url of the reference
     * @throws OioException
     *             if any error occurs during request execution
     * 
     */
    @Deprecated
    public void deleteReference(OioUrl url) throws OioException {
        deleteReference(url, new RequestContext());
    }

    /**
     * Deletes a reference from Oio directory. Reference should not be linked to
     * a any service to be dropped
     * 
     * @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 deleteReference(OioUrl url, RequestContext reqCtx)
            throws OioException {
        checkArgument(null != url, INVALID_URL_MSG);
        http.post(
                format(DIR_REF_DELETE_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container())))
                .hosts(hosts).verifier(REFERENCE_VERIFIER)
                .withRequestContext(reqCtx).execute().close();
    }

    /**
     * Attachs a service of the specified type to a reference
     * 
     * @param url
     *            the url of the reference
     * @param type
     *            the type of service to link
     * @return the linked services
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public List<LinkedServiceInfo> linkService(OioUrl url, String type)
            throws OioException {
        return linkService(url, type, new RequestContext());
    }

    /**
     * Attachs a service of the specified type to a reference
     * 
     * @param url
     *            the url of the reference
     * @param type
     *            the type of service to link
     * @param reqCtx
     *            common parameters to all requests
     * @return the linked services
     * @throws OioException
     *             if any error occurs during request execution
     */
    public List<LinkedServiceInfo> linkService(OioUrl url, String type,
            RequestContext reqCtx) throws OioException {
        checkArgument(!nullOrEmpty(type), "Missing type");
        OioHttpResponse resp = http.post(
                format(DIR_LINK_SRV_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container()), type))
                .hosts(hosts).verifier(REFERENCE_VERIFIER)
                .withRequestContext(reqCtx).execute();

        return listAndClose(resp);
    }

    /**
     * Retrieves informations about the specified reference
     * 
     * @param url
     *            the url of the reference to look for.
     * @param type
     *            the type of service to list. Could be {@code null} to list all
     *            services
     * @return the linked services
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public List<LinkedServiceInfo> listServices(OioUrl url, String type)
            throws OioException {
        return listServices(url, type, new RequestContext());
    }

    /**
     * Retrieves informations about the specified reference
     * 
     * @param url
     *            the url of the reference to look for.
     * @param type
     *            the type of service to list. Could be {@code null} to list all
     *            services
     * @param reqCtx
     *            common parameters to all requests
     * @return the linked services
     * @throws OioException
     *             if any error occurs during request execution
     */
    public List<LinkedServiceInfo> listServices(OioUrl url, String type,
            RequestContext reqCtx) throws OioException {
        checkArgument(null != url, INVALID_URL_MSG);
        checkArgument(!nullOrEmpty(type));
        return http.get(
                format(DIR_LIST_SRV_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container()), type))
                .hosts(hosts).verifier(REFERENCE_VERIFIER)
                .withRequestContext(reqCtx).execute(ReferenceInfo.class).srv();
    }

    /**
     * Detach services from the specified URL
     * 
     * @param url
     *            the url of the reference
     * @param type
     *            the service to unlink
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void unlinkService(OioUrl url, String type) throws OioException {
        unlinkService(url, type, new RequestContext());
    }

    /**
     * Detachs services from the specified url
     * 
     * @param url
     *            the url of the reference
     * @param type
     *            the type of service to unlink
     * @param reqCtx
     *            common parameters to all requests
     * @throws OioException
     *             if any error occurs during request execution
     */
    public void unlinkService(OioUrl url, String type, RequestContext reqCtx)
            throws OioException {
        checkArgument(null != url, INVALID_URL_MSG);
        checkArgument(!nullOrEmpty(type));
        http.post(
                format(DIR_UNLINK_SRV_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container()), type))
                .hosts(hosts).verifier(REFERENCE_VERIFIER)
                .withRequestContext(reqCtx).execute().close();

    }

    /* -- STORAGE -- */

    /**
     * Creates a container using the specified {@code OioUrl}
     *
     * @param url
     *            the url of the container to create
     * @return {@code ContainerInfo}
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public ContainerInfo createContainer(OioUrl url) throws OioException {
        return createContainer(url, null, null, new RequestContext());
    }

    /**
     * Creates a container using the specified {@code OioUrl}
     *
     * @param url
     *            the url of the container to create
     * @param reqCtx
     *            common parameters to all requests
     * @return {@code ContainerInfo}
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public ContainerInfo createContainer(OioUrl url, RequestContext reqCtx)
            throws OioException {
        return createContainer(url, null, null, reqCtx);
    }

    /**
     * Creates a container using the specified {@code OioUrl}
     *
     * @param url
     *            the url of the container to create
     * @param properties
     *            the user properties to set
     * @param reqCtx
     *            common parameters to all requests
     * @return {@code ContainerInfo}
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public ContainerInfo createContainer(OioUrl url,
            Map<String, String> properties, RequestContext reqCtx)
            throws OioException {
        return createContainer(url, properties, null, reqCtx);
    }

    /**
     * Creates a container using the specified {@code OioUrl}
     *
     * @param url
     *            the url of the container to create
     * @param properties
     *            the user properties to set
     * @param system
     *            the system properties to set
     * @param reqCtx
     *            common parameters to all requests
     * @return {@code ContainerInfo}
     * @throws OioException
     *             if any error occurs during request execution
     */
    public ContainerInfo createContainer(OioUrl url,
            Map<String, String> properties, Map<String, String> system,
            RequestContext reqCtx)
            throws OioException {
        checkArgument(null != url, INVALID_URL_MSG);
        String body = format("{\"properties\": %1$s, \"system\": %2$s}",
                (properties == null) ? "{}" : jsonFromMap(properties),
                (system == null) ? "{}" : jsonFromMap(system));
        OioHttpResponse resp = http.post(
                format(CREATE_CONTAINER_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container())))
                .header(OIO_ACTION_MODE_HEADER, "autocreate")
                .body(body)
                .hosts(hosts).verifier(CONTAINER_VERIFIER)
                .withRequestContext(reqCtx).execute().close();
        if (204 == resp.code())
            throw new ContainerExistException("Container already present");

        return new ContainerInfo(url.container());
    }

    /**
     * Returns informations about the specified container
     * 
     * @param url
     *            the url of the container
     * @return the container informations
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public ContainerInfo getContainerInfo(OioUrl url) throws OioException {
        return getContainerInfo(url, new RequestContext());
    }

    /**
     * Returns informations about the specified container
     * 
     * @param url
     *            the url of the container
     * @param reqCtx
     *            common parameters to all requests
     * @return the container informations
     * @throws OioException
     *             if any error occurs during request execution
     */
    public ContainerInfo getContainerInfo(OioUrl url, RequestContext reqCtx)
            throws OioException {
        checkArgument(null != url, INVALID_URL_MSG);
        OioHttpResponse resp = http.get(
                format(GET_CONTAINER_INFO_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container())))
                .hosts(hosts).verifier(CONTAINER_VERIFIER)
                .withRequestContext(reqCtx).execute().close();

        return new ContainerInfo(url.container())
                .account(resp.header(ACCOUNT_HEADER))
                .ctime(longHeader(resp, M2_CTIME_HEADER))
                .init(longHeader(resp, M2_INIT_HEADER))
                .usage(longHeader(resp, M2_USAGE_HEADER))
                .version(longHeader(resp, M2_VERSION_HEADER))
                .id(resp.header(CONTAINER_SYS_NAME_HEADER))
                .ns(resp.header(NS_HEADER))
                .type(resp.header(TYPE_HEADER))
                .user(resp.header(USER_NAME_HEADER))
                .schemavers(resp.header(SCHEMA_VERSION_HEADER))
                .versionMainAdmin(resp.header(VERSION_MAIN_ADMIN_HEADER))
                .versionMainAliases(resp.header(VERSION_MAIN_ALIASES_HEADER))
                .versionMainChunks(resp.header(VERSION_MAIN_CHUNKS_HEADER))
                .versionMainContents(resp.header(VERSION_MAIN_CONTENTS_HEADER))
                .versionMainProperties(resp.header(VERSION_MAIN_PROPERTIES_HEADER));
    }

    /**
     * Lists all object available inside a container.
     * 
     * @param url
     *            the {@code url} of the container to list
     * @param options
     *            the options to specified to the list request. See
     *            {@linkplain ListOptions} documentation
     * @return an {@link ObjectList} matching the specified parameters.
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public ObjectList listContainer(OioUrl url, ListOptions options)
            throws OioException {
        return listObjects(url, options, new RequestContext());
    }

    /**
     * Lists all object available inside a container.
     * 
     * @param url
     *            the {@code url} of the container to list
     * @param options
     *            the options to specified to the list request. See
     *            {@linkplain ListOptions} documentation
     * @param reqCtx
     *            common parameters to all requests
     * @return an {@link ObjectList} matching the specified parameters.
     * @throws OioException
     *             if any error occurs during request execution
     */
    public ObjectList listObjects(OioUrl url, ListOptions options,
            RequestContext reqCtx) throws OioException {
        checkArgument(null != url, INVALID_URL_MSG);
        checkArgument(null != options, "Invalid options");
        OioHttpResponse resp = http.get(
                format(LIST_OBJECTS_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container())))
                .query(MAX_PARAM, options.limit() > 0 ? String.valueOf(options.limit()) : null)
                .query(PREFIX_PARAM, options.prefix())
                .query(MARKER_PARAM, options.marker())
                .query(DELIMITER_PARAM, options.delimiter())
                .hosts(hosts).verifier(CONTAINER_VERIFIER)
                .withRequestContext(reqCtx).execute();
        boolean success = false;
        try {
            ObjectList objectList = gson().fromJson(
                    new JsonReader(new InputStreamReader(resp.body(),
                            OIO_CHARSET)),
                    ObjectList.class);
            String truncated = resp.header(LIST_TRUNCATED_HEADER);
            if (truncated != null) {
                objectList.truncated(Boolean.parseBoolean(truncated));
                objectList.nextMarker(resp.header(LIST_MARKER_HEADER));
            }
            success = true;
            return objectList;
        } finally {
            resp.close(success);
        }
    }

    /**
     * Deletes a container from the OpenIO namespace. The container should be
     * empty to be destroyed.
     * 
     * @param url
     *            the URL of the container to destroy
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void deleteContainer(OioUrl url) throws OioException {
        deleteContainer(url, new RequestContext());
    }

    /**
     * Deletes a container from the OpenIO namespace. The container should be
     * empty to be destroyed.
     * 
     * @param url
     *            the URL of the container to destroy
     * @param reqCtx
     *            common parameters to all requests
     * @throws OioException
     *             if any error occurs during request execution
     */
    public void deleteContainer(OioUrl url, RequestContext reqCtx)
            throws OioException {
        checkArgument(null != url, INVALID_URL_MSG);
        http.post(
                format(DELETE_CONTAINER_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container())))
                .hosts(hosts).verifier(CONTAINER_VERIFIER)
                .withRequestContext(reqCtx).execute().close();
    }

    /**
     * Prepares an object upload by asking some chunks available location.
     *
     * @param url
     *            the URL of the future object to create
     * @param size
     *            the size of the future object
     * @param reqCtx
     *            Common parameters to all requests
     * @return an {@link ObjectInfo} which contains all informations to upload
     *         the object
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public ObjectInfo preparePutObject(OioUrl url, long size,
            RequestContext reqCtx) throws OioException {
        ObjectCreationOptions options = new ObjectCreationOptions();
        return this.preparePutObject(url, size, options, reqCtx);
    }

    /**
     * Prepares an object upload by asking some chunks available location.
     *
     * @param url
     *            the URL of the future object to create
     * @param size
     *            the size of the future object
     * @param policy
     *            the policy of the future object
     * @param version
     *            the version of the future object
     * @param reqCtx
     *            Common parameters to all requests
     * @return an {@link ObjectInfo} which contains all informations to upload
     *         the object
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public ObjectInfo preparePutObject(OioUrl url, long size,
            String policy, Long version, RequestContext reqCtx)
            throws OioException {
        ObjectCreationOptions options = new ObjectCreationOptions()
                .policy(policy)
                .version(version);
        return this.preparePutObject(url, size, options, reqCtx);
    }

    /**
     * Prepares an object upload by asking some chunks available location.
     *
     * @param url
     *            the URL of the future object to create
     * @param size
     *            the size of the future object
     * @param options
     *            the options of the future object
     * @param reqCtx
     *            Common parameters to all requests
     * @return an {@link ObjectInfo} which contains all informations to upload
     *         the object
     * @throws OioException
     *             if any error occurs during request execution
     */
    public ObjectInfo preparePutObject(OioUrl url, long size,
            ObjectCreationOptions options, RequestContext reqCtx)
            throws OioException {
        checkArgument(url != null, INVALID_URL_MSG);
        checkArgument(options != null, INVALID_OPTIONS_MSG);
        BeansRequest beansRequest = new BeansRequest()
                .size(size)
                .policy(options.policy());
        RequestBuilder request = http.post(
                format(GET_BEANS_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container()),
                        Strings.urlEncode(url.object())));
        if (options.version() != null)
            request.query(VERSION_PARAM, options.version().toString());
        OioHttpResponse resp = request
                .header(ACTION_MODE_HEADER,
                        settings.autocreate() ? OioConstants.AUTOCREATE_ACTION_MODE : null)
                .body(gson().toJson(beansRequest))
                .hosts(hosts).verifier(OBJECT_VERIFIER)
                .withRequestContext(reqCtx).execute();
        return getBeansObjectInfoAndClose(url, resp);
    }

    /**
     * Validate an object upload in the OpenIO-SDS namespace.
     *
     * @param oinf
     *            the {@link ObjectInfo} containing informations about the
     *            uploaded object
     * @param version
     *            the version to set (could be {@code null} to the object
     * @param reqCtx
     *            Common parameters to all requests
     * @return the validated object.
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public ObjectInfo putObject(ObjectInfo oinf, Long version,
            RequestContext reqCtx) throws OioException {
        ObjectCreationOptions options = new ObjectCreationOptions()
                .version(version);
        return this.putObject(oinf, options, reqCtx);
    }

    /**
     * Validate an object upload in the OpenIO-SDS namespace.
     *
     * @param oinf
     *            the {@link ObjectInfo} containing informations about the
     *            uploaded object
     * @param reqCtx
     *            Common parameters to all requests
     * @return the validated object.
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public ObjectInfo putObject(ObjectInfo oinf, RequestContext reqCtx)
            throws OioException {
        ObjectCreationOptions options = new ObjectCreationOptions();
        return this.putObject(oinf, options, reqCtx);
    }

    /**
     * Validate an object upload in the OpenIO-SDS namespace.
     *
     * @param oinf
     *            the {@link ObjectInfo} containing informations about the
     *            uploaded object
     * @param options
     *            the options of the object to upload
     * @param reqCtx
     *            Common parameters to all requests
     * @return the validated object.
     * @throws OioException
     *             if any error occurs during request execution
     */
    public ObjectInfo putObject(ObjectInfo oinf, ObjectCreationOptions options,
            RequestContext reqCtx) throws OioException {
        checkArgument(oinf != null, INVALID_OBJECT_INFO_MSG);
        checkArgument(options != null, INVALID_OPTIONS_MSG);
        checkArgument(options.version() == null
                || options.version().equals(oinf.version()), "Invalid version");
        checkArgument(options.policy() == null
                || options.policy().equals(oinf.policy()), "Invalid policy");

        oinf.properties(options.properties());
        if (options.mimeType() != null)
            oinf.mimeType(options.mimeType());

        Map<String, String> props = oinf.properties();
        String body = format("{\"chunks\": %1$s, \"properties\": %2$s}",
                gsonForObject().toJson(oinf.chunks()),
                props != null ? jsonFromMap(props) : "{}");
        RequestBuilder request = http.post(
                format(PUT_OBJECT_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(oinf.url().account()),
                        Strings.urlEncode(oinf.url().container()),
                        Strings.urlEncode(oinf.url().object())))
                .header(CONTENT_META_CHUNK_METHOD_HEADER, oinf.chunkMethod())
                .header(CONTENT_META_HASH_HEADER, oinf.hash())
                .header(CONTENT_META_ID_HEADER, oinf.oid())
                .header(CONTENT_META_LENGTH_HEADER, String.valueOf(oinf.size()))
                .header(CONTENT_META_ID_HEADER, oinf.oid())
                .header(CONTENT_META_MIME_TYPE_HEADER, oinf.mimeType())
                .header(CONTENT_META_POLICY_HEADER, oinf.policy())
                .header(CONTENT_META_VERSION_HEADER, oinf.version().toString())
                .header(ACTION_MODE_HEADER,
                        settings.autocreate() ? OioConstants.AUTOCREATE_ACTION_MODE : null);
        if (options.simulateVersioning())
            request.header(SIMULATE_VERSIONING_HEADER, "1");
        request.body(body)
                .hosts(hosts).verifier(OBJECT_VERIFIER)
                .withRequestContext(reqCtx).execute().close();
        return oinf;
    }

    /**
     * Returns informations about the specified object
     * 
     * @param url
     *            the url of the object to look for
     * @return an {@link ObjectInfo} containing informations about the object
     */
    @Deprecated
    public ObjectInfo getObjectInfo(OioUrl url) throws OioException {
        return getObjectInfo(url, true);
    }

    /**
     * Returns informations about the specified object
     *
     * @param url
     *            the url of the object to look for
     * @param loadProperties
     *            Whether or not to load properties
     * @return an {@link ObjectInfo} containing informations about the object
     */
    @Deprecated
    public ObjectInfo getObjectInfo(OioUrl url, boolean loadProperties)
            throws OioException {
        return getObjectInfo(url, null, loadProperties);
    }

    /**
     * Returns informations about the specified object
     * 
     * @param url
     *            the url of the object to look for
     * @param version
     *            the version of the content to get
     * @return an {@link ObjectInfo} containing informations about the object
     */
    @Deprecated
    public ObjectInfo getObjectInfo(OioUrl url, Long version)
            throws OioException {
        return getObjectInfo(url, version, true);
    }

    /**
     * Returns informations about the specified object
     *
     * @param url
     *            the url of the object to look for
     * @param version
     *            the version of the content to get
     * @param loadProperties
     *            Whether or not to load properties
     * @return an {@link ObjectInfo} containing informations about the object
     */
    @Deprecated
    public ObjectInfo getObjectInfo(OioUrl url, Long version,
            boolean loadProperties) throws OioException {
        return getObjectInfo(url, version, new RequestContext(), loadProperties);
    }

    /**
     * Returns informations about the specified object
     * 
     * @param url
     *            the url of the object to look for
     * @param version
     *            the version to get (could be {@code null} to get latest
     *            version)
     * @param reqCtx
     *            common parameters to all requests
     * @return an {@link ObjectInfo} containing informations about the object
     */
    public ObjectInfo getObjectInfo(OioUrl url, Long version,
            RequestContext reqCtx) throws OioException {
        return getObjectInfo(url, version, reqCtx, true);
    }

    /**
     * Returns informations about the specified object
     *
     * @param url
     *            the url of the object to look for
     * @param version
     *            the version to get (could be {@code null} to get latest
     *            version)
     * @param reqCtx
     *            common parameters to all requests
     * @param loadProperties
     *            Whether or not to load properties
     * @return an {@link ObjectInfo} containing informations about the object
     */
    public ObjectInfo getObjectInfo(OioUrl url, Long version,
            RequestContext reqCtx, boolean loadProperties) throws OioException {
        checkArgument(null != url, INVALID_URL_MSG);
        RequestBuilder request = http.get(
                format(GET_OBJECT_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container()),
                        Strings.urlEncode(url.object())));
        if (version != null)
            request.query(VERSION_PARAM, version.toString());
        OioHttpResponse resp = request.hosts(hosts).verifier(OBJECT_VERIFIER)
                .withRequestContext(reqCtx).execute();
        ObjectInfo info = objectShowObjectInfoAndClose(url, resp, loadProperties);
        return info;
    }

    /**
     * Deletes an object from its container
     *
     * @param url
     *            the url of the object to delete
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void deleteObject(OioUrl url) throws OioException {
        ObjectDeletionOptions options = new ObjectDeletionOptions();
        this.deleteObject(url, options, new RequestContext());
    }

    /**
     * Deletes an object from its container
     *
     * @param url
     *            the url of the object to delete
     * @param version
     *            the version to delete (could be {@code null} to delete latest
     *            version)
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void deleteObject(OioUrl url, Long version) throws OioException {
        ObjectDeletionOptions options = new ObjectDeletionOptions()
                .version(version);
        this.deleteObject(url, options, new RequestContext());
    }

    /**
     * Deletes an object from its container
     *
     * @param url
     *            the url of the object to delete
     * @param version
     *            the version to delete (could be {@code null} to delete latest
     *            version)
     * @param reqCtx
     *            common paramters to all requests
     * @throws OioException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void deleteObject(OioUrl url, Long version, RequestContext reqCtx)
            throws OioException {
        ObjectDeletionOptions options = new ObjectDeletionOptions()
                .version(version);
        this.deleteObject(url, options, new RequestContext());
    }

    /**
     * Deletes an object from its container
     *
     * @param url
     *            the url of the object to delete
     * @param options
     *            the version to delete (could be {@code null} to delete latest
     *            version)
     * @param reqCtx
     *            common paramters to all requests
     * @throws OioException
     *             if any error occurs during request execution
     */
    public void deleteObject(OioUrl url, ObjectDeletionOptions options,
            RequestContext reqCtx) throws OioException {
        checkArgument(url != null, INVALID_URL_MSG);
        checkArgument(options != null, INVALID_OPTIONS_MSG);
        RequestBuilder request = http.post(
                format(DELETE_OBJECT_FORMAT, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container()),
                        Strings.urlEncode(url.object())));
        if (options.version() != null)
            request.query(VERSION_PARAM, options.version().toString());
        if (options.deleteMarker())
            request.query(DELETE_MARKER_PARAM, "1");
        if (options.simulateVersioning())
            request.header(SIMULATE_VERSIONING_HEADER, "1");
        request.hosts(hosts).verifier(OBJECT_VERIFIER)
                .withRequestContext(reqCtx).execute().close();
    }

    /* -- PROPERTIES -- */

    /**
     * Set properties to the specified container.
     *
     * @param url
     *            the URL of the container to set properties
     * @param properties
     *            the properties to set
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void setContainerProperties(OioUrl url,
            Map<String, String> properties) {
        setContainerProperties(url, properties, false, new RequestContext());
    }

    /**
     * Set properties to the specified container.
     *
     * @param url
     *            the URL of the container to set properties
     * @param properties
     *            the properties to set
     * @param reqCtx
     *            common parameters to all requests
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void setContainerProperties(OioUrl url,
            Map<String, String> properties, RequestContext reqCtx) {
        setContainerProperties(url, properties, false, reqCtx);
    }

    /**
     * Set properties to the specified container.
     *
     * @param url
     *            the URL of the container to set properties
     * @param properties
     *            the properties to set
     * @param clear
     *            clear previous properties
     * @param reqCtx
     *            common parameters to all requests
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void setContainerProperties(OioUrl url,
            Map<String, String> properties, boolean clear,
            RequestContext reqCtx) {
        setContainerProperties(url, properties, clear, null, reqCtx);
    }

    /**
     * Set properties to the specified container.
     *
     * @param url
     *            the URL of the container to set properties
     * @param properties
     *            the properties to set
     * @param system
     *            the system properties to set
     * @param clear
     *            clear previous properties
     * @param reqCtx
     *            common parameters to all requests
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    public void setContainerProperties(OioUrl url,
            Map<String, String> properties, boolean clear,
            Map<String, String> system, RequestContext reqCtx) {
        checkArgument(null != url, INVALID_URL_MSG);
        RequestBuilder request = http.post(
                format(CONTAINER_SET_PROP, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container())));
        if (clear)
            request.query(FLUSH_PARAM, "1");
        String body = format("{\"properties\": %1$s, \"system\": %2$s}",
                (properties == null) ? "{}" : jsonFromMap(properties),
                (system == null) ? "{}" : jsonFromMap(system));
        request.body(body)
                .hosts(hosts).verifier(CONTAINER_VERIFIER)
                .withRequestContext(reqCtx).execute().close();
    }

    /**
     * Retrieve user properties of the specified container.
     * 
     * @param url
     *            the url of the container
     * @return the user properties found on the container
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public Map<String, String> getContainerProperties(OioUrl url) {
        return getContainerProperties(url, new RequestContext());
    }

    /**
     * Retrieve user properties of the specified container.
     * 
     * @param url
     *            the url of the container
     * @param reqCtx
     *            common parameters to all requests
     * @return the user properties found on the container
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public Map<String, String> getContainerProperties(OioUrl url,
            RequestContext reqCtx) {
        return getAllContainerProperties(url, new RequestContext())
                .get("properties");
    }

    /**
     * Retrieve user properties and system properties
     * of the specified container.
     *
     * @param url
     *            the url of the container
     * @param reqCtx
     *            common parameters to all requests
     * @return the user properties and system properties found on the container
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    public Map<String, Map<String, String>> getAllContainerProperties(
            OioUrl url, RequestContext reqCtx) {
        checkArgument(null != url, INVALID_URL_MSG);
        OioHttpResponse resp = http.post(
                format(CONTAINER_GET_PROP, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container())))
                .hosts(hosts).verifier(CONTAINER_VERIFIER)
                .withRequestContext(reqCtx).execute();
        try {
            return JsonUtils.jsonToMapMap(resp.body());
        } finally {
            resp.close();
        }
    }

    /**
     * Deletes user properties from the specified container
     * 
     * @param url
     *            the url of the container
     * @param keys
     *            the property keys to drop
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void deleteContainerProperties(OioUrl url, String... keys) {
        deleteContainerProperties(new RequestContext(), url, keys);
    }

    /**
     * Deletes user properties from the specified container
     *
     * @param reqCtx
     *            common parameters to all requests
     * @param url
     *            the url of the container
     * @param keys
     *            the property keys to drop
     * 
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    public void deleteContainerProperties(RequestContext reqCtx, OioUrl url,
            String... keys) {
        checkArgument(null != url, INVALID_URL_MSG);
        if (keys == null)
            keys = Strings.EMPTY_ARRAY;
        http.post(
                format(CONTAINER_DEL_PROP, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container())))
                .body(gson().toJson(keys))
                .hosts(hosts).verifier(CONTAINER_VERIFIER)
                .withRequestContext(reqCtx).execute().close();
    }

    /**
     * Deletes user properties from the specified container
     * 
     * @param url
     *            the url of the container
     * @param keys
     *            the property keys to drop
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void deleteContainerProperties(OioUrl url, List<String> keys) {
        deleteContainerProperties(url, keys, new RequestContext());
    }

    /**
     * Deletes user properties from the specified container
     * 
     * @param url
     *            the url of the container
     * @param keys
     *            the property keys to drop
     * @param reqCtx
     *            common parameters to all requests
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    public void deleteContainerProperties(OioUrl url, List<String> keys,
            RequestContext reqCtx) {
        checkArgument(null != url, INVALID_URL_MSG);
        if (keys == null)
            keys = Collections.emptyList();
        http.post(
                format(CONTAINER_DEL_PROP, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container())))
                .body(gson().toJson(keys))
                .hosts(hosts).verifier(CONTAINER_VERIFIER)
                .withRequestContext(reqCtx).execute().close();
    }

    /**
     * Add properties to the specified object. The properties must be prefixed
     * with "user." and this prefix will be stored, and finally used to query
     * the parameters later.
     *
     * @param url
     *            the URL of the object
     * @param properties
     *            the properties to set
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void setObjectProperties(OioUrl url, Map<String, String> properties) {
        setObjectProperties(url, null, properties, false, new RequestContext());
    }

    /**
     * Add properties to the specified object. The properties must be prefixed
     * with "user." and this prefix will be stored, and finally used to query
     * the parameters later.
     *
     * @param url
     *            the URL of the object
     * @param properties
     *            the properties to set
     * @param reqCtx
     *            common parameters to all requests
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void setObjectProperties(OioUrl url, Map<String, String> properties,
            RequestContext reqCtx) {
        setObjectProperties(url, null, properties, false, reqCtx);
    }

    /**
     * Add properties to the specified object. The properties must be prefixed
     * with "user." and this prefix will be stored, and finally used to query
     * the parameters later.
     *
     * @param url
     *            the URL of the object
     * @param properties
     *            the properties to set
     * @param clear
     *            clear previous properties
     * @param reqCtx
     *            common parameters to all requests
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void setObjectProperties(OioUrl url, Map<String, String> properties,
            boolean clear, RequestContext reqCtx) {
        setObjectProperties(url, null, properties, clear, reqCtx);
    }

    /**
     * Add properties to the specified object. The properties must be prefixed
     * with "user." and this prefix will be stored, and finally used to query
     * the parameters later.
     *
     * @param url
     *            the URL of the object
     * @param version
     *            the version to manipulate
     *            (could be {@code null} to manipulate latest version)
     * @param properties
     *            the properties to set
     * @param clear
     *            clear previous properties
     * @param reqCtx
     *            common parameters to all requests
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    public void setObjectProperties(OioUrl url, Long version,
            Map<String, String> properties, boolean clear,
            RequestContext reqCtx) {
        checkArgument(null != url && null != url.object(), INVALID_URL_MSG);
        checkArgument(null != properties && properties.size() > 0, "Invalid properties");
        String body = format("{\"properties\": %1$s}",
                jsonFromMap(properties));
        RequestBuilder request = http.post(
                format(OBJECT_SET_PROP, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container()),
                        Strings.urlEncode(url.object())));
        if (version != null)
            request.query(VERSION_PARAM, version.toString());
        if (clear)
            request.query(FLUSH_PARAM, "1");
        request.body(body)
                .hosts(hosts).verifier(OBJECT_VERIFIER)
                .withRequestContext(reqCtx).execute().close();
    }

    /**
     * Retrieve user properties of the specified object
     *
     * @param url
     *            the url of the object
     * @return the user properties (i.e. prefixed with "user.") found on the
     *         object
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public Map<String, String> getObjectProperties(OioUrl url) {
        return getObjectProperties(url, null, new RequestContext());
    }

    /**
     * Retrieve user properties of the specified object
     *
     * @param url
     *            the url of the object
     * @param reqCtx
     *            common parameters to all requests
     * @return the user properties (i.e. prefixed with "user.") found on the
     *         object
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public Map<String, String> getObjectProperties(OioUrl url,
            RequestContext reqCtx) {
        return getObjectProperties(url, null, reqCtx);
    }

    /**
     * Retrieve user properties of the specified object
     *
     * @param url
     *            the url of the object
     * @param version
     *            the version to manipulate
     *            (could be {@code null} to manipulate latest version)
     * @param reqCtx
     *            common parameters to all requests
     * @return the user properties (i.e. prefixed with "user.") found on the
     *         object
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    public Map<String, String> getObjectProperties(OioUrl url, Long version,
            RequestContext reqCtx) {
        checkArgument(null != url && null != url.object(), INVALID_URL_MSG);
        RequestBuilder request = http.post(
                format(OBJECT_GET_PROP, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container()),
                        Strings.urlEncode(url.object())));
        if (version != null)
            request.query(VERSION_PARAM, version.toString());
        OioHttpResponse resp = request
                .hosts(hosts).verifier(OBJECT_VERIFIER)
                .withRequestContext(reqCtx).execute();
        try {
            Map<String, Map<String, String>> rootMap = JsonUtils.jsonToMapMap(
                    resp.body());
            return rootMap.get("properties");
        } finally {
            resp.close();
        }
    }

    /**
     * Deletes the specified properties from the object
     * 
     * @param url
     *            the url of the object
     * @param keys
     *            the property keys to drop
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void deleteObjectProperties(OioUrl url, String... keys) {
        deleteObjectProperties(new RequestContext(), url, null, keys);
    }

    /**
     * Deletes the specified properties from the object
     * 
     * @param url
     *            the url of the object
     * @param keys
     *            the property keys to drop
     * @param reqCtx
     *            common parameters to all requests
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void deleteObjectProperties(RequestContext reqCtx, OioUrl url,
            String... keys) {
        deleteObjectProperties(reqCtx, url, null, keys);
    }

    /**
     * Deletes the specified properties from the object
     * 
     * @param url
     *            the url of the object
     * @param version
     *            the version to manipulate
     *            (could be {@code null} to manipulate latest version)
     * @param keys
     *            the property keys to drop
     * @param reqCtx
     *            common parameters to all requests
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    public void deleteObjectProperties(RequestContext reqCtx, OioUrl url,
            Long version, String... keys) {
        checkArgument(null != url && null != url.object(), INVALID_URL_MSG);
        String body = "[]";
        if (keys != null)
            body = gson().toJson(keys);
        RequestBuilder request = http.post(
                format(OBJECT_DEL_PROP, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container()),
                        Strings.urlEncode(url.object())));
        if (version != null)
            request.query(VERSION_PARAM, version.toString());
        request.body(body)
                .hosts(hosts).verifier(OBJECT_VERIFIER)
                .withRequestContext(reqCtx).execute().close();
    }

    /**
     * Deletes the specified properties from the object
     * 
     * @param url
     *            the url of the object
     * @param keys
     *            the property keys to drop
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void deleteObjectProperties(OioUrl url, List<String> keys) {
        deleteObjectProperties(url, null, keys, new RequestContext());
    }

    /**
     * Deletes the specified properties from the object
     * 
     * @param url
     *            the url of the object
     * @param keys
     *            the property keys to drop
     * @param reqCtx
     *            common parameters to all requests
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    @Deprecated
    public void deleteObjectProperties(OioUrl url, List<String> keys,
            RequestContext reqCtx) {
        deleteObjectProperties(url, null, keys, reqCtx);
    }

    /**
     * Deletes the specified properties from the object
     * 
     * @param url
     *            the url of the object
     * @param version
     *            the version to manipulate
     *            (could be {@code null} to manipulate latest version)
     * @param keys
     *            the property keys to drop
     * @param reqCtx
     *            common parameters to all requests
     * @throws ContainerNotFoundException
     *             if the specified container doesn't exist
     * @throws ObjectNotFoundException
     *             if the specified object doesn't exist
     * @throws OioSystemException
     *             if any error occurs during request execution
     */
    public void deleteObjectProperties(OioUrl url, Long version,
            List<String> keys, RequestContext reqCtx) {
        checkArgument(null != url && null != url.object(), INVALID_URL_MSG);
        String body = "[]";
        if (keys != null)
            body = gson().toJson(keys);
        RequestBuilder request = http.post(
                format(OBJECT_DEL_PROP, settings.url(), settings.ns(),
                        Strings.urlEncode(url.account()),
                        Strings.urlEncode(url.container()),
                        Strings.urlEncode(url.object())));
        if (version != null)
            request.query(VERSION_PARAM, version.toString());
        request.body(body)
                .hosts(hosts).verifier(OBJECT_VERIFIER)
                .withRequestContext(reqCtx).execute().close();
    }

    /* -- INTERNALS -- */

    private ObjectInfo getBeansObjectInfoAndClose(OioUrl url,
            OioHttpResponse resp) {
        boolean success = false;
        try {
            ObjectInfo oinf = fillObjectInfo(url, resp, false);
            List<ChunkInfo> chunks = bodyChunk(resp);
            // check if we are using EC with ec daemon
            if (oinf.isEC()) {
                if (settings.ecdrain()) {
                    if (Strings.nullOrEmpty(settings.ecd()))
                        throw new OioException("Missing proxy#ecd configuration");
                } else {
                    // TODO impl EC in java
                    throw new IllegalStateException(
                            "Invalid configuration, we cannot do EC without ecd ATM");
                }
            }
            oinf.chunks(chunks);

            success = true;
            return oinf;
        } finally {
            resp.close(success);
        }
    }

    private ObjectInfo objectShowObjectInfoAndClose(OioUrl url,
            OioHttpResponse resp, boolean loadProperties) {
        boolean success = false;
        try {
            ObjectInfo oinf = fillObjectInfo(url, resp, loadProperties);
            List<ChunkInfo> chunks = bodyChunk(resp);
            // check if we are using EC with ecd
            if (oinf.chunkMethod().startsWith(OioConstants.EC_PREFIX)
                    && (!settings.ecdrain() || Strings.nullOrEmpty(settings.ecd())))
                throw new OioException("Unable to decode EC encoded object without ecd");
            oinf.chunks(chunks);
            success = true;
            return oinf;
        } finally {
            resp.close(success);
        }
    }

    private ObjectInfo fillObjectInfo(OioUrl url, OioHttpResponse r,
            boolean loadProperties) {
        ObjectInfo oinf = new ObjectInfo().url(url)
                .oid(r.header(CONTENT_META_ID_HEADER))
                .size(longHeader(r, CONTENT_META_LENGTH_HEADER))
                .ctime(longHeader(r, CONTENT_META_CTIME_HEADER))
                .chunkMethod(r.header(CONTENT_META_CHUNK_METHOD_HEADER))
                .policy(r.header(CONTENT_META_POLICY_HEADER))
                .version(longHeader(r, CONTENT_META_VERSION_HEADER))
                .hash(r.header(OioConstants.CONTENT_META_HASH_HEADER))
                .hashMethod(r.header(CONTENT_META_HASH_METHOD_HEADER))
                .mimeType(r.header(CONTENT_META_MIME_TYPE_HEADER));
        if (loadProperties)
            oinf.properties(propsFromHeaders(r.headers()));
        return oinf.withRequestContext(r.requestContext());
    }

    private List<ChunkInfo> bodyChunk(OioHttpResponse resp)
            throws OioException {
        try {
            return gson().fromJson(
                    new JsonReader(
                            new InputStreamReader(resp.body(), OIO_CHARSET)),
                    new TypeToken<List<ChunkInfo>>() {}.getType());
        } catch (Exception e) {
            throw new OioException("Body extraction error", e);
        }
    }

    private <T> List<T> listAndClose(OioHttpResponse resp) {
        boolean success = false;
        try {
            List<T> res = gson().fromJson(
                    new JsonReader(
                            new InputStreamReader(resp.body(), OIO_CHARSET)),
                    new TypeToken<List<T>>() {}.getType());
            success = true;
            return res;
        } catch (Exception e) {
            throw new OioException("Body extraction error", e);
        } finally {
            resp.close(success);
        }
    }

    private List<ServiceInfo> serviceInfoListAndClose(OioHttpResponse resp) {
        boolean success = false;
        try {
            List<ServiceInfo> res = gson().fromJson(
                    new JsonReader(
                        new InputStreamReader(resp.body(), OIO_CHARSET)),
                    new TypeToken<List<ServiceInfo>>() {}.getType());
            success = true;
            return res;
        } catch (Exception e) {
            throw new OioException("Body extraction error", e);
        } finally {
            resp.close(success);
        }
    }

    private Map<String, String> propsFromHeaders(HashMap<String,
            String> headers) {
        HashMap<String, String> props = new HashMap<String, String>();
        for (Entry<String, String> header : headers.entrySet()) {
            if (header.getKey().startsWith(CONTENT_HEADER_PREFIX)) {
                String shortKey = header.getKey()
                        .substring(CONTENT_HEADER_PREFIX.length());
                if (shortKey.startsWith("x-")
                        || !SYSMETA_KEYS.contains(shortKey)) {
                    try {
                        props.put(shortKey, URLDecoder.decode(header.getValue(),
                                StandardCharsets.UTF_8.toString()));
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e.getCause());
                    }
                }
            }
        }
        return props;
    }
}
import static io.openio.sds.models.OioUrl.url;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import java.util.UUID;
import io.openio.sds.RequestContext;
import io.openio.sds.TestHelper;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import io.openio.sds.common.SocketProvider;
import io.openio.sds.common.SocketProviders;
import io.openio.sds.exceptions.ContainerNotFoundException;
import io.openio.sds.exceptions.OioException;
import io.openio.sds.exceptions.ReferenceNotFoundException;
import io.openio.sds.http.OioHttp;
import io.openio.sds.http.OioHttpSettings;
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.ObjectInfo;
import io.openio.sds.models.OioUrl;
import io.openio.sds.models.ServiceInfo;
import io.openio.sds.pool.PoolingSettings;
import io.openio.sds.proxy.ProxyClient;
package io.openio.sds.proxy;






public class ProxyClientITest {

    private static ProxyClient proxy;

    @BeforeClass
    public static void setup() {
        ProxySettings pst = TestHelper.proxySettings();
        SocketProvider prov = SocketProviders.pooledSocketProvider(new PoolingSettings(),
                new OioHttpSettings(), pst.allHosts().get(0));
        proxy = new ProxyClient(OioHttp.http(pst.http(), prov), pst);
    }

    @Test
    public void namespaceInfo() {
        NamespaceInfo ni = proxy.getNamespaceInfo(new RequestContext());
        assertNotNull(ni);
        assertNotNull(ni.ns());
        assertNotNull(ni.options());
        assertNotNull(ni.storagePolicies());
    }

    @Test
    public void listServicesWithType() {
        List<ServiceInfo> rawx = proxy.getServices("rawx", new RequestContext());