Migration Guide

from v2.0.0 to v3.0.0

Fluent builder consistency for property configuration

As Ogham is fully extensible, there could be an issue of priority due to registration order. Therefore a developer could set a configuration value in its code but it could be overridden by an external property. This could be particularly annoying in tests.

Example 1. Example of the issue
java logo Automatic configuration

For example, Ogham could automatically provide a configuration for email SMTP port:

.sender(JavaMailBuilder.class)
  .port("${mail.smtp.port}",     (1)
        "25")                    (2)
1 Register the property mail.smtp.port for the SMTP port. If the property is set with a value, this value is used. A property is recognize because it is surrounded by ${}.
2 The default value to use if no value is set for the mail.smtp.port property.
java logo Developer code

Now developer wants to set the port in its code:

.sender(JavaMailBuilder.class)
  .port("465")                    (1)
1 Explicitly set the port to 465 using the string method.

The result is:

  • If the property is set anywhere then it is used so the value set by the developer is not used.

  • If the property is not set then the default value (25) is used.

The developer could still use the method port(int) instead that has higher priority but this was not the case for a string value.

Ogham now provides different methods for configuring properties, default values and values provided by the developer. There is now a clear priority order:

  1. value explicitly set by the developer

  2. property that has a value set

  3. default value

Example 2. Update your code
java logo Java
.sender(JavaMailBuilder.class)
  .port("465")                 (1)
  .port(465)                   (2)
1 The old way to set the value
2 The new way to set the value

In addition to a clearer and consistent API, the new API uses the right type instead of using strings.

Fluent API for message construction

Ogham now provides a fuent API for message content building in order to guide developer.

Here are examples with the new API:

Example 3. Fluent API for email body
java logo String body
oghamService.send(new Email()
  .subject("Simple")
  .content("string body")                     (1)
  .body().string("string body")               (2)
  .to("Recipient Name <recipient@sii.fr>"));
1 content() method can take a string to set the body of the email.
2 The new API provides a body() method to construct the email…​ body ! The body() method provides access to several methods to set the email body. The sample shows how to set a simple string as the body of the email (.string() method).
java logo Templated body
oghamService.send(new Email()
  .subject("Simple")
  .content(new TemplateContent("template.html", templateContext))    (1)
  .body().template("template.html", templateContext)                 (2)
  .to("Recipient Name <recipient@sii.fr>"));
1 content() method can take a TemplateContent to set the body of the email using a template.
2 The body() method provides access to several methods to set the email body. The sample shows how to use a template for the body of the email (.template() method).
java logo body + alternative
oghamService.send(new Email()
  .subject("Simple")
  .content(new MultiContent(                                    (1)
    new TemplateContent("altarnative.txt", templateContext),    (2)
    new TemplateContent("body.html", templateContext)))         (3)
  .html().template("body.html", templateContext)                (4)
  .text().template("alternative.txt", templateContext)          (5)
  .to("Recipient Name <recipient@sii.fr>"));
1 content() method can take a MultiContent to set the body and alternative of the email.
2 Use a text template for the alternative (new TemplateContent). The order is important: alternative MUST be first sub-content.
3 Use an html template for the body (new TemplateContent). The order is important: body MUST be second sub-content.
4 The new API provides a html() method to set the html body of the email. Call order doesn’t matter.
5 The new API provides a text() method to set the text alternative of the email. Call order doesn’t matter.
Shortcut

The new API also provides a shortcut to set both alternative and body based on templates:

oghamService.send(new Email()
  .subject("Simple")
  .body().template("template", templateContext)        (1)
  .to("Recipient Name <recipient@sii.fr>"));
1 No extension is provided so Ogham can automatically use template.html as body of the email if the template exists and use template.txt as the textual alternative of the email if the template exists.
Example 4. Fluent API for SMS message
java logo String message
oghamService.send(new Sms()
  .content("sms message")                     (1)
  .message().string("sms message")            (2)
  .to("0600000000"));
