Browse code

FEATBL-684 Fix: get rid of duplicates by fullName, which will remove double inboxes

Thomas Cataldo authored on 09/01/2019 13:38:37
Showing 5 changed files
... ...
@@ -33,6 +33,11 @@ public class MailReplicaContainerTypes {
33 33
 	public static final String REPAIR_SUBTREE_OP = "replication.subtree";
34 34
 
35 35
 	/**
36
+	 * Repair operation id for repairing the multiple inbox syndrom
37
+	 */
38
+	public static final String REPAIR_MINBOX_OP = "replication.minbox";
39
+
40
+	/**
36 41
 	 * Given the uid of a {@link MailboxFolder} item, computes the uid for
37 42
 	 * {@link MailboxItem} container.
38 43
 	 * 
... ...
@@ -18,7 +18,7 @@
18 18
 package net.bluemind.backend.mail.replica.persistence;
19 19
 
20 20
 import java.sql.SQLException;
21
-import java.util.Arrays;
21
+import java.util.Collections;
22 22
 
23 23
 import javax.sql.DataSource;
24 24
 
... ...
@@ -29,6 +29,7 @@ import net.bluemind.backend.mail.replica.api.MailboxReplica;
29 29
 import net.bluemind.core.container.model.Container;
30 30
 import net.bluemind.core.container.model.Item;
31 31
 import net.bluemind.core.container.persistance.AbstractItemValueStore;
32
+import net.bluemind.core.container.persistance.StringCreator;
32 33
 
33 34
 public class MailboxReplicaStore extends AbstractItemValueStore<MailboxReplica> {
34 35
 
... ...
@@ -81,7 +82,7 @@ public class MailboxReplicaStore extends AbstractItemValueStore<MailboxReplica>
81 82
 	public String byName(String name) throws SQLException {
82 83
 		String query = "SELECT item.uid FROM t_mailbox_replica mr INNER JOIN t_container_item item ON mr.item_id = item.id"
83 84
 				+ " WHERE item.container_id = ? and mr.name = ?";
84
-		String ret = unique(query, rs -> rs.getString(1), Arrays.asList(), new Object[] { container.id, name });
85
+		String ret = unique(query, StringCreator.FIRST, Collections.emptyList(), new Object[] { container.id, name });
85 86
 		if (logger.isDebugEnabled()) {
86 87
 			logger.debug("byName({}) in container {} => {}", name, container.id, ret);
87 88
 		}
... ...
@@ -116,5 +116,11 @@
116 116
             factory="net.bluemind.backend.mail.replica.service.internal.repair.ReplicationParentUidRepair$Factory">
117 117
       </repairSupport>
118 118
    </extension>
119
+   <extension
120
+         point="net.bluemind.directory.repairSupport">
121
+      <repairSupport
122
+            factory="net.bluemind.backend.mail.replica.service.internal.repair.MultiInboxRepair$Factory">
123
+      </repairSupport>
124
+   </extension>
119 125
 
120 126
 </plugin>
121 127
new file mode 100644
... ...
@@ -0,0 +1,147 @@
1
+/* BEGIN LICENSE
2
+  * Copyright © Blue Mind SAS, 2012-2018
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
+  * This program is distributed in the hope that it will be useful,
12
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
+  *
15
+  * See LICENSE.txt
16
+  * END LICENSE
17
+  */
18
+package net.bluemind.backend.mail.replica.service.internal.repair;
19
+
20
+import java.util.Collection;
21
+import java.util.Collections;
22
+import java.util.LinkedList;
23
+import java.util.List;
24
+import java.util.Set;
25
+import java.util.stream.Collectors;
26
+
27
+import org.slf4j.Logger;
28
+import org.slf4j.LoggerFactory;
29
+
30
+import com.google.common.collect.ArrayListMultimap;
31
+import com.google.common.collect.ImmutableSet;
32
+import com.google.common.collect.Multimap;
33
+
34
+import net.bluemind.backend.mail.replica.api.IDbByContainerReplicatedMailboxes;
35
+import net.bluemind.backend.mail.replica.api.MailReplicaContainerTypes;
36
+import net.bluemind.backend.mail.replica.api.MailboxReplica;
37
+import net.bluemind.core.api.report.DiagnosticReport;
38
+import net.bluemind.core.container.model.ItemValue;
39
+import net.bluemind.core.rest.BmContext;
40
+import net.bluemind.core.task.service.IServerTaskMonitor;
41
+import net.bluemind.directory.api.BaseDirEntry.Kind;
42
+import net.bluemind.directory.api.DirEntry;
43
+import net.bluemind.directory.api.MaintenanceOperation;
44
+import net.bluemind.directory.service.IDirEntryRepairSupport;
45
+import net.bluemind.mailbox.api.IMailboxes;
46
+import net.bluemind.mailbox.api.Mailbox;
47
+
48
+public class MultiInboxRepair implements IDirEntryRepairSupport {
49
+
50
+	public static class Factory implements IDirEntryRepairSupport.Factory {
51
+		@Override
52
+		public IDirEntryRepairSupport create(BmContext context) {
53
+			return new MultiInboxRepair(context);
54
+		}
55
+	}
56
+
57
+	private static final Logger logger = LoggerFactory.getLogger(MultiInboxRepair.class);
58
+	public static final MaintenanceOperation minboxOp = MaintenanceOperation
59
+			.create(MailReplicaContainerTypes.REPAIR_MINBOX_OP, "Multiple INBOX in subtree");
60
+
61
+	private static class FullNameDuplicatesMaintenance extends InternalMaintenanceOperation {
62
+
63
+		private final BmContext context;
64
+
65
+		public FullNameDuplicatesMaintenance(BmContext ctx) {
66
+			super(minboxOp.identifier, null, MailReplicaContainerTypes.REPAIR_SUBTREE_OP, 1);
67
+			this.context = ctx;
68
+		}
69
+
70
+		@Override
71
+		public void check(String domainUid, DirEntry entry, DiagnosticReport report, IServerTaskMonitor monitor) {
72
+			run(false, domainUid, entry, report, monitor);
73
+		}
74
+
75
+		@Override
76
+		public void repair(String domainUid, DirEntry entry, DiagnosticReport report, IServerTaskMonitor monitor) {
77
+			run(true, domainUid, entry, report, monitor);
78
+		}
79
+
80
+		public void run(boolean repair, String domainUid, DirEntry entry, DiagnosticReport report,
81
+				IServerTaskMonitor monitor) {
82
+			logger.info("Repair subtree {} {}", domainUid, entry);
83
+
84
+			IMailboxes mboxApi = context.provider().instance(IMailboxes.class, domainUid);
85
+			ItemValue<Mailbox> mbox = mboxApi.getComplete(entry.entryUid);
86
+			if (mbox == null) {
87
+				logger.warn("{} does not have a mailbox, nothing to repair", entry);
88
+				return;
89
+			}
90
+			if (mbox.value.dataLocation == null) {
91
+				logger.error("{} lacks a dataLocation, can't repair", mbox);
92
+				return;
93
+			}
94
+			IDbByContainerReplicatedMailboxes foldersApi = context.provider().instance(
95
+					IDbByContainerReplicatedMailboxes.class, MailReplicaContainerTypes.subtreeUid(domainUid, mbox));
96
+			List<ItemValue<MailboxReplica>> fullList = foldersApi.allReplicas();
97
+			monitor.begin(1, "Inspecting subtree for mailbox " + mbox.value.name + "@" + domainUid);
98
+			Multimap<String, ItemValue<MailboxReplica>> byFullName = ArrayListMultimap.create();
99
+			fullList.forEach(iv -> byFullName.put(iv.value.fullName, iv));
100
+			List<ItemValue<MailboxReplica>> toPurge = new LinkedList<>();
101
+			for (String fn : byFullName.keySet()) {
102
+				Collection<ItemValue<MailboxReplica>> shouldBeOne = byFullName.get(fn);
103
+				int len = shouldBeOne.size();
104
+				if (len > 1) {
105
+					List<ItemValue<MailboxReplica>> bestFirst = shouldBeOne.stream()
106
+							.sorted((i1, i2) -> Long.compare(i2.value.highestModSeq, i1.value.highestModSeq))
107
+							.collect(Collectors.toList());
108
+					toPurge.addAll(bestFirst.subList(1, bestFirst.size()));
109
+				}
110
+				monitor.progress(len, "process " + fn + " with " + (len - 1) + " duplicates");
111
+			}
112
+			for (ItemValue<MailboxReplica> itemValue : toPurge) {
113
+				monitor.log("Purge " + itemValue);
114
+				if (repair) {
115
+					foldersApi.delete(itemValue.uid);
116
+				}
117
+			}
118
+			report.ok(minboxOp.identifier, "Subtree duplicates fixed.");
119
+		}
120
+
121
+	}
122
+
123
+	private final BmContext context;
124
+
125
+	public MultiInboxRepair(BmContext context) {
126
+		this.context = context;
127
+	}
128
+
129
+	@Override
130
+	public Set<MaintenanceOperation> availableOperations(Kind kind) {
131
+		if (kind == Kind.USER || kind == Kind.MAILSHARE || kind == Kind.GROUP || kind == Kind.RESOURCE) {
132
+			return ImmutableSet.of(minboxOp);
133
+		} else {
134
+			return Collections.emptySet();
135
+		}
136
+	}
137
+
138
+	@Override
139
+	public Set<InternalMaintenanceOperation> ops(Kind kind) {
140
+		if (kind == Kind.USER || kind == Kind.MAILSHARE || kind == Kind.GROUP || kind == Kind.RESOURCE) {
141
+			return ImmutableSet.of(new FullNameDuplicatesMaintenance(context));
142
+		} else {
143
+			return Collections.emptySet();
144
+		}
145
+
146
+	}
147
+}
... ...
@@ -75,7 +75,8 @@ public class ProfileInfosCommand implements ICmdLet, Runnable {
75 75
 					.sorted((f1, f2) -> f1.value.fullName.compareTo(f2.value.fullName)).collect(Collectors.toList());
76 76
 			ctx.info("\tThe subtree has " + mailboxFolders.size() + " folder(s)");
77 77
 			for (ItemValue<MailboxReplica> mf : mailboxFolders) {
78
-				ctx.info("\t\t" + mf.uid + " " + mf.value.fullName + ",  parent: " + mf.value.parentUid);
78
+				ctx.info("\t\tid: " + mf.internalId + ", uid: " + mf.uid + ", fn:" + mf.value.fullName + ",  parent: "
79
+						+ mf.value.parentUid);
79 80
 			}
80 81
 			return node;
81 82
 		}