Browse code

Merge remote-tracking branch 'origin/master' into mvp/seek_and_destroy

kladier authored on 27/11/2019 10:42:23
Showing 71 changed files
... ...
@@ -36,7 +36,7 @@
36 36
 		<sonar.jacoco.excludes>**/*Test*,**/generated/**/*</sonar.jacoco.excludes>
37 37
 		<l10n.list>"en", "fr"</l10n.list>
38 38
 		<gwt-version>2.8.2</gwt-version>
39
-                <docker.devenv.tag>4.1.46757</docker.devenv.tag>
39
+                <docker.devenv.tag>4.1.46885</docker.devenv.tag>
40 40
 	</properties>
41 41
 
42 42
 	<repositories>
... ...
@@ -5,7 +5,7 @@ BMES="6.4.3-bluemind75"
5 5
 
6 6
 KAFKA="2.3.0-bluemind2"
7 7
 
8
-BMJDK="8u222-bluemind30"
8
+BMJDK="8u232-bluemind31"
9 9
 BMSNZIP="1.0.4-bluemind4"
10 10
 BMNGINX="1.16.1-bluemind81"
11 11
 BMPHP="5.6.40-bluemind79"
... ...
@@ -6,7 +6,7 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-cli
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30), jq
9
+Depends: bm-jdk (= 8u232-bluemind31), jq
10 10
 Recommends: s3cmd
11 11
 Description: BlueMind CLI
12 12
  BlueMind Command Line Interface
... ...
@@ -6,7 +6,7 @@ License:            GNU Affero General Public License v3
6 6
 Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9
-Requires:           bm-jdk = 8u222-bluemind30, jq
9
+Requires:           bm-jdk = 8u232-bluemind31, jq
10 10
 
11 11
 %description
12 12
 BlueMind CLI
... ...
@@ -6,7 +6,7 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-core
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30), bm-conf (= ${binary:Version}), openssl, bm-tika (= ${binary:Version}), bm-plugin-core-subscription (= ${binary:Version})
9
+Depends: bm-jdk (= 8u232-bluemind31), bm-conf (= ${binary:Version}), openssl, bm-tika (= ${binary:Version}), bm-plugin-core-subscription (= ${binary:Version})
10 10
 Recommends: bm-plugin-core-outlook (= ${binary:Version})
11 11
 Replaces: bm-ca, bm-cert, bm-plugin-core-backup, bm-plugin-core-hsm
12 12
 Conflicts: bm-ca, bm-cert
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, bm-conf = %{version}-%{release}, ghostscript-fonts, dejavu-sans-fonts, dejavu-sans-mono-fonts, dejavu-serif-fonts, openssl, ca-certificates, bm-tika = %{version}-%{release}, bm-plugin-core-subscription = %{version}-%{release}
10
+Requires:           bm-jdk = 8u232-bluemind31, bm-conf = %{version}-%{release}, ghostscript-fonts, dejavu-sans-fonts, dejavu-sans-mono-fonts, dejavu-serif-fonts, openssl, ca-certificates, bm-tika = %{version}-%{release}, bm-plugin-core-subscription = %{version}-%{release}
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 Conflicts:          bm-soap, bm-plugin-core-dataprotect-upgrade
13 13
 Obsoletes:          bm-soap, bm-plugin-core-dataprotect-upgrade
... ...
@@ -6,6 +6,6 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-eas
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30), bm-node (= ${binary:Version})
9
+Depends: bm-jdk (= 8u232-bluemind31), bm-node (= ${binary:Version})
10 10
 Description: BlueMind Mobile Synchronization server
11 11
  Mobile synchronization server for BlueMind
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, bm-node = %{version}-%{release}
10
+Requires:           bm-jdk = 8u232-bluemind31, bm-node = %{version}-%{release}
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 
13 13
 %description
... ...
@@ -6,6 +6,6 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-forest-node
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30), bm-kafka (= 2.3.0-bluemind2)
9
+Depends: bm-jdk (= 8u232-bluemind31), bm-kafka (= 2.3.0-bluemind2)
10 10
 Description: BlueMind forest node
11 11
  BlueMind forest node is used to federate multiple bluemind installations
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, bm-kafka = 2.3.0-bluemind2
10
+Requires:           bm-jdk = 8u232-bluemind31, bm-kafka = 2.3.0-bluemind2
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 
13 13
 %description
... ...
@@ -6,6 +6,6 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-hps
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30)
9
+Depends: bm-jdk (= 8u232-bluemind31)
10 10
 Description: BlueMind HTTP proxy server
11 11
  BlueMind HTTP proxy server with SSO capabilities
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, bm-conf = %{version}-%{release}
10
+Requires:           bm-jdk = 8u232-bluemind31, bm-conf = %{version}-%{release}
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 
13 13
 %description
... ...
@@ -6,6 +6,6 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-lmtpd
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30), openssl
9
+Depends: bm-jdk (= 8u232-bluemind31), openssl
10 10
 Description: Blue Mind LMTP proxy daemon
11 11
  LMTP proxy daemon for inbound mail processing
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, openssl
10
+Requires:           bm-jdk = 8u232-bluemind31, openssl
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 
13 13
 %description
... ...
@@ -6,6 +6,6 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-locator
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30), openssl
9
+Depends: bm-jdk (= 8u232-bluemind31), openssl
10 10
 Description: BlueMind locator service
11 11
  Locator server for BlueMind
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, vim-enhanced, emacs-nox, strace, lsof, telnet, openssh-clients, tar, gzip, bzip2, rsync, bm-conf = %{version}-%{release}, nfs-utils, sysstat, bm-pimp = %{version}-%{release}, iptables, sudo, httpd-tools
10
+Requires:           bm-jdk = 8u232-bluemind31, vim-enhanced, emacs-nox, strace, lsof, telnet, openssh-clients, tar, gzip, bzip2, rsync, bm-conf = %{version}-%{release}, nfs-utils, sysstat, bm-pimp = %{version}-%{release}, iptables, sudo, httpd-tools
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 
13 13
 %description
... ...
@@ -6,6 +6,6 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-milter
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30), bm-conf (= ${binary:Version})
9
+Depends: bm-jdk (= 8u232-bluemind31), bm-conf (= ${binary:Version})
10 10
 Description: BlueMind Milter
11 11
  BlueMind Milter filters
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, bm-conf = %{version}-%{release}
10
+Requires:           bm-jdk = 8u232-bluemind31, bm-conf = %{version}-%{release}
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 
13 13
 %description
... ...
@@ -6,7 +6,7 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-node
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30), tar, gzip, bzip2, rsync, gnupg, bm-conf (= ${binary:Version}), bm-pimp (= ${binary:Version}), iptables, sudo, apt-transport-https, bm-maintenance-tools (= ${binary:Version}), bm-cli (= ${binary:Version})
9
+Depends: bm-jdk (= 8u232-bluemind31), tar, gzip, bzip2, rsync, gnupg, bm-conf (= ${binary:Version}), bm-pimp (= ${binary:Version}), iptables, sudo, apt-transport-https, bm-maintenance-tools (= ${binary:Version}), bm-cli (= ${binary:Version})
10 10
 Description: BlueMind Node Daemon
11 11
  BlueMind Node handles all the remote tasks for BlueMind Core
12 12
 
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, tar, gzip, bzip2, rsync, bm-conf = %{version}-%{release}, bm-pimp = %{version}-%{release}, iptables, sudo, httpd-tools, bm-maintenance-tools = %{version}-%{release}, bm-cli = %{version}-%{release}
10
+Requires:           bm-jdk = 8u232-bluemind31, tar, gzip, bzip2, rsync, bm-conf = %{version}-%{release}, bm-pimp = %{version}-%{release}, iptables, sudo, httpd-tools, bm-maintenance-tools = %{version}-%{release}, bm-cli = %{version}-%{release}
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 Conflicts:          bm-mq
13 13
 Obsoletes:          bm-mq
... ...
@@ -6,6 +6,6 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-pimp
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30)
9
+Depends: bm-jdk (= 8u232-bluemind31)
10 10
 Description: BlueMind automated memory tuning
11 11
  BlueMind automated memory tuning
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30
10
+Requires:           bm-jdk = 8u232-bluemind31
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 
13 13
 %description
... ...
@@ -6,6 +6,6 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-sds-proxy
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30)
9
+Depends: bm-jdk (= 8u232-bluemind31)
10 10
 Description: BlueMind SDS proxy
