Browse code

FEATBL-834 Feat: add attachments to invitations

Thomas Fricker authored on 26/07/2019 12:19:16
Showing 12 changed files
... ...
@@ -37,6 +37,7 @@ import java.util.stream.Collectors;
37 37
 
38 38
 import org.apache.commons.lang.StringUtils;
39 39
 
40
+import net.bluemind.attachment.api.AttachedFile;
40 41
 import net.bluemind.calendar.api.VEvent;
41 42
 import net.bluemind.calendar.api.VEvent.Transparency;
42 43
 import net.bluemind.calendar.api.VEventOccurrence;
... ...
@@ -401,6 +402,13 @@ public class VEventServiceHelper extends ICal4jEventHelper<VEvent> {
401 402
 		appendXMsProperties(properties, vevent);
402 403
 		appendXMozProperties(properties);
403 404
 
405
+		if (vevent.attachments != null) {
406
+			for (AttachedFile file : vevent.attachments) {
407
+				XProperty prop = new XProperty("X-BM-ATTACHMENT", "(" + file.name + ")" + file.publicUrl);
408
+				properties.add(prop);
409
+			}
410
+		}
411
+
404 412
 		return ret;
405 413
 	}
406 414
 
... ...
@@ -19,6 +19,8 @@
19 19
 package net.bluemind.calendar.helper.mail;
20 20
 
21 21
 import java.util.Date;
22
+import java.util.List;
23
+import java.util.Optional;
22 24
 
23 25
 import org.apache.james.mime4j.MimeException;
24 26
 import org.apache.james.mime4j.dom.Header;
... ...
@@ -37,30 +39,40 @@ import net.bluemind.core.api.fault.ServerFault;
37 39
 import net.fortuna.ical4j.model.property.Method;
38 40
 
