Skip to content

Commit

Permalink
Preload email files during greenmail start (issue #250)
Browse files Browse the repository at this point in the history
- Added config property greenmail.preload.dir, usable by greenmail-standalone (or greenmail-docker/standalone)
  • Loading branch information
marcelmay committed Oct 22, 2023
1 parent e48e69d commit f51d9c7
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 9 deletions.
50 changes: 44 additions & 6 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ <h1>GreenMail</h1>
<nav id="nav_features" class="nav nav-pills flex-column collapse" data-parent="#sidebar">
<a class="nav-item nav-link ml-3" href="#features-api">API</a>
<a class="nav-item nav-link ml-3" href="#features-preload">Preloading emails</a>
<a class="nav-item nav-link ml-3" href="#features-dsn">Delivery status notifcation</a>
<a class="nav-item nav-link ml-3" href="#features-dsn">Delivery status notification</a>
</nav>
<a class="nav-item nav-link nav-pills" href="#faq">FAQ</a>
<a class="nav-item nav-link nav-pills" href="#download" data-toggle="collapse"
Expand Down Expand Up @@ -280,7 +280,7 @@ <h4 id="implementation" class="anchor">Implementation</h4>
<li><a href="https://eclipse-ee4j.github.io/jaf/">Java Activation Framework</a>
</li>
<li><a href="https://www.slf4j.org/">SLF4J Simple Logging Facade
for Java</a></li> for logging</li>
for Java</a> for logging</li>
<li><a href="https://junit.org">Junit</a> - required for easy test setup using Rules</li>
</ul>
<p>GreenMail contains several modules:</p>
Expand Down Expand Up @@ -618,7 +618,7 @@ <h3 id="deploy_standalone" class="anchor">Run GreenMail as standalone java appli
contains all dependencies and a default logging.</p>
<p>You can configure GreenMail by setting system
properties, and activate a minimal <a href="#features-api">RESTful API</a> (requires GreenMail 1.6.2+)
for eg adding users, purging mails or resetting GreenMail instance.</p>
for e.g. adding users, purging mails or resetting GreenMail instance.</p>

<h4>Starting GreenMail standalone</h4>
<pre><code class="language-bash"><!-- @formatter:off -->
Expand Down Expand Up @@ -707,6 +707,7 @@ <h4>Starting GreenMail standalone</h4>
<li>pop3s</li>
<li>api</li>
</ul>
<p>
<div class="bs-example">
<code>-Dgreenmail.smtp.port=3025 -Dgreenmail.smtp.hostname=127.0.0.1</code>
</div>
Expand All @@ -724,6 +725,7 @@ <h4>Starting GreenMail standalone</h4>
<div class="alert alert-info" role="alert">
Note: Default hostname is 127.0.0.1
</div>
<p>
<div class="bs-example"><!-- @formatter:off -->
<code>-Dgreenmail.smtp.port=3025 -Dgreenmail.imap.port=3143 -Dgreenmail.hostname=0.0.0.0</code>
</div><!-- @formatter:on -->
Expand All @@ -735,6 +737,7 @@ <h4>Starting GreenMail standalone</h4>
<p>The list of users is comma separated.</p>
<p>A single user is of format <i>local-part</i>:<i>password</i>[@<i>domain</i>] , where the <i>domain</i>
part is optional.</p>
<p>
<div class="bs-example">
<code>-Dgreenmail.users=foo:[email protected],jabber:[email protected],foo1:bar</code>
</div>
Expand All @@ -743,6 +746,7 @@ <h4>Starting GreenMail standalone</h4>
<tr>
<td class="text-nowrap">-Dgreenmail.users.login=(local_part|email)<br><mark><b>Requires 1.6.1</b></mark></td>
<td>Configures if local_part (default) or full email should be used for login when configuring users via <i>[email protected],...</i>.
<p>
<div class="alert alert-warning" role="alert">
Note: Only has effect if configured user is of type email (i.e. contains '@')
</div>
Expand All @@ -754,6 +758,7 @@ <h4>Starting GreenMail standalone</h4>
<tr>
<td class="text-nowrap">-Dgreenmail.tls.keystore.file<br><mark><b>Requires 1.6.8/2.0.0</b></mark></td>
<td>Configures a PKCS12 keystore for secure IMAP/SMTP/POP3 TLS certificates (see also <i>-Dgreenmail.tls.keystore.password</i>)
<p>
<div class="bd-example">
Example and default used in GreenMail container image:
<code>-Dgreenmail.tls.keystore.file=/home/greenmail/greenmail.p12</code>
Expand All @@ -763,6 +768,7 @@ <h4>Starting GreenMail standalone</h4>
<tr>
<td class="text-nowrap">-Dgreenmail.tls.keystore.password<br><mark><b>Requires 1.6.8/2.0.0</b></mark></td>
<td>Configures a PKCS12 keystore password (see also <i>-Dgreenmail.tls.keystore.file</i>)
<p>
<div class="bs-example">
Example and default used in GreenMail container image:
<code>-Dgreenmail.tls.keystore.password=changeit</code>
Expand All @@ -774,6 +780,7 @@ <h4>Starting GreenMail standalone</h4>
<td>Disables user authentication check, so that any password works.
Useful if you do not want to preconfigure user/passwords.
GreenMail automatically creates non-existent users.
<p>
<div class="bs-example">
Example: <code>-Dgreenmail.auth.disabled</code>
</div>
Expand All @@ -783,14 +790,15 @@ <h4>Starting GreenMail standalone</h4>
<td class="text-nowrap">-Dgreenmail.verbose</td>
<td>Enables verbose mode.
Useful if you want to see debug and protocol level output.
<p>
<div class="bs-example">
Example: <code>-Dgreenmail.verbose</code>
</div>
</td>
</tr>
<tr>
<td class="text-nowrap">-Dgreenmail.sieve.ignore.detail<br><mark><b>Requires 2.1.0-alpha-2</b></mark></td>
<td>Disables <a href="https://tools.ietf.org/html/rfc5233">RFC5233 sub adressing</a> handling.
<td>Disables <a href="https://tools.ietf.org/html/rfc5233">RFC5233 sub addressing</a> handling.
<p>
Use this flag if you want to avoid removing sub adress detail from email.
<div class="bs-example">
Expand All @@ -802,11 +810,41 @@ <h4>Starting GreenMail standalone</h4>
<td class="text-nowrap">-Dgreenmail.startup.timeout</td>
<td>Overrides the default server startup timeout of 1000ms.
Useful if you have a slow or overloaded environment.
<p>
<div class="bs-example">
Example with 2s startup timeout: <code>-Dgreenmail.startup.timeout=2000</code>
</div>
</td>
</tr>
<tr>
<td class="text-nowrap">-Dgreenmail.preload.dir<br><mark><b>Requires 1.6.15/2.0.1/2.1.0-alpha-3</b></mark></td>
<td>Preloads mails (including users) from filesystem
<p>
<div class="bs-example">
Example: <code>-Dgreenmail.preload.dir=/tmp/preload-example</code>
<pre>
tmp
└──preload-example
├── bar@localhost # Creates user (login and credentials default to email)
│   └── INBOX
│   └── test-5.eml # Loads email
├── drafts@localhost
│   └── Drafts # Creates folder
├── foo-bar@localhost
└── foo@localhost
├── Drafts
│   └── draft.eml
└── INBOX
├── f1
│   ├── f2
│   │   ├── test-3.eml
│   │   └── test-4.eml
│   └── test-2.eml
└── test-1.eml
</pre>
</div>
</td>
</tr>
<tr>
<td class="text-nowrap">-Dlog4j.configuration</td>
<td><p>Configures log4j using given configuration file.</p>
Expand Down Expand Up @@ -864,7 +902,7 @@ <h3 id="deploy_docker_standalone" class="anchor">Deploy as a standalone Docker i
but you can also build it yourself using our
<a href="https://github.com/greenmail-mail-test/greenmail/tree/master/greenmail-docker/standalone/Dockerfile">Dockerfile</a>.
</p>
<p>By default GreenMail standalone runs as user <i>greenmail</i> using the GreenMail test setup with the following default settings and exposed ports.</p>
<p>By default, GreenMail standalone runs as user <i>greenmail</i> using the GreenMail test setup with the following default settings and exposed ports.</p>
<table class="table table-striped table-sm caption-top">
<caption>Dockerfile configuration options</caption>
<thead>
Expand Down Expand Up @@ -1083,7 +1121,7 @@ <h2 id="features-dsn">Delivery Status Notification (DSN)</h2>
<h1 class="page-header">FAQ</h1>
<h4>How come I don't have to create any accounts to send/retrieve?</h4>

<p>By default GreenMail accepts all incoming emails. If there is no corresponding existing email
<p>By default, GreenMail accepts all incoming emails. If there is no corresponding existing email
account, one is automatically created with login and password being the same as the to-address.</p>

<h4>What other library dependencies are there?</h4>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.icegreen.greenmail.configuration;

import com.icegreen.greenmail.base.GreenMailOperations;
import com.icegreen.greenmail.store.FolderException;

import java.io.IOException;
import java.nio.file.Paths;

/**
* A version of GreenMailOperations that implements the configure() method.
Expand All @@ -24,6 +28,13 @@ protected void doConfigure() {
}
getUserManager().setAuthRequired(!config.isAuthenticationDisabled());
getUserManager().setSieveIgnoreDetail(config.isSieveIgnoreDetailEnabled());
if(config.hasPreloadDir()) {
try {
loadEmails(Paths.get(config.getPreloadDir()));
} catch (IOException | FolderException e) {
throw new IllegalArgumentException("Can not preload emails", e);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.icegreen.greenmail.configuration;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -10,6 +11,8 @@ public class GreenMailConfiguration {
private final List<UserBean> usersToCreate = new ArrayList<>();
private boolean disableAuthenticationCheck = false;
private boolean sieveIgnoreDetail = false;
private String preloadDir;


/**
* The given {@link com.icegreen.greenmail.user.GreenMailUser} will be created when servers will start.
Expand Down Expand Up @@ -51,7 +54,7 @@ public List<UserBean> getUsersToCreate() {

/**
* Disables authentication.
*
* <p>
* Useful if you want to avoid setting up users up front.
*
* @return Modified configuration.
Expand All @@ -71,7 +74,7 @@ public boolean isAuthenticationDisabled() {
}

/**
* Enables Sieve detail handling, also known as RFC 5233 subaddress extension.
* Enables Sieve detail handling, also known as RFC 5233 sub-address extension.
*
* @return Modified configuration.
*/
Expand All @@ -88,4 +91,31 @@ public GreenMailConfiguration withSieveIgnoreDetail() {
public boolean isSieveIgnoreDetailEnabled() {
return sieveIgnoreDetail;
}

/**
* Configures directory path for preloading emails from filesystem.
* @param preloadDir directory containing emails
* @see com.icegreen.greenmail.base.GreenMailOperations#loadEmails(Path)
* @return Modified configuration.
*/
public GreenMailConfiguration withPreloadDir(String preloadDir) {
this.preloadDir = preloadDir;
return this;
}

/**
* Gets preload directory value or null if not set.
* @return the directory
*/
public String getPreloadDir() {
return preloadDir;
}

/**
* Checks if preload directory value exists.
* @return true if available
*/
public boolean hasPreloadDir() {
return null != preloadDir;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.icegreen.greenmail.configuration;

import java.nio.file.Path;
import java.util.Arrays;
import java.util.Properties;
import java.util.function.BinaryOperator;
Expand All @@ -18,6 +19,8 @@
* greenmail.users.login : Overrides the login for authentication
* <p>By default use the local-part of an email. Can be changed to full email.</p>
* </li>
* <li>greenmail.preload.dir : Preloads emails from filesystem
* (see {@link com.icegreen.greenmail.base.GreenMailOperations#loadEmails(Path)} for expected structure)</li>
* </ul>
*/
public class PropertiesBasedGreenMailConfigurationBuilder {
Expand All @@ -39,6 +42,7 @@ public class PropertiesBasedGreenMailConfigurationBuilder {
public static final String GREENMAIL_AUTH_DISABLED = "greenmail.auth.disabled";

public static final String GREENMAIL_SIEVE_IGNORE_DETAIL = "greenmail.sieve.ignore.detail";
public static final String GREENMAIL_PRELOAD_DIR = "greenmail.preload.dir";

/**
* Configures how user login should be extracted from user of pattern local-part:password@domain .
Expand Down Expand Up @@ -80,6 +84,11 @@ public GreenMailConfiguration build(Properties properties) {
configuration.withSieveIgnoreDetail();
}

String preloadDir = properties.getProperty(GREENMAIL_PRELOAD_DIR);
if (null != preloadDir) {
configuration.withPreloadDir(preloadDir);
}

return configuration;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ public void testBuildWithSieveIgnoreDetailEnabledSetting() {
assertThat(config.isSieveIgnoreDetailEnabled()).isTrue();
}

@Test
public void testBuildWithPreloadDir() {
final String preloadDir = "/preload";
Properties props = createPropertiesFor(PropertiesBasedGreenMailConfigurationBuilder.GREENMAIL_PRELOAD_DIR, preloadDir);
GreenMailConfiguration config = new PropertiesBasedGreenMailConfigurationBuilder().build(props);
assertThat(config).isNotNull();
assertThat(config.getPreloadDir()).isEqualTo(preloadDir);
}

private Properties createPropertiesFor(String key, String value) {
Properties props = new Properties();
props.setProperty(key, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ protected ErrorMessage(String message) {
static class Configuration {
public ServerSetup[] serverSetups;
public boolean authenticationDisabled;
public boolean sieveIgnoreDetail;
public String preloadDirectory;
}

@GET
Expand All @@ -98,6 +100,8 @@ public Response configuration() {
final Configuration config = new Configuration();
config.serverSetups = serverSetups;
config.authenticationDisabled = configuration.isAuthenticationDisabled();
config.sieveIgnoreDetail = configuration.isSieveIgnoreDetailEnabled();
config.preloadDirectory = configuration.getPreloadDir();
return Response.status(Response.Status.OK)
.entity(config)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ public void testApi() {
"{\"port\":3025,\"address\":\"127.0.0.1\",\"protocol\":\"smtp\",\"isSecure\":false,\"readTimeout\":-1," +
"\"writeTimeout\":-1,\"connectionTimeout\":-1,\"serverStartupTimeout\":2000,\"isDynamicPort\":false}," +
"{\"port\":3143,\"address\":\"127.0.0.1\",\"protocol\":\"imap\",\"isSecure\":false,\"readTimeout\":-1," +
"\"writeTimeout\":-1,\"connectionTimeout\":-1,\"serverStartupTimeout\":2000,\"isDynamicPort\":false}],\"authenticationDisabled\":false" +
"\"writeTimeout\":-1,\"connectionTimeout\":-1,\"serverStartupTimeout\":2000,\"isDynamicPort\":false}]," +
"\"authenticationDisabled\":false," +
"\"sieveIgnoreDetail\":true," +
"\"preloadDirectory\":null" +
"}");

final Response userListResponse = api.path("/api/user")
Expand Down

0 comments on commit f51d9c7

Please sign in to comment.