1 content() method can take a string to set the message of the SMS.
2 The new API provides a message() method to construct the SMS…​ message ! The message() method provides access to several methods to set the SMS message. The sample shows how to set a simple string as the message of the SMS (.string() method).
java logo Templated message
oghamService.send(new Sms()
  .content(new TemplateContent("template.txt", templateContext))    (1)
  .message().template("template.txt", templateContext)              (2)
  .to("Recipient Name <recipient@sii.fr>"));
1 content() method can take a TemplateContent to set the message of the SMS using a template.
2 The message() method provides access to several methods to set the SMS message. The sample shows how to use a template for the message of the SMS (.template() method).

The old API is generic and for instance, a developer could misunderstand which class to use as content() method parameter. He could use a MultiContent for SMS. However, a MultiContent for a SMS has no meaning.

The new API provides adapted methods to avoid this kind of misunderstanding.

The old API is still available and useful for advanced behavior.

Ogham also provides a fuent API to join attachments to the email. Attachments are either:

  • attached (a file that is outside the body and is downloadable)

  • embedded (a resource that is used by the body of the email like an image for example). An embedded attachment MUST provide a Content-ID in order to be referenced in the email body.

Developers that use old API had to know which value to use for disposition parameter of Attachment constructors. If no disposition is specified, the default disposition is ContentDisposition.ATTACHMENT. If a developer wanted to embed an attachment, he had to know that the Content-ID must be specified in order to work. Moreover, Attachment constructors could take a name parameter that is either optional or mandatory depending on the type of the attached resource (optional for file, mandatory for stream, mandatory for byte array).

This behavior is still available but the new API is clearer:

Example 5. Fluent API for email attachments
java logo External file
Attachment embedded = new Attachment(                          (1)
  new File("path-to-file.pdf"),                                (2)
  "description",                                               (3)
  ContentDisposition.INLINE));                                 (4)
embedded.setContentId("Content-ID");                           (5)

oghamService.send(new Email()
  .subject("Simple")
  .body().string("string body")
  .to("Recipient Name <recipient@sii.fr>")
  .attach(new Attachment(new File("path-to-file.pdf")))         (6)
  .attach().file(new File("path-to-file.pdf"))                  (7)
  .attach(embedded);                                            (8)
  .embed().file("Content-ID", new File("path-to-file.pdf")));   (9)
1 As Attachment constructors doesn’t provide a way to set Content-ID for embedded attachments, developer has to extract to a variable to call setContentId().
2 Indicate the file to embed.
3 To indicate that the Attachment is embedded, developer had to use the constructor that takes 3 parameters. So he had to set a value for description of the embedded resource even if it is optional.
4 Indicate that the Attachment is embedded.
5 Set the Content-ID.
6 Add an attachment to the email (uses ContentDisposition.ATTACHMENT disposition).
7 New API to attach a file (disposition is set to ContentDisposition.ATTACHMENT).
8 Add the embedded attachment.
9 New API to embed a file (disposition is set to ContentDisposition.INLINE). As Content-ID is mandatory for embedded attachments, this method takes the Content-ID as first parameter.
java logo Resource
Attachment embedded = new Attachment(                              (1)
  new File("path-to-file.pdf"),                                    (2)
  "description",                                                   (3)
  ContentDisposition.INLINE));                                     (4)
embedded.setContentId("Content-ID");                               (5)