11 11
  BlueMind SDS proxy is mandatory to use cyrus-imapd with an S3 object store
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, bm-conf = %{version}-%{release}
10
+Requires:           bm-jdk = 8u232-bluemind31, bm-conf = %{version}-%{release}
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 
13 13
 %description
... ...
@@ -6,6 +6,6 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-tika
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30)
9
+Depends: bm-jdk (= 8u232-bluemind31)
10 10
 Description: BlueMind tika-based text extractor
11 11
  BlueMind tika based text extractor
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, bm-conf = %{version}-%{release}
10
+Requires:           bm-jdk = 8u232-bluemind31, bm-conf = %{version}-%{release}
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 
13 13
 %description
... ...
@@ -6,7 +6,7 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-webserver
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30), bm-conf (= ${binary:Version}), bm-nginx (= 1.16.1-bluemind81), bm-client-access (= ${binary:Version})
9
+Depends: bm-jdk (= 8u232-bluemind31), bm-conf (= ${binary:Version}), bm-nginx (= 1.16.1-bluemind81), bm-client-access (= ${binary:Version})
10 10
 Description: BlueMind web server
11 11
  BlueMind web server
12 12
 
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, bm-conf = %{version}-%{release}, bm-nginx = 1.16.1-bluemind81, bm-client-access = %{version}-%{release}
10
+Requires:           bm-jdk = 8u232-bluemind31, bm-conf = %{version}-%{release}, bm-nginx = 1.16.1-bluemind81, bm-client-access = %{version}-%{release}
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 Conflicts:          bm-tomcat
13 13
 Obsoletes:          bm-tomcat
... ...
@@ -6,6 +6,6 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-xivobridge
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30), bm-conf (= ${binary:Version})
9
+Depends: bm-jdk (= 8u232-bluemind31), bm-conf (= ${binary:Version})
10 10
 Description: BlueMind XiVO bridge server
11 11
  BlueMind XiVO bridge sends cti events to BlueMind components
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, bm-conf = %{version}-%{release}
10
+Requires:           bm-jdk = 8u232-bluemind31, bm-conf = %{version}-%{release}
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 
13 13
 %description
... ...
@@ -6,6 +6,6 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-xmpp
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30)
9
+Depends: bm-jdk (= 8u232-bluemind31)
10 10
 Description: BlueMind XMPP server
11 11
  BlueMind XMPP server
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, bm-conf = %{version}-%{release}
10
+Requires:           bm-jdk = 8u232-bluemind31, bm-conf = %{version}-%{release}
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 
13 13
 %description
... ...
@@ -6,7 +6,7 @@ Standards-Version: 3.9.1
6 6
 
7 7
 Package: bm-ysnp
8 8
 Architecture: amd64
9
-Depends: bm-jdk (= 8u222-bluemind30), bm-conf (= ${binary:Version})
9
+Depends: bm-jdk (= 8u232-bluemind31), bm-conf (= ${binary:Version})
10 10
 Description: BlueMind saslauthd
11 11
  BlueMind java based saslauthd replacement
12 12
 
... ...
@@ -7,7 +7,7 @@ Group:              Applications/messaging
7 7
 URL:                http://www.bluemind.net/
8 8
 ExcludeArch:        s390 s390x
9 9
 Requires(post):     systemd systemd-sysv
10
-Requires:           bm-jdk = 8u222-bluemind30, bm-conf = %{version}-%{release}
10
+Requires:           bm-jdk = 8u232-bluemind31, bm-conf = %{version}-%{release}
11 11
 Requires(post):     /bin/bash, initscripts
12 12
 Conflicts:          ysnp
13 13
 Obsoletes:          ysnp
... ...
@@ -102,4 +102,9 @@ public class VEventSeries {
102 102
 		return evts;
103 103
 	}
104 104
 
105
+	@Override
106
+	public String toString() {
107
+		return "VEventSeries{icsUid: " + icsUid + ", main: " + main + ", occs: " + occurrences + "}";
108
+	}
109
+
105 110
 }
