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.graph;
21  
22  import org.apache.log4j.Logger;
23  
24  import java.util.HashMap;
25  import java.util.Map;
26  
27  /**
28   * Map field names to actual graph properties.
29   * Properties can be native graph properties or extended properties mapped from MAPI ids.
30   * Some properties are searchable only through MAPI, others are straightforward.
31   */
32  public class GraphField {
33      protected static final Logger LOGGER = Logger.getLogger("davmail.exchange.graph.GraphField");
34  
35      @SuppressWarnings({"UnusedDeclaration"})
36      protected enum PropertyType {
37          ApplicationTime, ApplicationTimeArray, Binary, BinaryArray, Boolean, CLSID, CLSIDArray, Currency, CurrencyArray,
38          Double, DoubleArray, Error, Float, FloatArray, Integer, IntegerArray, Long, LongArray, Null, Object,
39          ObjectArray, Short, ShortArray, SystemTime, SystemTimeArray, String, StringArray
40      }
41  
42      @SuppressWarnings({"UnusedDeclaration"})
43      public enum DistinguishedPropertySetType {
44          Meeting, Appointment, Common, PublicStrings, Address, InternetHeaders, CalendarAssistant, UnifiedMessaging, Task
45      }
46  
47      private static final Map<String, GraphField> FIELD_MAP = new HashMap<>();
48  
49      static {
50          addFieldMap("id");
51  
52          // folder extended properties
53          addFieldMap("folderlastmodified", 0x3008, PropertyType.SystemTime);
54          addFieldMap("folderclass", 0x3613, PropertyType.String);
55          addFieldMap("ctag", 0x670a, PropertyType.SystemTime); // PR_LOCAL_COMMIT_TIME_MAX
56          addFieldMap("uidNext", 0x6751, PropertyType.Integer); // PR_ARTICLE_NUM_NEXT
57  
58          // message properties
59          addFieldMap("isRead", PropertyType.Boolean);
60          addFieldMap("isDraft");
61          addFieldMap("receivedDateTime", PropertyType.SystemTime);
62          addFieldMap("date", "receivedDateTime", PropertyType.SystemTime);
63          // addFieldMap("date", 0x0e06, PropertyType.SystemTime);  // PidTagOriginalDeliveryTime
64          addFieldMap("lastmodified", "lastModifiedDateTime", PropertyType.SystemTime);
65  
66          // message extended properties
67          addFieldMap("uid", 0x0FF9, PropertyType.Binary); // PR_RECORD_KEY
68          addFieldMap("messageFlags", 0x0e07, PropertyType.Integer); // PR_MESSAGE_FLAGS
69          addFieldMap("imapUid", 0x0e23, PropertyType.Integer);
70          addFieldMap("messageSize", 0x0e08, PropertyType.Integer);
71          addFieldMap("etag", 0x3008, PropertyType.SystemTime);
72          addFieldMap("contentclass", DistinguishedPropertySetType.InternetHeaders, "content-class");
73          addFieldMap("iconIndex", 0x1080, PropertyType.Integer); // PR_ICON_INDEX
74  
75          addFieldMap("keywords", "categories", PropertyType.StringArray); // special case, mapped to message categories
76          addFieldMap( "categories", PropertyType.StringArray);
77  
78          addFieldMap("@odata.etag");
79          addFieldMap("changeKey", "@odata.etag");
80  
81          addFieldMap("read", "isRead", PropertyType.Boolean);
82          addFieldMap("messageheaders", 0x007D, PropertyType.String);
83          addFieldMap("internetMessageHeaders");
84  
85          addFieldMap("to", DistinguishedPropertySetType.InternetHeaders, "to");
86          addFieldMap("cc", DistinguishedPropertySetType.InternetHeaders, "cc");
87          addFieldMap("from", DistinguishedPropertySetType.InternetHeaders, "from");
88  
89          addFieldMap("permanenturl", 0x670E, PropertyType.String); //PR_FLAT_URL_NAME
90          addFieldMap("lastVerbExecuted", 0x1081, PropertyType.Integer); // PR_ACTION_FLAG
91          addFieldMap("junk", 0x1083, PropertyType.Integer);
92          addFieldMap("flagStatus", 0x1090, PropertyType.Integer); // PidTagFlagStatus
93          addFieldMap("deleted", DistinguishedPropertySetType.Common, 0x8570, PropertyType.Integer); // PidLidImapDeleted
94  
95          addFieldMap("urlcompname", 0x10f3, PropertyType.String);
96  
97          // events and tasks https://learn.microsoft.com/en-us/graph/api/resources/event?view=graph-rest-beta
98  
99          addFieldMap("processed",  0x65e8, PropertyType.Boolean);
100 
101         addFieldMap("instancetype", DistinguishedPropertySetType.PublicStrings, "urn:schemas:calendar:instancetype", PropertyType.Integer);
102 
103         addFieldMap("isrecurring", DistinguishedPropertySetType.Appointment, 0x8223, PropertyType.Boolean); // PidLidRecurring
104 
105         addFieldMap("dtstart", "start");
106         addFieldMap("dtend", "end");
107         addFieldMap("subject");
108         addFieldMap("status");
109         addFieldMap("taskstatus");
110         addFieldMap("importance");
111         addFieldMap("createdDateTime", "createdDateTime", PropertyType.SystemTime);
112         addFieldMap("lastModifiedDateTime", "lastModifiedDateTime", PropertyType.SystemTime);
113 
114         // tasks
115         addFieldMap("summary", "title"); // title conflicts with job title
116         addFieldMap("dueDateTime");
117         addFieldMap("startDateTime");
118         addFieldMap("completedDateTime");
119         addFieldMap("datecompleted", "completedDateTime");
120         addFieldMap("objecttype");
121 
122         addFieldMap("originalStartTimeZone");
123 
124         addFieldMap("allowNewTimeProposals");
125         addFieldMap("attendees");
126         addFieldMap("bodyPreview");
127         addFieldMap("body");
128         addFieldMap("end");
129         addFieldMap("exceptionOccurrences");
130         addFieldMap("cancelledOccurrences");
131         addFieldMap("hasAttachments");
132         addFieldMap("isOnlineMeeting");
133         addFieldMap("isOrganizer");
134         addFieldMap("location");
135         addFieldMap("organizer");
136         addFieldMap("originalStart");
137         addFieldMap("recurrence");
138         addFieldMap("reminderMinutesBeforeStart");
139         addFieldMap("start");
140         addFieldMap("type");
141 
142         addFieldMap("iCalUId");
143         addFieldMap("transactionId");
144         //addFieldMap("iCalUId", DistinguishedPropertySetType.Meeting, 0x023, PropertyType.Binary); // PidLidGlobalObjectId
145 
146         addFieldMap("showAs");
147         addFieldMap("isAllDay", "isAllDay", PropertyType.Boolean);
148         addFieldMap("responseRequested");
149         addFieldMap("responseStatus");
150 
151         addFieldMap("isReminderOn", "isReminderOn", PropertyType.Boolean);
152 
153         addFieldMap("xmozsendinvitations", DistinguishedPropertySetType.PublicStrings, "xmozsendinvitations");
154         addFieldMap("xmozlastack", DistinguishedPropertySetType.PublicStrings, "xmozlastack");
155         addFieldMap("xmozsnoozetime", DistinguishedPropertySetType.PublicStrings, "xmozsnoozetime");
156 
157         // contacts https://learn.microsoft.com/en-us/graph/api/resources/contact
158         addFieldMap("displayname", "displayName"); // MAPI addFieldMap("displayname", 0x3001, PropertyType.String);
159 
160         addFieldMap("outlookmessageclass", 0x001A, PropertyType.String);
161         addFieldMap("fileas", "fileAs");
162 
163         addFieldMap("cn", "displayName");
164         addFieldMap("sn", "surname");
165         addFieldMap("givenName", "givenName");
166         addFieldMap("middlename", "middleName");
167         addFieldMap("personaltitle", "title"); // MAPI addFieldMap("personaltitle", 0x3A45, PropertyType.String);
168         addFieldMap("title", "jobTitle"); // MAPI /addFieldMap("title", 0x3A17, PropertyType.String);
169 
170         addFieldMap("description", 0x1000, PropertyType.String); // MAPI property for personalNotes
171 
172         addFieldMap("namesuffix", "generation");
173         addFieldMap("nickname", "nickName");
174         addFieldMap("mobile", 0x3A1C, PropertyType.String);
175         addFieldMap("telephoneNumber", 0x3A08, PropertyType.String);
176         addFieldMap("facsimiletelephonenumber", 0x3A24, PropertyType.String);
177         addFieldMap("pager", 0x3A21, PropertyType.String);
178 
179         addFieldMap("homeCity", 0x3A59, PropertyType.String);
180         addFieldMap("homeCountry", 0x3A5A, PropertyType.String);
181         addFieldMap("homePhone", 0x3A09, PropertyType.String);
182         addFieldMap("homePostalCode", 0x3A5B, PropertyType.String);
183         addFieldMap("homeState", 0x3A5C, PropertyType.String);
184         addFieldMap("homeStreet", 0x3A5D, PropertyType.String);
185         addFieldMap("homepostofficebox", 0x3A5E, PropertyType.String);
186 
187         addFieldMap("postofficebox", DistinguishedPropertySetType.Address, 0x804A, PropertyType.String);
188         addFieldMap("roomnumber", "officeLocation"); // MAPI addFieldMap("roomnumber", 0x3A19, PropertyType.String);
189         addFieldMap("street", DistinguishedPropertySetType.Address, 0x8045, PropertyType.String);
190 
191         addFieldMap("l", DistinguishedPropertySetType.Address, 0x8046, PropertyType.String);
192         addFieldMap("st", DistinguishedPropertySetType.Address, 0x8047, PropertyType.String);
193         addFieldMap("postalcode", DistinguishedPropertySetType.Address, 0x8048, PropertyType.String);
194         addFieldMap("co", DistinguishedPropertySetType.Address, 0x8049, PropertyType.String);
195 
196         // addFieldMap("o", DistinguishedPropertySetType.Address, 0x3A16, PropertyType.String); ??
197         //addFieldMap("o", DistinguishedPropertySetType.Address, 0x3A18, PropertyType.String); // department
198         addFieldMap("o", "companyName"); // department
199 
200         addFieldMap("department", "department");// MAPI addFieldMap("department", 0x3A18, PropertyType.String);
201 
202         addFieldMap("smtpemail1", DistinguishedPropertySetType.Address, 0x8083, PropertyType.String); // Email1EmailAddress
203         addFieldMap("smtpemail2", DistinguishedPropertySetType.Address, 0x8093, PropertyType.String); // Email2EmailAddress
204         addFieldMap("smtpemail3", DistinguishedPropertySetType.Address, 0x80A3, PropertyType.String); // Email3EmailAddress
205 
206         addFieldMap("businesshomepage", 0x3A51, PropertyType.String);
207         addFieldMap("personalHomePage", 0x3A50, PropertyType.String);
208 
209 
210         addFieldMap("extensionattribute1", DistinguishedPropertySetType.Address, 0x804F, PropertyType.String);
211         addFieldMap("extensionattribute2", DistinguishedPropertySetType.Address, 0x8050, PropertyType.String);
212         addFieldMap("extensionattribute3", DistinguishedPropertySetType.Address, 0x8051, PropertyType.String);
213         addFieldMap("extensionattribute4", DistinguishedPropertySetType.Address, 0x8052, PropertyType.String);
214 
215         addFieldMap("bday", "birthday", PropertyType.SystemTime); // MAPI addFieldMap("bday", DistinguishedPropertySetType.Address, 0x3A42, PropertyType.SystemTime);
216         addFieldMap("anniversary", "weddingAnniversary", PropertyType.SystemTime);  // MAPI addFieldMap("anniversary", DistinguishedPropertySetType.Address, 0x3A41, PropertyType.SystemTime);
217 
218         addFieldMap("otherstreet", 0x3A63, PropertyType.String);
219         addFieldMap("otherstate", 0x3A62, PropertyType.String);
220         addFieldMap("otherpostofficebox", 0x3A64, PropertyType.String);
221         addFieldMap("otherpostalcode", 0x3A61, PropertyType.String);
222         addFieldMap("othercountry", 0x3A60, PropertyType.String);
223         addFieldMap("othercity", 0x3A5F, PropertyType.String);
224 
225         addFieldMap("secretarycn", "assistantName"); // MAPI addFieldMap("secretarycn", 0x3A30, PropertyType.String);
226         addFieldMap("spousecn", "spouseName"); // MAPI addFieldMap("spousecn", 0x3A48, PropertyType.String);
227 
228         addFieldMap("private", DistinguishedPropertySetType.Common, 0x8506, PropertyType.Boolean);
229 
230         addFieldMap("im", DistinguishedPropertySetType.Address, 0x8062, PropertyType.String);
231         addFieldMap("fburl", DistinguishedPropertySetType.Address, 0x80D8, PropertyType.String);
232 
233         addFieldMap("haspicture", DistinguishedPropertySetType.Address, 0x8015, PropertyType.Boolean);
234 
235         addFieldMap("manager");// MAPI addFieldMap("manager", 0x3A4E, PropertyType.String);
236         addFieldMap("profession"); // MAPI addFieldMap("profession", 0x3A46, PropertyType.String);
237 
238         addFieldMap("othermobile", 0x3A1E, PropertyType.String); // PidTagCarTelephoneNumber
239         addFieldMap("otherTelephone", 0x3A21, PropertyType.String); // PidTagPagerTelephoneNumber
240 
241         addFieldMap("gender");// MAPI addFieldMap("gender", 0x3A4D, PropertyType.Short);
242 
243         addFieldMap("sensitivity", 0x0036, PropertyType.Integer);
244 
245         // does not map to anything over graph
246         addFieldMap("msexchangecertificate");
247         addFieldMap("usersmimecertificate");
248 
249     }
250 
251     protected static void addFieldMap(String alias, GraphField field) {
252         if (FIELD_MAP.containsKey(alias)) {
253             throw new IllegalArgumentException("Duplicate field alias: " + alias);
254         }
255         FIELD_MAP.put(alias, field);
256     }
257 
258     protected static void addFieldMap(String alias) {
259         addFieldMap(alias, new GraphField(alias));
260     }
261 
262     protected static void addFieldMap(String alias, String graphId) {
263         addFieldMap(alias, new GraphField(alias, graphId));
264     }
265 
266     protected static void addFieldMap(String alias, PropertyType propertyType) {
267         addFieldMap(alias, new GraphField(alias, alias, propertyType));
268     }
269 
270     protected static void addFieldMap(String alias, String graphId, PropertyType propertyType) {
271         addFieldMap(alias, new GraphField(alias, graphId, propertyType));
272     }
273 
274     protected static void addFieldMap(String alias, int intPropertyTag, PropertyType propertyType) {
275         addFieldMap(alias, new GraphField(alias, intPropertyTag, propertyType));
276     }
277 
278     protected static void addFieldMap(String alias, DistinguishedPropertySetType distinguishedPropertySetId, int intPropertyTag, PropertyType propertyType) {
279         addFieldMap(alias, new GraphField(alias, distinguishedPropertySetId, intPropertyTag, propertyType));
280     }
281 
282     protected static void addFieldMap(String alias, DistinguishedPropertySetType distinguishedPropertySetId, String propertyName, PropertyType propertyType) {
283         addFieldMap(alias, new GraphField(alias, distinguishedPropertySetId, propertyName, propertyType));
284     }
285 
286     protected static void addFieldMap(String alias, DistinguishedPropertySetType distinguishedPropertySetId, String propertyName) {
287         addFieldMap(alias, new GraphField(alias, distinguishedPropertySetId, propertyName));
288     }
289 
290     protected String alias;
291     protected String graphId;
292 
293     protected DistinguishedPropertySetType distinguishedPropertySetId;
294 
295     protected String propertyName;
296     protected int propertyId;
297     protected String propertyTag;
298     protected PropertyType propertyType;
299 
300 
301     private boolean extended = false;
302 
303     /**
304      * Basic graph field.
305      * @param alias property alias
306      */
307     public GraphField(String alias) {
308         this.alias = alias;
309         this.graphId = alias;
310     }
311 
312     public GraphField(String alias, String graphId) {
313         this.alias = alias;
314         this.graphId = graphId;
315     }
316 
317 
318     protected GraphField(String alias, String graphId, PropertyType propertyType) {
319         this.alias = alias;
320         this.graphId = graphId;
321         this.propertyType = propertyType;
322     }
323 
324     /**
325      * Header field or categories field.
326      * @param alias property alias
327      * @param distinguishedPropertySetId property type
328      */
329     public GraphField(String alias, DistinguishedPropertySetType distinguishedPropertySetId, String propertyName) {
330         this.extended = true;
331 
332         this.alias = alias;
333         this.propertyType = PropertyType.String;
334         this.distinguishedPropertySetId = distinguishedPropertySetId;
335         this.propertyName = propertyName;
336         this.graphId = buildGraphId();
337     }
338 
339     /**
340      * Create an extended field.
341      *
342      * @param alias          property alias
343      * @param intPropertyTag property tag as int
344      * @param propertyType   property type
345      */
346     protected GraphField(String alias, int intPropertyTag, PropertyType propertyType) {
347         this.extended = true;
348 
349         this.alias = alias;
350         this.propertyTag = "0x" + Integer.toHexString(intPropertyTag);
351         this.propertyType = propertyType;
352         this.graphId = buildGraphId();
353     }
354 
355     protected GraphField(String alias, DistinguishedPropertySetType distinguishedPropertySetId, int propertyId, PropertyType propertyType) {
356         this.extended = true;
357 
358         this.alias = alias;
359         this.distinguishedPropertySetId = distinguishedPropertySetId;
360         this.propertyType = propertyType;
361         this.propertyId = propertyId;
362         this.graphId = buildGraphId();
363     }
364 
365     protected GraphField(String alias, DistinguishedPropertySetType distinguishedPropertySetId, String propertyName, PropertyType propertyType) {
366         this.extended = true;
367 
368         this.alias = alias;
369         this.distinguishedPropertySetId = distinguishedPropertySetId;
370         this.propertyName = propertyName;
371         this.propertyType = propertyType;
372         this.graphId = buildGraphId();
373     }
374 
375     /**
376      * Builds a graph identifier based on the property type, namespace, and associated property details.
377      * The resulting identifier is formatted based on specific rules that include the property type,
378      * namespace GUID (if available), and either the property name or property ID, or in certain cases, the property tag.
379      * See <a href="https://learn.microsoft.com/en-us/graph/api/resources/extended-properties-overview">Outlook extended properties overview</a>
380      *
381      * @return the constructed graph identifier as a String
382      */
383     private String buildGraphId() {
384         // PropertyId values may only be in one of the following formats:
385         // 'MapiPropertyType namespaceGuid Name propertyName', 'MapiPropertyType namespaceGuid Id propertyId' or 'MapiPropertyType propertyTag'.
386 
387         String namespaceGuid = null;
388         if (distinguishedPropertySetId != null) {
389             switch (distinguishedPropertySetId) {
390                 case PublicStrings:
391                     namespaceGuid = "{00020329-0000-0000-c000-000000000046}";
392                     break;
393                 case InternetHeaders:
394                     namespaceGuid = "{00020386-0000-0000-c000-000000000046}";
395                     break;
396                 case Common:
397                     namespaceGuid = "{00062008-0000-0000-c000-000000000046}";
398                     break;
399                 case Address:
400                     namespaceGuid = "{00062004-0000-0000-c000-000000000046}";
401                     break;
402                 case Appointment:
403                     namespaceGuid = "{00062002-0000-0000-C000-000000000046}";
404                     break;
405                 case Meeting:
406                     namespaceGuid = "{6ED8DA90-450B-101B-98DA-00AA003F1305}";
407                     break;
408                 case Task:
409                     namespaceGuid = "{00062003-0000-0000-c000-000000000046}";
410                     break;
411             }
412         }
413 
414 
415         StringBuilder buffer = new StringBuilder();
416         if (namespaceGuid != null) {
417             buffer.append(propertyType.name()).append(" ").append(namespaceGuid);
418             if (propertyName != null) {
419                 buffer.append(" Name ").append(propertyName);
420             } else {
421                 buffer.append(" Id ").append("0x").append(Integer.toHexString(propertyId));
422             }
423         } else if (propertyTag != null) {
424             buffer.append(propertyType.name()).append(" ").append(propertyTag);
425         }
426         return buffer.toString();
427     }
428 
429     public boolean isExtended() {
430         return extended;
431     }
432 
433     public String getAlias() {
434         return alias;
435     }
436 
437     public String getGraphId() {
438         if (graphId != null) {
439             return graphId;
440         }
441         throw new IllegalStateException("Graph id not set on field " + alias);
442         //return alias;
443     }
444 
445     public boolean isMultiValued() {
446         return propertyType == PropertyType.StringArray;
447     }
448 
449     public boolean isNumber() {
450         return propertyType == PropertyType.Short || propertyType == PropertyType.Integer || propertyType == PropertyType.Long || propertyType == PropertyType.Double;
451     }
452 
453     public boolean isBinary() {
454         return propertyType == PropertyType.Binary;
455     }
456 
457     public boolean isBoolean() {
458         return propertyType == PropertyType.Boolean;
459     }
460 
461     public boolean isDate() {
462         return propertyType == PropertyType.SystemTime;
463     }
464 
465     public boolean isInternetHeaders() {
466         return distinguishedPropertySetId == DistinguishedPropertySetType.InternetHeaders;
467     }
468 
469     /**
470      * Get field by alias.
471      * @param alias property alias
472      * @return field definition
473      */
474     public static GraphField get(String alias) {
475         if (!FIELD_MAP.containsKey(alias)) {
476             LOGGER.warn("Missing mapping for " + alias);
477             return new GraphField(alias);
478         }
479         return FIELD_MAP.get(alias);
480     }
481 
482     public static String getGraphId(String alias) {
483         return get(alias).getGraphId();
484     }
485 
486 }