ImageInliningBuilder.java

1
package fr.sii.ogham.email.builder;
2
3
import fr.sii.ogham.core.builder.Builder;
4
import fr.sii.ogham.core.builder.context.BuildContext;
5
import fr.sii.ogham.core.builder.env.EnvironmentBuilder;
6
import fr.sii.ogham.core.builder.mimetype.MimetypeDetectionBuilder;
7
import fr.sii.ogham.core.builder.mimetype.MimetypeDetectionBuilderDelegate;
8
import fr.sii.ogham.core.builder.mimetype.SimpleMimetypeDetectionBuilder;
9
import fr.sii.ogham.core.builder.resolution.*;
10
import fr.sii.ogham.core.fluent.AbstractParent;
11
import fr.sii.ogham.core.mimetype.MimeTypeProvider;
12
import fr.sii.ogham.core.resource.path.LookupAwareRelativePathResolver;
13
import fr.sii.ogham.core.resource.path.RelativePathResolver;
14
import fr.sii.ogham.core.resource.resolver.FirstSupportingResourceResolver;
15
import fr.sii.ogham.core.resource.resolver.ResourceResolver;
16
import fr.sii.ogham.core.translator.content.ContentTranslator;
17
import fr.sii.ogham.html.inliner.EveryImageInliner;
18
import fr.sii.ogham.html.inliner.ImageInliner;
19
import fr.sii.ogham.html.translator.InlineImageTranslator;
20
import org.slf4j.Logger;
21
import org.slf4j.LoggerFactory;
22
23
import java.io.InputStream;
24
import java.nio.file.Files;
25
import java.util.List;
26
27
/**
28
 * Configures how images declared in the HTML content are automatically
29
 * transformed to make it work with email.
30
 * 
31
 * Images can be either:
32
 * <ul>
33
 * <li>Attached to the email</li>
34
 * <li>Encoded to a base64 string</li>
35
 * <li>Not inlined at all</li>
36
 * </ul>
37
 * 
38
 * This builder is used to enable the inlining modes (and to configure them).
39
 * Several modes can be enabled.
40
 * 
41
 * <p>
42
 * If {@link #attach()} is called, it enables image attachment.
43
 * 
44
 * Image defined in a html must be referenced by a
45
 * <a href="https://tools.ietf.org/html/rfc4021#section-2.2.2">Content-ID (or
46
 * CID)</a> if the image is attached to the email.
47
 * 
48
 * For example, if your template contains the following HTML code:
49
 * 
50
 * <pre>
51
 * {@code
52
 *    <img src="classpath:/foo.png" data-inline-image="attach" />
53
 * }
54
 * </pre>
55
 * 
56
 * Then the image will be loaded from the classpath and attached to the email.
57
 * The src attribute will be replaced by the Content-ID.
58
 * 
59
 * 
60
 * <p>
61
 * If {@link #base64()} is called, it enables inlining by converting image
62
 * content into base64 string and using the base64 string as image source.
63
 * 
64
 * For example, if your template contains the following HTML code:
65
 * 
66
 * <pre>
67
 * {@code
68
 *    <img src="classpath:/foo.png" data-inline-image="base64" />
69
 * }
70
 * </pre>
71
 * 
72
 * Then the image will be loaded from the classpath and encoded into a base64
73
 * string. This base64 string is used in the src attribute of the image.
74
 * 
75
 * <p>
76
 * If you don't want to inline a particular image, you can set the
77
 * "data-inline-image" attribute to "skip":
78
 * 
79
 * <pre>
80
 * {@code
81
 *    <img src="classpath:/foo.png" data-inline-image="skip" />
82
 * }
83
 * </pre>
84
 * 
85
 * Then the image won't be inlined at all.
86
 * 
87
 * <p>
88
 * If no inline mode is explicitly defined on the {@code <img>}:
89
 * 
90
 * <pre>
91
 * {@code
92
 *    <img src="classpath:/foo.png" />
93
 * }
94
 * </pre>
95
 * 
96
 * The behavior depends on what you have configured:
97
 * <ul>
98
 * <li>If {@link #attach()} is enabled (has been called), then image will be
99
 * loaded from the classpath and attached to the email. The src attribute will
100
 * be replaced by the Content-ID.</li>
101
 * <li>If {@link #attach()} is not enabled (never called) and {@link #base64()}
102
 * is enabled (has been called), then the image will be loaded from the
103
 * classpath and encoded into a base64 string. This base64 string is used in the
104
 * src attribute of the image.</li>
105
 * <li>If neither {@link #attach()} nor {@link #base64()} are enabled (never
106
 * called), then images won't be inlined at all</li>
107
 * </ul>
108
 * 
109
 * 
110
 * @author Aurélien Baudet
111
 *
112
 */
