You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2021/07/16 13:00:57 UTC

[sling-whiteboard] branch master updated: Adding support for persisting the thumbnails

This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 9438e18  Adding support for persisting the thumbnails
9438e18 is described below

commit 9438e1884e177559b55286fc2f9bd177c1f1898a
Author: Dan Klco <kl...@adobe.com>
AuthorDate: Fri Jul 16 09:00:43 2021 -0400

    Adding support for persisting the thumbnails
---
 org.apache.sling.thumbnails/pom.xml                |   2 +-
 .../apache/sling/thumbnails/RenditionSupport.java  |  82 ++++++++++++
 .../internal/DynamicTransformServlet.java          |  27 +++-
 .../thumbnails/internal/RenditionSupportImpl.java  |  99 ++++++++++++++
 .../thumbnails/internal/TransformServlet.java      |  86 ++++++-------
 .../internal/TransformationServiceUser.java        |   2 +-
 .../internal/DynamicTransformServletTest.java      |  14 +-
 .../internal/RenditionSupportImplTest.java         | 142 +++++++++++++++++++++
 .../thumbnails/internal/TransformServletTest.java  |   6 +-
 9 files changed, 400 insertions(+), 60 deletions(-)

diff --git a/org.apache.sling.thumbnails/pom.xml b/org.apache.sling.thumbnails/pom.xml
index cdd7433..5a3b07d 100644
--- a/org.apache.sling.thumbnails/pom.xml
+++ b/org.apache.sling.thumbnails/pom.xml
@@ -12,7 +12,7 @@
         <version>43</version>
     </parent>
     <artifactId>org.apache.sling.thumbnails</artifactId>
-    <name>Apache Sling Thumbnail</name>
+    <name>Apache Sling Thumbnail Support</name>
     <description>An API and Service for creating and transforming images and documents into thumbnails</description>
     <version>1.0.0-SNAPSHOT</version>
 
diff --git a/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/RenditionSupport.java b/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/RenditionSupport.java
new file mode 100644
index 0000000..f389e89
--- /dev/null
+++ b/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/RenditionSupport.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.thumbnails;
+
+import java.io.InputStream;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * Service for interacting with renditions
+ */
+@ProviderType
+public interface RenditionSupport {
+
+    /**
+     * Retrieves the rendition with the specified rendition name, if one exists.
+     * 
+     * @param file          the file from which to retrieve the rendition
+     * @param renditionName the rendition to retrieve
+     * @return the rendition resource or null
+     */
+    @Nullable
+    Resource getRendition(@NotNull Resource file, @NotNull String renditionName);
+
+    /**
+     * Retrieves the inputstream of the data of a rendition with the specified
+     * rendition name, if one exists.
+     * 
+     * @param file          the file from which to retrieve the rendition
+     * @param renditionName the rendition to retrieve
+     * @return the rendition contents or null
+     */
+    @Nullable
+    InputStream getRenditionContent(@NotNull Resource file, @NotNull String renditionName);
+
+    /**
+     * Returns true if the requested rendition exists for the specified file.
+     * 
+     * @param file          the file to check
+     * @param renditionName the rendition name to check (including extension)
+     * @return true if the rendition exists, false otherwise
+     */
+    boolean renditionExists(@NotNull Resource file, @NotNull String renditionName);
+
+    /**
+     * Checks if the file supports renditions, e.g. it's defined as a Persistable
+     * Type.
+     * 
+     * @param file the file to check
+     * @return true if the file supports renditons, false otherwise
+     */
+    boolean supportsRenditions(@NotNull Resource file);
+
+    /**
+     * Sets the content of the rendition, overriding any existing content
+     * 
+     * @param file
+     * @param renditionName
+     * @param baos
+     */
+    void setRendition(@NotNull Resource file, @NotNull String renditionName, @NotNull InputStream baos)
+            throws PersistenceException;
+
+}
diff --git a/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/DynamicTransformServlet.java b/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/DynamicTransformServlet.java
index dbd65ce..9c81b70 100644
--- a/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/DynamicTransformServlet.java
+++ b/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/DynamicTransformServlet.java
@@ -29,17 +29,19 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.poi.util.IOUtils;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.SlingHttpServletResponse;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.servlets.SlingAllMethodsServlet;
-import org.apache.sling.thumbnails.internal.models.TransformationHandlerConfigImpl;
-import org.apache.sling.thumbnails.internal.models.TransformationImpl;
 import org.apache.sling.thumbnails.BadRequestException;
 import org.apache.sling.thumbnails.OutputFileFormat;