... ...
@@ -57,6 +57,14 @@ public class JdbcTestHelper {
57 57
 	private JdbcTestHelper() {
58 58
 	}
59 59
 
60
+	@SuppressWarnings("serial")
61
+	private static class JdbcHelperException extends RuntimeException {
62
+		public JdbcHelperException(Throwable t) {
63
+			super(t);
64
+		}
65
+
66
+	}
67
+
60 68
 	public void beforeTest() throws Exception {
61 69
 		beforeTest("junit_" + System.nanoTime());
62 70
 	}
... ...
@@ -89,7 +97,6 @@ public class JdbcTestHelper {
89 97
 		String password = conf.get("password");
90 98
 		String dbName = conf.get("db");
91 99
 		String dbHost = conf.get("host");
92
-		System.out.println("conf: " + conf.getClass().getCanonicalName());
93 100
 
94 101
 		pool = BMPoolActivator.getDefault().newPool(dbType, login, password, dbName, dbHost,
95 102
 				Runtime.getRuntime().availableProcessors() * 2 - 1, schemaName);
... ...
@@ -119,10 +126,18 @@ public class JdbcTestHelper {
119 126
 	}
120 127
 
121 128
 	private void initializeSchema() {
122
-		logger.info("directory pool init schema");
123
-		DbSchemaService.getService(pool.getDataSource(), true).initialize(false);
129
+		Thread dirSchema = new Thread(() -> {
130
+			DbSchemaService.getService(pool.getDataSource(), true).initialize(false);
131
+		});
132
+		dirSchema.start();
124 133
 		logger.info("data pool init schema");
125 134
 		DbSchemaService.getService(dataPool.getDataSource(), true).initialize(false);
135
+		try {
136
+			dirSchema.join();
137
+		} catch (InterruptedException e) {
138
+			Thread.currentThread().interrupt();
139
+			throw new JdbcHelperException(e);
140
+		}
126 141
 	}
127 142
 
128 143
 	public void initNewServer(String ip) throws Exception {
... ...
@@ -143,20 +158,30 @@ public class JdbcTestHelper {
143 158
 	}
144 159
 
145 160
 	public void afterTest() throws Exception {
146
-		if (pool != null) {
147
-			stopPool(pool);
148
-			JdbcActivator.getInstance().setDataSource(null);
149
-		}
161
+		Thread stopMain = new Thread(() -> {
162
+			if (pool != null) {
163
+				try {
164
+					stopPool(pool);
165
+				} catch (Exception e) {
166
+					throw new JdbcHelperException(e);
167
+				}
168
+				JdbcActivator.getInstance().setDataSource(null);
169
+				pool = null;
170
+			}
171
+		});
172
+		stopMain.start();
150 173
 		if (dataPool != null) {
151 174
 			stopPool(dataPool);
152 175
 			JdbcActivator.getInstance().setMailboxDataSource(Collections.emptyMap());
176
+			dataPool = null;
153 177
 		}
154 178
 		for (Pool other : otherPools) {
155 179
 			stopPool(other);
156 180
 		}
157 181
 		otherPools.clear();
182
+		stopMain.join();
158 183
 		// to ensure finalize overrides are executed
159
-		System.gc();
184
+		System.gc(); // NOSONAR
160 185
 	}
161 186
 
162 187
 	private void stopPool(Pool pool) throws Exception {
... ...
@@ -18,6 +18,7 @@
18 18
  */
19 19
 package net.bluemind.core.rest.http.internal;
20 20
 
21
+import java.nio.ByteBuffer;
21 22
 import java.util.List;
22 23
 import java.util.Map.Entry;
23 24
 
... ...
@@ -92,8 +93,8 @@ public class AsyncCompletionHandler extends AsyncCompletionHandlerBase {
92 93
 				}
93 94
 				resp.headers = h;
94 95
 
95
-				byte[] responseBody = response.getResponseBodyAsBytes();
96
-				if (responseBody != null && responseBody.length > 0) {
96
+				ByteBuffer responseBody = response.getResponseBodyAsByteBuffer();
97
+				if (responseBody != null) {
97 98
 					resp.data = new Buffer(Unpooled.wrappedBuffer(responseBody));
98 99
 				}
99 100
 
... ...
@@ -134,6 +134,9 @@ public class DirEntryColumns {
134 134
 			case ORG_UNIT:
135 135
 				value.path = domainUid + "/ous/" + value.entryUid;
136 136
 				break;
137
+			case EXTERNALUSER:
138
+				value.path = domainUid + "/externaluser/" + value.entryUid;
139
+				break;
137 140
 			case DOMAIN:
138 141
 				value.path = domainUid;
139 142
 			default:
... ...
@@ -39,6 +39,7 @@ import org.vertx.java.core.Handler;
39 39
 
40 40
 import com.google.common.collect.Lists;
41 41
 
42
+import net.bluemind.addressbook.api.VCard;
42 43
 import net.bluemind.core.api.Email;
43 44
 import net.bluemind.core.api.fault.ErrorCode;
44 45
 import net.bluemind.core.api.fault.ServerFault;
... ...
@@ -176,6 +177,8 @@ public class DirectoryTests {
176 177
 				domainUid);
177 178
 		User admin = new User();
178 179
 		admin.login = "test";
180
+		admin.contactInfos = new VCard();
181
+		admin.contactInfos.identification.name.familyNames = "myName";
179 182
 		admin.password = "test";
180 183
 		admin.routing = Mailbox.Routing.none;
181 184
 
... ...
@@ -283,6 +286,8 @@ public class DirectoryTests {
283 286
 				domainUid);
284 287
 		User admin = new User();
285 288
 		admin.login = "test";
289
+		admin.contactInfos = new VCard();
290
+		admin.contactInfos.identification.name.familyNames = "myName";
286 291
 		admin.password = "test";
287 292
 		admin.routing = Mailbox.Routing.none;
288 293
 
... ...
@@ -306,6 +311,8 @@ public class DirectoryTests {
306 311
 				domainUid);
307 312
 		User u = new User();
308 313
 		u.login = "logn" + UUID.randomUUID();
314
+		u.contactInfos = new VCard();
315
+		u.contactInfos.identification.name.familyNames = "myName";
309 316
 		u.password = "password";
310 317
 		u.routing = Mailbox.Routing.none;
311 318
 		u.dataLocation = PopulateHelper.FAKE_CYRUS_IP;
... ...
@@ -240,7 +240,7 @@ public class ExternalUserServiceTests {
240 240
 			getExternalUserService().create(extUserItemUid, externalUser);
241 241
 			fail("can't create an external user whose right part is a domain alias already used by an user.");
242 242
 		} catch (ServerFault sf) {
243
-			assertEquals(ErrorCode.ALREADY_EXISTS, sf.getCode());
243
+			assertEquals(ErrorCode.EMAIL_ALREADY_USED, sf.getCode());
244 244
 		}
245 245
 	}
246 246
 
... ...
@@ -257,7 +257,7 @@ public class ExternalUserServiceTests {
257 257
 			getExternalUserService().create(itemUid2, eu2);
258 258
 			fail("can't create an external user with same email than an existing external user.");
259 259
 		} catch (ServerFault sf) {
260
-			assertEquals(ErrorCode.ALREADY_EXISTS, sf.getCode());
260
+			assertEquals(ErrorCode.EMAIL_ALREADY_USED, sf.getCode());
261 261
 		}
262 262
 	}
263 263
 
... ...
@@ -370,8 +370,9 @@ public class ExternalUserServiceTests {
370 370
 		String itemUid = UUID.randomUUID().toString();
371 371
 		ExternalUser eu = new ExternalUser();
372 372
 		String myEmail = "mail@external.com";
373
+		String myName = "myName";
373 374
 		eu.contactInfos = new VCard();
374
-		eu.contactInfos.identification.formatedName = VCard.Identification.FormatedName.create("myName");
375
+		eu.contactInfos.identification.name.familyNames = myName;
375 376
 		eu.contactInfos.communications.emails = new ArrayList<>();
376 377
 		eu.contactInfos.communications.emails.add(VCard.Communications.Email.create(myEmail));
377 378
 		eu.hidden = true;
... ...
@@ -384,9 +385,9 @@ public class ExternalUserServiceTests {
384 385
 		ExternalUser created = externalUserService.getComplete(itemUid).value;
385 386
 
386 387
 		assertEquals(1, created.contactInfos.communications.emails.size());
387
-		assertEquals(myEmail, created.contactInfos.communications.emails.iterator().next().value);
388
-		assertEquals(eu.contactInfos.identification.formatedName.value,
389
-				created.contactInfos.identification.formatedName.value);
388
+		assertEquals(myEmail, created.contactInfos.defaultMail());
389
+		assertEquals(myName, created.contactInfos.identification.name.familyNames);
390
+		assertEquals(myName, created.contactInfos.identification.formatedName.value);
390 391
 	}
391 392
 
392 393
 	private Group createGroup(String name, String desc, String groupUid) {
... ...
@@ -20,17 +20,17 @@ public class ExternalUserSanitizer implements ISanitizer<ExternalUser> {
20 20
 
21 21
 	@Override
22 22
 	public void create(ExternalUser extUser) throws ServerFault {
23
-		sanitizeExternalUser(extUser);
23
+		sanitize(extUser);
24 24
 		new Sanitizer(context).create(extUser.contactInfos);
25 25
 	}
26 26
 
27 27
 	@Override
28 28
 	public void update(ExternalUser current, ExternalUser updated) throws ServerFault {
29
-		sanitizeExternalUser(updated);
29
+		sanitize(updated);
30 30
 		new Sanitizer(context).update(current.contactInfos, updated.contactInfos);
31 31
 	}
32 32
 
33
-	private void sanitizeExternalUser(ExternalUser extUser) {
33
+	private void sanitize(ExternalUser extUser) {
34 34
 		// sanitize vcard
35 35
 		if (extUser.contactInfos == null) {
36 36
 			extUser.contactInfos = new VCard();
... ...
@@ -17,6 +17,8 @@
17 17
   */
18 18
 package net.bluemind.externaluser.service.internal;
19 19
 
20
+import com.google.common.base.Strings;
21
+
20 22
 import net.bluemind.core.api.ParametersValidator;
21 23
 import net.bluemind.core.api.fault.ErrorCode;
22 24
 import net.bluemind.core.api.fault.ServerFault;
... ...
@@ -41,12 +43,17 @@ public class ExternalUserValidator {
41 43
 		ParametersValidator.notNullAndNotEmpty(eu.contactInfos.identification.formatedName.value);
42 44
 		ParametersValidator.notNullAndNotEmpty(eu.dataLocation);
43 45
 
46
+		String familyName = eu.contactInfos.identification.name.familyNames;
47
+		if (Strings.isNullOrEmpty(familyName)) {
48
+			throw new ServerFault("An external user should have a last name.", ErrorCode.EMPTY_LASTNAME);
49
+		}
50
+
44 51
 		DirEntry dirEntry = bmContext.provider().instance(IDirectory.class, domainUid)
45 52
 				.getByEmail(eu.defaultEmailAddress());
46 53
 		if (dirEntry != null && !dirEntry.entryUid.equals(externalUserUid)) {
47 54
 			throw new ServerFault(
48 55
 					"Can't create external user: An entry with the same email address already exists in this domain",
49
-					ErrorCode.ALREADY_EXISTS);
56
+					ErrorCode.EMAIL_ALREADY_USED);
50 57
 		}
51 58
 	}
52 59
 }
... ...
@@ -41,6 +41,8 @@ import org.w3c.dom.Document;
41 41
 import org.w3c.dom.Element;
42 42
 import org.xml.sax.SAXException;
43 43
 
44
+import com.google.common.base.Strings;
45
+
44 46
 import io.netty.handler.codec.http.cookie.Cookie;
45 47
 import io.netty.handler.codec.http.cookie.DefaultCookie;
46 48
 import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
... ...
@@ -187,16 +189,16 @@ public class CasProtocol implements IAuthProtocol {
187 189
 		Element status = DOMUtils.getUniqueElement(document.getDocumentElement(), "cas:authenticationSuccess");
188 190
 		if (status != null) {
189 191
 			String userName = DOMUtils.getUniqueElement(document.getDocumentElement(), "cas:user").getTextContent();
190
-			if (userName != null && !userName.equals("")) {
192
+			if (!Strings.isNullOrEmpty(userName)) {
191 193
 				// OK we've got an user
192 194
 				logger.info("[CAS] Ticket validation successful for user : " + userName);
193 195
 
194 196
 				ExternalCreds creds = new ExternalCreds(casDomain);
195 197
 				creds.setTicket(ticket);
196 198
 				if (userName.contains("@")) {
197
-					creds.setLoginAtDomain(userName);
199
+					creds.setLoginAtDomain(userName.toLowerCase());
198 200
 				} else {
199
-					creds.setLoginAtDomain(userName + "@" + casDomain);
201
+					creds.setLoginAtDomain(userName.toLowerCase() + "@" + casDomain.toLowerCase());
200 202
 				}
201 203
 
202 204
 				return Optional.of(creds);
... ...
@@ -22,6 +22,7 @@ import org.vertx.java.core.Handler;
22 22
 
23 23
 import com.google.common.base.Strings;
24 24
 
25
+import net.bluemind.addressbook.api.VCard;
25 26
 import net.bluemind.core.api.AsyncHandler;
26 27
 import net.bluemind.core.api.Email;
27 28
 import net.bluemind.core.api.fault.ServerFault;
... ...
@@ -163,6 +164,8 @@ public class C2ProviderTests {
163 164
 		User user = new User();
164 165
 		user.login = userLogin;
165 166
 		user.routing = Routing.internal;
167
+		user.contactInfos = new VCard();
168
+		user.contactInfos.identification.name.familyNames = "myName";
166 169
 		user.archived = true;
167 170
 
168 171
 		String emailAlias = String.format("mail.%s@%s", userLogin, domainUid);
... ...
@@ -196,6 +199,8 @@ public class C2ProviderTests {
196 199
 
197 200
 		User user = new User();
198 201
 		user.login = userLogin;
202
+		user.contactInfos = new VCard();
203
+		user.contactInfos.identification.name.familyNames = "myName";
199 204
 		user.routing = Routing.internal;
200 205
 
201 206
 		String emailAlias = String.format("mail.%s@%s", userLogin, domainUid);
... ...
@@ -229,6 +234,8 @@ public class C2ProviderTests {
229 234
 
230 235
 		User user = new User();
231 236
 		user.login = userLogin;
237
+		user.contactInfos = new VCard();
238
+		user.contactInfos.identification.name.familyNames = "myName";
232 239
 		user.routing = Routing.internal;
233 240
 
234 241
 		String emailAlias = String.format("mail.%s@%s", userLogin, domainUid);
... ...
@@ -262,6 +269,8 @@ public class C2ProviderTests {
262 269
 
263 270
 		User user = new User();
264 271
 		user.login = userLogin;
272
+		user.contactInfos = new VCard();
273
+		user.contactInfos.identification.name.familyNames = "myName";
265 274
 		user.routing = Routing.none;
266 275
 
267 276
 		String userUid = UUID.randomUUID().toString();
... ...
@@ -181,6 +181,7 @@ public class ImportLoginValidationTests {
181 181
 				user.value.login = "login-" + System.nanoTime();
182 182
 				user.value.routing = Routing.internal;
183 183
 				user.value.contactInfos = new VCard();
184
+				user.value.contactInfos.identification.name.familyNames = "myName";
184 185
 			}
185 186
 
186 187
 			@Override
... ...
@@ -317,6 +317,7 @@ public class LdapExportServiceTests {
317 317
 		ldapCon.modify(modifyRequest);
318 318
 
319 319
 		user.value.contactInfos = new VCard();
320
+		user.value.contactInfos.identification.name.familyNames = "myName";
320 321
 		user.value.contactInfos.explanatory.note = "Updated description";
321 322
 		userService.update(user.uid, user.value);
322 323
 
323 324
new file mode 100644
... ...
@@ -0,0 +1,43 @@
1
+/* BEGIN LICENSE
2
+ * Copyright © Blue Mind SAS, 2012-2019
3
+ *
4
+ * This file is part of BlueMind. BlueMind is a messaging and collaborative
5
+ * solution.
6
+ *
7
+ * This program is free software; you can redistribute it and/or modify
8
+ * it under the terms of either the GNU Affero General Public License as
9
+ * published by the Free Software Foundation (version 3 of the License).
10
+ *
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15
+ *
16
+ * See LICENSE.txt
17
+ * END LICENSE
18
+ */
19
+package net.bluemind.backend.cyrus.syncclient.mgmt;
20
+
21
+import java.util.Collection;
22
+
23
+import net.bluemind.backend.cyrus.syncclient.mgmt.api.ISyncClientMgmt;
24
+
25
+public class MultiClientManager implements ISyncClientMgmt {
26
+
27
+	private final Collection<ISyncClientMgmt> delegates;
28
+
29
+	public MultiClientManager(Collection<ISyncClientMgmt> delegates) {
30
+		this.delegates = delegates;
31
+	}
32
+
33
+	@Override
34
+	public void stopRollingReplication() {
35
+		delegates.forEach(ISyncClientMgmt::stopRollingReplication);
36
+	}
37
+
38
+	@Override
39
+	public void startRollingReplication() {
40
+		delegates.forEach(ISyncClientMgmt::startRollingReplication);
41
+	}
42
+
43
+}
... ...
@@ -33,7 +33,6 @@ import net.bluemind.backend.cyrus.syncclient.mgmt.api.ISyncClientMgmt;
33 33
 import net.bluemind.backend.cyrus.syncclient.mgmt.api.ISyncClientObserver;
34 34
 import net.bluemind.core.api.fault.ServerFault;
35 35
 import net.bluemind.node.api.INodeClient;
36
-import net.bluemind.node.api.NCUtils;
37 36
 import net.bluemind.node.api.NodeActivator;
38 37
 import net.bluemind.node.api.ProcessHandler;
39 38
 import net.bluemind.node.shared.ExecDescriptor;
... ...
@@ -53,16 +52,16 @@ public class SyncClientMgmt implements ISyncClientMgmt, ProcessHandler {
53 52
 	private boolean started;
54 53
 	private String activeTask;
55 54
 	private final Handler<Message<JsonObject>> uplinkHandler;
55
+	private final int shardIndex;
56 56
 
57
-	public SyncClientMgmt(Vertx vertx, String cyrusBackendAddress, String replicationChannel,
57
+	public SyncClientMgmt(Vertx vertx, String cyrusBackendAddress, String replicationChannel, int shardIndex,
58 58
 			List<ISyncClientObserver> obs, Executor observersPool) {
59 59
 		this.observers = ImmutableList.copyOf(obs);
60 60
 		this.observersPool = observersPool;
61 61
 		this.node = NodeActivator.get(cyrusBackendAddress);
62 62
 		this.vertx = vertx;
63 63
 		this.replicationChannel = replicationChannel;
64
-		// ensure we start in a consistent state
65
-		NCUtils.execNoOut(this.node, "/usr/bin/killall sync_client");
64
+		this.shardIndex = shardIndex;
66 65
 		this.uplinkHandler = (Message<JsonObject> msg) -> {
67 66
 			String linkStatus = msg.body().getString("status");
68 67
 			if ("UP".equals(linkStatus)) {
... ...
@@ -83,15 +82,14 @@ public class SyncClientMgmt implements ISyncClientMgmt, ProcessHandler {
83 82
 		}
84 83
 		vertx.eventBus().registerLocalHandler("mailreplica.uplink", uplinkHandler);
85 84
 
86
-		ExecRequest syncClientReq = ExecRequest.named("mail_replication", "sync_client",
87
-				"/usr/sbin/sync_client -n " + replicationChannel + " -R -l -v", Options.REPLACE_IF_EXISTS);
85
+		ExecRequest syncClientReq = ExecRequest.named("mail_replication_" + shardIndex, "sync_client",
86
+				"/usr/sbin/sync_client -n " + replicationChannel + " -i " + shardIndex + " -R -l -v",
87
+				Options.REPLACE_IF_EXISTS);
88 88
 		try {
89 89
 			node.asyncExecute(syncClientReq, this);
90 90
 		} catch (ServerFault sf) {
91 91
 			logger.warn("Failed to start sync_client ({}), retrying in 1sec.", sf.getMessage());
92
-			vertx.setTimer(1000, tid -> {
93
-				startRollingReplication();
94
-			});
92
+			vertx.setTimer(1000, tid -> startRollingReplication());
95 93
 		}
96 94
 	}
97 95
 
... ...
@@ -116,7 +114,7 @@ public class SyncClientMgmt implements ISyncClientMgmt, ProcessHandler {
116 114
 		} else {
117 115
 			logger.info("SyncClient termined with exitCode {}", exitCode);
118 116
 			for (ISyncClientObserver obs : observers) {
119
-				observersPool.execute(() -> obs.replicationStopped());
117
+				observersPool.execute(obs::replicationStopped);
120 118
 			}
121 119
 		}
122 120
 	}
... ...
@@ -20,10 +20,15 @@ package net.bluemind.backend.cyrus.syncclient.mgmt.api;
20 20
 import java.util.LinkedList;
21 21
 import java.util.List;
22 22
 import java.util.concurrent.Executor;
23
+import java.util.stream.Collectors;
24
+import java.util.stream.IntStream;
23 25
 
24 26
 import org.vertx.java.core.Vertx;
25 27
 
28
+import net.bluemind.backend.cyrus.syncclient.mgmt.MultiClientManager;
26 29
 import net.bluemind.backend.cyrus.syncclient.mgmt.SyncClientMgmt;
30
+import net.bluemind.node.api.NCUtils;
31
+import net.bluemind.node.api.NodeActivator;
27 32
 
28 33
 public interface ISyncClientMgmt {
29 34
 
... ...
@@ -64,7 +69,13 @@ public interface ISyncClientMgmt {
64 69
 		}
65 70
 
66 71
 		public ISyncClientMgmt build() {
67
-			return new SyncClientMgmt(vertx, cyrusBackendAddress, replicationChannel, observers, observersPool);
72
+			// ensure we start in a consistent state
73
+			NCUtils.execNoOut(NodeActivator.get(cyrusBackendAddress), "/usr/bin/killall sync_client");
74
+
75
+			List<ISyncClientMgmt> syncClients = IntStream.range(0, 4).mapToObj(shardIndex -> new SyncClientMgmt(vertx,
76
+					cyrusBackendAddress, replicationChannel, shardIndex, observers, observersPool))
77
+					.collect(Collectors.toList());
78
+			return new MultiClientManager(syncClients);
68 79
 		}
69 80
 
70 81
 	}
... ...
@@ -237,10 +237,8 @@ public class CyrusService {
237 237
 	}
238 238
 
239 239
 	/**
240
-	 * @param boxName
241
-	 *            eg. user/john@bm.lan
242
-	 * @param quota
243
-	 *            unit is KB
240
+	 * @param boxName eg. user/john@bm.lan
241
+	 * @param quota   unit is KB
244 242
 	 * @throws ServerFault
245 243
 	 */
246 244
 	public void setQuota(String boxName, int quota) throws ServerFault {
... ...
@@ -311,7 +309,7 @@ public class CyrusService {
311 309
 	}
312 310
 
313 311
 	public void reset() throws ServerFault {
314
-		NCUtils.execNoOut(nodeClient, "/usr/share/bm-cyrus/resetCyrus.sh");
315
-		NCUtils.execNoOut(nodeClient, "rm -rf /var/lib/cyrus/sync");
312
+		NCUtils.execNoOut(nodeClient, "/usr/share/bm-cyrus/resetCyrus.sh", 5, TimeUnit.SECONDS);
313
+		NCUtils.execNoOut(nodeClient, "rm -rf /var/lib/cyrus/sync", 5, TimeUnit.SECONDS);
316 314
 	}
317 315
 }
... ...
@@ -522,6 +522,9 @@ public class CyrusMailboxesStorage implements IMailboxesStorage {
522 522
 			List<MailFolder> ret = new ArrayList<MailFolder>();
523 523
 
524 524
 			Set<String> done = new HashSet<String>();
525
+
526
+			String inbox = "user/" + mailbox.value.name + "/INBOX@" + domainUid;
527
+			done.add(inbox);
525 528
 			ListResult lr = sc.listSubFoldersMailbox(boxname(mailbox.value, domainUid));
526 529
 			// list returns /a, /a/b, /a/b/c
527 530
 			// reverse the list to /a/b/c, /a/b, /a so we only check /a/b/c
... ...
@@ -554,6 +557,7 @@ public class CyrusMailboxesStorage implements IMailboxesStorage {
554 557
 					String mailboxName = mailboxBuilder.toString() + "@" + domainUid;
555 558
 
556 559
 					done.add(mailboxName);
560
+
557 561
 					try {
558 562
 						if (!sc.select(mailboxName)) {
559 563
 							logger.error("{} does not exist. create it", mailboxName);
... ...
@@ -138,8 +138,7 @@ public class MessageBody {
138 138
 
139 139
 		private static boolean hasRealAttachments(Part structure, Part parent) {
140 140
 			boolean ret = false;
141
-			if (parent != null && DispositionType.ATTACHMENT == structure.dispositionType
142
-					&& !"multipart/related".equals(parent.mime)) {
141
+			if (parent != null && DispositionType.ATTACHMENT == structure.dispositionType) {
143 142
 				return true;
144 143
 			}
145 144
 			for (Part p : structure.children) {
... ...
@@ -172,8 +171,7 @@ public class MessageBody {
172 171
 		}
173 172
 
174 173
 		private static List<Part> inlineAttachments(Part structure, Part parent, List<Part> attach) {
175
-			if (parent != null
176
-					&& (DispositionType.INLINE == structure.dispositionType || structure.contentId != null)) {
174
+			if (parent != null && DispositionType.INLINE == structure.dispositionType && structure.contentId != null) {
177 175
 				attach.add(structure);
178 176
 			}
179 177
 			for (Part p : structure.children) {
... ...
@@ -50,6 +50,7 @@ import org.apache.james.mime4j.dom.field.ContentTypeField;
50 50
 import org.apache.james.mime4j.dom.field.FieldName;
51 51
 import org.apache.james.mime4j.stream.Field;
52 52
 import org.jsoup.Jsoup;
53
+import org.jsoup.nodes.Document;
53 54
 import org.slf4j.Logger;
54 55
 import org.slf4j.LoggerFactory;
55 56
 
... ...
@@ -107,7 +108,7 @@ public class BodyStreamProcessor {
107 108
 	static {
108 109
 		// initialization in a separate static bloc enables tests to modify this final
109 110
 		// field using reflection
110
-		BODY_VERSION = 3;
111
+		BODY_VERSION = 4;
111 112
 	}
112 113
 
113 114
 	private static Set<String> buildWhiteList() {
... ...
@@ -186,6 +187,8 @@ public class BodyStreamProcessor {
186 187
 				logger.info("Body ({} byte(s)) processed in {}ms.", mb.size, time);
187 188
 			}
188 189
 
190
+			cleanUnreferencedInlineAttachments(mb, parsed);
191
+
189 192
 			MessageBodyData bodyData = new MessageBodyData(mb, bodyTxt.toString(), filenames, with,
190 193
 					mapHeaders(mb.headers));
191 194
 			logger.debug("Processed {}", bodyData);
... ...
@@ -196,9 +199,61 @@ public class BodyStreamProcessor {
196 199
 
197 200
 	}
198 201
 
202
+	private static void cleanUnreferencedInlineAttachments(MessageBody mb, Message parsed) {
203
+		List<Part> withContentIds = partsWithContentIds(mb.structure, null, new LinkedList<>());
204
+		if (!withContentIds.isEmpty()) {
205
+			Optional<AddressableEntity> ae = htmlBody(parsed);
206
+			String body = ae.map(BodyStreamProcessor::getBodyContent).orElse("");
207
+			Set<String> refCids = findCIDs(Jsoup.parse(body));
208
+			for (Part p : withContentIds) {
209
+				String cid = CharMatcher.anyOf("<>").trimFrom(p.contentId);
210
+				if (!refCids.contains(cid)) {
211
+					p.dispositionType = DispositionType.ATTACHMENT;
212
+					p.contentId = null;
213
+				} else {
214
+					p.dispositionType = DispositionType.INLINE;
215
+				}
216
+			}
217
+		}
218
+	}
219
+
220
+	private static List<Part> partsWithContentIds(Part structure, Part parent, List<Part> attach) {
221
+		if (parent != null && structure.contentId != null) {
222
+			attach.add(structure);
223
+		}
224
+		for (Part p : structure.children) {
225
+			partsWithContentIds(p, structure, attach);
226
+		}
227
+		return attach;
228
+	}
229
+
230
+	private static Optional<AddressableEntity> htmlBody(Message message) {
231
+		Body body = message.getBody();
232
+
233
+		if (body instanceof Multipart) {
234
+			Multipart mp = (Multipart) body;
235
+			List<AddressableEntity> parts = Mime4JHelper.expandParts(mp.getBodyParts());
236
+
237
+			for (AddressableEntity ae : parts) {
238
+				String mime = ae.getMimeType();
239
+				if (Mime4JHelper.TEXT_HTML.equals(mime) && !Mime4JHelper.isAttachment(ae)) {
240
+					return Optional.of(ae);
241
+				}
242
+			}
243
+		}
244
+		return Optional.empty();
245
+	}
246
+
247
+	private static Set<String> findCIDs(Document doc) {
248
+		return doc.select("[src^=cid:]").stream().map(e -> {
249
+			String src = e.attr("src");
250
+			return src.substring(4);
251
+		}).collect(Collectors.toSet());
252
+	}
253
+
199 254
 	private static List<String> processReferences(Multimap<String, String> mmapHeaders) {
200 255
 		for (String headerName : mmapHeaders.keySet()) {
201
-			if (headerName.toLowerCase().equals("references")) {
256
+			if ("references".equalsIgnoreCase(headerName)) {
202 257
 				return Arrays.asList(mmapHeaders.get(headerName).iterator().next().split(" "));
203 258
 			}
204 259
 		}
... ...
@@ -19,11 +19,14 @@ package net.bluemind.backend.mail.replica.persistence;
19 19
 
20 20
 import java.sql.SQLException;
21 21
 import java.sql.Timestamp;
22
+import java.util.ArrayList;
22 23
 import java.util.Arrays;
23 24
 import java.util.Collections;
24 25
 import java.util.Date;
25 26
 import java.util.HashSet;
26 27
 import java.util.List;
28
+import java.util.Map;
29
+import java.util.Optional;
27 30
 import java.util.Set;
28 31
 import java.util.stream.Collectors;
29 32
 
... ...
@@ -159,12 +162,16 @@ public class MailboxRecordStore extends AbstractItemValueStore<MailboxRecord> {
159 162
 				"SELECT item_id, imap_uid, encode(message_body_guid, 'hex') FROM t_mailbox_record "
160 163
 				+ "INNER JOIN t_container_item ci ON ci.id=item_id " + "WHERE ci.container_id=? AND item_id IN ("
161 164
 				+ inString + ")";
162
-		return select(query, rs -> new ImapBinding(), (rs, index, value) -> {
165
+		List<ImapBinding> notSorted = select(query, rs -> new ImapBinding(), (rs, index, value) -> {
163 166
 			value.itemId = rs.getInt(index++);
164 167
 			value.imapUid = rs.getInt(index++);
165 168
 			value.bodyGuid = rs.getString(index++);
166 169
 			return index;
167 170
 		}, new Object[] { container.id });
171
+		List<ImapBinding> ret = new ArrayList<>(notSorted.size());
172
+		Map<Long, ImapBinding> sortHelper = notSorted.stream().collect(Collectors.toMap(ib -> ib.itemId, ib -> ib));
173
+		itemIds.forEach(k -> Optional.ofNullable(sortHelper.get(k)).ifPresent(v -> ret.add(v)));
174
+		return ret;
168 175
 	}
169 176
 
170 177
 	public List<Long> sortedIds(SortDescriptor sorted) throws SQLException {
... ...
@@ -235,8 +235,7 @@ public abstract class AbstractRollingReplicationTests {
235 235
 
236 236
 	@After
237 237
 	public void after() throws Exception {
238
-		System.out.println("Waiting for last events (remove this sleep ?)...");
239
-		Thread.sleep(1000);
238
+		System.err.println("test is over, time for after()");
240 239
 		cyrusReplication.stopReplication().get(5, TimeUnit.SECONDS);
241 240
 		JdbcTestHelper.getInstance().afterTest();
242 241
 	}
... ...
@@ -172,23 +172,29 @@ public class BodyStreamProcessorTests {
172 172
 	}
173 173
 
174 174
 	@Test
175
-	public void testDispositionTypeIsKept()
175
+	public void testDispositionTypeFixed()
176 176
 			throws InterruptedException, ExecutionException, TimeoutException, IOException {
177 177
 		Stream stream = openResource("data/with_inlines.eml");
178
+		// Apple-Mail inline attachments not displayed by other client
179
+
178 180
 		MessageBodyData result = BodyStreamProcessor.processBody(stream).get(2, TimeUnit.SECONDS);
179 181
 		assertNotNull(result);
180
-		// the second and third children of the multipart should have an inline
182
+
183
+		JsonObject asJs = new JsonObject(JsonUtils.asString(result.body.structure));
184
+		System.out.println("JS: " + asJs.encodePrettily());
185
+
186
+		// the second and third children of the multipart should have a fixed attachment
181 187
 		// disposition type
182 188
 		final DispositionType secondChildDispositionType = result.body.structure.children.get(1).dispositionType;
183
-		Assert.assertEquals(DispositionType.INLINE, secondChildDispositionType);
189
+		Assert.assertEquals(DispositionType.ATTACHMENT, secondChildDispositionType);
184 190
 		final DispositionType thirdChildDispositionType = result.body.structure.children.get(2).dispositionType;
185
-		Assert.assertEquals(DispositionType.INLINE, thirdChildDispositionType);
191
+		Assert.assertEquals(DispositionType.ATTACHMENT, thirdChildDispositionType);
186 192
 		// the first child should not have one
187 193
 		final DispositionType firstChildDispositionType = result.body.structure.children.get(0).dispositionType;
188 194
 		Assert.assertNull(firstChildDispositionType);
189
-		// should not have real attachments (it is based on disposition type)
190
-		Assert.assertFalse(result.body.structure.hasRealAttachments());
191
-		Assert.assertEquals(0, result.body.structure.nonInlineAttachments().size());
195
+		// should have real attachments (it is based on disposition type)
196
+		Assert.assertTrue(result.body.structure.hasRealAttachments());
197
+		Assert.assertEquals(2, result.body.structure.nonInlineAttachments().size());
192 198
 	}
193 199
 
194 200
 	/**
... ...
@@ -155,4 +155,18 @@ public class CyrusValidationServiceTests {
155 155
 		boolean result = cli.prevalidate("devenv.blue!super^vision.Sent", null);
156 156
 		assertTrue(result);
157 157
 	}
158
+
159
+	@Test
160
+	public void inboxIsNotValid() {
161
+		ICyrusValidation cli = getService(domainAdminSecurityContext);
162
+		boolean result = cli.prevalidate("devenv.blue!user.leslie.INBOX", backendIp + "__devenv_blue");
163
+		assertFalse(result);
164
+	}
165
+
166
+	@Test
167
+	public void inboxSubfolderIsValid() {
168
+		ICyrusValidation cli = getService(domainAdminSecurityContext);
169
+		boolean result = cli.prevalidate("devenv.blue!user.leslie.sub.INBOX", backendIp + "__devenv_blue");
170
+		assertTrue(result);
171
+	}
158 172
 }
... ...
@@ -147,6 +147,9 @@ public class InboxSubFolderReplicationTests extends AbstractRollingReplicationTe
147 147
 		assertEquals(inbox.get().uid, subFolder.get().value.parentUid);
148 148
 		assertEquals(subFolderName, subFolder.get().value.fullName);
149 149
 
150
+		long inboxCount = allBoxes.stream().filter(mailbox -> "INBOX".equals(mailbox.value.name)).count();
151
+		assertEquals(1, inboxCount);
152
+
150 153
 	}
151 154
 
152 155
 	@Test
... ...
@@ -33,6 +33,7 @@ import java.util.Collections;
33 33
 import java.util.LinkedList;
34 34
 import java.util.List;
35 35
 import java.util.Optional;
36
+import java.util.Set;
36 37
 import java.util.concurrent.CompletableFuture;
37 38
 import java.util.concurrent.CountDownLatch;
38 39
 import java.util.concurrent.ExecutionException;
... ...
@@ -43,7 +44,6 @@ import java.util.concurrent.atomic.AtomicReference;
43 44
 import java.util.function.Predicate;
44 45
 import java.util.stream.Collectors;
45 46
 
46
-import org.junit.After;
47 47
 import org.junit.Before;
48 48
 import org.junit.BeforeClass;
49 49
 import org.junit.Test;
... ...
@@ -141,6 +141,7 @@ public class ReplicationStackTests extends AbstractRollingReplicationTests {
141 141
 	}
142 142
 
143 143
 	@Before
144
+	@Override
144 145
 	public void before() throws Exception {
145 146
 		super.before();
146 147
 
... ...
@@ -165,21 +166,40 @@ public class ReplicationStackTests extends AbstractRollingReplicationTests {
165 166
 		long delay = System.currentTimeMillis();
166 167
 		Hierarchy hierarchy = null;
167 168
 		do {
168
-			Thread.sleep(400);
169
+			Thread.sleep(50);
169 170
 			hierarchy = rec.hierarchy(domainUid, userUid);
170 171
 			System.out.println("Hierarchy version is " + hierarchy.exactVersion);
171 172
 			if (System.currentTimeMillis() - delay > 30000) {
172
-				throw new TimeoutException("Hierarchy init took more than 20sec");
173
+				throw new TimeoutException("Hierarchy init took more than 30sec");
173 174
 			}
174 175
 		} while (hierarchy.exactVersion < 7);
175 176
 		System.out.println("Hierarchy is now at version " + hierarchy.exactVersion);
176
-		System.err.println("before is complete, starting test.");
177
-	}
178 177
 
179
-	@After
180
-	public void after() throws Exception {
181
-		System.err.println("Test is over, after starts...");
182
-		super.after();
178
+		IServiceProvider prov = provider();
179
+
180
+		IMailboxFolders userMboxesApi = prov.instance(IMailboxFolders.class, partition, mboxRoot);
181
+		List<ItemValue<MailboxFolder>> found = userMboxesApi.all();
182
+		assertNotNull(found);
183
+		ItemValue<MailboxFolder> inbox = null;
184
+		for (ItemValue<MailboxFolder> iv : found) {
185
+			if (iv.value.name.equals("INBOX")) {
186
+				inbox = iv;
187
+				break;
188
+			}
189
+		}
190
+		assertNotNull(inbox);
191
+		System.err.println("Wait for record in inbox...");
192
+		IMailboxItems recordsApi = prov.instance(IMailboxItems.class, inbox.uid);
193
+		ContainerChangeset<Long> allById = recordsApi.changesetById(0L);
194
+		delay = System.currentTimeMillis();
195
+		while (allById.created.isEmpty()) {
196
+			Thread.sleep(50);
197
+			if (System.currentTimeMillis() - delay > 30000) {
198
+				throw new TimeoutException("Wait for record took more than 30sec");
199
+			}
200
+			allById = recordsApi.changesetById(0L);
201
+		}
202
+		System.err.println("before() is complete, starting test...");
183 203
 	}
184 204
 
185 205
 	@Test
... ...
@@ -1796,31 +1816,35 @@ public class ReplicationStackTests extends AbstractRollingReplicationTests {
1796 1816
 		// append mail into root/src
1797 1817
 		ItemValue<MailboxFolder> src = foldersApi.byName(root + "/src");
1798 1818
 		long id = offlineId++;
1799
-		addDraft(src, id);
1819
+		ItemValue<MailboxItem> draf1 = addDraft(src, id);
1800 1820
 
1801 1821
 		long id2 = offlineId++;
1802
-		addDraft(src, id2);
1803
-
1804
-		IMailboxItems itemApi = provider().instance(IMailboxItems.class, src.uid);
1822
+		ItemValue<MailboxItem> draft2 = addDraft(src, id2);
1823
+		System.err.println("Draft1 " + draf1.internalId + ", Draft2 " + draft2.internalId);
1805 1824
 
1806 1825
 		// move into root/src/dst
1807 1826
 		long expectedId = offlineId++;
1808
-		long expectedId2 = offlineId++;
1827
+		long expectedId2 = offlineId;
1809 1828
 
1810 1829
 		ImportMailboxItemSet toMove = ImportMailboxItemSet.moveIn(src.internalId,
1811 1830
 				Arrays.asList(MailboxItemId.of(id), MailboxItemId.of(id2)),
1812 1831
 				Arrays.asList(MailboxItemId.of(expectedId), MailboxItemId.of(expectedId2)));
1813 1832
 
1814 1833
 		ItemValue<MailboxFolder> dst = foldersApi.byName(root + "/src/dst");
1834
+
1835
+		System.err.println("MoveIn " + toMove + "...");
1815 1836
 		ImportMailboxItemsStatus ret = foldersApi.importItems(dst.internalId, toMove);
1816 1837
 
1817 1838
 		assertEquals(ImportStatus.SUCCESS, ret.status);
1818 1839
 		assertEquals(2, ret.doneIds.size());
1840
+		Set<Long> done = ret.doneIds.stream().map(m -> m.destination).collect(Collectors.toSet());
1841
+		assertTrue(done.contains(expectedId));
1842
+		assertTrue(done.contains(expectedId2));
1819 1843
 		assertEquals(expectedId, ret.doneIds.get(0).destination);
1820 1844
 		assertEquals(expectedId2, ret.doneIds.get(1).destination);
1821 1845
 
1822 1846
 		// check
1823
-		itemApi = provider().instance(IMailboxItems.class, dst.uid);
1847
+		IMailboxItems itemApi = provider().instance(IMailboxItems.class, dst.uid);
1824 1848
 		ItemValue<MailboxItem> copy = itemApi.getCompleteById(expectedId);
1825 1849
 		assertNotNull(copy);
1826 1850
 
... ...
@@ -1942,26 +1966,26 @@ public class ReplicationStackTests extends AbstractRollingReplicationTests {
1942 1966
 	}
1943 1967
 
1944 1968
 	@Test
1945
-	public void testApiPreservesDispositionType() throws IMAPException, InterruptedException, IOException {
1969
+	public void testApiFixDispositionType() throws IMAPException, InterruptedException, IOException {
1946 1970
 		// create inbox
1947 1971
 		final IMailboxFolders mailboxFolders = provider().instance(IMailboxFolders.class, partition, mboxRoot);
1948 1972
 		final ItemValue<MailboxFolder> inbox = mailboxFolders.byName("INBOX");
1949 1973
 		assertNotNull(inbox);
1950 1974
 
1951
-		// create draft containing inline parts
1975
+		// create APPLE-MAIL draft containing inline parts not displayed by other client
1952 1976
 		final ItemValue<MailboxItem> reloaded = addDraft(inbox);
1953 1977
 
1954
-		// check disposition type is kept ( 0:text without disposition, 1:image
1955
-		// inline,
1956
-		// 2:image inline)
1978
+		// check disposition types are fixed ( 0:text without disposition, 1:image
1979
+		// attachment,
1980
+		// 2:image attachment)
1957 1981
 		final List<Part> subParts = reloaded.value.body.structure.children;
1958
-		assertEquals(DispositionType.INLINE, subParts.get(1).dispositionType);
1959
-		assertEquals(DispositionType.INLINE, subParts.get(2).dispositionType);
1982
+		assertEquals(DispositionType.ATTACHMENT, subParts.get(1).dispositionType);
1983
+		assertEquals(DispositionType.ATTACHMENT, subParts.get(2).dispositionType);
1960 1984
 		assertNull(subParts.get(0).dispositionType);
1961 1985
 
1962
-		// should not have real attachments (it is based on disposition type)
1963
-		assertFalse(reloaded.value.body.structure.hasRealAttachments());
1964
-		assertEquals(0, reloaded.value.body.structure.nonInlineAttachments().size());
1986
+		// should have real attachments (it is based on disposition type)
1987
+		assertTrue(reloaded.value.body.structure.hasRealAttachments());
1988
+		assertEquals(2, reloaded.value.body.structure.nonInlineAttachments().size());
1965 1989
 	}
1966 1990
 
1967 1991
 	/**
... ...
@@ -21,7 +21,6 @@ import net.bluemind.network.topology.Topology;
21 21
 
22 22
 public class CyrusValidationService implements ICyrusValidation {
23 23
 	private static final Logger logger = LoggerFactory.getLogger(CyrusValidationService.class);
24
-
25 24
 	private static final String DEFAULT_PARTITION = "default";
26 25
 
27 26
 	private final BmContext ctx;
... ...
@@ -33,6 +32,7 @@ public class CyrusValidationService implements ICyrusValidation {
33 32
 	@Override
34 33
 	public boolean prevalidate(String mailbox, String partition) {
35 34
 		// bm-master__devenv_blue/devenv.blue!user.leslie => accept
35
+		// bm-master__devenv_blue/devenv.blue!user.leslie.INBOX => reject
36 36
 		// (null)/devenv.blue!user.leslie.Sent => accept
37 37
 		// (null)/devenv.blue!user.titi => reject
38 38
 
... ...
@@ -46,6 +46,10 @@ public class CyrusValidationService implements ICyrusValidation {
46 46
 			return false;
47 47
 		}
48 48
 
49
+		if (!box.mailboxRoot && "INBOX".equals(box.folderName)) {
50
+			return false;
51
+		}
52
+
49 53
 		if (!box.mailboxRoot) {
50 54
 			// null partition is fine for non-root folders
51 55
 			return true;
... ...
@@ -326,7 +326,7 @@ public class ImapReplicatedMailboxesService extends BaseReplicatedMailboxesServi
326 326
 
327 327
 		int len = mailboxItems.ids.size();
328 328
 		if (expectedIds == null || expectedIds.isEmpty()) {
329
-			expectedIds = new ArrayList<MailboxItemId>(len);
329
+			expectedIds = new ArrayList<>(len);
330 330
 			IOfflineMgmt idAllocator = context.provider().instance(IOfflineMgmt.class, container.domainUid,
331 331
 					container.owner);
332 332
 			IdRange idRange = idAllocator.allocateOfflineIds(len);
... ...
@@ -339,8 +339,6 @@ public class ImapReplicatedMailboxesService extends BaseReplicatedMailboxesServi
339 339
 			throw new ServerFault("expectedIds size does not match with itemIds size", ErrorCode.INVALID_PARAMETER);
340 340
 		}
341 341
 
342
-		Iterator<MailboxItemId> expectedIdsIterator = expectedIds.iterator();
343
-
344 342
 		ItemValue<MailboxFolder> destinationFolder = getCompleteById(id);
345 343
 		if (destinationFolder == null) {
346 344
 			throw new ServerFault("Cannot find destination mailboxfolder");
... ...
@@ -359,6 +357,8 @@ public class ImapReplicatedMailboxesService extends BaseReplicatedMailboxesServi
359 357
 
360 358
 		logger.info("[{}] Preparing to import {} item(s) from {} into {}", imapContext.latd, mailboxItems.ids.size(),
361 359
 				sourceFolder.value.fullName, destinationFolder.value.fullName);
360
+		Iterator<MailboxItemId> expectedIdsIterator = expectedIds.iterator();
361
+
362 362
 		Lists.partition(mailboxItems.ids, 200).forEach(ids -> {
363 363
 
364 364
 			List<Long> idSlice = ids.stream().map(k -> k.id).collect(Collectors.toList());
... ...
@@ -75,9 +75,7 @@ public class VXStoreClient {
75 75
 	public static VXStoreClient create(String host, int port, String login, String password) {
76 76
 		CompletableFuture<VXStoreClient> cli = new CompletableFuture<>();
77 77
 		Vertx vx = VertxPlatform.getVertx();
78
-		vx.setTimer(1, tid -> {
79
-			cli.complete(new VXStoreClient(vx, host, port, login, password));
80
-		});
78
+		vx.setTimer(1, tid -> cli.complete(new VXStoreClient(vx, host, port, login, password)));
81 79
 		return cli.join();
82 80
 	}
83 81
 
... ...
@@ -85,27 +83,29 @@ public class VXStoreClient {
85 83
 		this.vertx = vertx;
86 84
 		this.login = login;
87 85
 		this.password = password;
88
-		this.connectFuture = new CompletableFuture<NetSocket>();
89
-		this.closeFuture = new CompletableFuture<Void>();
90
-		this.setupFuture = new CompletableFuture<ConnectionState>();
86
+		this.connectFuture = new CompletableFuture<>();
87
+		this.closeFuture = new CompletableFuture<>();
88
+		this.setupFuture = new CompletableFuture<>();
91 89
 
92
-		logger.info("Before netClient...");
93 90
 		client = vertx.createNetClient();
94
-		logger.info("NetClient created {}", client);
91
+		logger.debug("NetClient created {}", client);
95 92
 		client.setReuseAddress(true).setTCPNoDelay(true).setTCPKeepAlive(true).setUsePooledBuffers(true);
96 93
 		recordParser = new ImapRecordParser();
97 94
 
98 95
 		ImapProtocolListener<ConnectionState> bannerListener = new ImapProtocolListener<ConnectionState>(setupFuture) {
99 96
 
97
+			@Override
100 98
 			public void onStatusResponse(ByteBuf banner) {
101
-				logger.info("Got banner {}", banner.toString(StandardCharsets.US_ASCII));
99
+				if (logger.isDebugEnabled()) {
100
+					logger.debug("Got banner {}", banner.toString(StandardCharsets.US_ASCII));
101
+				}
102 102
 				future.complete(new ConnectionState(vertx.currentContext(), connectFuture.join()));
103 103
 			}
104 104
 
105 105
 		};
106 106
 		recordParser.listener(bannerListener);
107 107
 
108
-		logger.info("Connecting...");
108
+		logger.info("Connecting to {}:{}...", host, port);
109 109
 		client.connect(port, host, ar -> {
110 110
 			if (ar.succeeded()) {
111 111
 				NetSocket sock = ar.result();
... ...
@@ -128,12 +128,10 @@ public class VXStoreClient {
128 128
 	}
129 129
 
130 130
 	/**
131
-	 * @param cmdCons
132
-	 *                     the consumers receives a {@link StringBuilder} with the
133
-	 *                     IMAP tag + space char already added
134
-	 * @param listener
135
-	 *                     the listener receives the spurious responses (eg. * FETCH
136
-	 *                     42...)
131
+	 * @param cmdCons  the consumers receives a {@link StringBuilder} with the IMAP
132
+	 *                 tag + space char already added
133
+	 * @param listener the listener receives the spurious responses (eg. * FETCH
134
+	 *                 42...)
137 135
 	 * @return
138 136
 	 */
139 137
 	private <T> CompletableFuture<ImapResponseStatus<T>> run(Consumer<StringBuilder> cmdCons,
... ...
@@ -154,18 +152,16 @@ public class VXStoreClient {
154 152
 			ImapProtocolListener<T> listener, ReadStream<W> literal) {
155 153
 		String tag = "V" + (++tags);
156 154
 
157
-		Runnable onGoAhead = () -> {
158
-			setupFuture.thenAccept(conState -> {
159
-				conState.context.runOnContext(v -> {
160
-					literal.endHandler(end -> {
161
-						logger.info("Finished streaming literal.");
162
-						conState.socket.write("\r\n");
163
-					});
164
-					Pump pump = Pump.createPump(literal, conState.socket);
165
-					pump.start();
155
+		Runnable onGoAhead = () -> setupFuture.thenAccept(conState -> {
156
+			conState.context.runOnContext(v -> {
157
+				literal.endHandler(end -> {
158
+					logger.debug("Finished streaming literal {}.", literal);
159
+					conState.socket.write("\r\n");
166 160
 				});
161
+				Pump pump = Pump.createPump(literal, conState.socket);
162
+				pump.start();
167 163
 			});
168
-		};
164
+		});
169 165
 
170 166
 		TagOrGoAheadListener<T> tl = new TagOrGoAheadListener<>(tag, listener, onGoAhead);
171 167
 		setupFuture.thenAccept(conState -> {
... ...
@@ -195,10 +191,6 @@ public class VXStoreClient {
195 191
 	private static final CompletableFuture<ImapResponseStatus<SelectResponse>> SELECTED = CompletableFuture
196 192
 			.completedFuture(new ImapResponseStatus<SelectResponse>(Status.Ok, new SelectResponse()));
197 193
 
198
-	public void unselect() {
199
-		selected = null;
200
-	}
201
-
202 194
 	public CompletableFuture<ImapResponseStatus<SelectResponse>> select(String mailbox) {
203 195
 		if (selected != null && selected.equals(mailbox)) {
204 196
 			return SELECTED;
... ...
@@ -41,15 +41,15 @@ public class UserValidatorTest {
41 41
 		String l5 = "0-.-__test1";
42 42
 
43 43
 		user.login = l1;
44
-		validator.validate(user);
44
+		validator.validateLogin(user);
45 45
 		user.login = l2;
46
-		validator.validate(user);
46
+		validator.validateLogin(user);
47 47
 		user.login = l3;
48
-		validator.validate(user);
48
+		validator.validateLogin(user);
49 49
 		user.login = l4;
50
-		validator.validate(user);
50
+		validator.validateLogin(user);
51 51
 		user.login = l5;
52
-		validator.validate(user);
52
+		validator.validateLogin(user);
53 53
 	}
54 54
 
55 55
 	@Test
... ...
@@ -76,7 +76,7 @@ public class UserValidatorTest {
76 76
 		try {
77 77
 			User user = new User();
78 78
 			user.login = login;
79
-			validator.validate(user);
79
+			validator.validateLogin(user);
80 80
 			fail();
81 81
 		} catch (Exception e) {
82 82
 		}
... ...
@@ -18,6 +18,8 @@
18 18
  */
19