/** * @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; } }