113
public class ImageInliningBuilder extends AbstractParent<ImageHandlingBuilder> implements ResourceResolutionBuilder<ImageInliningBuilder>, Builder<ContentTranslator> {
114
	private static final Logger LOG = LoggerFactory.getLogger(ImageInliningBuilder.class);
115
116
	private final BuildContext buildContext;
117
	private final ResourceResolutionBuilderHelper<ImageInliningBuilder> resourceResolutionBuilderHelper;
118
	private AttachImageBuilder attachBuilder;
119
	private Base64InliningBuilder base64Builder;
120
	private MimetypeDetectionBuilder<ImageInliningBuilder> mimetypeBuilder;
121
122
	/**
123
	 * Initializes the builder with a parent builder. The parent builder is used
124
	 * when calling {@link #and()} method. The {@link EnvironmentBuilder} is
125
	 * used to evaluate properties when {@link #build()} method is called.
126
	 * 
127
	 * @param parent
128
	 *            the parent builder
129
	 * @param buildContext
130
	 *            for registering instances and property evaluation
131
	 */
132
	public ImageInliningBuilder(ImageHandlingBuilder parent, BuildContext buildContext) {
133
		super(parent);
134
		this.buildContext = buildContext;
135
		resourceResolutionBuilderHelper = new ResourceResolutionBuilderHelper<>(this, buildContext);
136
	}
137
138
	/**
139
	 * Configures how attachment of images is handled.
140
	 * 
141
	 * <p>
142
	 * If this method is called, it enables image attachment.
143
	 * 
144
	 * Image defined in a html must be referenced by a
145
	 * <a href="https://tools.ietf.org/html/rfc4021#section-2.2.2">Content-ID
146
	 * (or CID)</a> if the image is attached to the email.
147
	 * 
148
	 * For example, if your template contains the following HTML code:
149
	 * 
150
	 * <pre>
151
	 * {@code
152
	 *    <img src="classpath:/foo.png" data-inline-image="attach" />
153
	 * }
154
	 * </pre>
155
	 * 
156
	 * Then the image will be loaded from the classpath and attached to the
157
	 * email. The src attribute will be replaced by the Content-ID.
158
	 * 
159
	 * 
160
	 * 
161
	 * <p>
162
	 * In the same way, if your template contains the following code:
163
	 * 
164
	 * <pre>
165
	 * <code>
166
	 *  &lt;style&gt;
167
	 *     .some-class {
168
	 *       background: url('classpath:/foo.png');
169
	 *       --inline-image: attach;
170
	 *     }
171
	 *  &lt;/style&gt;
172
	 * </code>
173
	 * </pre>
174
	 * 
175
	 * Or directly on {@code style} attribute:
176
	 * 
177
	 * <pre>
178
	 * {@code
179
	 * 	<div style=
180
	"background: url('classpath:/foo.png'); --inline-image: attach;"></div>
181
	 * }
182
	 * </pre>
183
	 * 
184
	 * Then the image will be loaded from the classpath and attached to the
185
	 * email. The url will be replaced by the Content-ID.
186
	 * 
187
	 * <p>
188
	 * If no inline mode is defined, image attachment is used by default:
189
	 * 
190
	 * <pre>
191
	 * {@code
192
	 *    <img src="classpath:/foo.png" />
193
	 * }
194
	 * </pre>
195
	 * 
196
	 * Or:
197
	 * 
198
	 * <pre>
199
	 * <code>
200
	 *  &lt;style&gt;
201
	 *     .some-class {
202
	 *       background: url('classpath:/foo.png');
203
	 *     }
204
	 *  &lt;/style&gt;
205
	 * </code>
206
	 * </pre>
207
	 * 
208
	 * Or:
209
	 * 
210
	 * <pre>
211
	 * {@code
212
	 * 	<div style="background: url('classpath:/foo.png');"></div>
213
	 * }
214
	 * </pre>
215
	 * 
216
	 * The examples above have the same result as the example that explicitly
217
	 * defines the inline mode.
218
	 * 
219
	 * 
220
	 * @return the builder to configure how images are automatically attached
221
	 */
222
	public AttachImageBuilder attach() {
223 1 1. attach : negated conditional → RUN_ERROR
		if (attachBuilder == null) {
224
			attachBuilder = new AttachImageBuilder(this, buildContext);
225
		}
226 1 1. attach : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::attach → RUN_ERROR
		return attachBuilder;
227
	}
228
229
	/**
230
	 * Configures how attachment of images is handled.
231
	 * 
232
	 * <p>
233
	 * If this method is called, it enables inlining by converting image content
234
	 * into base64 string and using the base64 string as image source.
235
	 * 
236
	 * For example, if your template contains the following HTML code:
237
	 * 
238
	 * <pre>
239
	 * {@code
240
	 *    <img src="classpath:/foo.png" data-inline-image="base64" />
241
	 * }
242
	 * </pre>
243
	 * 
244
	 * Then the image will be loaded from the classpath and encoded into a
245
	 * base64 string. This base64 string is used in the src attribute of the
246
	 * {@code <img>}.
247
	 * 
248
	 * 
249
	 * <p>
250
	 * In the same way, if your template contains the following code:
251
	 * 
252
	 * <pre>
253
	 * <code>
254
	 *  &lt;style&gt;
255
	 *     .some-class {
256
	 *       background: url('classpath:/foo.png');
257
	 *       --inline-image: base64;
258
	 *     }
259
	 *  &lt;/style&gt;
260
	 * </code>
261
	 * </pre>
262
	 * 
263
	 * Or directly on {@code style} attribute:
264
	 * 
265
	 * <pre>
266
	 * {@code
267
	 * 	<div style="background: url('classpath:/foo.png'); --inline-image: base64;"></div>
268
	 * }
269
	 * </pre>
270
	 * 
271
	 * Then the image will be loaded from the classpath and encoded into a
272
	 * base64 string. The url is updated with the base64 string.
273
	 * 
274
	 * <p>
275
	 * If no inline mode is defined <strong>and</strong> image attachment is not
276
	 * enable ({@link #attach()} is not called), this mode is used by default:
277
	 * 
278
	 * <pre>
279
	 * {@code
280
	 *    <img src="classpath:/foo.png" />
281
	 * }
282
	 * </pre>
283
	 * 
284
	 * In this case, the example above as the same result as the example that
285
	 * explicitly defines the inline mode.
286
	 * 
287
	 * 
288
	 * @return the builder to configure how images are automatically converted
289
	 *         to base64
290
	 */
291
	public Base64InliningBuilder base64() {
292 1 1. base64 : negated conditional → RUN_ERROR
		if (base64Builder == null) {
293
			base64Builder = new Base64InliningBuilder(this, buildContext);
294
		}
295 1 1. base64 : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::base64 → RUN_ERROR
		return base64Builder;
296
	}
297
298
	/**
299
	 * Builder that configures mimetype detection.
300
	 * 
301
	 * There exists several implementations to provide the mimetype:
302
	 * <ul>
303
	 * <li>Using Java activation</li>
304
	 * <li>Using Java 7 {@link Files#probeContentType(java.nio.file.Path)}</li>
305
	 * <li>Using <a href="http://tika.apache.org/">Apache Tika</a></li>
306
	 * <li>Using
307
	 * <a href="https://github.com/arimus/jmimemagic">JMimeMagic</a></li>
308
	 * </ul>
309
	 * 
310
	 * <p>
311
	 * Both implementations provided by Java are based on file extensions. This
312
	 * can't be used in most cases as we often handle {@link InputStream}s.
313
	 * </p>
314
	 * 
315
	 * <p>
316
	 * In previous version of Ogham, JMimeMagic was used and was working quite
317
	 * well. Unfortunately, the library is no more maintained.
318
	 * </p>
319
	 * 
320
	 * <p>
321
	 * You can configure how Tika will detect mimetype:
322
	 * 
323
	 * <pre>
324
	 * .mimetype()
325
	 *    .tika()
326
	 *       ...
327
	 * </pre>
328
	 * 
329
	 * <p>
330
	 * This builder allows to use several providers. It will chain them until
331
	 * one can find a valid mimetype. If none is found, you can explicitly
332
	 * provide the default one:
333
	 * 
334
	 * <pre>
335
	 * .mimetype()
336
	 *    .defaultMimetype("text/html")
337
	 * </pre>
338
	 * 
339
	 * <p>
340
	 * If no mimetype detector was previously defined, it creates a new one.
341
	 * Then each time you call {@link #mimetype()}, the same instance is used.
342
	 * </p>
343
	 * 
344
	 * @return the builder to configure mimetype detection
345
	 */
346
	public MimetypeDetectionBuilder<ImageInliningBuilder> mimetype() {
347 1 1. mimetype : negated conditional → RUN_ERROR
		if (mimetypeBuilder == null) {
348
			mimetypeBuilder = new SimpleMimetypeDetectionBuilder<>(this, buildContext);
349
		}
350 1 1. mimetype : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::mimetype → RUN_ERROR
		return mimetypeBuilder;
351
	}
352
353
	/**
354
	 * NOTE: this is mostly for advance usage (when creating a custom module).
355
	 * 
356
	 * Inherits mimetype configuration from another builder. This is useful for
357
	 * configuring independently different parts of Ogham but keeping a whole
358
	 * coherence.
359
	 * 
360
	 * The same instance is shared meaning that all changes done here will also
361
	 * impact the other builder.
362
	 * 
363
	 * <p>
364
	 * If a previous builder was defined (by calling {@link #mimetype()} for
365
	 * example), the new builder will override it.
366
	 * 
367
	 * @param builder
368
	 *            the builder to inherit
369
	 * @return this instance for fluent chaining
370
	 */
371
	public ImageInliningBuilder mimetype(MimetypeDetectionBuilder<?> builder) {
372
		mimetypeBuilder = new MimetypeDetectionBuilderDelegate<>(this, builder);
373 1 1. mimetype : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::mimetype → NO_COVERAGE
		return this;
374
	}
375
376
	@Override
377
	public ClassPathResolutionBuilder<ImageInliningBuilder> classpath() {
378 1 1. classpath : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::classpath → RUN_ERROR
		return resourceResolutionBuilderHelper.classpath();
379
	}
380
381
	@Override
382
	public FileResolutionBuilder<ImageInliningBuilder> file() {
383 1 1. file : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::file → RUN_ERROR
		return resourceResolutionBuilderHelper.file();
384
	}
385
386
	@Override
387
	public StringResolutionBuilder<ImageInliningBuilder> string() {
388 1 1. string : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::string → RUN_ERROR
		return resourceResolutionBuilderHelper.string();
389
	}
390
391
	@Override
392
	public ImageInliningBuilder resolver(ResourceResolver resolver) {
393 1 1. resolver : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::resolver → NO_COVERAGE
		return resourceResolutionBuilderHelper.resolver(resolver);
394
	}
395
396
	@Override
397
	public ContentTranslator build() {
398
		MimeTypeProvider mimetypeProvider = buildMimetypeProvider();
399 1 1. build : negated conditional → RUN_ERROR
		if (mimetypeProvider == null) {
400
			LOG.info("Images won't be inlined because no mimetype detector is configured");
401
			return null;
402
		}
403
		LOG.info("Images will be inlined");
404 1 1. build : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::build → RUN_ERROR
		return buildContext.register(new InlineImageTranslator(buildInliner(), buildResolver(), mimetypeProvider, buildRelativePathProvider()));
405
	}
406
407
	private MimeTypeProvider buildMimetypeProvider() {
408 1 1. buildMimetypeProvider : negated conditional → RUN_ERROR
		if (mimetypeBuilder == null) {
409
			return null;
410
		}
411 1 1. buildMimetypeProvider : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::buildMimetypeProvider → RUN_ERROR
		return mimetypeBuilder.build();
412
	}
413
414
	private ImageInliner buildInliner() {
415
		EveryImageInliner inliner = buildContext.register(new EveryImageInliner());
416 1 1. buildInliner : negated conditional → RUN_ERROR
		if (attachBuilder != null) {
417
			inliner.addInliner(attachBuilder.build());
418
		}
419 1 1. buildInliner : negated conditional → RUN_ERROR
		if (base64Builder != null) {
420
			inliner.addInliner(base64Builder.build());
421
		}
422 1 1. buildInliner : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::buildInliner → RUN_ERROR
		return inliner;
423
	}
424
425
	private ResourceResolver buildResolver() {
426
		List<ResourceResolver> resolvers = resourceResolutionBuilderHelper.buildResolvers();
427 1 1. buildResolver : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::buildResolver → RUN_ERROR
		return buildContext.register(new FirstSupportingResourceResolver(resolvers));
428
	}
429
430
	private RelativePathResolver buildRelativePathProvider() {
431 1 1. buildRelativePathProvider : replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::buildRelativePathProvider → RUN_ERROR
		return buildContext.register(new LookupAwareRelativePathResolver(resourceResolutionBuilderHelper.getAllLookups()));
432
	}
433
}