oghamService.send(new Email()
  .subject("Simple")
  .body().string("string body")
  .to("Recipient Name <recipient@sii.fr>")
  .attach(new Attachment("classpath:path-to-file.pdf"))            (6)
  .attach().resource("classpath:path-to-file.pdf")                 (7)
  .attach(embedded);                                               (8)
  .embed().resource("Content-ID", "classpath:path-to-file.pdf")    (9)
1 As Attachment constructors doesn’t provide a way to set Content-ID for embedded attachments, developer has to extract to a variable to call setContentId().
2 Indicate the file to embed.
3 To indicate that the Attachment is embedded, developer had to use the constructor that takes 3 parameters. So he had to set a value for description of the embedded resource even if it is optional.
4 Indicate that the Attachment is embedded.
5 Set the Content-ID.
6 Add an attachment to the email (uses ContentDisposition.ATTACHMENT disposition).
7 New API to attach a file (disposition is set to ContentDisposition.ATTACHMENT).
8 Add the embedded attachment.
9 New API to embed a file (disposition is set to ContentDisposition.INLINE). As Content-ID is mandatory for embedded attachments, this method takes the Content-ID as first parameter.
java logo Custom name
oghamService.send(new Email()
  .subject("Simple")
  .body().string("string body")
  .to("Recipient Name <recipient@sii.fr>")
  .attach(new Attachment("custom-name.pdf",                        (1)
    getClass().getResourceAsStream("path-to-file.pdf")))           (2)
  .attach().resource("custom-name.pdf",                            (3)
    "classpath:/attachment/path-to-file.pdf")                      (4)
1 Developer must use the constructor that takes the name as first argument and a byte[] or InputStream as second argument. See note below.
2 A simple path can’t be provided, developer must write Java code. See note below.
3 The new API provides a method that allow to provide a custom name…​
4 and point to a resource using a simple path.
Why new API is better

As Attachment doesn’t have a constructor that takes both path to a file or resource with custom name, developer has to update its code to not use a path anymore but use directly Java code to get the an InputStream or a byte[]. This means that developer can’t benefit from Ogham resource resolution. He can’t make the path configurable to point either to a file in the classpath or to an external file according to running environment (file present in classpath in tests but external file in production for instance), he has to either handle it manually or change its code.

On the contrary, with the new API, the developer just has to add the name parameter and that’s it.

Custom Content-Type

The new API also provides a simplest way to set a custom Content-Type for an attachment instead of automatic detection.

Images and CSS inlining HTML attributes consistency

As described in HTML body with CSS and images section, images and CSS rules can be inlined into the email. Ogham uses HTML attributes to indicate how inlining should behave. Those attributes have been renamed to be more consistent:

The attribute ogham-inline-mode was used for image inlining. It has been renamed to data-inline-image. The data- prefix is used to comply with HTML standards. The new attribute name also indicates which kind of inlining is targeted. The data-inline-image still uses the same values:

  • attach (default) to attach the images with the email (with Content-Disposition header set to inline)

  • base64 to convert to a data URL scheme and inlined directly in the HTML using src attribute

  • skip to prevent inlining of the image

Example 6. Update your code
thymeleaf html ThymeLeaf template
<div class="left">
  <img src="images/left.gif" ogham-inline-mode="base64" />      (1)
  <img src="images/left.gif" data-inline-image="base64" />      (2)
  <p class="text">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit
  </p>
</div>
1 The old attribute name
2 The new attribute name

CSS inlining was using the attribute data-skip-inline attribute. The name of this attribute was not clear on its purpose (was it for CSS or for images ?). This attribute has been removed and the new attribute data-inine-styles is used instead. The value is the name of the strategy to use for handling inline (as for images). Currently, only one inlining strategy is available but in future versions, other may come. For now the only possible value is:

  • skip to prevent inlining of CSS rules on the node

Example 7. Update your code
thymeleaf html ThymeLeaf template
<div class="sender-info">
  &reg; Someone, somewhere 2013<br />
  <a href="#" class="white" data-skip-inline="true">Unsubscribe</a> to this newsletter instantly      (1)
  <a href="#" class="white" data-inline-styles="skip">Unsubscribe</a> to this newsletter instantly    (2)
</div>
1 The old attribute name
2 The new attribute name

Properties consistency

Ogham heavily uses configuration properties to make it fully and easily customizable.

Some properties were using different keys between standalone usage (without framework) and Spring usage. Now the keys are aligned to be consistent.

Other properties were using a key that may be misinterpreted (on what they do and the scope of the property). Their name didn’t follow same convention (for example some were missing the protocol to indicate that the property is only handled by that protocol and not common to all protocols). These properties are renamed.

Properties for default values

  • ogham.email.bcc renamed to ogham.email.bcc.default-value

  • ogham.email.cc renamed to ogham.email.cc.default-value

  • ogham.email.from renamed to ogham.email.from.default-value

  • ogham.email.to renamed to ogham.email.to.default-value

  • ogham.email.subject renamed to ogham.email.subject.default-value

  • ogham.sms.from renamed to ogham.sms.from.default-value

  • ogham.sms.to renamed to ogham.sms.to.default-value

The new property names clearly indicate now that the value is used only if nothing else is defined. There is another benefit: we can use other property names for configuring other parts (such as ogham.email.subject.extract-from-text.first-line-prefix). This is consistent with Spring behavior.

Example 8. Update your code
java logo Java
package fr.sii.ogham.sample.standard.email;

import java.util.Properties;

import fr.sii.ogham.core.builder.MessagingBuilder;
import fr.sii.ogham.core.exception.MessagingException;
import fr.sii.ogham.core.service.MessagingService;
import fr.sii.ogham.email.message.Email;

public class BasicSample {

  public static void main(String[] args) throws MessagingException {
    // configure properties (could be stored in a properties file or defined
    // in System properties)
    Properties properties = new Properties();
    properties.put("mail.smtp.host", "<your server host>");
    properties.put("mail.smtp.port", "<your server port>");
    properties.put("ogham.email.from", "<email address to display for the sender user>");                 (1)
    properties.put("ogham.email.from.default-value", "<email address to display for the sender user>");   (2)
    // Instantiate the messaging service using default behavior and
    // provided properties
    MessagingService service = MessagingBuilder.standard()
        .environment()
          .properties(properties)
          .properties()
            .set("ogham.email.subject", "Default subject")                                        (3)
            .set("ogham.email.subject.default-value", "Default subject")                          (4)
            .and()
          .and()
        .build();
    // send the email using fluent API
    service.send(new Email()
            .body().string("email content")
            .to("ogham-test@yopmail.com"));
  }

}
1 The old property for default sender address
2 The new property for default sender address
3 The old property for default subject (defined using fluent API)
4 The new property for default subject (defined using fluent API)

Properties for subject extraction

ogham.email.subject.text.first-line-prefix renamed to ogham.email.subject.extract-from-text.first-line-prefix:

This property is used to extract the first line of the text part (if first line starts with with the value of this property) of an email and use it as the subject of the email. The new name of the property is consistent with ogham.email.subject.extract-html-title.enable. Moreover, in future versions we could add more properties to control the subject extraction from text part using additional properties (ogham.email.subject.extract-from-text.something-else for example). The new name also complies with Spring property handling.

Example 9. Update your code
java logo Java
package fr.sii.ogham.sample.standard.email;

import java.util.Properties;

import fr.sii.ogham.core.builder.MessagingBuilder;
import fr.sii.ogham.core.exception.MessagingException;
import fr.sii.ogham.core.service.MessagingService;
import fr.sii.ogham.email.message.Email;

public class BasicSample {

  public static void main(String[] args) throws MessagingException {
    // configure properties (could be stored in a properties file or defined
    // in System properties)
    Properties properties = new Properties();
    properties.put("mail.smtp.host", "<your server host>");
    properties.put("mail.smtp.port", "<your server port>");
    properties.put("ogham.email.subject.text.first-line-prefix", "Subject:");                     (1)
    properties.put("ogham.email.subject.extract-from-text.first-line-prefix", "Subject:");        (2)
    // Instantiate the messaging service using default behavior and
    // provided properties
    MessagingService service = MessagingBuilder.standard()
        .environment()
          .properties(properties)
          .properties()
            .set("ogham.email.subject.text.first-line-prefix", "Subject:")                (3)
            .set("ogham.email.subject.extract-from-text.first-line-prefix", "Subject:")   (4)
            .and()
          .and()
        .build();
    // send the email using fluent API
    service.send(new Email()
                    .from("sender@yopmail.com")
            .body().string("email content")
            .to("ogham-test@yopmail.com"));
  }

}
1 The old property to enable extraction of subject from text alternative if is starts with the provided prefix.
2 The new property to enable extraction of subject from text alternative if is starts with the provided prefix.
3 The old property to enable extraction of subject from text alternative if is starts with the provided prefix (defined using fluent API).
4 The new property to enable extraction of subject from text alternative if is starts with the provided prefix (defined using fluent API).

SendGrid properties

ogham.email.sengrid.api-key renamed to ogham.email.sendgrid.api-key

Fix misspelling (missing d character).

Example 10. Update your code
java logo Java
package fr.sii.ogham.sample.standard.email;

import java.util.Properties;

import fr.sii.ogham.core.builder.MessagingBuilder;
import fr.sii.ogham.core.exception.MessagingException;
import fr.sii.ogham.core.service.MessagingService;
import fr.sii.ogham.email.message.Email;

public class BasicSample {

  public static void main(String[] args) throws MessagingException {
    // configure properties (could be stored in a properties file or defined
    // in System properties)
    Properties properties = new Properties();
    properties.put("mail.smtp.host", "<your server host>");
    properties.put("mail.smtp.port", "<your server port>");
    properties.put("ogham.email.sengrid.api-key", "<SendGrid API key>");           (1)
    properties.put("ogham.email.sendgrid.api-key", "<SendGrid API key>");          (2)
    // Instantiate the messaging service using default behavior and
    // provided properties
    MessagingService service = MessagingBuilder.standard()
        .environment()
          .properties(properties)
          .properties()
            .set("ogham.email.sengrid.api-key", "<SendGrid API key>")      (3)
            .set("ogham.email.sendgrid.api-key", "<SendGrid API key>")     (4)
            .and()
          .and()
        .build();
    // send the email using fluent API
    service.send(new Email()
                    .from("sender@yopmail.com")
            .body().string("email content")
            .to("ogham-test@yopmail.com"));
  }

}
1 The old property for SendGrid API key.
2 The new property for SendGrid API key.
3 The old property for SendGrid API key (defined using fluent API).
4 The new property for SendGrid API key (defined using fluent API).

FreeMarker properties

  • ogham.sms.freemarker.prefix renamed to ogham.sms.freemarker.path-prefix

  • ogham.sms.freemarker.suffix renamed to ogham.sms.freemarker.path-suffix

Fix missing path-.

Cloudhopper session properties

  • ogham.sms.cloudhopper.bind-timeout renamed to ogham.sms.cloudhopper.session.bind-timeout,

  • ogham.sms.cloudhopper.connect-timeout renamed to ogham.sms.cloudhopper.session.connect-timeout

  • ogham.sms.cloudhopper.response-timeout renamed to ogham.sms.cloudhopper.session.response-timeout

  • ogham.sms.cloudhopper.request-expiry-timeout renamed to ogham.sms.cloudhopper.session.request-expiry-timeout

  • ogham.sms.cloudhopper.session-name renamed to ogham.sms.cloudhopper.session.name

  • ogham.sms.cloudhopper.unbind-timeout renamed to ogham.sms.cloudhopper.session.unbind-timeout

  • ogham.sms.cloudhopper.window-monitor-interval renamed to ogham.sms.cloudhopper.session.window-monitor-interval

  • ogham.sms.cloudhopper.window-size renamed to ogham.sms.cloudhopper.session.window-size

  • ogham.sms.cloudhopper.window-wait-timeout renamed to ogham.sms.cloudhopper.session.window-wait-timeout

  • ogham.sms.cloudhopper.write-timeout renamed to ogham.sms.cloudhopper.session.write-timeout

Rename all the properties that configures Cloudhopper session for clarity.

  • ogham.sms.cloudhopper.connect-max-retry renamed to ogham.sms.cloudhopper.session.connect-retry.max-attempts

  • ogham.sms.cloudhopper.connect-retry-delay renamed to ogham.sms.cloudhopper.session.connect-retry.delay-between-attempts

Rename properties used for reconnection of session for clarity.

General SMS properties

  • ogham.sms.from-format-enable-alphanumeric renamed to ogham.sms.from.alphanumeric-code-format.enable

  • ogham.sms.from-format-enable-international renamed to ogham.sms.from.international-format.enable

  • ogham.sms.from-format-enable-shortcode renamed to ogham.sms.from.short-code-format.enable

  • ogham.sms.to-format-enable-international renamed to ogham.sms.to.international-format.enable

Follows same convention as all other "enable" properties.

OVH SMS properties

  • ogham.sms.ovh.no-stop renamed to ogham.sms.ovh.options.no-stop

  • ogham.sms.ovh.sms-coding renamed to ogham.sms.ovh.options.sms-coding

  • ogham.sms.ovh.tag renamed to ogham.sms.ovh.options.tag

Distinguish mandatory/important properties from options.

SMS

Instead of static mapping that doesn’t fit SMPP behavior, Ogham now provides an automatic encoding guessing that is based on the characters used in the message to use the right character table and protocol parameters (like Data Coding Scheme).

If you were using ogham.sms.cloudhopper.smpp-charset=<charset name> to use a fixed SMPP charset, you should update your configuration like this:

  • You should use ogham.sms.smpp.encoder.default-charset=<charset name> (or its alias ogham.sms.cloudhopper.encoder.default-charset) instead of ogham.sms.cloudhopper.smpp-charset=<charset name>.

  • You may need to disable automatic guessing ogham.sms.smpp.encoder.auto-guess.enable=false (or its alias ogham.sms.cloudhopper.encoder.auto-guess.enable) to always use the default encoding configured above.

  • You may also need to force 0 value for Data Coding Scheme using the configuration property ogham.sms.smpp.data-coding-scheme.value=0 (or its alias ogham.sms.cloudhopper.data-coding-scheme.value).

  • You may also need to disable automatic Data Coding Scheme guessing using ogham.sms.smpp.data-coding-scheme.auto.enable=false (or its alias ogham.sms.cloudhopper.data-coding-scheme.auto.enable).

You should also read the section that explains automatic encoding guessing to fully understand the purpose of each property.

Test utilities

Ogham provides utilities for testing reception of email or SMS. In order to be concise, Ogham provides a method for applying the same assertions on all received messages. This method has been renamed from forEach() to every().

Example 11. Update your code
java logo Java
package fr.sii.ogham.sample.test;

import static com.icegreen.greenmail.util.ServerSetupTest.SMTP;
import static fr.sii.ogham.testing.assertion.OghamAssertions.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.emptyIterable;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;

import java.io.IOException;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import com.icegreen.greenmail.junit4.GreenMailRule;

import fr.sii.ogham.core.builder.MessagingBuilder;
import fr.sii.ogham.core.exception.MessagingException;
import fr.sii.ogham.core.service.MessagingService;
import fr.sii.ogham.email.message.Email;

public class SeveralRecipientsTestSample {
  private MessagingService oghamService;

  @Rule
  public final GreenMailRule greenMail = new GreenMailRule(SMTP);

  @Before
  public void setUp() throws IOException {
    oghamService = MessagingBuilder.standard()
        .environment()
          .properties()
            .set("ogham.email.from.default-value", "Sender Name <test.sender@sii.fr>")
            .set("mail.smtp.host", SMTP.getBindAddress())
            .set("mail.smtp.port", String.valueOf(SMTP.getPort()))
            .and()
          .and()
        .build();
  }

  @Test
  public void severalRecipients() throws MessagingException, javax.mail.MessagingException {
    // @formatter:off
    oghamService.send(new Email()
                .subject("Simple")
                .body().string("string body")
                .to("recipient1@sii.fr", "recipient2@sii.fr", "recipient3@sii.fr")
                .cc("recipient4@sii.fr", "recipient5@sii.fr")
                .bcc("recipient6@sii.fr"));
    assertThat(greenMail).receivedMessages()
      .count(is(6))
      .forEach()                                                                  (1)
      .every()                                                                    (2)
        .subject(is("Simple"))
        .from()
          .address(hasItems("test.sender@sii.fr"))
          .personal(hasItems("Sender Name")).and()
        .to()
          .address(containsInAnyOrder("recipient1@sii.fr",
                        "recipient2@sii.fr",
                        "recipient3@sii.fr")).and()
        .cc()
          .address(containsInAnyOrder("recipient4@sii.fr",
                        "recipient5@sii.fr")).and()
        .body()
          .contentAsString(is("string body"))
          .contentType(startsWith("text/plain")).and()
        .alternative(nullValue())
        .attachments(emptyIterable());
    // @formatter:on
  }
}
1 The old method
2 The new method

Version: 3.1.0-SNAPSHOT.

Reflow Maven skin by devacfr.