39 41
 public class CalendarMail {
40
-	public Mailbox from;
41
-	public Mailbox sender;
42
-	public MailboxList to;
43
-	public MailboxList cc;
44
-	public String subject;
45
-	public BodyPart html;
46
-	public BodyPart ics;
47
-	public Method method;
42
+	public final Mailbox from;
43
+	public final Mailbox sender;
44
+	public final MailboxList to;
45
+	public final Method method;
46
+	public final String subject;
47
+	public final BodyPart html;
48
+	public final Optional<MailboxList> cc;
49
+	public final Optional<BodyPart> ics;
50
+	public final Optional<List<EventAttachment>> attachments;
51
+
52
+	private CalendarMail(Mailbox from, Mailbox sender, MailboxList to, Optional<MailboxList> cc, String subject,
53
+			BodyPart html, Optional<BodyPart> ics, Optional<List<EventAttachment>> attachments, Method method) {
54
+		this.from = from;
55
+		this.sender = sender;
56
+		this.to = to;
57
+		this.cc = cc;
58
+		this.subject = subject;
59
+		this.html = html;
60
+		this.ics = ics;
61
+		this.attachments = attachments;
62
+		this.method = method;
63
+	}
48 64
 
49 65
 	public Message getMessage() throws ServerFault {
50
-		MessageBuilder builder = null;
51
-		try {
52
-			builder = MessageServiceFactory.newInstance().newMessageBuilder();
53
-		} catch (MimeException e) {
54
-			throw new ServerFault("Cannot create MessageBuilder", e);
55
-		}
66
+		MessageBuilder builder = createBuilder();
56 67
 
57 68
 		MessageImpl m = new MessageImpl();
69
+		Header messageHeader = builder.newHeader();
58 70
 		m.setDate(new Date());
59 71
 		m.setSubject(subject);
60 72
 		m.setSender(sender);
61 73
 		m.setFrom(from);
62 74
 		m.setTo(to);
63
-		m.setCc(cc);
75
+		cc.ifPresent(c -> m.setCc(c));
64 76
 
65 77
 		Header h = builder.newHeader();
66 78
 		h = builder.newHeader();
... ...
@@ -68,32 +80,8 @@ public class CalendarMail {
68 80
 		h.setField(Fields.contentTransferEncoding("quoted-printable"));
69 81
 		html.setHeader(h);
70 82
 
71
-		BodyPart textCalendar = null;
72
-		BodyPart attachment = null;
73
-		if (ics != null) {
74
-			textCalendar = new BodyPart();
75
-			textCalendar.setBody(ics.getBody());
76
-			textCalendar.setFilename("event.ics");
77
-			h = builder.newHeader();
78
-			h.setField(Fields.contentType("text/calendar; charset=UTF-8; method=" + method.getValue()));
79
-			h.setField(Fields.contentTransferEncoding("8bit"));
80
-			textCalendar.setHeader(h);
81
-
82
-			attachment = new BodyPart();
83
-			attachment.setBody(ics.getBody());
84
-			attachment.setFilename("event.ics");
85
-			h = builder.newHeader();
86
-			h.setField(Fields.contentType("application/ics; name=\"event.ics\""));
87
-			h.setField(Fields.contentDisposition("attachment; filename=\"event.ics\""));
88
-			h.setField(Fields.contentTransferEncoding("base64"));
89
-			attachment.setHeader(h);
90
-		}
91
-
92 83
 		Multipart alternative = new MultipartImpl("alternative");
93 84
 		alternative.addBodyPart(html);
94
-		if (textCalendar != null) {
95
-			alternative.addBodyPart(textCalendar);
96
-		}
97 85
 
98 86
 		MessageImpl alternativeMessage = new MessageImpl();
99 87
 		alternativeMessage.setMultipart(alternative);
... ...
@@ -103,12 +91,129 @@ public class CalendarMail {
103 91
 
104 92
 		Multipart mixed = new MultipartImpl("mixed");
105 93
 		mixed.addBodyPart(alternativeMessage);
106
-		if (attachment != null) {
94
+
95
+		BodyPart textCalendar = new BodyPart();
96
+		BodyPart attachment = new BodyPart();
97
+		ics.ifPresent(icsData -> {
98
+			textCalendar.setBody(icsData.getBody());
99
+			textCalendar.setFilename("event.ics");
100
+			Header tcHeader = builder.newHeader();
101
+			tcHeader.setField(Fields.contentType("text/calendar; charset=UTF-8; method=" + method.getValue()));
102
+			tcHeader.setField(Fields.contentTransferEncoding("8bit"));
103
+			textCalendar.setHeader(tcHeader);
104
+
105
+			attachment.setBody(icsData.getBody());
106
+			attachment.setFilename("event.ics");
107
+			Header attHeader = builder.newHeader();
108
+			attHeader.setField(Fields.contentType("application/ics; name=\"event.ics\""));
109
+			attHeader.setField(Fields.contentDisposition("attachment; filename=\"event.ics\""));
110
+			attHeader.setField(Fields.contentTransferEncoding("base64"));
111
+			attachment.setHeader(attHeader);
112
+
113
+			alternative.addBodyPart(textCalendar);
107 114
 			mixed.addBodyPart(attachment);
108
-		}
115
+		});
116
+
117
+		attachments.ifPresent(atts -> {
118
+			for (EventAttachment att : atts) {
119
+				BodyPart attBody = new BodyPart();
120
+				attBody.setBody(att.part.getBody());
121
+				attBody.setFilename(att.name);
122
+				Header header = builder.newHeader();
123
+				header.setField(Fields.contentType(att.contentType + "; name=\"" + att.name + "\""));
124
+				header.setField(Fields.contentDisposition("attachment; filename=\"" + att.name + "\""));
125
+				header.setField(Fields.contentTransferEncoding("base64"));
126
+				attBody.setHeader(header);
127
+				mixed.addBodyPart(attBody);
128
+			}
129
+		});
109 130
 
110 131
 		m.setMultipart(mixed);
132
+		m.setHeader(messageHeader);
111 133
 
112 134
 		return m;
113 135
 	}
136
+
137
+	private MessageBuilder createBuilder() {
138
+		try {
139
+			return MessageServiceFactory.newInstance().newMessageBuilder();
140
+		} catch (MimeException e) {
141
+			throw new ServerFault("Cannot create MessageBuilder", e);
142
+		}
143
+	}
144
+
145
+	public static class CalendarMailBuilder {
146
+		private Mailbox from;
147
+		private Mailbox sender;
148
+		private MailboxList to;
149
+		private Method method;
150
+		private String subject;
151
+		private BodyPart html;
152
+		private MailboxList cc;
153
+		private BodyPart ics;
154
+		private List<EventAttachment> attachments;
155
+
156
+		public CalendarMail build() {
157
+			check(from, "from");
158
+			check(sender, "sender");
159
+			check(to, "to");
160
+			check(method, "method");
161
+			check(subject, "subject");
162
+			check(html, "html");
163
+
164
+			return new CalendarMail(from, sender, to, Optional.ofNullable(cc), subject, html, Optional.ofNullable(ics),
165
+					Optional.ofNullable(attachments), method);
166
+		}
167
+
168
+		private void check(Object obj, String field) {
169
+			if (obj == null) {
170
+				throw new ServerFault("Cannot create CalendarMail. " + field + " is null");
171
+			}
172
+		}
173
+
174
+		public CalendarMailBuilder from(Mailbox from) {
175
+			this.from = from;
176
+			return this;
177
+		}
178
+
179
+		public CalendarMailBuilder sender(Mailbox sender) {
180
+			this.sender = sender;
181
+			return this;
182
+		}
183
+
184
+		public CalendarMailBuilder to(MailboxList to) {
185
+			this.to = to;
186
+			return this;
187
+		}
188
+
189
+		public CalendarMailBuilder method(Method method) {
190
+			this.method = method;
191
+			return this;
192
+		}
193
+
194
+		public CalendarMailBuilder subject(String subject) {
195
+			this.subject = subject;
196
+			return this;
197
+		}
198
+
199
+		public CalendarMailBuilder html(BodyPart html) {
200
+			this.html = html;
201
+			return this;
202
+		}
203
+
204
+		public CalendarMailBuilder cc(MailboxList cc) {
205
+			this.cc = cc;
206
+			return this;
207
+		}
208
+
209
+		public CalendarMailBuilder ics(BodyPart ics) {
210
+			this.ics = ics;
211
+			return this;
212
+		}
213
+
214
+		public CalendarMailBuilder attachments(List<EventAttachment> attachments) {
215
+			this.attachments = attachments;
216
+			return this;
217
+		}
218
+	}
114 219
 }
... ...
@@ -28,6 +28,9 @@ import java.util.Locale;
28 28
 import java.util.Map;
29 29
 
30 30
 import org.apache.commons.lang.StringUtils;
31
+import org.apache.james.mime4j.dom.BinaryBody;
32
+import org.apache.james.mime4j.message.BasicBodyFactory;
33
+import org.apache.james.mime4j.message.BodyPart;
31 34
 
32 35
 import freemarker.template.Configuration;
33 36
 import freemarker.template.Template;
... ...
@@ -46,18 +49,31 @@ public class CalendarMailHelper extends ReminderMailHelper<VEvent> {
46 49
 		cfg.setClassForTemplateLoading(this.getClass(), "/");
47 50
 	}
48 51
 
52
+	public BodyPart createBinaryPart(byte[] part) {
53
+
54
+		BasicBodyFactory bodyFactory = new BasicBodyFactory();
55
+		BinaryBody body = bodyFactory.binaryBody(part);
56
+
57
+		BodyPart bodyPart = new BodyPart();
58
+		bodyPart.setBody(body);
59
+
60
+		return bodyPart;
61
+	}
62
+
49 63
 	/**
50 64
 	 * Extract {@link VEvent} data
51 65
 	 * 
52 66
 	 * @param vevent
53
-	 *            the {@link VEvent} to extract
67
+	 *                   the {@link VEvent} to extract
54 68
 	 * @return a {@link Map} containing the {@link VEvent} data
55 69
 	 */
56 70
 	public Map<String, Object> extractVEventDataToMap(VEvent vevent, VAlarm valarm) {
57 71
 		Map<String, Object> data = new HashMap<String, Object>();
58 72
 
59
-		Long duration = vevent.dtend != null ? (new BmDateTimeWrapper(vevent.dtend).toUTCTimestamp()
60
-				- new BmDateTimeWrapper(vevent.dtstart).toUTCTimestamp()) / 1000 : null;
73
+		Long duration = vevent.dtend != null
74
+				? (new BmDateTimeWrapper(vevent.dtend).toUTCTimestamp()
75
+						- new BmDateTimeWrapper(vevent.dtstart).toUTCTimestamp()) / 1000
76
+				: null;
61 77
 
62 78
 		data.put("duration", duration);
63 79
 
64 80
new file mode 100644
... ...
@@ -0,0 +1,37 @@
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
+ *
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.calendar.helper.mail;
20
+
21
+import org.apache.james.mime4j.message.BodyPart;
22
+
23
+public class EventAttachment {
24
+
25
+	public final String uri;
26
+	public final String name;
27
+	public final String contentType;
28
+	public final BodyPart part;
29
+
30
+	public EventAttachment(String uri, String name, String contentType, BodyPart part) {
31
+		this.uri = uri;
32
+		this.name = name;
33
+		this.contentType = contentType;
34
+		this.part = part;
35
+	}
36
+
37
+}
... ...
@@ -26,6 +26,7 @@ Require-Bundle: net.bluemind.slf4j;bundle-version="1.0.0",
26 26
  net.bluemind.common.freemarker,
27 27
  net.bluemind.icalendar.parser,
28 28
  net.bluemind.calendar.auditlog,
29
- net.bluemind.core.auditlog
29
+ net.bluemind.core.auditlog,
30
+ net.bluemind.attachment.api
30 31
 Import-Package: net.bluemind.directory.api
31 32
 
... ...
@@ -18,8 +18,12 @@
18 18
  */
19 19
 package net.bluemind.calendar.hook;
20 20
 
21
+import java.io.BufferedInputStream;
22
+import java.io.ByteArrayOutputStream;
21 23
 import java.io.IOException;
24
+import java.net.URL;
22 25
 import java.time.ZonedDateTime;
26
+import java.security.SecureRandom;
23 27
 import java.util.ArrayList;
24 28
 import java.util.Arrays;
25 29
 import java.util.Collections;
... ...
@@ -35,6 +39,10 @@ import java.util.function.BiFunction;
35 39
 import java.util.function.BinaryOperator;
36 40
 import java.util.stream.Collectors;
37 41
 
42
+import javax.net.ssl.HttpsURLConnection;
43
+import javax.net.ssl.SSLContext;
44
+import javax.net.ssl.TrustManager;
45
+
38 46
 import org.apache.james.mime4j.dom.Message;
39 47
 import org.apache.james.mime4j.dom.address.Mailbox;
40 48
 import org.apache.james.mime4j.dom.address.MailboxList;
... ...
@@ -45,13 +53,15 @@ import org.slf4j.LoggerFactory;
45 53
 import com.google.common.collect.Sets;
46 54
 
47 55
 import freemarker.template.TemplateException;
56
+import net.bluemind.attachment.api.AttachedFile;
48 57
 import net.bluemind.calendar.api.VEvent;
49 58
 import net.bluemind.calendar.api.VEventOccurrence;
50 59
 import net.bluemind.calendar.api.VEventSeries;
51 60
 import net.bluemind.calendar.auditlog.CalendarAuditor;
52 61
 import net.bluemind.calendar.helper.ical4j.VEventServiceHelper;
53
-import net.bluemind.calendar.helper.mail.CalendarMail;
62
+import net.bluemind.calendar.helper.mail.CalendarMail.CalendarMailBuilder;
54 63
 import net.bluemind.calendar.helper.mail.CalendarMailHelper;
64
+import net.bluemind.calendar.helper.mail.EventAttachment;
55 65
 import net.bluemind.calendar.helper.mail.Messages;
56 66
 import net.bluemind.calendar.hook.internal.VEventMessage;
57 67
 import net.bluemind.common.freemarker.FreeMarkerMsg;
... ...
@@ -78,9 +88,11 @@ import net.bluemind.icalendar.api.ICalendarElement.Attendee;
78 88
 import net.bluemind.icalendar.api.ICalendarElement.Organizer;
79 89
 import net.bluemind.icalendar.api.ICalendarElement.ParticipationStatus;
80 90
 import net.bluemind.icalendar.api.ICalendarElement.Role;
91
+import net.bluemind.icalendar.parser.Mime;
81 92
 import net.bluemind.user.api.IUser;
82 93
 import net.bluemind.user.api.IUserSettings;
83 94
 import net.bluemind.user.api.User;
95
+import net.bluemind.utils.Trust;
84 96
 import net.fortuna.ical4j.model.property.Method;
85 97
 
86 98
 /**
... ...
@@ -704,9 +716,22 @@ public class IcsHook implements ICalendarHook {
704 716
 					settings = userSettingsService.get(user.uid);
705 717
 				}
706 718
 
719
+				List<EventAttachment> attachments = new ArrayList<>();
720
+				for (AttachedFile att : event.attachments) {
721
+					BodyPart binaryPart;
722
+					try {
723
+						binaryPart = new CalendarMailHelper().createBinaryPart(loadAttachment(att));
724
+						// FIXME use mime-class
725
+						attachments.add(
726
+								new EventAttachment(att.publicUrl, att.name, Mime.getMimeType(att.name), binaryPart));
727
+					} catch (IOException e) {
728
+						logger.warn("Cannot read event attachment from url {}", att.publicUrl, e);
729
+					}
730
+				}
731
+
707 732
 				Message mail = buildMailMessage(from, from, attendeeListTo, attendeeListCc, subjectTemplate, template,
708 733
 						messagesResolverProvider.getResolver(new Locale(getLocale(settings))), data,
709
-						createBodyPart(message.itemUid, ics), settings, event, method);
734
+						createBodyPart(message.itemUid, ics), settings, event, method, attachments);
710 735
 				mailer.send(from.getAddress(), from.getDomain(), new MailboxList(Arrays.asList(recipient), true), mail);
711 736
 				mail.dispose();
712 737
 
... ...
@@ -719,6 +744,24 @@ public class IcsHook implements ICalendarHook {
719 744
 
720 745
 	}
721 746
 
747
+	private byte[] loadAttachment(AttachedFile attachment) throws IOException {
748
+		try {
749
+			SSLContext sc = SSLContext.getInstance("SSL");
750
+			sc.init(null, new TrustManager[] { Trust.createTrustManager() }, new SecureRandom());
751
+			HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
752
+		} catch (Exception e) {
753
+		}
754
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
755
+		try (BufferedInputStream in = new BufferedInputStream(new URL(attachment.publicUrl).openStream())) {
756
+			byte dataBuffer[] = new byte[1024];
757
+			int bytesRead = 0;
758
+			while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
759
+				baos.write(dataBuffer, 0, bytesRead);
760
+			}
761
+		}
762
+		return baos.toByteArray();
763
+	}
764
+
722 765
 	private static Map<String, String> getSenderSettings(VEventMessage message, DirEntry fromDirEntry)
723 766
 			throws ServerFault {
724 767
 		ServerSideServiceProvider sp = ServerSideServiceProvider.getProvider(SecurityContext.SYSTEM);
... ...
@@ -729,7 +772,7 @@ public class IcsHook implements ICalendarHook {
729 772
 	private Message buildMailMessage(Mailbox from, Mailbox sender, List<Mailbox> attendeeListTo,
730 773
 			List<Mailbox> attendeeListCc, String subjectTemplate, String templateName,
731 774
 			MessagesResolver messagesResolver, Map<String, Object> data, BodyPart ics, Map<String, String> settings,
732
-			VEvent vevent, Method method) throws ServerFault {
775
+			VEvent vevent, Method method, List<EventAttachment> attachments) throws ServerFault {
733 776
 		try {
734 777
 			String subject = new CalendarMailHelper().buildSubject(subjectTemplate, settings.get("lang"),
735 778
 					messagesResolver, data);
... ...
@@ -749,7 +792,7 @@ public class IcsHook implements ICalendarHook {
749 792
 			}
750 793
 
751 794
 			return getMessage(from, sender, attendeeListTo, attendeeListCc, subject, templateName, settings.get("lang"),
752
-					messagesResolver, data, ics, method);
795
+					messagesResolver, data, ics, method, attachments);
753 796
 
754 797
 		} catch (TemplateException e) {
755 798
 			throw new ServerFault(e);
... ...
@@ -967,6 +1010,7 @@ public class IcsHook implements ICalendarHook {
967 1010
 	 * @param data
968 1011
 	 * @param ics
969 1012
 	 * @param method
1013
+	 * @param attachments
970 1014
 	 * @return
971 1015
 	 * @throws TemplateException
972 1016
 	 * @throws IOException
... ...
@@ -974,25 +1018,28 @@ public class IcsHook implements ICalendarHook {
974 1018
 	 */
975 1019
 	private Message getMessage(Mailbox from, Mailbox sender, List<Mailbox> attendeeListTo, List<Mailbox> attendeeListCc,
976 1020
 			String subject, String templateName, String locale, MessagesResolver messagesResolver,
977
-			Map<String, Object> data, BodyPart ics, Method method) throws TemplateException, IOException, ServerFault {
978
-		CalendarMail m = new CalendarMail();
1021
+			Map<String, Object> data, BodyPart ics, Method method, List<EventAttachment> attachments)
1022
+			throws TemplateException, IOException, ServerFault {
979 1023
 
980
-		m.from = from;
981
-		m.sender = sender;
982
-		m.to = new MailboxList(attendeeListTo, true);
983
-		if (attendeeListCc != null) {
984
-			m.cc = new MailboxList(attendeeListCc, true);
985
-		}
986
-		m.method = method;
1024
+		data.put("msg", new FreeMarkerMsg(messagesResolver));
987 1025
 
988
-		if (ics != null) {
989
-			m.ics = ics;
1026
+		CalendarMailBuilder mailBuilder = new CalendarMailBuilder() //
1027
+				.from(from) //
1028
+				.sender(sender) //
1029
+				.to(new MailboxList(attendeeListTo, true)) //
1030
+				.method(method) //
1031
+				.html(new CalendarMailHelper().buildBody(templateName, locale, messagesResolver, data)) //
1032
+				.subject(subject) //
1033
+				.ics(ics) //
1034
+				.attachments(attachments);
1035
+
1036
+		logger.info("CalMail for attachments : {}", mailBuilder.build().attachments.get().size());
1037
+
1038
+		if (attendeeListCc != null) {
1039
+			mailBuilder.cc(new MailboxList(attendeeListCc, true));
990 1040
 		}
991
-		data.put("msg", new FreeMarkerMsg(messagesResolver));
992
-		m.html = new CalendarMailHelper().buildBody(templateName, locale, messagesResolver, data);
993
-		m.subject = subject;
994 1041
 
995
-		return m.getMessage();
1042
+		return mailBuilder.build().getMessage();
996 1043
 	}
997 1044
 
998 1045
 	private String getIcsPart(String uid, Method method, VEventSeries series, Attendee attendee) throws ServerFault {
... ...
@@ -45,7 +45,6 @@ public class Mime {
45 45
 	private static final String APPLICATION_SRGS_XML = "application/srgs+xml";
46 46
 	private static final String APPLICATION_VND_MIF = "application/vnd.mif";
47 47
 	private static final String APPLICATION_VND_MSEXCEL = "application/vnd.ms-excel";
48
-	private static final String APPLICATION_VND_MSEXCEL_2007 = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
49 48
 	private static final String APPLICATION_VND_SPREADSHEET = "application/vnd.oasis.opendocument.spreadsheet";
50 49
 	private static final String APPLICATION_VND_MSPOWERPOINT = "application/vnd.ms-powerpoint";
51 50
 	private static final String APPLICATION_VND_RNREALMEDIA = "application/vnd.rn-realmedia";
... ...
@@ -135,125 +134,136 @@ public class Mime {
135 134
 	private static final String VIDEO_X_SGI_MOVIE = "video/x-sgi-movie";
136 135
 	private static final String X_CONFERENCE_X_COOLTALK = "x-conference/x-cooltalk";
137 136
 
138
-	private static HashMap<String, String> mapping;
137
+	private static HashMap<String, String> mimeTypeToExtension;
138
+	private static HashMap<String, String> extensionToMimeType;
139 139
 
140 140
 	static {
141
-		mapping = new HashMap<String, String>(200);
141
+		mimeTypeToExtension = new HashMap<String, String>(200);
142
+		extensionToMimeType = new HashMap<String, String>(200);
142 143
 
143
-		mapping.put(APPLICATION_VND_MOZZILLA_XUL_XML, "xul");
144
-		mapping.put(APPLICATION_JSON, "json");
145
-		mapping.put(X_CONFERENCE_X_COOLTALK, "ice");
146
-		mapping.put(VIDEO_X_SGI_MOVIE, "movie");
147
-		mapping.put(VIDEO_X_MSVIDEO, "avi");
148
-		mapping.put(VIDEO_X_MS_WMV, "wmv");
149
-		mapping.put(VIDEO_VND_MPEGURL, "m4u");
150
-		mapping.put(TEXT_X_COMPONENT, "htc");
151
-		mapping.put(TEXT_X_SETEXT, "etx");
152
-		mapping.put(TEXT_VND_WAP_WMLSCRIPT, "wmls");
153
-		mapping.put(TEXT_VND_WAP_XML, "wml");
154
-		mapping.put(TEXT_TAB_SEPARATED_VALUES, "tsv");
155
-		mapping.put(TEXT_SGML, "sgml");
156
-		mapping.put(TEXT_CSS, "css");
157
-		mapping.put(TEXT_CALENDAR, "ics");
158
-		mapping.put(MODEL_VRLM, "vrlm");
159
-		mapping.put(MODEL_MESH, "mesh");
160
-		mapping.put(MODEL_IGES, "iges");
161
-		mapping.put(IMAGE_X_RGB, "rgb");
162
-		mapping.put(IMAGE_X_PORTABLE_PIXMAP, "ppm");
163
-		mapping.put(IMAGE_X_PORTABLE_GRAYMAP, "pgm");
164
-		mapping.put(IMAGE_X_PORTABLE_BITMAP, "pbm");
165
-		mapping.put(IMAGE_X_PORTABLE_ANYMAP, "pnm");
166
-		mapping.put(IMAGE_X_ICON, "ico");
167
-		mapping.put(IMAGE_X_CMU_RASTER, "ras");
168
-		mapping.put(IMAGE_WAP_WBMP, "wbmp");
169
-		mapping.put(IMAGE_VND_DJVU, "djvu");
170
-		mapping.put(IMAGE_SVG_XML, "svg");
171
-		mapping.put(IMAGE_IEF, "ief");
172
-		mapping.put(IMAGE_CGM, "cgm");
173
-		mapping.put(IMAGE_BMP, "bmp");
174
-		mapping.put(CHEMICAL_X_XYZ, "xyz");
175
-		mapping.put(CHEMICAL_X_PDB, "pdb");
176
-		mapping.put(AUDIO_X_PN_REALAUDIO, "ra");
177
-		mapping.put(AUDIO_X_MPEGURL, "m3u");
178
-		mapping.put(AUDIO_X_AIFF, "aiff");
179
-		mapping.put(AUDIO_MPEG, "mp3");
180
-		mapping.put(AUDIO_MIDI, "midi");
181
-		mapping.put(APPLICATION_XML_DTD, "dtd");
182
-		mapping.put(APPLICATION_XML, "xml");
183
-		mapping.put(APPLICATION_XSLT_XML, "xslt");
184
-		mapping.put(APPLICATION_XHTML_XML, "xhtml");
185
-		mapping.put(APPLICATION_X_WAIS_SOURCE, "src");
186
-		mapping.put(APPLICATION_X_USTAR, "ustar");
187
-		mapping.put(APPLICATION_X_TROFF_MS, "ms");
188
-		mapping.put(APPLICATION_X_TROFF_ME, "me");
189
-		mapping.put(APPLICATION_X_TROFF_MAN, "man");
190
-		mapping.put(APPLICATION_X_TROFF, "roff");
191
-		mapping.put(APPLICATION_X_TEXINFO, "texi");
192
-		mapping.put(APPLICATION_X_TEX, "tex");
193
-		mapping.put(APPLICATION_X_TCL, "tcl");
194
-		mapping.put(APPLICATION_X_SV4CRC, "sv4crc");
195
-		mapping.put(APPLICATION_X_SV4CPIO, "sv4cpio");
196
-		mapping.put(APPLICATION_X_STUFFIT, "sit");
197
-		mapping.put(APPLICATION_X_SHOCKWAVE_FLASH, "swf");
198
-		mapping.put(APPLICATION_X_SHAR, "shar");
199
-		mapping.put(APPLICATION_X_SH, "sh");
200
-		mapping.put(APPLICATION_X_NETCDF, "cdf");
201
-		mapping.put(APPLICATION_X_LATEX, "latex");
202
-		mapping.put(APPLICATION_X_KOAN, "skm");
203
-		mapping.put(APPLICATION_X_JAVASCRIPT, "js");
204
-		mapping.put(APPLICATION_X_HDF, "hdf");
205
-		mapping.put(APPLICATION_X_GTAR, "gtar");
206
-		mapping.put(APPLICATION_X_FUTURESPLASH, "spl");
207
-		mapping.put(APPLICATION_X_DVI, "dvi");
208
-		mapping.put(APPLICATION_X_DIRECTOR, "dir");
209
-		mapping.put(APPLICATION_X_CSH, "csh");
210
-		mapping.put(APPLICATION_X_CPIO, "cpio");
211
-		mapping.put(APPLICATION_X_CHESS_PGN, "pgn");
212
-		mapping.put(APPLICATION_X_CDLINK, "vcd");
213
-		mapping.put(APPLICATION_X_BCPIO, "bcpio");
214
-		mapping.put(APPLICATION_VND_RNREALMEDIA, "rm");
215
-		mapping.put(APPLICATION_VND_MSPOWERPOINT, "ppt");
216
-		mapping.put(APPLICATION_VND_MIF, "mif");
217
-		mapping.put(APPLICATION_SRGS_XML, "grxml");
218
-		mapping.put(APPLICATION_SRGS, "gram");
219
-		mapping.put(APPLICATION_RDF_SMIL, "smil");
220
-		mapping.put(APPLICATION_RDF_XML, "rdf");
221
-		mapping.put(APPLICATION_X_OGG, "ogg");
222
-		mapping.put(APPLICATION_ODA, "oda");
223
-		mapping.put(APPLICATION_MATHML_XML, "mathml");
224
-		mapping.put(APPLICATION_MAC_COMPACTPRO, "cpt");
225
-		mapping.put(APPLICATION_MAC_BINHEX40, "hqx");
226
-		mapping.put(APPLICATION_JNLP, "jnlp");
227
-		mapping.put(APPLICATION_ANDREW_INSET, "ez");
228
-		mapping.put(TEXT_PLAIN, "txt");
229
-		mapping.put(TEXT_RTF, "rtf");
230
-		mapping.put(TEXT_RICHTEXT, "rtx");
231
-		mapping.put(TEXT_HTML, "html");
232
-		mapping.put(APPLICATION_ZIP, "zip");
233
-		mapping.put(APPLICATION_X_RAR_COMPRESSED, "rar");
234
-		mapping.put(APPLICATION_X_GZIP, "gzip");
235
-		mapping.put(APPLICATION_TGZ, "tgz");
236
-		mapping.put(APPLICATION_X_TAR, "tar");
237
-		mapping.put(IMAGE_GIF, "gif");
238
-		mapping.put(IMAGE_JPEG, "jpg");
239
-		mapping.put(IMAGE_TIFF, "tiff");
240
-		mapping.put(IMAGE_PNG, "png");
241
-		mapping.put(AUDIO_BASIC, "au");
242
-		mapping.put(AUDIO_X_WAV, "wav");
243
-		mapping.put(VIDEO_QUICKTIME, "mov");
244
-		mapping.put(VIDEO_MPEG, "mpg");
245
-		mapping.put(APPLICATION_MSWORD, "doc");
246
-		mapping.put(APPLICATION_MSWORD_2007, "docx");
247
-		mapping.put(APPLICATION_VND_TEXT, "odt");
248
-		mapping.put(APPLICATION_VND_MSEXCEL, "xls");
249
-		mapping.put(APPLICATION_VND_SPREADSHEET, "ods");
250
-		mapping.put(APPLICATION_POSTSCRIPT, "ps");
251
-		mapping.put(APPLICATION_PDF, "pdf");
252
-		mapping.put(APPLICATION_OCTET_STREAM, "exe");
253
-		mapping.put(APPLICATION_JAVA_ARCHIVE, "jar");
144
+		mimeTypeToExtension.put(APPLICATION_VND_MOZZILLA_XUL_XML, "xul");
145
+		mimeTypeToExtension.put(APPLICATION_JSON, "json");
146
+		mimeTypeToExtension.put(X_CONFERENCE_X_COOLTALK, "ice");
147
+		mimeTypeToExtension.put(VIDEO_X_SGI_MOVIE, "movie");
148
+		mimeTypeToExtension.put(VIDEO_X_MSVIDEO, "avi");
149
+		mimeTypeToExtension.put(VIDEO_X_MS_WMV, "wmv");
150
+		mimeTypeToExtension.put(VIDEO_VND_MPEGURL, "m4u");
151
+		mimeTypeToExtension.put(TEXT_X_COMPONENT, "htc");
152
+		mimeTypeToExtension.put(TEXT_X_SETEXT, "etx");
153
+		mimeTypeToExtension.put(TEXT_VND_WAP_WMLSCRIPT, "wmls");
154
+		mimeTypeToExtension.put(TEXT_VND_WAP_XML, "wml");
155
+		mimeTypeToExtension.put(TEXT_TAB_SEPARATED_VALUES, "tsv");
156
+		mimeTypeToExtension.put(TEXT_SGML, "sgml");
157
+		mimeTypeToExtension.put(TEXT_CSS, "css");
158
+		mimeTypeToExtension.put(TEXT_CALENDAR, "ics");
159
+		mimeTypeToExtension.put(MODEL_VRLM, "vrlm");
160
+		mimeTypeToExtension.put(MODEL_MESH, "mesh");
161
+		mimeTypeToExtension.put(MODEL_IGES, "iges");
162
+		mimeTypeToExtension.put(IMAGE_X_RGB, "rgb");
163
+		mimeTypeToExtension.put(IMAGE_X_PORTABLE_PIXMAP, "ppm");
164
+		mimeTypeToExtension.put(IMAGE_X_PORTABLE_GRAYMAP, "pgm");
165
+		mimeTypeToExtension.put(IMAGE_X_PORTABLE_BITMAP, "pbm");
166
+		mimeTypeToExtension.put(IMAGE_X_PORTABLE_ANYMAP, "pnm");
167
+		mimeTypeToExtension.put(IMAGE_X_ICON, "ico");
168
+		mimeTypeToExtension.put(IMAGE_X_CMU_RASTER, "ras");
169
+		mimeTypeToExtension.put(IMAGE_WAP_WBMP, "wbmp");
170
+		mimeTypeToExtension.put(IMAGE_VND_DJVU, "djvu");
171
+		mimeTypeToExtension.put(IMAGE_SVG_XML, "svg");
172
+		mimeTypeToExtension.put(IMAGE_IEF, "ief");
173
+		mimeTypeToExtension.put(IMAGE_CGM, "cgm");
174
+		mimeTypeToExtension.put(IMAGE_BMP, "bmp");
175
+		mimeTypeToExtension.put(CHEMICAL_X_XYZ, "xyz");
176
+		mimeTypeToExtension.put(CHEMICAL_X_PDB, "pdb");
177
+		mimeTypeToExtension.put(AUDIO_X_PN_REALAUDIO, "ra");
178
+		mimeTypeToExtension.put(AUDIO_X_MPEGURL, "m3u");
179
+		mimeTypeToExtension.put(AUDIO_X_AIFF, "aiff");
180
+		mimeTypeToExtension.put(AUDIO_MPEG, "mp3");
181
+		mimeTypeToExtension.put(AUDIO_MIDI, "midi");
182
+		mimeTypeToExtension.put(APPLICATION_XML_DTD, "dtd");
183
+		mimeTypeToExtension.put(APPLICATION_XML, "xml");
184
+		mimeTypeToExtension.put(APPLICATION_XSLT_XML, "xslt");
185
+		mimeTypeToExtension.put(APPLICATION_XHTML_XML, "xhtml");
186
+		mimeTypeToExtension.put(APPLICATION_X_WAIS_SOURCE, "src");
187
+		mimeTypeToExtension.put(APPLICATION_X_USTAR, "ustar");
188
+		mimeTypeToExtension.put(APPLICATION_X_TROFF_MS, "ms");
189
+		mimeTypeToExtension.put(APPLICATION_X_TROFF_ME, "me");
190
+		mimeTypeToExtension.put(APPLICATION_X_TROFF_MAN, "man");
191
+		mimeTypeToExtension.put(APPLICATION_X_TROFF, "roff");
192
+		mimeTypeToExtension.put(APPLICATION_X_TEXINFO, "texi");
193
+		mimeTypeToExtension.put(APPLICATION_X_TEX, "tex");
194
+		mimeTypeToExtension.put(APPLICATION_X_TCL, "tcl");
195
+		mimeTypeToExtension.put(APPLICATION_X_SV4CRC, "sv4crc");
196
+		mimeTypeToExtension.put(APPLICATION_X_SV4CPIO, "sv4cpio");
197
+		mimeTypeToExtension.put(APPLICATION_X_STUFFIT, "sit");
198
+		mimeTypeToExtension.put(APPLICATION_X_SHOCKWAVE_FLASH, "swf");
199
+		mimeTypeToExtension.put(APPLICATION_X_SHAR, "shar");
200
+		mimeTypeToExtension.put(APPLICATION_X_SH, "sh");
201
+		mimeTypeToExtension.put(APPLICATION_X_NETCDF, "cdf");
202
+		mimeTypeToExtension.put(APPLICATION_X_LATEX, "latex");
203
+		mimeTypeToExtension.put(APPLICATION_X_KOAN, "skm");
204
+		mimeTypeToExtension.put(APPLICATION_X_JAVASCRIPT, "js");
205
+		mimeTypeToExtension.put(APPLICATION_X_HDF, "hdf");
206
+		mimeTypeToExtension.put(APPLICATION_X_GTAR, "gtar");
207
+		mimeTypeToExtension.put(APPLICATION_X_FUTURESPLASH, "spl");
208
+		mimeTypeToExtension.put(APPLICATION_X_DVI, "dvi");
209
+		mimeTypeToExtension.put(APPLICATION_X_DIRECTOR, "dir");
210
+		mimeTypeToExtension.put(APPLICATION_X_CSH, "csh");
211
+		mimeTypeToExtension.put(APPLICATION_X_CPIO, "cpio");
212
+		mimeTypeToExtension.put(APPLICATION_X_CHESS_PGN, "pgn");
213
+		mimeTypeToExtension.put(APPLICATION_X_CDLINK, "vcd");
214
+		mimeTypeToExtension.put(APPLICATION_X_BCPIO, "bcpio");
215
+		mimeTypeToExtension.put(APPLICATION_VND_RNREALMEDIA, "rm");
216
+		mimeTypeToExtension.put(APPLICATION_VND_MSPOWERPOINT, "ppt");
217
+		mimeTypeToExtension.put(APPLICATION_VND_MIF, "mif");
218
+		mimeTypeToExtension.put(APPLICATION_SRGS_XML, "grxml");
219
+		mimeTypeToExtension.put(APPLICATION_SRGS, "gram");
220
+		mimeTypeToExtension.put(APPLICATION_RDF_SMIL, "smil");
221
+		mimeTypeToExtension.put(APPLICATION_RDF_XML, "rdf");
222
+		mimeTypeToExtension.put(APPLICATION_X_OGG, "ogg");
223
+		mimeTypeToExtension.put(APPLICATION_ODA, "oda");
224
+		mimeTypeToExtension.put(APPLICATION_MATHML_XML, "mathml");
225
+		mimeTypeToExtension.put(APPLICATION_MAC_COMPACTPRO, "cpt");
226
+		mimeTypeToExtension.put(APPLICATION_MAC_BINHEX40, "hqx");
227
+		mimeTypeToExtension.put(APPLICATION_JNLP, "jnlp");
228
+		mimeTypeToExtension.put(APPLICATION_ANDREW_INSET, "ez");
229
+		mimeTypeToExtension.put(TEXT_PLAIN, "txt");
230
+		mimeTypeToExtension.put(TEXT_RTF, "rtf");
231
+		mimeTypeToExtension.put(TEXT_RICHTEXT, "rtx");
232
+		mimeTypeToExtension.put(TEXT_HTML, "html");
233
+		mimeTypeToExtension.put(APPLICATION_ZIP, "zip");
234
+		mimeTypeToExtension.put(APPLICATION_X_RAR_COMPRESSED, "rar");
235
+		mimeTypeToExtension.put(APPLICATION_X_GZIP, "gzip");
236
+		mimeTypeToExtension.put(APPLICATION_TGZ, "tgz");
237
+		mimeTypeToExtension.put(APPLICATION_X_TAR, "tar");
238
+		mimeTypeToExtension.put(IMAGE_GIF, "gif");
239
+		mimeTypeToExtension.put(IMAGE_JPEG, "jpg");
240
+		mimeTypeToExtension.put(IMAGE_TIFF, "tiff");
241
+		mimeTypeToExtension.put(IMAGE_PNG, "png");
242
+		mimeTypeToExtension.put(AUDIO_BASIC, "au");
243
+		mimeTypeToExtension.put(AUDIO_X_WAV, "wav");
244
+		mimeTypeToExtension.put(VIDEO_QUICKTIME, "mov");
245
+		mimeTypeToExtension.put(VIDEO_MPEG, "mpg");
246
+		mimeTypeToExtension.put(APPLICATION_MSWORD, "doc");
247
+		mimeTypeToExtension.put(APPLICATION_MSWORD_2007, "docx");
248
+		mimeTypeToExtension.put(APPLICATION_VND_TEXT, "odt");
249
+		mimeTypeToExtension.put(APPLICATION_VND_MSEXCEL, "xls");
250
+		mimeTypeToExtension.put(APPLICATION_VND_SPREADSHEET, "ods");
251
+		mimeTypeToExtension.put(APPLICATION_POSTSCRIPT, "ps");
252
+		mimeTypeToExtension.put(APPLICATION_PDF, "pdf");
253
+		mimeTypeToExtension.put(APPLICATION_OCTET_STREAM, "exe");
254
+		mimeTypeToExtension.put(APPLICATION_JAVA_ARCHIVE, "jar");
255
+
256
+		mimeTypeToExtension.entrySet().forEach(entry -> {
257
+			extensionToMimeType.put(entry.getValue(), entry.getKey());
258
+		});
254 259
 	}
255 260
 
256 261
 	public static String getExtension(String mimeType) {
257
-		return mapping.getOrDefault(mimeType, "data");
262
+		return mimeTypeToExtension.getOrDefault(mimeType, "data");
263
+	}
264
+
265
+	public static String getMimeType(String name) {
266
+		String extension = name.contains(".") ? name.substring(name.lastIndexOf(".") + 1) : name;
267
+		return extensionToMimeType.getOrDefault(extension, "application/octet-stream");
258 268
 	}
259 269
 }
... ...
@@ -326,14 +326,14 @@ public class EventRequestHandler extends RequestHandler implements IIMIPHandler
326 326
 				data.put("tz", tz.getDisplayName(new Locale(settings.get("lang"))));
327 327
 			}
328 328
 
329
-			CalendarMail m = new CalendarMail();
330
-			m.from = from;
331
-			m.sender = from;
332
-			m.to = new MailboxList(Arrays.asList(to), true);
333
-			m.method = method;
334
-			m.ics = icsPart;
335
-			m.html = new CalendarMailHelper().buildBody(templateName, settings.get("lang"), resolver, data);
336
-			m.subject = subject;
329
+			CalendarMail m = new CalendarMail.CalendarMailBuilder() //
330
+					.from(from) //
331
+					.sender(from) //
332
+					.to(new MailboxList(Arrays.asList(to), true)) //
333
+					.method(method) //
334
+					.ics(icsPart) //
335
+					.html(new CalendarMailHelper().buildBody(templateName, settings.get("lang"), resolver, data)) //
336
+					.subject(subject).build();
337 337
 			return m.getMessage();
338 338
 
339 339
 		} catch (TemplateException e) {
... ...
@@ -26,7 +26,7 @@
26 26
 			<arguments>
27 27
 				<dictionary>
28 28
 					<key>LaunchConfigHandle</key>
29
-					<value>&lt;project&gt;/.externalToolBuilders/com.google.gdt.eclipse.core.webAppProjectValidator (40).launch</value>
29
+					<value>&lt;project&gt;/.externalToolBuilders/com.google.gdt.eclipse.core.webAppProjectValidator (42).launch</value>
30 30
 				</dictionary>
31 31
 			</arguments>
32 32
 		</buildCommand>
... ...
@@ -36,7 +36,7 @@
36 36
 			<arguments>
37 37
 				<dictionary>
38 38
 					<key>LaunchConfigHandle</key>
39
-					<value>&lt;project&gt;/.externalToolBuilders/com.google.gwt.eclipse.core.gwtProjectValidator (40).launch</value>
39
+					<value>&lt;project&gt;/.externalToolBuilders/com.google.gwt.eclipse.core.gwtProjectValidator (42).launch</value>
40 40
 				</dictionary>
41 41
 			</arguments>
42 42
 		</buildCommand>
... ...
@@ -4,14 +4,15 @@ Bundle-Name: Tests
4 4
 Bundle-SymbolicName: net.bluemind.attachment.api.gwt;singleton:=true
5 5
 Bundle-Version: 4.1.0.qualifier
6 6
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
7
-Require-Bundle: net.bluemind.core.commons.gwt;bundle-version="4.1.0";visibility:=reexport,
7
+Require-Bundle: net.bluemind.attachment.api;bundle-version="4.1.0",
8
+ net.bluemind.restbus.api.gwt;bundle-version="4.1.0",
8 9
  com.google.gwt.user;resolution:=optional,
9
- net.bluemind.attachment.api;bundle-version="4.1.0";visibility:=reexport,
10
+ net.bluemind.core.commons.gwt;bundle-version="4.1.0"
10 11
  net.bluemind.user.api.gwt;bundle-version="4.1.0";visibility:=reexport,
11 12
  net.bluemind.restbus.api.gwt;bundle-version="4.1.0";visibility:=reexport
12
-Export-Package: net.bluemind.attachment,
13
- net.bluemind.attachment.api.gwt.endpoint,
14
- net.bluemind.attachment.api.gwt.serder,
13
+Export-Package: net.bluemind.attachment.api.gwt.endpoint,
14
+ net.bluemind.attachment.api.gwt.js,
15
+ net.bluemind.attachment.api.gwt.serder
15 16
  net.bluemind.attachment.api.gwt.js
16 17
 Automatic-Module-Name: net.bluemind.attachment.api.gwt
17 18
 
... ...
@@ -12,6 +12,7 @@ Require-Bundle: net.bluemind.core.commons.gwt;bundle-version="4.1.0",
12 12
  com.google.gwt.user;resolution:=optional,
13 13
  net.bluemind.user.api.gwt;bundle-version="4.1.0";visibility:=reexport,
14 14
  net.bluemind.attachment.api.gwt;bundle-version="4.1.0"
15
+ net.bluemind.attachment.api;bundle-version="4.1.0"
15 16
 Export-Package: net.bluemind.icalendar.api.gwt.js,
16 17
  net.bluemind.icalendar.api.gwt.serder
17 18
 
... ...
@@ -13,6 +13,7 @@
13 13
 	<modules>
14 14
 		<module>net.bluemind.core.commons.gwt</module>
15 15
 		<module>net.bluemind.core.container.api.gwt</module>
16
+		<module>net.bluemind.attachment.api.gwt</module>
16 17
 		<module>net.bluemind.core.task.api.gwt</module>
17 18
 		<module>net.bluemind.tag.api.gwt</module>
18 19
 		<module>net.bluemind.hsm.api.gwt</module>