Mutations

223

1.1
Location : attach
Killed by :
negated conditional → RUN_ERROR

226

1.1
Location : attach
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::attach → RUN_ERROR

292

1.1
Location : base64
Killed by :
negated conditional → RUN_ERROR

295

1.1
Location : base64
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::base64 → RUN_ERROR

347

1.1
Location : mimetype
Killed by :
negated conditional → RUN_ERROR

350

1.1
Location : mimetype
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::mimetype → RUN_ERROR

373

1.1
Location : mimetype
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::mimetype → NO_COVERAGE

378

1.1
Location : classpath
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::classpath → RUN_ERROR

383

1.1
Location : file
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::file → RUN_ERROR

388

1.1
Location : string
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::string → RUN_ERROR

393

1.1
Location : resolver
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::resolver → NO_COVERAGE

399

1.1
Location : build
Killed by :
negated conditional → RUN_ERROR

404

1.1
Location : build
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::build → RUN_ERROR

408

1.1
Location : buildMimetypeProvider
Killed by :
negated conditional → RUN_ERROR

411

1.1
Location : buildMimetypeProvider
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::buildMimetypeProvider → RUN_ERROR

416

1.1
Location : buildInliner
Killed by :
negated conditional → RUN_ERROR

419

1.1
Location : buildInliner
Killed by :
negated conditional → RUN_ERROR

422

1.1
Location : buildInliner
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::buildInliner → RUN_ERROR

427

1.1
Location : buildResolver
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::buildResolver → RUN_ERROR

431

1.1
Location : buildRelativePathProvider
Killed by :
replaced return value with null for fr/sii/ogham/email/builder/ImageInliningBuilder::buildRelativePathProvider → RUN_ERROR

Active mutators

Tests examined


Report generated by PIT 1.13.1