Browse code

FEATBL-684 Feat: init cli command to inspect a user's mapi profile

Thomas Cataldo authored on 07/01/2019 11:42:42
Showing 14 changed files
... ...
@@ -7,7 +7,7 @@
7 7
    </configIni>
8 8
 
9 9
    <launcherArgs>
10
-      <programArgs>-consoleLog help
10
+      <programArgs>-consoleLog mapi infos tom@ex2016.vmw
11 11
       </programArgs>
12 12
       <vmArgs>-Duser.timezone=GMT -Djava.awt.headless=true -Dnet.bluemind.property.product=bm-cli
13 13
       </vmArgs>
... ...
@@ -51,6 +51,8 @@
51 51
       <plugin id="joda-time"/>
52 52
       <plugin id="jul.to.slf4j"/>
53 53
       <plugin id="net.bluemind.addressbook.api"/>
54
+      <plugin id="net.bluemind.backend.mail.api"/>
55
+      <plugin id="net.bluemind.backend.mail.replica.api"/>
54 56
       <plugin id="net.bluemind.calendar.api"/>
55 57
       <plugin id="net.bluemind.cli.adm"/>
56 58
       <plugin id="net.bluemind.cli.calendar"/>
... ...
@@ -60,6 +62,7 @@
60 62
       <plugin id="net.bluemind.cli.hollow"/>
61 63
       <plugin id="net.bluemind.cli.index"/>
62 64
       <plugin id="net.bluemind.cli.launcher"/>
65
+      <plugin id="net.bluemind.cli.mapi"/>
63 66
       <plugin id="net.bluemind.cli.metrics"/>
64 67
       <plugin id="net.bluemind.cli.node"/>
65 68
       <plugin id="net.bluemind.cli.user"/>
... ...
@@ -82,6 +85,7 @@
82 85
       <plugin id="net.bluemind.directory.hollow.datamodel.consumer"/>
83 86
       <plugin id="net.bluemind.domain.api"/>
84 87
       <plugin id="net.bluemind.eclipse.common"/>
88
+      <plugin id="net.bluemind.exchange.mapi.api"/>
85 89
       <plugin id="net.bluemind.group.api"/>
86 90
       <plugin id="net.bluemind.hornetq.client"/>
87 91
       <plugin id="net.bluemind.hsm.api"/>
... ...
@@ -56,4 +56,22 @@ public class CliContext {
56 56
 		return adminServices;
57 57
 	}
58 58
 
59
+	/**
60
+	 * Prints a red message (and avoids sonar error)
61
+	 * 
62
+	 * @param msg
63
+	 */
64
+	public void error(String msg) {
65
+		System.out.println(ansi().fgBrightRed().a(msg).reset()); // NOSONAR
66
+	}
67
+
68
+	/**
69
+	 * Use this to avoid sonar errors about logger usage
70
+	 * 
71
+	 * @param msg
72
+	 */
73
+	public void info(String msg) {
74
+		System.out.println(msg); // NOSONAR
75
+	}
76
+
59 77
 }