+import org.apache.sling.thumbnails.RenditionSupport;
 import org.apache.sling.thumbnails.Transformation;
 import org.apache.sling.thumbnails.Transformer;
+import org.apache.sling.thumbnails.internal.models.TransformationHandlerConfigImpl;
+import org.apache.sling.thumbnails.internal.models.TransformationImpl;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
@@ -58,8 +60,11 @@ public class DynamicTransformServlet extends SlingAllMethodsServlet {
 
     private final transient Transformer transformer;
 
+    private final transient RenditionSupport renditionSupport;
+
     @Activate
-    public DynamicTransformServlet(@Reference Transformer transformer) {
+    public DynamicTransformServlet(@Reference Transformer transformer, @Reference RenditionSupport renditionSupport) {
+        this.renditionSupport = renditionSupport;
         this.transformer = transformer;
     }
 
@@ -88,8 +93,20 @@ public class DynamicTransformServlet extends SlingAllMethodsServlet {
             List<TransformationHandlerConfigImpl> transformations = parsePostBody(request, objectMapper);
 
             log.debug("Transforming resource: {} with transformation: {} to {}", resource, transformations, format);
-            transform(resource, response, format.toString(), new TransformationImpl(transformations));
-
+            ByteArrayOutputStream baos = transform(resource, response, format.toString(),
+                    new TransformationImpl(transformations));
+
+            String renditionName = request.getParameter("renditionName");
+            if (StringUtils.isNotBlank(renditionName)) {
+                log.debug("Setting rendition: {}", renditionName);
+                if (renditionSupport.supportsRenditions(resource)) {
+                    renditionSupport.setRendition(resource, renditionName,
+                            new ByteArrayInputStream(baos.toByteArray()));
+                } else {
+                    throw new BadRequestException(
+                            "Type " + resource.getResourceType() + " does not support persisting renditions");
+                }
+            }
         } catch (BadRequestException e) {
             log.error("Could not render thumbnail due to bad request", e);
             response.sendError(400, "Could not render thumbnail due to bad request: " + e.getMessage());
diff --git a/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/RenditionSupportImpl.java b/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/RenditionSupportImpl.java
new file mode 100644
index 0000000..a17c13d
--- /dev/null
+++ b/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/RenditionSupportImpl.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.thumbnails.internal;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.thumbnails.RenditionSupport;
+import org.apache.sling.thumbnails.ThumbnailSupport;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component(service = RenditionSupport.class)
+public class RenditionSupportImpl implements RenditionSupport {
+
+    private final ThumbnailSupport thumbnailSupport;
+    private final TransformationServiceUser transformationServiceUser;
+
+    @Activate
+    public RenditionSupportImpl(@Reference ThumbnailSupport thumbnailSupport,
+            @Reference TransformationServiceUser transformationServiceUser) {
+        this.thumbnailSupport = thumbnailSupport;
+        this.transformationServiceUser = transformationServiceUser;
+    }
+
+    @Override
+    public @Nullable Resource getRendition(@NotNull Resource file, @NotNull String renditionName) {
+        if (supportsRenditions(file)) {
+            String subpath = thumbnailSupport.getRenditionPath(file.getResourceType());
+            return file.getChild(subpath + "/" + renditionName);
+        }
+        return null;
+    }
+
+    @Override
+    public @Nullable InputStream getRenditionContent(@NotNull Resource file, @NotNull String renditionName) {
+        return Optional.ofNullable(getRendition(file, renditionName)).map(r -> r.adaptTo(InputStream.class))
+                .orElse(null);
+    }
+
+    @Override
+    public boolean renditionExists(@NotNull Resource file, @NotNull String renditionName) {
+        return getRendition(file, renditionName) != null;
+    }
+
+    @Override
+    public boolean supportsRenditions(@NotNull Resource file) {
+        return thumbnailSupport.getPersistableTypes().contains(file.getResourceType());
+    }
+
+    @Override
+    public void setRendition(@NotNull Resource file, @NotNull String renditionName, @NotNull InputStream contents)
+            throws PersistenceException {
+
+        try (ResourceResolver serviceResolver = transformationServiceUser.getTransformationServiceUser()) {
+
+            Resource renditionFile = ResourceUtil.getOrCreateResource(serviceResolver,
+                    file.getPath() + "/" + thumbnailSupport.getRenditionPath(file.getResourceType()) + "/"
+                            + renditionName,
+                    Collections.singletonMap(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_FILE),
+                    JcrConstants.NT_UNSTRUCTURED, false);
+            Map<String, Object> properties = new HashMap<>();
+            properties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+            properties.put(JcrConstants.JCR_DATA, contents);
+            ResourceUtil.getOrCreateResource(serviceResolver, renditionFile.getPath() + "/" + JcrConstants.JCR_CONTENT,
+                    properties, JcrConstants.NT_UNSTRUCTURED, true);
+        } catch (LoginException le) {
+            throw new PersistenceException("Could not save due to LoginException", le);
+        }
+
+    }
+
+}
diff --git a/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/TransformServlet.java b/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/TransformServlet.java
index e2bd9d8..17c25e0 100644
--- a/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/TransformServlet.java
+++ b/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/TransformServlet.java
@@ -19,30 +19,26 @@ package org.apache.sling.thumbnails.internal;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collections;
 import java.util.Dictionary;
-import java.util.HashMap;
 import java.util.Hashtable;
-import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.ExecutionException;
 
 import javax.servlet.RequestDispatcher;
 import javax.servlet.Servlet;
 import javax.servlet.ServletException;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.jackrabbit.JcrConstants;
 import org.apache.poi.util.IOUtils;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.SlingHttpServletResponse;
 import org.apache.sling.api.request.RequestDispatcherOptions;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.servlets.SlingAllMethodsServlet;
 import org.apache.sling.thumbnails.BadRequestException;
 import org.apache.sling.thumbnails.OutputFileFormat;
+import org.apache.sling.thumbnails.RenditionSupport;
 import org.apache.sling.thumbnails.ThumbnailSupport;
 import org.apache.sling.thumbnails.Transformation;
 import org.apache.sling.thumbnails.Transformer;
@@ -68,7 +64,9 @@ public class TransformServlet extends SlingAllMethodsServlet {
 
     private static final long serialVersionUID = -1513067546618762171L;
 
-    public static final String SERVICE_USER = "sling-thumbnails";
+    private final transient RenditionSupport renditionSupport;
+
+    private final transient ServiceRegistration<Servlet> servletRegistration;
 
     private final transient TransformationServiceUser transformationServiceUser;
 
@@ -78,12 +76,12 @@ public class TransformServlet extends SlingAllMethodsServlet {
 
     private final transient TransformationCache transformationCache;
 
-    private final transient ServiceRegistration<Servlet> servletRegistration;
-
     @Activate
     public TransformServlet(@Reference ThumbnailSupport thumbnailSupport, @Reference Transformer transformer,
             @Reference TransformationServiceUser transformationServiceUser,
-            @Reference TransformationCache transformationCache, BundleContext context) {
+            @Reference TransformationCache transformationCache, @Reference RenditionSupport renditionSupport,
+            BundleContext context) {
+        this.renditionSupport = renditionSupport;
         this.thumbnailSupport = thumbnailSupport;
         this.transformer = transformer;
         this.transformationServiceUser = transformationServiceUser;
@@ -112,19 +110,22 @@ public class TransformServlet extends SlingAllMethodsServlet {
             throws ServletException, IOException {
         log.trace("doGet");
 
-        String name = StringUtils.substringBeforeLast(request.getRequestPathInfo().getSuffix(), ".");
-        response.setHeader("Content-Disposition", "filename=" + request.getResource().getName());
+        String transformationName = StringUtils.substringBeforeLast(request.getRequestPathInfo().getSuffix(), ".");
+        String renditionName = request.getRequestPathInfo().getSuffix();
         String format = StringUtils.substringAfterLast(request.getRequestPathInfo().getSuffix(), ".");
-        log.debug("Transforming resource: {} with transformation: {} to {}", request.getResource(), name, format);
-        String original = response.getContentType();
-        try (ResourceResolver serviceResolver = transformationServiceUser.getTransformationServiceUser()) {
-            Optional<Transformation> transformation = transformationCache.getTransformation(serviceResolver, name);
-            if (transformation.isPresent()) {
-                performTransformation(request, response, name, format, serviceResolver, transformation.get());
+        response.setHeader("Content-Disposition", "filename=" + request.getResource().getName());
+
+        log.debug("Transforming resource: {} with transformation: {} to {}", request.getResource(), transformationName,
+                format);
+        try {
+            Resource file = request.getResource();
+            if (renditionSupport.renditionExists(file, renditionName)) {
+                response.setContentType(OutputFileFormat.forRequest(request).getMimeType());
+                IOUtils.copy(renditionSupport.getRenditionContent(file, renditionName), response.getOutputStream());
             } else {
-                log.error("Unable to find transformation: {}", name);
-                response.setContentType(original);
-                response.sendError(404, "Could not find transformation: " + name);
+                try (ResourceResolver servicResolver = transformationServiceUser.getTransformationServiceUser()) {
+                    performTransformation(request, response, transformationName, renditionName, servicResolver);
+                }
             }
         } catch (BadRequestException e) {
             log.error("Could not render thumbnail due to bad request", e);
@@ -140,34 +141,27 @@ public class TransformServlet extends SlingAllMethodsServlet {
         }
     }
 
-    private void performTransformation(SlingHttpServletRequest request, SlingHttpServletResponse response, String name,
-            String format, ResourceResolver serviceResolver, Transformation transformation) throws IOException {
+    private void performTransformation(SlingHttpServletRequest request, SlingHttpServletResponse response,
+            String transformationName, String renditionName, ResourceResolver serviceResolver)
+            throws IOException, ExecutionException {
+        Resource file = request.getResource();
+        String originalContentType = response.getContentType();
         response.setContentType(OutputFileFormat.forRequest(request).getMimeType());
-
-        String resourceType = request.getResource().getResourceType();
-        if (thumbnailSupport.getPersistableTypes().contains(resourceType)) {
-            String expectedPath = thumbnailSupport.getRenditionPath(resourceType) + "/" + name + "." + format;
-            Resource rendition = request.getResource().getChild(expectedPath);
-            if (rendition != null) {
-                log.debug("Using existing rendition {}", name);
-                IOUtils.copy(rendition.adaptTo(InputStream.class), response.getOutputStream());
-            } else {
-                ByteArrayOutputStream baos = transform(request, response, transformation);
-                Resource file = ResourceUtil.getOrCreateResource(serviceResolver,
-                        request.getResource().getPath() + "/" + expectedPath,
-                        Collections.singletonMap(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_FILE),
-                        JcrConstants.NT_UNSTRUCTURED, false);
-                Map<String, Object> properties = new HashMap<>();
-                properties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
-                properties.put(JcrConstants.JCR_DATA, new ByteArrayInputStream(baos.toByteArray()));
-                ResourceUtil.getOrCreateResource(serviceResolver, file.getPath() + "/" + JcrConstants.JCR_CONTENT,
-                        properties, JcrConstants.NT_UNSTRUCTURED, true);
-            }
+        Optional<Transformation> transformationOp = transformationCache.getTransformation(serviceResolver,
+                transformationName);
+        if (!transformationOp.isPresent()) {
+            log.error("Unable to find transformation: {}", transformationName);
+            response.setContentType(originalContentType);
+            response.sendError(404, "Unable to find transformation: " + transformationName);
         } else {
-            log.debug("Sending transformation to response....");
-            transform(request, response, transformation);
+            Transformation transformation = transformationOp.get();
+            log.debug("Transforming file...");
+            ByteArrayOutputStream baos = transform(request, response, transformation);
+            if (renditionSupport.supportsRenditions(file)) {
+                log.debug("Saving rendition...");
+                renditionSupport.setRendition(file, renditionName, new ByteArrayInputStream(baos.toByteArray()));
+            }
         }
-
     }
 
     private ByteArrayOutputStream transform(SlingHttpServletRequest request, SlingHttpServletResponse response,
diff --git a/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/TransformationServiceUser.java b/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/TransformationServiceUser.java
index bcb841d..d5478b9 100644
--- a/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/TransformationServiceUser.java
+++ b/org.apache.sling.thumbnails/src/main/java/org/apache/sling/thumbnails/internal/TransformationServiceUser.java
@@ -33,7 +33,7 @@ import org.osgi.service.component.annotations.Reference;
 @Component(service = TransformationServiceUser.class)
 public class TransformationServiceUser {
 
-    public static final String SERVICE_USER = "sling-commons-thumbnails";
+    public static final String SERVICE_USER = "sling-thumbnails";
 
     private final ResourceResolverFactory resolverFactory;
 
diff --git a/org.apache.sling.thumbnails/src/test/java/org/apache/sling/thumbnails/internal/DynamicTransformServletTest.java b/org.apache.sling.thumbnails/src/test/java/org/apache/sling/thumbnails/internal/DynamicTransformServletTest.java
index ce6b4f4..3764879 100644
--- a/org.apache.sling.thumbnails/src/test/java/org/apache/sling/thumbnails/internal/DynamicTransformServletTest.java
+++ b/org.apache.sling.thumbnails/src/test/java/org/apache/sling/thumbnails/internal/DynamicTransformServletTest.java
@@ -29,14 +29,14 @@ import java.util.List;
 import javax.servlet.ServletException;
 
 import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.thumbnails.ThumbnailSupport;
 import org.apache.sling.thumbnails.extension.ThumbnailProvider;
 import org.apache.sling.thumbnails.extension.TransformationHandler;
 import org.apache.sling.thumbnails.internal.providers.ImageThumbnailProvider;
 import org.apache.sling.thumbnails.internal.providers.PdfThumbnailProvider;
 import org.apache.sling.thumbnails.internal.transformers.CropHandler;
 import org.apache.sling.thumbnails.internal.transformers.ResizeHandler;
-import org.apache.sling.testing.mock.sling.junit.SlingContext;
-import org.apache.sling.thumbnails.ThumbnailSupport;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -66,8 +66,12 @@ public class DynamicTransformServletTest {
         when(thumbnailSupport.getSupportedTypes()).thenReturn(Collections.singleton("nt:file"));
         when(thumbnailSupport.getMetaTypePropertyPath("nt:file")).thenReturn("jcr:content/jcr:mimeType");
 
+        TransformationServiceUser tsu = mock(TransformationServiceUser.class);
+        when(tsu.getTransformationServiceUser()).thenReturn(context.resourceResolver());
+
+        RenditionSupportImpl renditionSupport = new RenditionSupportImpl(thumbnailSupport, tsu);
         TransformerImpl transformer = new TransformerImpl(providers, thumbnailSupport, th);
-        dts = new DynamicTransformServlet(transformer);
+        dts = new DynamicTransformServlet(transformer, renditionSupport);
 
     }
 
@@ -99,11 +103,11 @@ public class DynamicTransformServletTest {
         assertEquals(400, context.response().getStatus());
     }
 
-
     @Test
     public void testMissingResource() throws IOException, ServletException {
 
-        context.request().addRequestParameter("resource", "/content/apache/sling-apache-org/index/wow-look-at-this-file.png");
+        context.request().addRequestParameter("resource",
+                "/content/apache/sling-apache-org/index/wow-look-at-this-file.png");
         context.request().addRequestParameter("format", "png");
         context.request().setContent(
                 "[{\"handlerType\":\"sling/thumbnails/transformers/crop\",\"properties\":{\"position\":\"CENTER\",\"width\":1000,\"height\":1000}}]"
diff --git a/org.apache.sling.thumbnails/src/test/java/org/apache/sling/thumbnails/internal/RenditionSupportImplTest.java b/org.apache.sling.thumbnails/src/test/java/org/apache/sling/thumbnails/internal/RenditionSupportImplTest.java
new file mode 100644
index 0000000..2fe9ed5
--- /dev/null
+++ b/org.apache.sling.thumbnails/src/test/java/org/apache/sling/thumbnails/internal/RenditionSupportImplTest.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.thumbnails.internal;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.thumbnails.RenditionSupport;
+import org.apache.sling.thumbnails.ThumbnailSupport;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class RenditionSupportImplTest {
+
+    private RenditionSupport renditionSupport;
+
+    @Rule
+    public final SlingContext context = new SlingContext();
+
+    private @NotNull Resource slingFolderResource;
+
+    private @NotNull Resource ntFileresource;
+
+    private @NotNull Resource slingFileResource;
+
+    private TransformationServiceUser tsu;
+
+    @Before
+    public void init() throws IllegalAccessException, LoginException {
+
+        ContextHelper.initContext(context);
+
+        ThumbnailSupport thumbnailSupport = mock(ThumbnailSupport.class);
+        when(thumbnailSupport.getPersistableTypes()).thenReturn(Collections.singleton("sling:File"));
+        when(thumbnailSupport.getRenditionPath("sling:File")).thenReturn("jcr:content/renditions");
+
+        Set<String> supportedTypes = new HashSet<>();
+        supportedTypes.add("sling:File");
+        supportedTypes.add("nt:file");
+        when(thumbnailSupport.getSupportedTypes()).thenReturn(supportedTypes);
+        when(thumbnailSupport.getMetaTypePropertyPath(anyString())).thenReturn("jcr:content/jcr:mimeType");
+
+        tsu = mock(TransformationServiceUser.class);
+        when(tsu.getTransformationServiceUser()).thenReturn(context.resourceResolver());
+
+        renditionSupport = new RenditionSupportImpl(thumbnailSupport, tsu);
+
+        slingFolderResource = context.resourceResolver().getResource("/content");
+        Map<String, Object> ntFileProperties = new HashMap<>();
+        ntFileProperties.put("jcr:primaryType", JcrConstants.NT_FILE);
+        ntFileProperties.put("jcr:content/jcr:primaryType", JcrConstants.NT_RESOURCE);
+        ntFileProperties.put("jcr:content/jcr:data", new byte[] { 1, 0 });
+        ntFileProperties.put("jcr:content/jcr:mimeType", "image/jpeg");
+        ntFileresource = context.create().resource("/content/ntfile.jpg", ntFileProperties);
+
+        slingFileResource = context.create().resource("/content/slingfile.jpg",
+                Collections.singletonMap(JcrConstants.JCR_PRIMARYTYPE, "sling:File"));
+        Map<String, Object> slingFileProperties = new HashMap<>();
+        slingFileProperties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+        slingFileProperties.put(JcrConstants.JCR_DATA, new byte[] { 1, 0 });
+        slingFileProperties.put("jcr:mimeType", "image/jpeg");
+        context.create().resource("/content/slingfile.jpg/jcr:content", slingFileProperties);
+
+    }
+
+    @Test
+    public void testSupportsRenditions() {
+        assertFalse(renditionSupport.supportsRenditions(ntFileresource));
+        assertFalse(renditionSupport.supportsRenditions(slingFolderResource));
+        assertTrue(renditionSupport.supportsRenditions(slingFileResource));
+    }
+
+    @Test
+    public void testRenditionExists() {
+        assertFalse(renditionSupport.renditionExists(ntFileresource, "myrendition.png"));
+
+        ntFileresource = context.create().resource("/content/ntfile.jpg/jcr:content/renditions",
+                Collections.singletonMap(JcrConstants.JCR_PRIMARYTYPE, "sling:Folder"));
+
+        Map<String, Object> ntFileProperties = new HashMap<>();
+        ntFileProperties.put("jcr:primaryType", JcrConstants.NT_FILE);
+        ntFileProperties.put("jcr:content/jcr:primaryType", JcrConstants.NT_RESOURCE);
+        ntFileProperties.put("jcr:content/jcr:data", new byte[] { 1, 0 });
+        ntFileProperties.put("jcr:content/jcr:mimeType", "image/png");
+        context.create().resource("/content/slingfile.jpg/jcr:content/renditions/myrendition.png", ntFileProperties);
+
+        assertTrue(renditionSupport.renditionExists(slingFileResource, "myrendition.png"));
+        assertFalse(renditionSupport.renditionExists(slingFileResource, "myrendition.jpg"));
+        assertFalse(renditionSupport.renditionExists(slingFileResource, "myrendition2.ong"));
+    }
+
+    @Test
+    public void testCreateRendition() throws PersistenceException {
+        assertFalse(renditionSupport.renditionExists(slingFileResource, "myrendition.png"));
+        assertNull(renditionSupport.getRenditionContent(slingFileResource, "myrendition.png"));
+        renditionSupport.setRendition(slingFileResource, "myrendition.png",
+                new ByteArrayInputStream(new byte[] { 0, 1 }));
+        assertTrue(renditionSupport.renditionExists(slingFileResource, "myrendition.png"));
+        assertNotNull(renditionSupport.getRenditionContent(slingFileResource, "myrendition.png"));
+    }
+
+    @Test(expected = PersistenceException.class)
+    public void testLoginFailure() throws PersistenceException, LoginException {
+
+        when(tsu.getTransformationServiceUser()).thenThrow(new LoginException("I'm sorry, I can't do that Dave"));
+        renditionSupport.setRendition(slingFileResource, "myrendition.png",
+                new ByteArrayInputStream(new byte[] { 0, 1 }));
+    }
+}
diff --git a/org.apache.sling.thumbnails/src/test/java/org/apache/sling/thumbnails/internal/TransformServletTest.java b/org.apache.sling.thumbnails/src/test/java/org/apache/sling/thumbnails/internal/TransformServletTest.java
index 42c1f0e..92b6643 100644
--- a/org.apache.sling.thumbnails/src/test/java/org/apache/sling/thumbnails/internal/TransformServletTest.java
+++ b/org.apache.sling.thumbnails/src/test/java/org/apache/sling/thumbnails/internal/TransformServletTest.java
@@ -118,7 +118,10 @@ public class TransformServletTest {
         when(thumbnailSupport.getServletErrorResourcePath()).thenReturn("/content/error");
 
         TransformerImpl transformer = new TransformerImpl(providers, thumbnailSupport, th);
-        ts = new TransformServlet(thumbnailSupport, transformer, tsu, new TransformationCache(tsu),
+
+        RenditionSupportImpl renditionSupport = new RenditionSupportImpl(thumbnailSupport, tsu);
+
+        ts = new TransformServlet(thumbnailSupport, transformer, tsu, new TransformationCache(tsu), renditionSupport,
                 mock(BundleContext.class));
 
         MockRequestDispatcherFactory dispatcherFactory = mock(MockRequestDispatcherFactory.class);
@@ -163,7 +166,6 @@ public class TransformServletTest {
         ts.doGet(context.request(), context.response());
 
         assertEquals(500, context.response().getStatus());
-        assertEquals("image/jpeg", context.response().getContentType());
 
         verify(dispatcher).forward(any(), any());
     }