@Override
    public HttpResponseOutputStream<FileEntity> write(final Path file, final TransferStatus status, final ConnectionCallback callback) throws BackgroundException {
        final String uploadUri;
        FileUploadPartEntity uploadPartEntity = null;
        if(StringUtils.isBlank(status.getUrl())) {
            uploadPartEntity = new BrickUploadFeature(session, this).startUpload(file);
            uploadUri = uploadPartEntity.getUploadUri();
        }
        else {
            uploadUri = status.getUrl();
        }
        final HttpResponseOutputStream<FileEntity> stream = this.write(file, status, new DelayedHttpEntityCallable<FileEntity>(file) {
            @Override
            public FileEntity call(final HttpEntity entity) throws BackgroundException {
                try {
                    final HttpPut request = new HttpPut(uploadUri);
                    request.setEntity(entity);
                    request.setHeader(HttpHeaders.CONTENT_TYPE, MimeTypeService.DEFAULT_CONTENT_TYPE);
                    final HttpResponse response = session.getClient().execute(request);
                    // Validate response
                    try {
                        switch(response.getStatusLine().getStatusCode()) {
                            case HttpStatus.SC_OK:
                                if(log.isInfoEnabled()) {
                                    log.info(String.format("Received response %s for part number %d", response, status.getPart()));
                                }
                                // Upload complete
                                if(response.containsHeader("ETag")) {
                                    if(file.getType().contains(Path.Type.encrypted)) {
                                        log.warn(String.format("Skip checksum verification for %s with client side encryption enabled", file));
                                    }
                                    else {
                                        if(HashAlgorithm.md5.equals(status.getChecksum().algorithm)) {
                                            final Checksum etag = Checksum.parse(StringUtils.remove(response.getFirstHeader("ETag").getValue(), '"'));
                                            if(!status.getChecksum().equals(etag)) {
                                                throw new ChecksumException(MessageFormat.format(LocaleFactory.localizedString("Upload {0} failed", "Error"), file.getName()),
                                                        MessageFormat.format("Mismatch between {0} hash {1} of uploaded data and ETag {2} returned by the server",
                                                                etag.algorithm.toString(), status.getChecksum().hash, etag.hash));
                                            }
                                        }
                                    }
                                }
                                else {
                                    if(log.isDebugEnabled()) {
                                        log.debug("No ETag header in response available");
                                    }
                                }
                                return null;
                            default:
                                EntityUtils.updateEntity(response, new BufferedHttpEntity(response.getEntity()));
                                throw new DefaultHttpResponseExceptionMappingService().map("Upload {0} failed",
                                        new HttpResponseException(response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()), file);
                        }
                    }
                    finally {
                        EntityUtils.consume(response.getEntity());
                    }
                }
                catch(HttpResponseException e) {
                    throw new DefaultHttpResponseExceptionMappingService().map("Upload {0} failed", e, file);
                }
                catch(IOException e) {
                    throw new DefaultIOExceptionMappingService().map("Upload {0} failed", e, file);
                }
            }

            @Override
            public long getContentLength() {
                return status.getLength();
            }
        });
        if(StringUtils.isBlank(status.getUrl())) {
            final String ref = uploadPartEntity.getRef();
            return new HttpResponseOutputStream<FileEntity>(new ProxyOutputStream(stream),
                    new BrickAttributesFinderFeature(session), status) {
                private final AtomicBoolean close = new AtomicBoolean();

                @Override
                public FileEntity getStatus() throws BackgroundException {
                    return stream.getStatus();
                }

                @Override
                public void close() throws IOException {
                    if(close.get()) {
                        log.warn(String.format("Skip double close of stream %s", this));
                        return;
                    }
                    super.close();
                    try {
                        new BrickUploadFeature(session, BrickWriteFeature.this)
                                .completeUpload(file, ref, status, Collections.singletonList(status));
                    }
                    catch(BackgroundException e) {
                        throw new IOException(e.getMessage(), e);
                    }
                    finally {
                        close.set(true);
                    }
                }
            };
        }
        return stream;
    }
@Test
    public void testWriteZeroLength() throws Exception {
        final BrickWriteFeature feature = new BrickWriteFeature(session);
        final Path container = new Path("/", EnumSet.of(Path.Type.directory, Path.Type.volume));
        final byte[] content = RandomUtils.nextBytes(0);
        final Path file = new Path(container, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file));
        final TransferStatus status = new TransferStatus().withLength(content.length);
        final HttpResponseOutputStream<FileEntity> out = feature.write(file, status, new DisabledConnectionCallback());
        final ByteArrayInputStream in = new ByteArrayInputStream(content);
        assertEquals(content.length, IOUtils.copyLarge(in, out));
        in.close();
        out.close();
        assertTrue(new DefaultFindFeature(session).find(file));
        final PathAttributes attributes = new BrickAttributesFinderFeature(session).find(file);
        assertEquals(content.length, attributes.getSize());
        final byte[] compare = new byte[content.length];
        final InputStream stream = new BrickReadFeature(session).read(file, new TransferStatus().withLength(content.length), new DisabledConnectionCallback());
        IOUtils.readFully(stream, compare);
        stream.close();
        assertArrayEquals(content, compare);
        new BrickDeleteFeature(session).delete(Collections.singletonList(file), new DisabledLoginCallback(), new Delete.DisabledCallback());
    }