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("instancetype", DistinguishedPropertySetType.PublicStrings, "urn:schemas:calendar:instancetype", PropertyType.Integer);
100 
101         addFieldMap("dtstart", "start");
102         addFieldMap("dtend", "end");
103         addFieldMap("subject");
104         addFieldMap("status");
105         addFieldMap("taskstatus");
106         addFieldMap("importance");
107         addFieldMap("createdDateTime", "createdDateTime", PropertyType.SystemTime);
108         addFieldMap("lastModifiedDateTime", "lastModifiedDateTime", PropertyType.SystemTime);
109 
110         addFieldMap("originalStartTimeZone");
111 
112         addFieldMap("allowNewTimeProposals");
113         addFieldMap("attendees");
114         addFieldMap("bodyPreview");
115         addFieldMap("body");
116         addFieldMap("end");
117         addFieldMap("exceptionOccurrences");
118         addFieldMap("cancelledOccurrences");
119         addFieldMap("hasAttachments");
120         addFieldMap("isOnlineMeeting");
121         addFieldMap("isOrganizer");
122         addFieldMap("location");
123         addFieldMap("organizer");
124         addFieldMap("originalStart");
125         addFieldMap("recurrence");
126         addFieldMap("reminderMinutesBeforeStart");
127         addFieldMap("start");
128         addFieldMap("type");
129 
130         addFieldMap("iCalUId");
131         addFieldMap("showAs");
132         addFieldMap("isAllDay", "isAllDay", PropertyType.Boolean);
133         addFieldMap("responseRequested");
134         addFieldMap("isReminderOn", "isReminderOn", PropertyType.Boolean);
135 
136         addFieldMap("xmozsendinvitations", DistinguishedPropertySetType.PublicStrings, "xmozsendinvitations");
137         addFieldMap("xmozlastack", DistinguishedPropertySetType.PublicStrings, "xmozlastack");
138         addFieldMap("xmozsnoozetime", DistinguishedPropertySetType.PublicStrings, "xmozsnoozetime");
139 
140         // contacts https://learn.microsoft.com/en-us/graph/api/resources/contact
141         addFieldMap("displayname", "displayName"); // MAPI addFieldMap("displayname", 0x3001, PropertyType.String);
142 
143         addFieldMap("outlookmessageclass", 0x001A, PropertyType.String);
144         addFieldMap("fileas", "fileAs");
145 
146         addFieldMap("cn", "displayName");
147         addFieldMap("sn", "surname");
148         addFieldMap("givenName", "givenName");
149         addFieldMap("middlename", "middleName");
150         addFieldMap("personaltitle", "title"); // MAPI addFieldMap("personaltitle", 0x3A45, PropertyType.String);
151         addFieldMap("title", "jobTitle"); // MAPI /addFieldMap("title", 0x3A17, PropertyType.String);
152 
153         addFieldMap("description", 0x1000, PropertyType.String); // MAPI property for personalNotes
154 
155         addFieldMap("namesuffix", "generation");
156         addFieldMap("nickname", "nickName");
157         addFieldMap("mobile", 0x3A1C, PropertyType.String);
158         addFieldMap("telephoneNumber", 0x3A08, PropertyType.String);
159         addFieldMap("facsimiletelephonenumber", 0x3A24, PropertyType.String);
160         addFieldMap("pager", 0x3A21, PropertyType.String);
161 
162         addFieldMap("homeCity", 0x3A59, PropertyType.String);
163         addFieldMap("homeCountry", 0x3A5A, PropertyType.String);
164         addFieldMap("homePhone", 0x3A09, PropertyType.String);
165         addFieldMap("homePostalCode", 0x3A5B, PropertyType.String);
166         addFieldMap("homeState", 0x3A5C, PropertyType.String);
167         addFieldMap("homeStreet", 0x3A5D, PropertyType.String);
168         addFieldMap("homepostofficebox", 0x3A5E, PropertyType.String);
169 
170         addFieldMap("postofficebox", DistinguishedPropertySetType.Address, 0x804A, PropertyType.String);
171         addFieldMap("roomnumber", "officeLocation"); // MAPI addFieldMap("roomnumber", 0x3A19, PropertyType.String);
172         addFieldMap("street", DistinguishedPropertySetType.Address, 0x8045, PropertyType.String);
173 
174         addFieldMap("l", DistinguishedPropertySetType.Address, 0x8046, PropertyType.String);
175         addFieldMap("st", DistinguishedPropertySetType.Address, 0x8047, PropertyType.String);
176         addFieldMap("postalcode", DistinguishedPropertySetType.Address, 0x8048, PropertyType.String);
177         addFieldMap("co", DistinguishedPropertySetType.Address, 0x8049, PropertyType.String);
178 
179         // addFieldMap("o", DistinguishedPropertySetType.Address, 0x3A16, PropertyType.String); ??
180         //addFieldMap("o", DistinguishedPropertySetType.Address, 0x3A18, PropertyType.String); // department
181         addFieldMap("o", "companyName"); // department
182 
183         addFieldMap("department", "department");// MAPI addFieldMap("department",  0x3A18, PropertyType.String);
184 
185         addFieldMap("smtpemail1", DistinguishedPropertySetType.Address, 0x8083, PropertyType.String); // Email1EmailAddress
186         addFieldMap("smtpemail2", DistinguishedPropertySetType.Address, 0x8093, PropertyType.String); // Email2EmailAddress
187         addFieldMap("smtpemail3", DistinguishedPropertySetType.Address, 0x80A3, PropertyType.String); // Email3EmailAddress
188 
189         addFieldMap("businesshomepage", 0x3A51, PropertyType.String);
190         addFieldMap("personalHomePage", 0x3A50, PropertyType.String);
191 
192 
193         addFieldMap("extensionattribute1", DistinguishedPropertySetType.Address, 0x804F, PropertyType.String);
194         addFieldMap("extensionattribute2", DistinguishedPropertySetType.Address, 0x8050, PropertyType.String);
195         addFieldMap("extensionattribute3", DistinguishedPropertySetType.Address, 0x8051, PropertyType.String);
196         addFieldMap("extensionattribute4", DistinguishedPropertySetType.Address, 0x8052, PropertyType.String);
197 
198         addFieldMap("bday", "birthday", PropertyType.SystemTime); // MAPI addFieldMap("bday", DistinguishedPropertySetType.Address, 0x3A42, PropertyType.SystemTime);
199         addFieldMap("anniversary", "weddingAnniversary", PropertyType.SystemTime);  // MAPI addFieldMap("anniversary", DistinguishedPropertySetType.Address, 0x3A41, PropertyType.SystemTime);
200 
201         addFieldMap("otherstreet", 0x3A63, PropertyType.String);
202         addFieldMap("otherstate", 0x3A62, PropertyType.String);
203         addFieldMap("otherpostofficebox", 0x3A64, PropertyType.String);
204         addFieldMap("otherpostalcode", 0x3A61, PropertyType.String);
205         addFieldMap("othercountry", 0x3A60, PropertyType.String);
206         addFieldMap("othercity", 0x3A5F, PropertyType.String);
207 
208         addFieldMap("secretarycn", "assistantName"); // MAPI addFieldMap("secretarycn", 0x3A30, PropertyType.String);
209         addFieldMap("spousecn", "spouseName"); // MAPI addFieldMap("spousecn", 0x3A48, PropertyType.String);
210 
211         addFieldMap("private", DistinguishedPropertySetType.Common, 0x8506, PropertyType.Boolean);
212 
213         addFieldMap("im", DistinguishedPropertySetType.Address, 0x8062, PropertyType.String);
214         addFieldMap("fburl", DistinguishedPropertySetType.Address, 0x80D8, PropertyType.String);
215 
216         addFieldMap("haspicture", DistinguishedPropertySetType.Address, 0x8015, PropertyType.Boolean);
217 
218         addFieldMap("manager");// MAPI addFieldMap("manager", 0x3A4E, PropertyType.String);
219         addFieldMap("profession"); // MAPI addFieldMap("profession", 0x3A46, PropertyType.String);
220 
221         addFieldMap("othermobile", 0x3A1E, PropertyType.String); // PidTagCarTelephoneNumber
222         addFieldMap("otherTelephone", 0x3A21, PropertyType.String); // PidTagPagerTelephoneNumber
223 
224         addFieldMap("gender");// MAPI addFieldMap("gender", 0x3A4D, PropertyType.Short);
225 
226         addFieldMap("sensitivity", 0x0036, PropertyType.Integer);
227 
228         // does not map to anything over graph
229         addFieldMap("msexchangecertificate");
230         addFieldMap("usersmimecertificate");
231 
232     }
233 
234     protected static void addFieldMap(String alias, GraphField field) {
235         if (FIELD_MAP.containsKey(alias)) {
236             throw new IllegalArgumentException("Duplicate field alias: " + alias);
237         }
238         FIELD_MAP.put(alias, field);
239     }
240 
241     protected static void addFieldMap(String alias) {
242         addFieldMap(alias, new GraphField(alias));
243     }
244 
245     protected static void addFieldMap(String alias, String graphId) {
246         addFieldMap(alias, new GraphField(alias, graphId));
247     }
248 
249     protected static void addFieldMap(String alias, PropertyType propertyType) {
250         addFieldMap(alias, new GraphField(alias, alias, propertyType));
251     }
252 
253     protected static void addFieldMap(String alias, String graphId, PropertyType propertyType) {
254         addFieldMap(alias, new GraphField(alias, graphId, propertyType));
255     }
256 
257     protected static void addFieldMap(String alias, int intPropertyTag, PropertyType propertyType) {
258         addFieldMap(alias, new GraphField(alias, intPropertyTag, propertyType));
259     }
260 
261     protected static void addFieldMap(String alias, DistinguishedPropertySetType distinguishedPropertySetId, int intPropertyTag, PropertyType propertyType) {
262         addFieldMap(alias, new GraphField(alias, distinguishedPropertySetId, intPropertyTag, propertyType));
263     }
264 
265     protected static void addFieldMap(String alias, DistinguishedPropertySetType distinguishedPropertySetId, String propertyName, PropertyType propertyType) {
266         addFieldMap(alias, new GraphField(alias, distinguishedPropertySetId, propertyName, propertyType));
267     }
268 
269     protected static void addFieldMap(String alias, DistinguishedPropertySetType distinguishedPropertySetId, String propertyName) {
270         addFieldMap(alias, new GraphField(alias, distinguishedPropertySetId, propertyName));
271     }
272 
273     protected String alias;
274     protected String graphId;
275 
276     protected DistinguishedPropertySetType distinguishedPropertySetId;
277 
278     protected String propertyName;
279     protected int propertyId;
280     protected String propertyTag;
281     protected PropertyType propertyType;
282 
283 
284     private boolean extended = false;
285 
286     /**
287      * Basic graph field.
288      * @param alias property alias
289      */
290     public GraphField(String alias) {
291         this.alias = alias;
292         this.graphId = alias;
293     }
294 
295     public GraphField(String alias, String graphId) {
296         this.alias = alias;
297         this.graphId = graphId;
298     }
299 
300 
301     protected GraphField(String alias, String graphId, PropertyType propertyType) {
302         this.alias = alias;
303         this.graphId = graphId;
304         this.propertyType = propertyType;
305     }
306 
307     /**
308      * Header field or categories field.
309      * @param alias property alias
310      * @param distinguishedPropertySetId property type
311      */
312     public GraphField(String alias, DistinguishedPropertySetType distinguishedPropertySetId, String propertyName) {
313         this.extended = true;
314 
315         this.alias = alias;
316         this.propertyType = PropertyType.String;
317         this.distinguishedPropertySetId = distinguishedPropertySetId;
318         this.propertyName = propertyName;
319         this.graphId = buildGraphId();
320     }
321 
322     /**
323      * Create extended field.
324      *
325      * @param alias          property alias
326      * @param intPropertyTag property tag as int
327      * @param propertyType   property type
328      */
329     protected GraphField(String alias, int intPropertyTag, PropertyType propertyType) {
330         this.extended = true;
331 
332         this.alias = alias;
333         this.propertyTag = "0x" + Integer.toHexString(intPropertyTag);
334         this.propertyType = propertyType;
335         this.graphId = buildGraphId();
336     }
337 
338     protected GraphField(String alias, DistinguishedPropertySetType distinguishedPropertySetId, int propertyId, PropertyType propertyType) {
339         this.extended = true;
340 
341         this.alias = alias;
342         this.distinguishedPropertySetId = distinguishedPropertySetId;
343         this.propertyType = propertyType;
344         this.propertyId = propertyId;
345         this.graphId = buildGraphId();
346     }
347 
348     protected GraphField(String alias, DistinguishedPropertySetType distinguishedPropertySetId, String propertyName, PropertyType propertyType) {
349         this.extended = true;
350 
351         this.alias = alias;
352         this.distinguishedPropertySetId = distinguishedPropertySetId;
353         this.propertyName = propertyName;
354         this.propertyType = propertyType;
355         this.graphId = buildGraphId();
356     }
357 
358     /**
359      * Builds a graph identifier based on the property type, namespace, and associated property details.
360      * The resulting identifier is formatted based on specific rules that include the property type,
361      * namespace GUID (if available), and either the property name or property ID, or in certain cases, the property tag.
362      * See <a href="https://learn.microsoft.com/en-us/graph/api/resources/extended-properties-overview">Outlook extended properties overview</a>
363      *
364      * @return the constructed graph identifier as a String
365      */
366     private String buildGraphId() {
367         // PropertyId values may only be in one of the following formats:
368         // 'MapiPropertyType namespaceGuid Name propertyName', 'MapiPropertyType namespaceGuid Id propertyId' or 'MapiPropertyType propertyTag'.
369 
370         String namespaceGuid = null;
371         if (distinguishedPropertySetId != null) {
372             switch (distinguishedPropertySetId) {
373                 case PublicStrings:
374                     namespaceGuid = "{00020329-0000-0000-c000-000000000046}";
375                     break;
376                 case InternetHeaders:
377                     namespaceGuid = "{00020386-0000-0000-c000-000000000046}";
378                     break;
379                 case Common:
380                     namespaceGuid = "{00062008-0000-0000-c000-000000000046}";
381                     break;
382                 case Address:
383                     namespaceGuid = "{00062004-0000-0000-c000-000000000046}";
384                     break;
385                 case Task:
386                     namespaceGuid = "{00062003-0000-0000-c000-000000000046}";
387                     break;
388             }
389         }
390 
391 
392         StringBuilder buffer = new StringBuilder();
393         if (namespaceGuid != null) {
394             buffer.append(propertyType.name()).append(" ").append(namespaceGuid);
395             if (propertyName != null) {
396                 buffer.append(" Name ").append(propertyName);
397             } else {
398                 buffer.append(" Id ").append("0x").append(Integer.toHexString(propertyId));
399             }
400         } else if (propertyTag != null) {
401             buffer.append(propertyType.name()).append(" ").append(propertyTag);
402         }
403         return buffer.toString();
404     }
405 
406     public boolean isExtended() {
407         return extended;
408     }
409 
410     public String getAlias() {
411         return alias;
412     }
413 
414     public String getGraphId() {
415         if (graphId != null) {
416             return graphId;
417         }
418         throw new IllegalStateException("Graph id not set on field " + alias);
419         //return alias;
420     }
421 
422     public boolean isMultiValued() {
423         return propertyType == PropertyType.StringArray;
424     }
425 
426     public boolean isNumber() {
427         return propertyType == PropertyType.Short || propertyType == PropertyType.Integer || propertyType == PropertyType.Long || propertyType == PropertyType.Double;
428     }
429 
430     public boolean isBinary() {
431         return propertyType == PropertyType.Binary;
432     }
433 
434     public boolean isBoolean() {
435         return propertyType == PropertyType.Boolean;
436     }
437 
438     public boolean isDate() {
439         return propertyType == PropertyType.SystemTime;
440     }
441 
442     public boolean isInternetHeaders() {
443         return distinguishedPropertySetId == DistinguishedPropertySetType.InternetHeaders;
444     }
445 
446     /**
447      * Get field by alias.
448      * @param alias property alias
449      * @return field definition
450      */
451     public static GraphField get(String alias) {
452         if (!FIELD_MAP.containsKey(alias)) {
453             LOGGER.warn("Missing mapping for " + alias);
454             return new GraphField(alias);
455         }
456         return FIELD_MAP.get(alias);
457     }
458 
459     public static String getGraphId(String alias) {
460         return get(alias).getGraphId();
461     }
462 
463 }