View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2010  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18   */
19  
20  package davmail.exchange.ews;
21  
22  import davmail.exchange.ExchangeSession;
23  import org.apache.log4j.Logger;
24  
25  import java.io.IOException;
26  import java.io.Writer;
27  import java.text.SimpleDateFormat;
28  import java.util.Date;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.Locale;
32  
33  /**
34   * Handle calendar item recurrence update
35   * See <a href="https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/recurrence-patterns-and-ews">...</a>
36   */
37  public class RecurrenceFieldUpdate extends FieldUpdate {
38      public static final Logger LOGGER = Logger.getLogger(RecurrenceFieldUpdate.class);
39      static final HashMap<String, String> calDayToDayOfWeek = new HashMap<>();
40      static {
41          calDayToDayOfWeek.put("SU", "Sunday");
42          calDayToDayOfWeek.put("MO", "Monday");
43          calDayToDayOfWeek.put("TU", "Tuesday");
44          calDayToDayOfWeek.put("WE", "Wednesday");
45          calDayToDayOfWeek.put("TH", "Thursday");
46          calDayToDayOfWeek.put("FR", "Friday");
47          calDayToDayOfWeek.put("SA", "Saturday");
48      }
49      protected Date startDate;
50      protected Date endDate;
51      protected String count;
52      protected HashSet<String> byDays = null;
53      protected String dayOfMonth;
54      protected String month;
55      protected String dayOfWeekIndex;
56      protected String firstDayOfWeek;
57  
58      public void setStartDate(Date startDate) {
59          this.startDate = startDate;
60      }
61  
62      public void setEndDate(Date endDate) {
63          this.endDate = endDate;
64      }
65  
66      public void setByDay(String[] days) {
67          byDays = new HashSet<>();
68          for (String day: days) {
69              // strip leading positional index (e.g. "2MO" -> "MO", "-1FR" -> "FR")
70              String dayCode = day.replaceAll("^-?\\d+", "");
71              String value = calDayToDayOfWeek.get(dayCode);
72              if (value == null) {
73                  LOGGER.warn("Invalid day value: "+day);
74              } else {
75                  byDays.add(value);
76              }
77          }
78          // fix recurrence for Thunderbird weekday pattern
79          if (recurrencePattern == RecurrencePattern.DailyRecurrence) {
80              recurrencePattern = RecurrencePattern.WeeklyRecurrence;
81          }
82      }
83  
84      public void setDayOfMonth(String dayOfMonth) {
85          this.dayOfMonth = dayOfMonth;
86      }
87  
88      public void setMonthFromByMonth(String byMonth) {
89          this.month = convertByMonthToName(byMonth);
90      }
91  
92      public void setDayOfWeekIndexFromByDay(String byDay) {
93          this.dayOfWeekIndex = extractDayOfWeekIndex(byDay);
94      }
95  
96      public void setFirstDayOfWeek(String firstDayOfWeek) {
97          this.firstDayOfWeek = firstDayOfWeek;
98      }
99  
100     public void setCount(String count) {
101         this.count = count;
102     }
103 
104     public enum RecurrencePattern {DailyRecurrence, WeeklyRecurrence, AbsoluteMonthlyRecurrence, AbsoluteYearlyRecurrence, RelativeMonthlyRecurrence, RelativeYearlyRecurrence}
105 
106     RecurrencePattern recurrencePattern;
107     int recurrenceInterval = 1;
108 
109     /**
110      * Create a recurrence field update.
111      */
112     public RecurrenceFieldUpdate() {
113     }
114 
115     public void setRecurrencePattern(RecurrencePattern recurrencePattern) {
116         this.recurrencePattern = recurrencePattern;
117     }
118 
119     public void setRecurrencePattern(String value) {
120         if ("DAILY".equals(value)) {
121             setRecurrencePattern(RecurrenceFieldUpdate.RecurrencePattern.DailyRecurrence);
122         } else if ("WEEKLY".equals(value)) {
123             setRecurrencePattern(RecurrencePattern.WeeklyRecurrence);
124         } else if ("MONTHLY".equals(value)) {
125             setRecurrencePattern(RecurrencePattern.AbsoluteMonthlyRecurrence);
126         } else if ("YEARLY".equals(value)) {
127             setRecurrencePattern(RecurrencePattern.AbsoluteYearlyRecurrence);
128         } else if ("RELATIVEMONTHLY".equals(value)) {
129             setRecurrencePattern(RecurrencePattern.RelativeMonthlyRecurrence);
130         } else if ("RELATIVEYEARLY".equals(value)) {
131             setRecurrencePattern(RecurrencePattern.RelativeYearlyRecurrence);
132         }
133     }
134 
135     public void setRecurrenceInterval(String interval) {
136         this.recurrenceInterval = Integer.parseInt(interval);
137     }
138 
139     /**
140      * Write this field to request writer.
141      *
142      * @param itemType item type
143      * @param writer   request writer
144      * @throws IOException on error
145      */
146     public void write(String itemType, Writer writer) throws IOException {
147         if (itemType != null) {
148             writer.write("<t:Set");
149             writer.write(itemType);
150             writer.write("Field>");
151         }
152 
153         // do not try to set empty value on create
154         if (itemType != null) {
155             writer.write("<t:FieldURI FieldURI=\"calendar:Recurrence\"/>");
156             writer.write("<t:CalendarItem>");
157 
158             writer.write("<t:Recurrence>");
159             writer.write("<t:");
160             writer.write(recurrencePattern.toString());
161             writer.write(">");
162             if (recurrencePattern == RecurrencePattern.AbsoluteYearlyRecurrence) {
163                 writeDayOfMonth(writer);
164                 writeMonth(writer);
165             } else if (recurrencePattern == RecurrencePattern.AbsoluteMonthlyRecurrence) {
166                 writeInterval(writer);
167                 writeDayOfMonth(writer);
168             } else if (recurrencePattern == RecurrencePattern.RelativeYearlyRecurrence) {
169                 writeDaysOfWeek(writer);
170                 writeDayOfWeekIndex(writer);
171                 writeMonth(writer);
172             } else if (recurrencePattern == RecurrencePattern.RelativeMonthlyRecurrence) {
173                 writeInterval(writer);
174                 writeDaysOfWeek(writer);
175                 writeDayOfWeekIndex(writer);
176             } else if (recurrencePattern == RecurrencePattern.WeeklyRecurrence) {
177                 writeInterval(writer);
178                 writeDaysOfWeek(writer);
179                 if (firstDayOfWeek != null) {
180                     writeFirstDayOfWeek(writer);
181                 }
182             } else if (recurrencePattern == RecurrencePattern.DailyRecurrence) {
183                 writeInterval(writer);
184             }
185             writer.write("</t:");
186             writer.write(recurrencePattern.toString());
187             writer.write(">");
188             writeStartEnd(writer);
189             writer.write("</t:Recurrence>");
190 
191             writer.write("</t:CalendarItem>");
192         }
193 
194         if (itemType != null) {
195             writer.write("</t:Set");
196             writer.write(itemType);
197             writer.write("Field>");
198         }
199     }
200 
201     private void writeInterval(Writer writer) throws IOException {
202         writer.write("<t:Interval>");
203         writer.write(String.valueOf(recurrenceInterval));
204         writer.write("</t:Interval>");
205     }
206 
207     private void writeStartEnd(Writer writer) throws IOException {
208         if (count != null) {
209             writer.write("<t:NumberedRecurrence>");
210             writer.write("<t:StartDate>");
211             writer.write(getFormattedDate(startDate));
212             writer.write("</t:StartDate>");
213             writer.write("<t:NumberOfOccurrences>");
214             writer.write(count);
215             writer.write("</t:NumberOfOccurrences>");
216             writer.write("</t:NumberedRecurrence>");
217         } else if (endDate == null) {
218             writer.write("<t:NoEndRecurrence><t:StartDate>");
219             writer.write(getFormattedDate(startDate));
220             writer.write("</t:StartDate></t:NoEndRecurrence>");
221         } else {
222             writer.write("<t:EndDateRecurrence>");
223             writer.write("<t:StartDate>");
224             writer.write(getFormattedDate(startDate));
225             writer.write("</t:StartDate>");
226             writer.write("<t:EndDate>");
227             writer.write(getFormattedDate(endDate));
228             writer.write("</t:EndDate>");
229             writer.write("</t:EndDateRecurrence>");
230 
231         }
232     }
233 
234     private void writeDaysOfWeek(Writer writer) throws IOException {
235         writer.write("<t:DaysOfWeek>");
236         if (byDays != null) {
237             boolean first = true;
238             for (String dayOfeek:byDays) {
239                 if (first) {
240                     first = false;
241                 } else {
242                     writer.write(' ');
243                 }
244                 writer.write(dayOfeek);
245             }
246         } else {
247             writer.write(getDayOfWeek());
248         }
249         writer.write("</t:DaysOfWeek>");
250     }
251 
252     private void writeDayOfMonth(Writer writer) throws IOException {
253         writer.write("<t:DayOfMonth>");
254         writer.write(dayOfMonth != null ? dayOfMonth : getDayOfMonth());
255         writer.write("</t:DayOfMonth>");
256     }
257 
258     private void writeMonth(Writer writer) throws IOException {
259         writer.write("<t:Month>");
260         writer.write(month != null ? month : getMonth());
261         writer.write("</t:Month>");
262     }
263 
264     private void writeDayOfWeekIndex(Writer writer) throws IOException {
265         writer.write("<t:DayOfWeekIndex>");
266         writer.write(dayOfWeekIndex != null ? dayOfWeekIndex : "First");
267         writer.write("</t:DayOfWeekIndex>");
268     }
269 
270     private void writeFirstDayOfWeek(Writer writer) throws IOException {
271         writer.write("<t:FirstDayOfWeek>");
272         writer.write(firstDayOfWeek);
273         writer.write("</t:FirstDayOfWeek>");
274     }
275 
276     private String getDayOfWeek() {
277         SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEEE", Locale.ENGLISH);
278         simpleDateFormat.setTimeZone(ExchangeSession.GMT_TIMEZONE);
279         return simpleDateFormat.format(startDate);
280     }
281 
282     private String getMonth() {
283         SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MMMM", Locale.ENGLISH);
284         simpleDateFormat.setTimeZone(ExchangeSession.GMT_TIMEZONE);
285         return simpleDateFormat.format(startDate);
286     }
287 
288     private String getDayOfMonth() {
289         SimpleDateFormat simpleDateFormat = new SimpleDateFormat("d", Locale.ENGLISH);
290         simpleDateFormat.setTimeZone(ExchangeSession.GMT_TIMEZONE);
291         return simpleDateFormat.format(startDate);
292     }
293 
294     private String getFormattedDate(Date date) {
295         SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
296         simpleDateFormat.setTimeZone(ExchangeSession.GMT_TIMEZONE);
297         return simpleDateFormat.format(date);
298     }
299 
300     private String extractDayOfWeekIndex(String byDay) {
301         // extract numeric prefix from first entry, e.g. "2MO" -> "Second", "-1FR" -> "Last"
302         java.util.regex.Matcher m = java.util.regex.Pattern.compile("^(-?\\d+)").matcher(byDay);
303         if (m.find()) {
304             switch (m.group(1)) {
305                 case "2":  return "Second";
306                 case "3":  return "Third";
307                 case "4":  return "Fourth";
308                 case "-1": return "Last";
309                 default:   return "First";
310             }
311         }
312         return "First";
313     }
314 
315     private static final String[] MONTH_NAMES = {
316             "January","February","March","April","May","June",
317             "July","August","September","October","November","December"
318     };
319 
320     private String convertByMonthToName(String byMonth) {
321         try {
322             int m = Integer.parseInt(byMonth);
323             if (m >= 1 && m <= 12) return MONTH_NAMES[m - 1];
324         } catch (NumberFormatException e) {
325             // ignore
326         }
327         return byMonth;
328     }
329 
330 
331 }