| 1 | package fr.sii.ogham.html.translator; | |
| 2 | ||
| 3 | import static fr.sii.ogham.core.util.HtmlUtils.getDistinctCssImageUrls; | |
| 4 | import static fr.sii.ogham.core.util.HtmlUtils.getDistinctImageUrls; | |
| 5 | import static fr.sii.ogham.core.util.HtmlUtils.skipExternalUrls; | |
| 6 | import static fr.sii.ogham.html.inliner.impl.jsoup.ImageInlineUtils.removeOghamAttributes; | |
| 7 | import static fr.sii.ogham.html.inliner.impl.regexp.CssImageInlineUtils.removeOghamProperties; | |
| 8 | ||
| 9 | import java.io.ByteArrayInputStream; | |
| 10 | import java.io.File; | |
| 11 | import java.io.IOException; | |
| 12 | import java.util.ArrayList; | |
| 13 | import java.util.List; | |
| 14 | ||
| 15 | import org.slf4j.Logger; | |
| 16 | import org.slf4j.LoggerFactory; | |
| 17 | ||
| 18 | import fr.sii.ogham.core.exception.handler.ContentTranslatorException; | |
| 19 | import fr.sii.ogham.core.exception.handler.ImageInliningException; | |
| 20 | import fr.sii.ogham.core.exception.mimetype.MimeTypeDetectionException; | |
| 21 | import fr.sii.ogham.core.exception.resource.ResourceResolutionException; | |
| 22 | import fr.sii.ogham.core.message.content.Content; | |
| 23 | import fr.sii.ogham.core.message.content.HasResourcePath; | |
| 24 | import fr.sii.ogham.core.message.content.MayHaveStringContent; | |
| 25 | import fr.sii.ogham.core.message.content.StringContent; | |
| 26 | import fr.sii.ogham.core.message.content.UpdatableStringContent; | |
| 27 | import fr.sii.ogham.core.mimetype.MimeTypeProvider; | |
| 28 | import fr.sii.ogham.core.resource.path.RelativePath; | |
| 29 | import fr.sii.ogham.core.resource.path.RelativePathResolver; | |
| 30 | import fr.sii.ogham.core.resource.path.ResourcePath; | |
| 31 | import fr.sii.ogham.core.resource.path.UnresolvedPath; | |
| 32 | import fr.sii.ogham.core.resource.resolver.ResourceResolver; | |
| 33 | import fr.sii.ogham.core.translator.content.ContentTranslator; | |
| 34 | import fr.sii.ogham.core.util.IOUtils; | |
| 35 | import fr.sii.ogham.email.message.content.ContentWithAttachments; | |
| 36 | import fr.sii.ogham.html.inliner.ContentWithImages; | |
| 37 | import fr.sii.ogham.html.inliner.ImageInliner; | |
| 38 | import fr.sii.ogham.html.inliner.ImageResource; | |
| 39 | ||
| 40 | /** | |
| 41 | * Translator that transforms HTML content. If not HTML, the translator has no | |
| 42 | * effect. The HTML is analyzed in order to find images. For each found image, | |
| 43 | * it uses the resource resolver in order to find the image file. Once all | |
| 44 | * images are found, the HTML is transformed in order to inline the images. The | |
| 45 | * images can be inlined using several methods: | |
| 46 | * <ul> | |
| 47 | * <li>Base64 inliner to convert images to base64 equivalent</li> | |
| 48 | * <li>Extract images and generate attachments to join to the email</li> | |
| 49 | * <li>Maybe anything else</li> | |
| 50 | * </ul> | |
| 51 | * | |
| 52 | * @author Aurélien Baudet | |
| 53 | * | |
| 54 | */ | |
| 55 | public class InlineImageTranslator implements ContentTranslator { | |
| 56 | private static final Logger LOG = LoggerFactory.getLogger(InlineImageTranslator.class); | |
| 57 | | |
| 58 | /** | |
| 59 | * The image inliner | |
| 60 | */ | |
| 61 | private final ImageInliner inliner; | |
| 62 | ||
| 63 | /** | |
| 64 | * The resource resolver used to find images | |
| 65 | */ | |
| 66 | private final ResourceResolver resourceResolver; | |
| 67 | ||
| 68 | /** | |
| 69 | * The provider that detects the mimetype for each image | |
| 70 | */ | |
| 71 | private final MimeTypeProvider mimetypeProvider; | |
| 72 | | |
| 73 | /** | |
| 74 | * Provides an instance used to resolve relative path from source path and relative path | |
| 75 | */ | |
| 76 | private final RelativePathResolver relativePathProvider; | |
| 77 | ||
| 78 | public InlineImageTranslator(ImageInliner inliner, ResourceResolver resourceResolver, MimeTypeProvider mimetypeProvider, RelativePathResolver relativePathProvider) { | |
| 79 | super(); | |
| 80 | this.inliner = inliner; | |
| 81 | this.resourceResolver = resourceResolver; | |
| 82 | this.mimetypeProvider = mimetypeProvider; | |
| 83 | this.relativePathProvider = relativePathProvider; | |
| 84 | } | |
| 85 | ||
| 86 | @Override | |
| 87 | public Content translate(Content content) throws ContentTranslatorException { | |
| 88 |
10
1. translate : negated conditional → SURVIVED 2. translate : negated conditional → NO_COVERAGE 3. translate : negated conditional → SURVIVED 4. translate : negated conditional → NO_COVERAGE 5. translate : negated conditional → TIMED_OUT 6. translate : negated conditional → TIMED_OUT 7. translate : negated conditional → KILLED 8. translate : negated conditional → KILLED 9. translate : negated conditional → KILLED 10. translate : negated conditional → KILLED |
if (content instanceof MayHaveStringContent && ((MayHaveStringContent) content).canProvideString()) { |
| 89 | String stringContent = ((MayHaveStringContent) content).asString(); | |
| 90 | List<String> images = skipExternalUrls(merge(getDistinctImageUrls(stringContent), getDistinctCssImageUrls(stringContent))); | |
| 91 |
5
1. translate : negated conditional → NO_COVERAGE 2. translate : negated conditional → SURVIVED 3. translate : negated conditional → TIMED_OUT 4. translate : negated conditional → KILLED 5. translate : negated conditional → KILLED |
if (!images.isEmpty()) { |
| 92 | LOG.debug("inlining {} images", images.size()); | |
| 93 | // prepare list of images paths/urls with their content | |
| 94 | List<ImageResource> imageResources = load(getSourcePath(content), images); | |
| 95 | // generate new HTML with inlined images | |
| 96 | ContentWithImages contentWithImages = inliner.inline(stringContent, imageResources); | |
| 97 | // remove ogham attributes | |
| 98 | ContentWithImages cleaned = clean(contentWithImages); | |
| 99 | // update the HTML content | |
| 100 | Content inlinedContent = updateHtmlContent(content, cleaned); | |
| 101 | LOG.debug("{} images inlined", contentWithImages.getAttachments().size()); | |
| 102 | // if it was already a content with attachments then update it otherwise create a new one | |
| 103 |
3
1. translate : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::translate → NO_COVERAGE 2. translate : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::translate → KILLED 3. translate : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::translate → KILLED |
return generateFinalContent(content, cleaned, inlinedContent); |
| 104 | } | |
| 105 | } else { | |
| 106 | LOG.debug("Neither content usable as string nor HTML. Skip image inlining"); | |
| 107 | LOG.trace("content: {}", content); | |
| 108 | } | |
| 109 |
5
1. translate : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::translate → SURVIVED 2. translate : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::translate → NO_COVERAGE 3. translate : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::translate → TIMED_OUT 4. translate : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::translate → KILLED 5. translate : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::translate → KILLED |
return content; |
| 110 | } | |
| 111 | ||
| 112 | private static List<String> merge(List<String> distinctImageUrls, List<String> distinctCssImageUrls) { | |
| 113 | List<String> merged = new ArrayList<>(distinctImageUrls); | |
| 114 | merged.addAll(distinctCssImageUrls); | |
| 115 |
5
1. merge : replaced return value with Collections.emptyList for fr/sii/ogham/html/translator/InlineImageTranslator::merge → SURVIVED 2. merge : replaced return value with Collections.emptyList for fr/sii/ogham/html/translator/InlineImageTranslator::merge → NO_COVERAGE 3. merge : replaced return value with Collections.emptyList for fr/sii/ogham/html/translator/InlineImageTranslator::merge → TIMED_OUT 4. merge : replaced return value with Collections.emptyList for fr/sii/ogham/html/translator/InlineImageTranslator::merge → KILLED 5. merge : replaced return value with Collections.emptyList for fr/sii/ogham/html/translator/InlineImageTranslator::merge → KILLED |
return merged; |
| 116 | } | |
| 117 | ||
| 118 | private static ContentWithImages clean(ContentWithImages contentWithImages) { | |
| 119 | String html = contentWithImages.getContent(); | |
| 120 | html = removeOghamAttributes(html); | |
| 121 | html = removeOghamProperties(html); | |
| 122 |
3
1. clean : removed call to fr/sii/ogham/html/inliner/ContentWithImages::setContent → NO_COVERAGE 2. clean : removed call to fr/sii/ogham/html/inliner/ContentWithImages::setContent → KILLED 3. clean : removed call to fr/sii/ogham/html/inliner/ContentWithImages::setContent → KILLED |
contentWithImages.setContent(html); |
| 123 |
3
1. clean : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::clean → NO_COVERAGE 2. clean : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::clean → KILLED 3. clean : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::clean → KILLED |
return contentWithImages; |
| 124 | } | |
| 125 | ||
| 126 | private static ResourcePath getSourcePath(Content content) { | |
| 127 |
3
1. getSourcePath : negated conditional → NO_COVERAGE 2. getSourcePath : negated conditional → KILLED 3. getSourcePath : negated conditional → KILLED |
if(content instanceof HasResourcePath) { |
| 128 |
2
1. getSourcePath : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::getSourcePath → NO_COVERAGE 2. getSourcePath : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::getSourcePath → KILLED |
return ((HasResourcePath) content).getPath(); |
| 129 | } | |
| 130 |
3
1. getSourcePath : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::getSourcePath → NO_COVERAGE 2. getSourcePath : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::getSourcePath → KILLED 3. getSourcePath : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::getSourcePath → KILLED |
return new UnresolvedPath(""); |
| 131 | } | |
| 132 | | |
| 133 | private List<ImageResource> load(ResourcePath sourcePath, List<String> images) throws ContentTranslatorException { | |
| 134 | List<ImageResource> imageResources = new ArrayList<>(images.size()); | |
| 135 | for (String path : images) { | |
| 136 |
3
1. load : removed call to fr/sii/ogham/html/translator/InlineImageTranslator::load → NO_COVERAGE 2. load : removed call to fr/sii/ogham/html/translator/InlineImageTranslator::load → KILLED 3. load : removed call to fr/sii/ogham/html/translator/InlineImageTranslator::load → KILLED |
load(imageResources, relativePathProvider.resolve(sourcePath, path)); |
| 137 | } | |
| 138 |
3
1. load : replaced return value with Collections.emptyList for fr/sii/ogham/html/translator/InlineImageTranslator::load → NO_COVERAGE 2. load : replaced return value with Collections.emptyList for fr/sii/ogham/html/translator/InlineImageTranslator::load → KILLED 3. load : replaced return value with Collections.emptyList for fr/sii/ogham/html/translator/InlineImageTranslator::load → KILLED |
return imageResources; |
| 139 | } | |
| 140 | ||
| 141 | @SuppressWarnings("squid:S1192") | |
| 142 | private void load(List<ImageResource> imageResources, RelativePath path) throws ContentTranslatorException { | |
| 143 | try { | |
| 144 | byte[] imgContent = IOUtils.toByteArray(resourceResolver.getResource(path).getInputStream()); | |
| 145 | String mimetype = mimetypeProvider.detect(new ByteArrayInputStream(imgContent)).toString(); | |
| 146 | String imgName = new File(path.getOriginalPath()).getName(); | |
| 147 | imageResources.add(new ImageResource(imgName, path.getRelativePath().getOriginalPath(), path, imgContent, mimetype)); | |
| 148 | } catch (IOException e) { | |
| 149 | throw new ImageInliningException("Failed to inline image file " + path + " because it can't be read", e); | |
| 150 | } catch (ResourceResolutionException e) { | |
| 151 | throw new ImageInliningException("Failed to inline image file " + path + " because it can't be resolved", e); | |
| 152 | } catch (MimeTypeDetectionException e) { | |
| 153 | throw new ImageInliningException("Failed to inline image file " + path + " because mimetype can't be detected", e); | |
| 154 | } | |
| 155 | } | |
| 156 | ||
| 157 | private static Content updateHtmlContent(Content content, ContentWithImages contentWithImages) { | |
| 158 |
2
1. updateHtmlContent : negated conditional → NO_COVERAGE 2. updateHtmlContent : negated conditional → SURVIVED |
if(content instanceof UpdatableStringContent) { |
| 159 | LOG.debug("Content is updatable => update it with inlined images"); | |
| 160 |
3
1. updateHtmlContent : removed call to fr/sii/ogham/core/message/content/UpdatableStringContent::setStringContent → NO_COVERAGE 2. updateHtmlContent : removed call to fr/sii/ogham/core/message/content/UpdatableStringContent::setStringContent → KILLED 3. updateHtmlContent : removed call to fr/sii/ogham/core/message/content/UpdatableStringContent::setStringContent → KILLED |
((UpdatableStringContent) content).setStringContent(contentWithImages.getContent()); |
| 161 |
3
1. updateHtmlContent : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::updateHtmlContent → NO_COVERAGE 2. updateHtmlContent : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::updateHtmlContent → KILLED 3. updateHtmlContent : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::updateHtmlContent → KILLED |
return content; |
| 162 | } | |
| 163 | LOG.info("Content is not updatable => create a new StringContent for image inlining result"); | |
| 164 |
1
1. updateHtmlContent : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::updateHtmlContent → NO_COVERAGE |
return new StringContent(contentWithImages.getContent()); |
| 165 | } | |
| 166 | ||
| 167 | private static Content generateFinalContent(Content content, ContentWithImages contentWithImages, Content inlinedContent) { | |
| 168 |
3
1. generateFinalContent : negated conditional → NO_COVERAGE 2. generateFinalContent : negated conditional → KILLED 3. generateFinalContent : negated conditional → KILLED |
if(content instanceof ContentWithAttachments) { |
| 169 | ContentWithAttachments finalContent = (ContentWithAttachments) content; | |
| 170 |
1
1. generateFinalContent : removed call to fr/sii/ogham/email/message/content/ContentWithAttachments::addAttachments → NO_COVERAGE |
finalContent.addAttachments(contentWithImages.getAttachments()); |
| 171 |
1
1. generateFinalContent : removed call to fr/sii/ogham/email/message/content/ContentWithAttachments::setContent → NO_COVERAGE |
finalContent.setContent(inlinedContent); |
| 172 |
1
1. generateFinalContent : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::generateFinalContent → NO_COVERAGE |
return finalContent; |
| 173 | } | |
| 174 |
3
1. generateFinalContent : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::generateFinalContent → NO_COVERAGE 2. generateFinalContent : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::generateFinalContent → KILLED 3. generateFinalContent : replaced return value with null for fr/sii/ogham/html/translator/InlineImageTranslator::generateFinalContent → KILLED |
return new ContentWithAttachments(inlinedContent, contentWithImages.getAttachments()); |
| 175 | } | |
| 176 | ||
| 177 | @Override | |
| 178 | public String toString() { | |
| 179 |
3
1. toString : replaced return value with "" for fr/sii/ogham/html/translator/InlineImageTranslator::toString → NO_COVERAGE 2. toString : replaced return value with "" for fr/sii/ogham/html/translator/InlineImageTranslator::toString → SURVIVED 3. toString : replaced return value with "" for fr/sii/ogham/html/translator/InlineImageTranslator::toString → TIMED_OUT |
return "InlineImageTranslator"; |
| 180 | } | |
| 181 | | |
| 182 | } | |
Mutations | ||
| 88 |
1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 10.10 |
|
| 91 |
1.1 2.2 3.3 4.4 5.5 |
|
| 103 |
1.1 2.2 3.3 |
|
| 109 |
1.1 2.2 3.3 4.4 5.5 |
|
| 115 |
1.1 2.2 3.3 4.4 5.5 |
|
| 122 |
1.1 2.2 3.3 |
|
| 123 |
1.1 2.2 3.3 |
|
| 127 |
1.1 2.2 3.3 |
|
| 128 |
1.1 2.2 |
|
| 130 |
1.1 2.2 3.3 |
|
| 136 |
1.1 2.2 3.3 |
|
| 138 |
1.1 2.2 3.3 |
|
| 158 |
1.1 2.2 |
|
| 160 |
1.1 2.2 3.3 |
|
| 161 |
1.1 2.2 3.3 |
|
| 164 |
1.1 |
|
| 168 |
1.1 2.2 3.3 |
|
| 170 |
1.1 |
|
| 171 |
1.1 |
|
| 172 |
1.1 |
|
| 174 |
1.1 2.2 3.3 |
|
| 179 |
1.1 2.2 3.3 |