class GoogleStorageWriteFeature extends AbstractHttpWriteFeature<VersionId> implements Write<VersionId> {

    @Override
    public HttpResponseOutputStream<VersionId> write(final Path file, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException {
        final DelayedHttpEntityCallable<VersionId> command = new DelayedHttpEntityCallable<VersionId>() {
            @Override
            public VersionId call(final AbstractHttpEntity entity) throws BackgroundException {
                try {
                    // POST /upload/storage/v1/b/myBucket/o
                    final HttpEntityEnclosingRequestBase request = new HttpPost(String.format("%supload/storage/v1/b/%s/o?uploadType=resumable",
                        session.getClient().getRootUrl(), containerService.getContainer(file).getName()));
                    final StringBuilder metadata = new StringBuilder();
                    metadata.append(String.format("{\"name\": \"%s\"", containerService.getKey(file)));
                    metadata.append(",\"metadata\": {");
                    for(Map.Entry<String, String> item : status.getMetadata().entrySet()) {
                        metadata.append(String.format("\"%s\": \"%s\"", item.getKey(), item.getValue()));
                    }
                    metadata.append("}");
                    if(StringUtils.isNotBlank(status.getMime())) {
                        metadata.append(String.format(", \"contentType\": \"%s\"", status.getMime()));
                    }
                    if(StringUtils.isNotBlank(status.getStorageClass())) {
                        metadata.append(String.format(", \"storageClass\": \"%s\"", status.getStorageClass()));
                    }
                    metadata.append("}");
                    request.setEntity(new StringEntity(metadata.toString(),
                        ContentType.create("application/json", StandardCharsets.UTF_8.name())));
                    if(StringUtils.isNotBlank(status.getMime())) {
                        // Set to the media MIME type of the upload data to be transferred in subsequent requests.
                        request.addHeader("X-Upload-Content-Type", status.getMime());
                    }
                    request.addHeader(HTTP.CONTENT_TYPE, MEDIA_TYPE);
                    final HttpClient client = session.getHttpClient();
                    final HttpResponse response = client.execute(request);
                    try {
                        switch(response.getStatusLine().getStatusCode()) {
                            case HttpStatus.SC_OK:
                                break;
                            default:
                                throw new DefaultHttpResponseExceptionMappingService().map(
                                    new HttpResponseException(response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()));
                        }
                    }
                    finally {
                        EntityUtils.consume(response.getEntity());
                    }
                    if(response.containsHeader(HttpHeaders.LOCATION)) {
                        final String putTarget = response.getFirstHeader(HttpHeaders.LOCATION).getValue();
                        // Upload the file
                        final HttpPut put = new HttpPut(putTarget);
                        put.setEntity(entity);
                        final HttpResponse putResponse = client.execute(put);
                        try {
                            switch(putResponse.getStatusLine().getStatusCode()) {
                                case HttpStatus.SC_OK:
                                case HttpStatus.SC_CREATED:
                                    try (JsonReader reader = new JsonReader(new InputStreamReader(putResponse.getEntity().getContent(), StandardCharsets.UTF_8))) {
                                        reader.beginObject();
                                        while(reader.hasNext()) {
                                            final String name = reader.nextName();
                                            final String value = reader.nextString();
                                            switch(name) {
                                                case "generation":
                                                    final VersionId version = new VersionId(value);
                                                    status.setVersion(version);
                                                    return version;
                                            }
                                        }
                                        reader.endObject();
                                    }
                                    break;
                                default:
                                    throw new DefaultHttpResponseExceptionMappingService().map(
                                        new HttpResponseException(response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()));
                            }
                        }
                        finally {
                            EntityUtils.consume(putResponse.getEntity());
                        }
                    }
                    else {
                        throw new DefaultHttpResponseExceptionMappingService().map(
                            new HttpResponseException(response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()));
                    }
                    return new VersionId(null);
                }
                catch(IOException e) {
                    throw new GoogleStorageExceptionMappingService().map("Upload failed", e, file);
                }
            }

            @Override
            public long getContentLength() {
                return status.getLength();
            }
        };
        return this.write(file, status, command);
    }

    public  GoogleStorageWriteFeature(final GoogleStorageSession session);
    public  GoogleStorageWriteFeature(final GoogleStorageSession session, final Find finder, final AttributesFinder attributes);

}