@Override
    public PathAttributes find(final Path file, final ListProgressListener listener) throws BackgroundException {
        if(file.isRoot()) {
            return PathAttributes.EMPTY;
        }
        if(containerService.isContainer(file)) {
            final PathAttributes attributes = new PathAttributes();
            if(log.isDebugEnabled()) {
                log.debug(String.format("Read location for bucket %s", file));
            }
            attributes.setRegion(new S3LocationFeature(session, session.getClient().getRegionEndpointCache()).getLocation(file).getIdentifier());
            return attributes;
        }
        if(file.getType().contains(Path.Type.upload)) {
            final Write.Append append = new S3MultipartUploadService(session, new S3WriteFeature(session, acl), acl).append(file, new TransferStatus());
            if(append.append) {
                return new PathAttributes().withSize(append.offset);
            }
            throw new NotfoundException(file.getAbsolute());
        }
        try {
            PathAttributes attr;
            final Path bucket = containerService.getContainer(file);
            try {
                attr = new S3AttributesAdapter(session.getHost()).toAttributes(session.getClient().getVersionedObjectDetails(
                        file.attributes().getVersionId(), bucket.isRoot() ? StringUtils.EMPTY : bucket.getName(), containerService.getKey(file)));
            }
            catch(ServiceException e) {
                switch(e.getResponseCode()) {
                    case 405:
                        if(log.isDebugEnabled()) {
                            log.debug(String.format("Mark file %s as delete marker", file));
                        }
                        // Only DELETE method is allowed for delete markers
                        attr = new PathAttributes();
                        attr.setCustom(Collections.singletonMap(KEY_DELETE_MARKER, Boolean.TRUE.toString()));
                        attr.setDuplicate(true);
                        return attr;
                }
                throw new S3ExceptionMappingService().map("Failure to read attributes of {0}", e, file);
            }
            if(StringUtils.isNotBlank(attr.getVersionId())) {
                if(log.isDebugEnabled()) {
                    log.debug(String.format("Determine if %s is latest version for %s", attr.getVersionId(), file));
                }
                // Determine if latest version
                try {
                    final String latest = new S3AttributesAdapter(session.getHost()).toAttributes(session.getClient().getObjectDetails(
                            bucket.isRoot() ? StringUtils.EMPTY : bucket.getName(), containerService.getKey(file))).getVersionId();
                    if(null != latest) {
                        if(log.isDebugEnabled()) {
                            log.debug(String.format("Found later version %s for %s", latest, file));
                        }
                        // Duplicate if not latest version
                        attr.setDuplicate(!latest.equals(attr.getVersionId()));
                    }
                }
                catch(ServiceException e) {
                    final BackgroundException failure = new S3ExceptionMappingService().map("Failure to read attributes of {0}", e, file);
                    if(failure instanceof NotfoundException) {
                        attr.setDuplicate(true);
                    }
                    else {
                        throw failure;
                    }
                }
            }
            return attr;
        }
        catch(NotfoundException e) {
            if(file.isDirectory()) {
                if(log.isDebugEnabled()) {
                    log.debug(String.format("Search for common prefix %s", file));
                }
                // File may be marked as placeholder but no placeholder file exists. Check for common prefix returned.
                try {
                    new S3ObjectListService(session, acl).list(file, new CancellingListProgressListener(), String.valueOf(Path.DELIMITER), 1);
                }
                catch(ListCanceledException l) {
                    // Found common prefix
                    return PathAttributes.EMPTY;
                }
                catch(NotfoundException n) {
                    throw e;
                }
                // Found common prefix
                return PathAttributes.EMPTY;
            }
            throw e;
        }
    }
@Test
    public void testDeletedWithMarker() throws Exception {
        final Path bucket = new Path("versioning-test-eu-central-1-cyberduck", EnumSet.of(Path.Type.volume, Path.Type.directory));
        final Path test = new S3TouchFeature(session, new S3AccessControlListFeature(session)).touch(new Path(bucket, new AlphanumericRandomStringService().random(), EnumSet.of(Path.Type.file)), new TransferStatus());
        assertNotNull(test.attributes().getVersionId());
        assertNotEquals(PathAttributes.EMPTY, new S3AttributesFinderFeature(session, new S3AccessControlListFeature(session)).find(test));
        // Add delete marker
        new S3DefaultDeleteFeature(session).delete(Collections.singletonList(new Path(test).withAttributes(PathAttributes.EMPTY)), new DisabledPasswordCallback(), new Delete.DisabledCallback());
        assertTrue(new S3FindFeature(session, new S3AccessControlListFeature(session)).find(new Path(test)));
        assertFalse(new S3AttributesFinderFeature(session, new S3AccessControlListFeature(session)).find(test).getCustom().containsKey(KEY_DELETE_MARKER));
        assertFalse(new S3FindFeature(session, new S3AccessControlListFeature(session)).find(new Path(test).withAttributes(PathAttributes.EMPTY)));
        // Test reading delete marker itself
        final Path marker = new S3VersionedObjectListService(session, new S3AccessControlListFeature(session)).list(bucket, new DisabledListProgressListener()).find(new SimplePathPredicate(test));
        assertTrue(marker.attributes().isDuplicate());
        assertTrue(marker.attributes().getCustom().containsKey(KEY_DELETE_MARKER));
        assertTrue(new S3AttributesFinderFeature(session, new S3AccessControlListFeature(session)).find(marker).getCustom().containsKey(KEY_DELETE_MARKER));
        assertTrue(new S3FindFeature(session, new S3AccessControlListFeature(session)).find(marker));
    }