private boolean isTransformableMimetypeAndSize(
     String sourceMimetype,
     long sourceSize,
     String targetMimetype,
     TransformationOptions options) {
   boolean result = false;
   for (ContentTransformer ct : this.transformers) {
     if (ct.isTransformableMimetype(sourceMimetype, targetMimetype, options)) {
       if (sourceSize < 0) {
         result = true;
         break;
       } else {
         try {
           transformerDebug.pushIsTransformableSize(this);
           if (ct.isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options)) {
             result = true;
             break;
           }
         } finally {
           transformerDebug.popIsTransformableSize();
         }
       }
     }
   }
   return result;
 }
 @SuppressWarnings("deprecation")
 public boolean isExplicitTransformation(
     String sourceMimetype, String targetMimetype, TransformationOptions options) {
   boolean result = true;
   for (ContentTransformer ct : this.transformers) {
     if (ct.isExplicitTransformation(sourceMimetype, targetMimetype, options) == false) {
       result = false;
     }
   }
   return result;
 }
  /**
   * @see
   *     org.alfresco.repo.content.transform.ContentTransformer#transform(org.alfresco.service.cmr.repository.ContentReader,
   *     org.alfresco.service.cmr.repository.ContentWriter,
   *     org.alfresco.service.cmr.repository.TransformationOptions)
   */
  public final void transform(
      ContentReader reader, ContentWriter writer, TransformationOptions options)
      throws ContentIOException {
    try {
      depth.set(depth.get() + 1);

      // begin timing
      long before = System.currentTimeMillis();

      String sourceMimetype = reader.getMimetype();
      String targetMimetype = writer.getMimetype();

      // check options map
      if (options == null) {
        options = new TransformationOptions();
      }

      try {
        if (transformerDebug.isEnabled()) {
          transformerDebug.pushTransform(
              this,
              reader.getContentUrl(),
              sourceMimetype,
              targetMimetype,
              reader.getSize(),
              options);
        }

        // Check the transformability
        checkTransformable(reader, writer, options);

        // Pass on any limits to the reader
        setReaderLimits(reader, writer, options);

        // Transform
        // MNT-12238: CLONE - CLONE - Upload of PPTX causes very high memory usage leading to system
        // instability
        // Limiting transformation up to configured amount of milliseconds to avoid very high RAM
        // consumption
        // and OOM during transforming problematic documents
        TransformationOptionLimits limits =
            getLimits(reader.getMimetype(), writer.getMimetype(), options);

        long timeoutMs = limits.getTimeoutMs();
        if (!useTimeoutThread || (null == limits) || (-1 == timeoutMs)) {
          transformInternal(reader, writer, options);
        } else {
          Future<?> submittedTask = null;
          StreamAwareContentReaderProxy proxiedReader = new StreamAwareContentReaderProxy(reader);
          StreamAwareContentWriterProxy proxiedWriter = new StreamAwareContentWriterProxy(writer);

          try {
            submittedTask =
                getExecutorService()
                    .submit(new TransformInternalCallable(proxiedReader, proxiedWriter, options));
            submittedTask.get(timeoutMs + additionalThreadTimout, TimeUnit.MILLISECONDS);
          } catch (TimeoutException e) {
            releaseResources(submittedTask, proxiedReader, proxiedWriter);
            throw new TimeoutException("Transformation failed due to timeout limit");
          } catch (InterruptedException e) {
            releaseResources(submittedTask, proxiedReader, proxiedWriter);
            throw new InterruptedException(
                "Transformation failed, because the thread of the transformation was interrupted");
          } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof TransformInternalCallableException) {
              cause = ((TransformInternalCallableException) cause).getCause();
            }

            throw cause;
          }
        }

        // record time
        long after = System.currentTimeMillis();
        recordTime(sourceMimetype, targetMimetype, after - before);
      } catch (ContentServiceTransientException cste) {
        // A transient failure has occurred within the content transformer.
        // This should not be interpreted as a failure and therefore we should not
        // update the transformer's average time.
        if (logger.isDebugEnabled()) {
          logger.debug(
              "Transformation has been transiently declined: \n"
                  + "   reader: "
                  + reader
                  + "\n"
                  + "   writer: "
                  + writer
                  + "\n"
                  + "   options: "
                  + options
                  + "\n"
                  + "   transformer: "
                  + this);
        }
        // the finally block below will still perform tidyup. Otherwise we're done.
        // We rethrow the exception
        throw cste;
      } catch (UnsupportedTransformationException e) {
        // Don't record an error or even the time, as this is normal in compound transformations.
        transformerDebug.debug("          Failed", e);
        throw e;
      } catch (Throwable e) {
        // Make sure that this transformation gets set back i.t.o. time taken.
        // This will ensure that transformers that compete for the same transformation
        // will be prejudiced against transformers that tend to fail
        long after = System.currentTimeMillis();
        recordError(sourceMimetype, targetMimetype, after - before);

        // Ask Tika to detect the document, and report back on if
        //  the current mime type is plausible
        String differentType = getMimetypeService().getMimetypeIfNotMatches(reader.getReader());

        // Report the error
        if (differentType == null) {
          transformerDebug.debug("          Failed", e);
          throw new ContentIOException(
              "Content conversion failed: \n"
                  + "   reader: "
                  + reader
                  + "\n"
                  + "   writer: "
                  + writer
                  + "\n"
                  + "   options: "
                  + options.toString(false)
                  + "\n"
                  + "   limits: "
                  + getLimits(reader, writer, options),
              e);
        } else {
          transformerDebug.debug("          Failed: Mime type was '" + differentType + "'", e);

          if (this.retryTransformOnDifferentMimeType) {
            // MNT-11015 fix.
            // Set a new reader to refresh the input stream.
            reader = reader.getReader();
            // set the actual file MIME type detected by Tika for content reader
            reader.setMimetype(differentType);

            // Get correct transformer according actual file MIME type and try to transform file
            // with
            // actual transformer
            ContentTransformer transformer =
                this.registry.getTransformer(
                    differentType, reader.getSize(), targetMimetype, options);
            if (null != transformer) {
              transformer.transform(reader, writer, options);
            } else {
              transformerDebug.debug("          Failed", e);
              throw new ContentIOException(
                  "Content conversion failed: \n"
                      + "   reader: "
                      + reader
                      + "\n"
                      + "   writer: "
                      + writer
                      + "\n"
                      + "   options: "
                      + options.toString(false)
                      + "\n"
                      + "   limits: "
                      + getLimits(reader, writer, options)
                      + "\n"
                      + "   claimed mime type: "
                      + reader.getMimetype()
                      + "\n"
                      + "   detected mime type: "
                      + differentType
                      + "\n"
                      + "   transformer not found"
                      + "\n",
                  e);
            }
          } else {
            throw new ContentIOException(
                "Content conversion failed: \n"
                    + "   reader: "
                    + reader
                    + "\n"
                    + "   writer: "
                    + writer
                    + "\n"
                    + "   options: "
                    + options.toString(false)
                    + "\n"
                    + "   limits: "
                    + getLimits(reader, writer, options)
                    + "\n"
                    + "   claimed mime type: "
                    + reader.getMimetype()
                    + "\n"
                    + "   detected mime type: "
                    + differentType,
                e);
          }
        }
      } finally {
        transformerDebug.popTransform();

        // check that the reader and writer are both closed
        if (reader.isChannelOpen()) {
          logger.error(
              "Content reader not closed by transformer: \n"
                  + "   reader: "
                  + reader
                  + "\n"
                  + "   transformer: "
                  + this);
        }
        if (writer.isChannelOpen()) {
          logger.error(
              "Content writer not closed by transformer: \n"
                  + "   writer: "
                  + writer
                  + "\n"
                  + "   transformer: "
                  + this);
        }
      }

      // done
      if (logger.isDebugEnabled()) {
        logger.debug(
            "Completed transformation: \n"
                + "   reader: "
                + reader
                + "\n"
                + "   writer: "
                + writer
                + "\n"
                + "   options: "
                + options
                + "\n"
                + "   transformer: "
                + this);
      }
    } finally {
      depth.set(depth.get() - 1);
    }
  }
  /**
   * @see
   *     org.alfresco.repo.content.transform.AbstractContentTransformer2#transformInternal(org.alfresco.service.cmr.repository.ContentReader,
   *     org.alfresco.service.cmr.repository.ContentWriter,
   *     org.alfresco.service.cmr.repository.TransformationOptions)
   */
  @Override
  public void transformInternal(
      ContentReader reader, ContentWriter writer, TransformationOptions options) throws Exception {
    final String outputMimetype = writer.getMimetype();
    final String outputFileExt = getMimetypeService().getExtension(outputMimetype);

    // We need to keep a reference to thrown exceptions as we're going to catch them and
    // then move on to the next transformer. In the event that they all fail, we will throw
    // the final exception.
    Exception transformationException = null;

    for (int i = 0; i < transformers.size(); i++) {
      int oneBasedCount = i + 1;
      ContentTransformer transf = transformers.get(i);
      ContentWriter currentWriter = null;
      File tempFile = null;
      try {
        if (logger.isDebugEnabled()) {
          logger.debug(
              "Transformation attempt "
                  + oneBasedCount
                  + " of "
                  + transformers.size()
                  + ": "
                  + transf);
        }

        if (!transf.isTransformable(
            reader.getMimetype(), reader.getSize(), outputMimetype, options)) {
          throw new UnsupportedTransformationException(
              "Unsupported transformation: " + reader.getMimetype() + " to " + outputMimetype);
        }

        // We can't know in advance which transformer in the sequence will work - if any.
        // Therefore we can't write into the ContentWriter stream.
        // So make a temporary file writer with the current transformer name.
        tempFile =
            TempFileProvider.createTempFile(
                "FailoverTransformer_intermediate_" + transf.getClass().getSimpleName() + "_",
                "." + outputFileExt);
        currentWriter = new FileContentWriter(tempFile);
        currentWriter.setMimetype(outputMimetype);
        currentWriter.setEncoding(writer.getEncoding());

        // attempt to transform
        transf.transform(reader, currentWriter, options);

        // TODO Could add a check for zero-length output and treat that as a failure
        // final long writtenSize = currentWriter.getSize();
      } catch (Exception are) {
        if (transformationException == null) {
          transformationException = are;
        }

        if (logger.isDebugEnabled()) {
          logger.debug("Transformation " + oneBasedCount + " was unsuccessful.");
          if (i != transformers.size() - 1) {
            // We don't log the last exception as we're going to throw it.
            logger.debug("The below exception is provided for information purposes only.", are);
          }
        }

        // Set a new reader to refresh the input stream.
        reader = reader.getReader();
        // and move to the next transformer
        continue;
      }
      // No need to close input or output streams

      // At this point the current transformation was successful i.e. it did not throw an exception.

      // Now we must copy the content from the temporary file into the ContentWriter stream.
      if (tempFile != null) {
        writer.putContent(tempFile);
      }

      if (logger.isInfoEnabled()) {
        logger.info("Transformation was successful");
      }
      return;
    }
    // At this point we have tried all transformers in the sequence without apparent success.
    if (transformationException != null) {
      transformerDebug.debug("          No more transformations to failover to");
      if (logger.isDebugEnabled()) {
        logger.debug(
            "All transformations were unsuccessful. Throwing first exception.",
            transformationException);
      }
      throw transformationException;
    }
  }