60 78
new file mode 100644
... ...
@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<classpath>
3
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
4
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
5
+	<classpathentry kind="src" path="src"/>
6
+	<classpathentry kind="output" path="bin"/>
7
+</classpath>
0 8
new file mode 100644
... ...
@@ -0,0 +1,28 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<projectDescription>
3
+	<name>net.bluemind.cli.mapi</name>
4
+	<comment></comment>
5
+	<projects>
6
+	</projects>
7
+	<buildSpec>
8
+		<buildCommand>
9
+			<name>org.eclipse.jdt.core.javabuilder</name>
10
+			<arguments>
11
+			</arguments>
12
+		</buildCommand>
13
+		<buildCommand>
14
+			<name>org.eclipse.pde.ManifestBuilder</name>
15
+			<arguments>
16
+			</arguments>
17
+		</buildCommand>
18
+		<buildCommand>
19
+			<name>org.eclipse.pde.SchemaBuilder</name>
20
+			<arguments>
21
+			</arguments>
22
+		</buildCommand>
23
+	</buildSpec>
24
+	<natures>
25
+		<nature>org.eclipse.pde.PluginNature</nature>
26
+		<nature>org.eclipse.jdt.core.javanature</nature>
27
+	</natures>
28
+</projectDescription>
0 29
new file mode 100644
... ...
@@ -0,0 +1,20 @@
1
+Manifest-Version: 1.0
2
+Bundle-ManifestVersion: 2
3
+Bundle-Name: net.bluemind.cli.mapi
4
+Bundle-SymbolicName: net.bluemind.cli.mapi;singleton:=true
5
+Bundle-Version: 4.1.0.qualifier
6
+Bundle-Activator: net.bluemind.cli.mapi.MapiCliActivator
7
+Bundle-Vendor: bluemind.net
8
+Require-Bundle: org.eclipse.core.runtime,
9
+ net.bluemind.cli.cmd.api,
10
+ net.bluemind.cli.utils,
11
+ net.bluemind.mailbox.api,
12
+ net.bluemind.exchange.mapi.api,
13
+ com.google.guava,
14
+ net.bluemind.addressbook.api,
15
+ net.bluemind.calendar.api,
16
+ net.bluemind.backend.mail.replica.api,
17
+ net.bluemind.todolist.api
18
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
19
+Automatic-Module-Name: net.bluemind.cli.mapi
20
+Bundle-ActivationPolicy: lazy
0 21
new file mode 100644
... ...
@@ -0,0 +1,5 @@
1
+source.. = src/
2
+output.. = bin/
3
+bin.includes = META-INF/,\
4
+               .,\
5
+               plugin.xml
0 6
new file mode 100644
... ...
@@ -0,0 +1,13 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<?eclipse version="3.4"?>
3
+<plugin>
4
+
5
+   <extension
6
+         point="net.bluemind.cli.cmd.api.cmdlet">
7
+      <registration
8
+            impl="net.bluemind.cli.mapi.ProfileInfosCommand$Reg"
9
+            priority="500">
10
+      </registration>
11
+   </extension>
12
+
13
+</plugin>
0 14
new file mode 100644
... ...
@@ -0,0 +1,10 @@
1
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2
+  <modelVersion>4.0.0</modelVersion>
3
+  <parent>
4
+    <groupId>net.bluemind</groupId>
5
+    <version>4.1.0-SNAPSHOT</version>
6
+    <artifactId>net.bluemind.exchange.mapi.plugins</artifactId>
7
+  </parent>
8
+  <artifactId>net.bluemind.cli.mapi</artifactId>
9
+  <packaging>eclipse-plugin</packaging>
10
+</project>
0 11
new file mode 100644
... ...
@@ -0,0 +1,30 @@
1
+package net.bluemind.cli.mapi;
2
+
3
+import org.osgi.framework.BundleActivator;
4
+import org.osgi.framework.BundleContext;
5
+
6
+public class MapiCliActivator implements BundleActivator {
7
+
8
+	private static BundleContext context;
9
+
10
+	static BundleContext getContext() {
11
+		return context;
12
+	}
13
+
14
+	/*
15
+	 * (non-Javadoc)
16
+	 * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
17
+	 */
18
+	public void start(BundleContext bundleContext) throws Exception {
19
+		MapiCliActivator.context = bundleContext;
20
+	}
21
+
22
+	/*
23
+	 * (non-Javadoc)
24
+	 * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
25
+	 */
26
+	public void stop(BundleContext bundleContext) throws Exception {
27
+		MapiCliActivator.context = null;
28
+	}
29
+
30
+}
0 31
new file mode 100644
... ...
@@ -0,0 +1,28 @@
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
+  * 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.cli.mapi;
19
+
20
+import net.bluemind.cli.cmd.api.CliContext;
21
+import net.bluemind.core.container.api.ContainerHierarchyNode;
22
+import net.bluemind.core.container.model.ItemValue;
23
+
24
+public interface NodeProcessor {
25
+
26
+	ItemValue<ContainerHierarchyNode> visit(CliContext ctx, ItemValue<ContainerHierarchyNode> node);
27
+
28
+}
0 29
new file mode 100644
... ...
@@ -0,0 +1,194 @@
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
+  * 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.cli.mapi;
19
+
20
+import java.util.List;
21
+import java.util.Map;
22
+import java.util.Optional;
23
+import java.util.Set;
24
+import java.util.stream.Collectors;
25
+
26
+import org.vertx.java.core.json.JsonObject;
27
+
28
+import com.google.common.collect.ImmutableMap;
29
+import com.google.common.collect.Sets;
30
+
31
+import io.airlift.airline.Arguments;
32
+import io.airlift.airline.Command;
33
+import net.bluemind.addressbook.api.AddressBookContainerType;
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.calendar.api.CalendarContainerType;
38
+import net.bluemind.cli.cmd.api.CliContext;
39
+import net.bluemind.cli.cmd.api.ICmdLet;
40
+import net.bluemind.cli.cmd.api.ICmdLetRegistration;
41
+import net.bluemind.cli.utils.CliUtils;
42
+import net.bluemind.core.api.Regex;
43
+import net.bluemind.core.api.fault.ServerFault;
44
+import net.bluemind.core.container.api.ContainerHierarchyNode;
45
+import net.bluemind.core.container.api.Count;
46
+import net.bluemind.core.container.api.IContainersFlatHierarchy;
47
+import net.bluemind.core.container.model.ItemFlagFilter;
48
+import net.bluemind.core.container.model.ItemValue;
49
+import net.bluemind.exchange.mapi.api.IMapiFolder;
50
+import net.bluemind.exchange.mapi.api.IMapiFolderAssociatedInformation;
51
+import net.bluemind.exchange.mapi.api.IMapiMailbox;
52
+import net.bluemind.exchange.mapi.api.MapiFAI;
53
+import net.bluemind.exchange.mapi.api.MapiFAIContainer;
54
+import net.bluemind.exchange.mapi.api.MapiFolderContainer;
55
+import net.bluemind.exchange.mapi.api.MapiReplica;
56
+import net.bluemind.mailbox.api.IMailboxes;
57
+import net.bluemind.mailbox.api.Mailbox;
58
+import net.bluemind.todolist.api.TodoListContainerType;
59
+
60
+@Command(name = "infos", description = "Show profile infos")
61
+public class ProfileInfosCommand implements ICmdLet, Runnable {
62
+
63
+	Set<String> mapiRelatedTypes = Sets.newHashSet(AddressBookContainerType.TYPE, CalendarContainerType.TYPE,
64
+			MapiFolderContainer.TYPE, MapiFAIContainer.TYPE, MailReplicaContainerTypes.REPLICATED_MBOXES,
65
+			TodoListContainerType.TYPE);
66
+
67
+	private static class SubtreeProc implements NodeProcessor {
68
+
69
+		@Override
70
+		public ItemValue<ContainerHierarchyNode> visit(CliContext ctx, ItemValue<ContainerHierarchyNode> node) {
71
+			ctx.info("* SUBTREE " + node);
72
+			IDbByContainerReplicatedMailboxes mboxesApi = ctx.adminApi()
73
+					.instance(IDbByContainerReplicatedMailboxes.class, node.value.containerUid);
74
+			List<ItemValue<MailboxReplica>> mailboxFolders = mboxesApi.allReplicas().stream()
75
+					.sorted((f1, f2) -> f1.value.fullName.compareTo(f2.value.fullName)).collect(Collectors.toList());
76
+			ctx.info("\tThe subtree has " + mailboxFolders.size() + " folder(s)");
77
+			for (ItemValue<MailboxReplica> mf : mailboxFolders) {
78
+				ctx.info("\t\t" + mf.uid + " " + mf.value.fullName + ",  parent: " + mf.value.parentUid);
79
+			}
80
+			return node;
81
+		}
82
+
83
+	}
84
+
85
+	private static class MapiFolderProc implements NodeProcessor {
86
+
87
+		@Override
88
+		public ItemValue<ContainerHierarchyNode> visit(CliContext ctx, ItemValue<ContainerHierarchyNode> node) {
89
+			IMapiFolder mapiFolderApi = ctx.adminApi().instance(IMapiFolder.class, node.value.containerUid);
90
+			Count count = mapiFolderApi.count(ItemFlagFilter.all());
91
+			ctx.info("* MAPI_FOLDER " + node.uid + "\t\t" + count.total + " item(s)");
92
+			return node;
93
+		}
94
+
95
+	}
96
+
97
+	private static class MapiFaiProc implements NodeProcessor {
98
+
99
+		@Override
100
+		public ItemValue<ContainerHierarchyNode> visit(CliContext ctx, ItemValue<ContainerHierarchyNode> node) {
101
+			String parsedReplica = node.value.containerUid.substring("mapi_fai_".length());
102
+			IMapiFolderAssociatedInformation faiApi = ctx.adminApi().instance(IMapiFolderAssociatedInformation.class,
103
+					parsedReplica);
104
+			List<ItemValue<MapiFAI>> allFais = faiApi.all().stream()
105
+					.sorted((fai1, fai2) -> fai1.value.folderId.compareTo(fai2.value.folderId))
106
+					.collect(Collectors.toList());
107
+			ctx.info("FAIs found for replica " + parsedReplica + " => " + allFais.size() + " message(s)");
108
+			for (ItemValue<MapiFAI> fai : allFais) {
109
+				JsonObject content = new JsonObject(fai.value.faiJson).getObject("setProperties");
110
+				String mClass = content.getString("PidTagMessageClass");
111
+				ctx.info("\t* FAI " + mClass + " in folder " + fai.value.folderId);
112
+			}
113
+			return node;
114
+		}
115
+
116
+	}
117
+
118
+	private static final NodeProcessor DEFAULT_PROC = (CliContext ctx, ItemValue<ContainerHierarchyNode> node) -> {
119
+		ctx.info("* NODE " + node.value.containerUid);
120
+		return node;
121
+	};
122
+
123
+	private static final Map<String, NodeProcessor> PROCESSORS = ImmutableMap.of(//
124
+			MailReplicaContainerTypes.REPLICATED_MBOXES, new SubtreeProc(), //
125
+			MapiFolderContainer.TYPE, new MapiFolderProc(), //
126
+			MapiFAIContainer.TYPE, new MapiFaiProc()//
127
+	);
128
+
129
+	public static class Reg implements ICmdLetRegistration {
130
+
131
+		@Override
132
+		public Optional<String> group() {
133
+			return Optional.of("mapi");
134
+		}
135
+
136
+		@Override
137
+		public Class<? extends ICmdLet> commandClass() {
138
+			return ProfileInfosCommand.class;
139
+		}
140
+	}
141
+
142
+	private CliContext ctx;
143
+
144
+	@Arguments(required = true, description = "email address")
145
+	public String email;
146
+
147
+	@Override
148
+	public void run() {
149
+		if (!Regex.EMAIL.validate(email)) {
150
+			throw new ServerFault("Not an email");
151
+		}
152
+		CliUtils cliUtils = new CliUtils(ctx);
153
+		String domainUid = cliUtils.getDomainUidFromEmailOrDomain(email);
154
+
155
+		IMailboxes boxApi = ctx.adminApi().instance(IMailboxes.class, domainUid);
156
+		ItemValue<Mailbox> mailbox = boxApi.byEmail(email);
157
+		if (mailbox == null) {
158
+			ctx.error("Mailbox not found for email '" + email + "'");
159
+			return;
160
+		}
161
+		ctx.info("Profile " + email + " has mailbox uid " + mailbox.uid);
162
+		IMapiMailbox mapiApi = ctx.adminApi().instance(IMapiMailbox.class, domainUid, mailbox.uid);
163
+		MapiReplica replica = mapiApi.get();
164
+		if (replica == null) {
165
+			ctx.error("Missing replica for email " + email);
166
+			return;
167
+		}
168
+		ctx.info("Replica local: " + replica.localReplicaGuid + ", logon: " + replica.logonReplicaGuid + ", mailbox: "
169
+				+ replica.mailboxGuid);
170
+
171
+		IContainersFlatHierarchy hierApi = ctx.adminApi().instance(IContainersFlatHierarchy.class, domainUid,
172
+				mailbox.uid);
173
+		List<ItemValue<ContainerHierarchyNode>> sortedAndFiltered = hierApi.list()//
174
+				.stream()//
175
+				.filter(v -> mapiRelatedTypes.contains(v.value.containerType))//
176
+				.sorted((n1, n2) -> {
177
+					int byType = n1.value.containerType.compareTo(n2.value.containerType);
178
+					if (byType == 0) {
179
+						return n1.uid.compareTo(n2.uid);
180
+					} else {
181
+						return byType;
182
+					}
183
+				}).map(n -> PROCESSORS.getOrDefault(n.value.containerType, DEFAULT_PROC).visit(ctx, n))
184
+				.collect(Collectors.toList());
185
+		ctx.info("Profile has " + sortedAndFiltered.size() + " (filtered) node(s)");
186
+
187
+	}
188
+
189
+	@Override
190
+	public Runnable forContext(CliContext ctx) {
191
+		this.ctx = ctx;
192
+		return this;
193
+	}
194
+}
... ...
@@ -40,8 +40,7 @@ public interface IMapiFolderAssociatedInformation extends IDataShardSupport {
40 40
 	/**
41 41
 	 * Creates or updates an FAI with the given globalCounter (itemId in bm)
42 42
 	 * 
43
-	 * @param gc
44
-	 *            to itemId to update/assign
43
+	 * @param gc  to itemId to update/assign
45 44
 	 * @param fai
46 45
 	 * @return
47 46
 	 * @throws ServerFault
... ...
@@ -64,8 +63,7 @@ public interface IMapiFolderAssociatedInformation extends IDataShardSupport {
64 63
 	/**
65 64
 	 * Fetches all the FAIs for a given {@link MapiFAI#id}
66 65
 	 * 
67
-	 * @param id
68
-	 *            the folder id
66
+	 * @param id the folder id
69 67
 	 * @return the values of FAIs
70 68
 	 * @throws ServerFault
71 69
 	 */
... ...
@@ -90,4 +88,8 @@ public interface IMapiFolderAssociatedInformation extends IDataShardSupport {
90 88
 	@Path("_deleteall")
91 89
 	void deleteAll() throws ServerFault;
92 90
 
91
+	@GET
92
+	@Path("_all")
93
+	List<ItemValue<MapiFAI>> all() throws ServerFault;
94
+
93 95
 }
... ...
@@ -64,7 +64,7 @@ public class MapiFAIService implements IMapiFolderAssociatedInformation {
64 64
 	}
65 65
 
66 66
 	@Override
67
-	public Collection<Long> deleteByIds(Collection<Long> internalIds) throws ServerFault {
67
+	public Collection<Long> deleteByIds(Collection<Long> internalIds) {
68 68
 		List<Long> deleted = new ArrayList<>(internalIds.size());
69 69
 		for (long itemId : internalIds) {
70 70
 			ItemVersion done = storeService.delete(faiUid(itemId));
... ...
@@ -117,6 +117,11 @@ public class MapiFAIService implements IMapiFolderAssociatedInformation {
117 117
 	}
118 118
 
119 119
 	@Override
120
+	public List<ItemValue<MapiFAI>> all() {
121
+		return storeService.all();
122
+	}
123
+
124
+	@Override
120 125
 	public List<ItemValue<MapiFAI>> getByFolderId(String folderId) throws ServerFault {
121 126
 		try {
122 127
 			List<String> found = mapiFaiStore.byFolder(folderId);
... ...
@@ -18,6 +18,7 @@
18 18
 		<module>net.bluemind.exchange.mapi.notifications</module>
19 19
 		<module>net.bluemind.exchange.publicfolders.common</module>
20 20
 		<module>net.bluemind.exchange.publicfolders.hierarchy</module>
21
+		<module>net.bluemind.cli.mapi</module>
21 22
 	</modules>
22 23
 
23 24
 </project>