Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

James 4099 Configurable path delimiter #2588

Merged
15 changes: 15 additions & 0 deletions docs/modules/servers/partials/configure/jvm.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ when a property affects very early JVM start behaviour.

For testing purposes, you may specify a different file path via the command line option `-Dextra.props=/some/other/jvm.properties`.

== Adjusting the Mailbox folder delimiter

The delimiter used to separate parent/child folders.

WARNING: This value should only be changed when setting up a new deployment. Changing the parameter for an existing deployments will likely lead to failure of some system components, as occurrences of old the delimiter will still be present in the database/data store.

Optional. String. Defaults to 'dot'

Ex in `jvm.properties`
----
james.mailbox.folder.delimiter=dot
----

Allowed values are: dot (will use '.' as delimiter), slash (will use '/' as delimiter), pipe ('|'), comma (','), colon (':'), semicolon (';').

== Control the threshold memory
This governs the threshold MimeMessageInputStreamSource relies on for storing MimeMessage content on disk.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,61 @@

package org.apache.james.mailbox.model;

import java.util.Optional;

/**
* Constants which are used within the mailbox api and implementations
*/
public interface MailboxConstants {
public class MailboxConstants {

/**
* The char which is used to prefix a namespace
*/
char NAMESPACE_PREFIX_CHAR = '#';
public static final char NAMESPACE_PREFIX_CHAR = '#';

/** The namespace used for store user inboxes */
String USER_NAMESPACE = NAMESPACE_PREFIX_CHAR + "private";
public static final String USER_NAMESPACE = NAMESPACE_PREFIX_CHAR + "private";

/** The delimiter used to seperated parent/child folders */
public static char FOLDER_DELIMITER = Optional.ofNullable(System.getProperty("james.mailbox.folder.delimiter"))
.map(MailboxFolderDelimiter::parse).orElse(MailboxFolderDelimiter.DOT).value;

public enum MailboxFolderDelimiter {
// NOTE: When changing this list, make sure to adjust the MailboxFolderDelimiterAwareTests as well.
// Values currently left-out explicitly:
// hash sign '#' (Clashes with namespace prefix character)
// backslash '\\' (Anticipated some problems with the PrefixedRegex matching.
// Also, because it is the escaping character, it can generally be a bit more annoying
// to deal with in strings)
DOT('.'),
SLASH('/'),
PIPE('|'),
COMMA(','),
COLON(':'),
SEMICOLON(';');

public final char value;

MailboxFolderDelimiter(char value) {
this.value = value;
}

/** The default delimiter used to seperated parent/child folders */
char DEFAULT_DELIMITER = '.';
static MailboxFolderDelimiter parse(String input) {
for (MailboxFolderDelimiter delimiter: values()) {
if (delimiter.name().equalsIgnoreCase(input)) {
return delimiter;
}
}
throw new IllegalArgumentException(String.format("Invalid mailbox delimiter `%s`", input));
}
}

/** The name of the INBOX */
String INBOX = "INBOX";
public static final String INBOX = "INBOX";

/** The limitation of annotation data */
int DEFAULT_LIMIT_ANNOTATION_SIZE = 1024;
public static final int DEFAULT_LIMIT_ANNOTATION_SIZE = 1024;

/** The maximum number of annotations on a mailbox */
int DEFAULT_LIMIT_ANNOTATIONS_ON_MAILBOX = 10;
public static final int DEFAULT_LIMIT_ANNOTATIONS_ON_MAILBOX = 10;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.apache.james.mailbox.exception.MailboxNameException;
import org.apache.james.mailbox.exception.TooLongMailboxNameException;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
Expand All @@ -51,7 +52,8 @@ public class MailboxPath {
private static final Joiner PARTS_JOINER = Joiner.on(':');
private static final LookupTranslator USERNAME_ESCAPER = new LookupTranslator(Map.of(":", "/;", "/", "//"));
private static final LookupTranslator USERNAME_UNESCAPER = new LookupTranslator(Map.of("/;", ":", "//", "/"));
private static final boolean RELAX_MAILBOX_NAME_VALIDATION = Boolean.parseBoolean(System.getProperty("james.relaxed.mailbox.name.validation", "false"));
@VisibleForTesting
static boolean RELAX_MAILBOX_NAME_VALIDATION = Boolean.parseBoolean(System.getProperty("james.relaxed.mailbox.name.validation", "false"));

/**
* Return a {@link MailboxPath} which represent the INBOX of the given
Expand Down Expand Up @@ -95,15 +97,18 @@ private static Username getUsername(List<String> parts) {
return Username.of(USERNAME_UNESCAPER.translate(parts.get(1)));
}

private static String evaluateInvalidChars() {
@VisibleForTesting
static String evaluateInvalidChars() {
if (RELAX_MAILBOX_NAME_VALIDATION) {
return "\r\n";
}
return "%*\r\n";
}

private static final String INVALID_CHARS = evaluateInvalidChars();
private static final CharMatcher INVALID_CHARS_MATCHER = CharMatcher.anyOf(INVALID_CHARS);
@VisibleForTesting
static String INVALID_CHARS = evaluateInvalidChars();
@VisibleForTesting
static CharMatcher INVALID_CHARS_MATCHER = CharMatcher.anyOf(INVALID_CHARS);
// This is the size that all mailbox backend should support
public static final int MAX_MAILBOX_NAME_LENGTH = 200;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,24 @@

public class MailboxSessionUtil {
public static MailboxSession create(Username username) {
return create(username, MailboxSession.SessionId.of(ThreadLocalRandom.current().nextLong()));
return create(username, MailboxConstants.FOLDER_DELIMITER);
}

public static MailboxSession create(Username username, char folderDelimiter) {
return create(username, MailboxSession.SessionId.of(ThreadLocalRandom.current().nextLong()), folderDelimiter);
}

@VisibleForTesting
public static MailboxSession create(Username username, MailboxSession.SessionId sessionId) {
public static MailboxSession create(Username username, MailboxSession.SessionId sessionId,
char folderDelimiter) {
ArrayList<Locale> locales = new ArrayList<>();

return new MailboxSession(
sessionId,
username,
Optional.of(username),
locales,
MailboxConstants.DEFAULT_DELIMITER,
folderDelimiter,
MailboxSession.SessionType.User);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/

package org.apache.james.mailbox.model;

import org.apache.james.core.Username;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

/**
* A base class for all tests that have to deal with the mailbox path delimiter.
* It will make sure to install the delimiter specified by the subclass during the test lifecycle and provides
* some utility methods for writing tests using the active delimiter.
* <p>
* NOTE: If you add a new folder delimiter, make sure to add tests for it in all classes extending this one!
*/
public abstract class MailboxFolderDelimiterAwareTest {
public abstract char folderDelimiter();

static char initialFolderDelimiter;

@BeforeEach
public void setUp() {
initialFolderDelimiter = MailboxConstants.FOLDER_DELIMITER;
MailboxConstants.FOLDER_DELIMITER = folderDelimiter();
}

@AfterEach
public void tearDown() {
MailboxConstants.FOLDER_DELIMITER = initialFolderDelimiter;
}

/**
* Adjust the given string assumed to contain path delimiter dots ('.') to an equivalent version for a different
* delimiter.
* For example, a string "folder.subfolder.subsubfolder" would be converted into "folder/subfolder/subsubfolder" when
* the active FOLDER_DELIMITER is '/'.
* This is used to test that all delimiters are handled correctly in a lot of different scenarios
* without having to manually assemble strings with the active path delimiter
* (like "folder" + MailboxConstants.FOLDER_DELIMITER + "subfolder" + MailboxConstants.FOLDER_DELIMITER + "subsubfolder")
* everywhere, which quickly becomes tedious.
*/
public static String adjustToActiveFolderDelimiter(String valueWithDots) {
// Because the test setup will configure the desired delimiter to be used,
// we do not need to pass it in manually here.
return valueWithDots.replace('.', MailboxConstants.FOLDER_DELIMITER);
}

/**
* See {@link #adjustToActiveFolderDelimiter(String)}.
*/
public static Username adjustToActiveFolderDelimiter(Username username) {
return Username.of(adjustToActiveFolderDelimiter(username.asString()));
}
}
Loading