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  package davmail.exchange.dav;
20  
21  import davmail.Settings;
22  import davmail.util.StringUtil;
23  import org.apache.jackrabbit.webdav.property.DavPropertyName;
24  import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
25  import org.apache.jackrabbit.webdav.property.PropEntry;
26  import org.apache.jackrabbit.webdav.xml.DomUtil;
27  import org.apache.jackrabbit.webdav.xml.Namespace;
28  import org.apache.jackrabbit.webdav.xml.XmlSerializable;
29  import org.w3c.dom.Document;
30  import org.w3c.dom.Element;
31  
32  import java.util.ArrayList;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Map;
36  
37  /**
38   * WebDav Field
39   */
40  public class Field {
41  
42      protected static final Map<DistinguishedPropertySetType, String> distinguishedPropertySetMap = new HashMap<DistinguishedPropertySetType, String>();
43  
44      static {
45          distinguishedPropertySetMap.put(DistinguishedPropertySetType.Meeting, "6ed8da90-450b-101b-98da-00aa003f1305");
46          distinguishedPropertySetMap.put(DistinguishedPropertySetType.Appointment, "00062002-0000-0000-c000-000000000046");
47          distinguishedPropertySetMap.put(DistinguishedPropertySetType.Common, "00062008-0000-0000-c000-000000000046");
48          distinguishedPropertySetMap.put(DistinguishedPropertySetType.PublicStrings, "00020329-0000-0000-c000-000000000046");
49          distinguishedPropertySetMap.put(DistinguishedPropertySetType.Address, "00062004-0000-0000-c000-000000000046");
50          distinguishedPropertySetMap.put(DistinguishedPropertySetType.InternetHeaders, "00020386-0000-0000-c000-000000000046");
51          distinguishedPropertySetMap.put(DistinguishedPropertySetType.UnifiedMessaging, "4442858e-a9e3-4e80-b900-317a210cc15b");
52          distinguishedPropertySetMap.put(DistinguishedPropertySetType.Task, "00062003-0000-0000-c000-000000000046");
53      }
54  
55      protected static final Namespace EMPTY = Namespace.getNamespace("");
56      protected static final Namespace XML = Namespace.getNamespace("xml:");
57      protected static final Namespace DAV = Namespace.getNamespace("DAV:");
58      protected static final Namespace URN_SCHEMAS_HTTPMAIL = Namespace.getNamespace("urn:schemas:httpmail:");
59      protected static final Namespace URN_SCHEMAS_MAILHEADER = Namespace.getNamespace("urn:schemas:mailheader:");
60  
61      protected static final Namespace SCHEMAS_EXCHANGE = Namespace.getNamespace("http://schemas.microsoft.com/exchange/");
62      protected static final Namespace SCHEMAS_MAPI = Namespace.getNamespace("http://schemas.microsoft.com/mapi/");
63      protected static final Namespace SCHEMAS_MAPI_PROPTAG = Namespace.getNamespace("http://schemas.microsoft.com/mapi/proptag/");
64      protected static final Namespace SCHEMAS_MAPI_ID = Namespace.getNamespace("http://schemas.microsoft.com/mapi/id/");
65      protected static final Namespace SCHEMAS_MAPI_STRING = Namespace.getNamespace("http://schemas.microsoft.com/mapi/string/");
66      protected static final Namespace SCHEMAS_REPL = Namespace.getNamespace("http://schemas.microsoft.com/repl/");
67      protected static final Namespace URN_SCHEMAS_CONTACTS = Namespace.getNamespace("urn:schemas:contacts:");
68      protected static final Namespace URN_SCHEMAS_CALENDAR = Namespace.getNamespace("urn:schemas:calendar:");
69  
70      protected static final Namespace SCHEMAS_MAPI_STRING_INTERNET_HEADERS =
71              Namespace.getNamespace(SCHEMAS_MAPI_STRING.getURI() +
72                      '{' + distinguishedPropertySetMap.get(DistinguishedPropertySetType.InternetHeaders) + "}/");
73  
74      protected static final Map<PropertyType, String> propertyTypeMap = new HashMap<PropertyType, String>();
75  
76      static {
77          propertyTypeMap.put(PropertyType.Integer, "0003"); // PT_INT
78          propertyTypeMap.put(PropertyType.Boolean, "000b"); // PT_BOOLEAN
79          propertyTypeMap.put(PropertyType.SystemTime, "0040"); // PT_SYSTIME
80          propertyTypeMap.put(PropertyType.String, "001f"); // 001f is PT_UNICODE_STRING, 001E is PT_STRING
81          propertyTypeMap.put(PropertyType.Binary, "0102"); // PT_BINARY
82          propertyTypeMap.put(PropertyType.Double, "0005"); // PT_DOUBLE
83      }
84  
85      @SuppressWarnings({"UnusedDeclaration"})
86      protected static enum DistinguishedPropertySetType {
87          Meeting, Appointment, Common, PublicStrings, Address, InternetHeaders, CalendarAssistant, UnifiedMessaging, Task
88      }
89  
90  
91      protected static final Map<String, Field> fieldMap = new HashMap<String, Field>();
92  
93      static {
94          // well known folders
95          createField(URN_SCHEMAS_HTTPMAIL, "inbox");
96          createField(URN_SCHEMAS_HTTPMAIL, "deleteditems");
97          createField(URN_SCHEMAS_HTTPMAIL, "sentitems");
98          createField(URN_SCHEMAS_HTTPMAIL, "sendmsg");
99          createField(URN_SCHEMAS_HTTPMAIL, "drafts");
100         createField(URN_SCHEMAS_HTTPMAIL, "calendar");
101         createField(URN_SCHEMAS_HTTPMAIL, "tasks");
102         createField(URN_SCHEMAS_HTTPMAIL, "contacts");
103         createField(URN_SCHEMAS_HTTPMAIL, "outbox");
104 
105 
106         // folder
107         createField("folderclass", SCHEMAS_EXCHANGE, "outlookfolderclass");
108         createField(DAV, "hassubs");
109         createField(DAV, "nosubs");
110         createField("count", DAV, "objectcount");
111         createField(URN_SCHEMAS_HTTPMAIL, "unreadcount");
112         createField(SCHEMAS_REPL, "contenttag");
113 
114         createField("uidNext", 0x6751, PropertyType.Integer);// PR_ARTICLE_NUM_NEXT
115         createField("highestUid", 0x6752, PropertyType.Integer);// PR_IMAP_LAST_ARTICLE_ID
116 
117         createField(DAV, "isfolder");
118 
119         // item uid, do not use as search parameter, see http://support.microsoft.com/kb/320749
120         createField(DAV, "uid"); // based on PR_RECORD_KEY
121 
122         // POP and IMAP message
123         createField("messageSize", 0x0e08, PropertyType.Integer);//PR_MESSAGE_SIZE
124         createField("imapUid", 0x0e23, PropertyType.Integer);//PR_INTERNET_ARTICLE_NUMBER
125         createField("junk", 0x1083, PropertyType.Integer); //PR_SPAMTYPE
126         createField("flagStatus", 0x1090, PropertyType.Integer);//PR_FLAG_STATUS
127         createField("messageFlags", 0x0e07, PropertyType.Integer);//PR_MESSAGE_FLAGS
128         createField("lastVerbExecuted", 0x1081, PropertyType.Integer);//PR_LAST_VERB_EXECUTED
129         createField("iconIndex", 0x1080, PropertyType.Integer);//PR_ICON_INDEX
130         createField(URN_SCHEMAS_HTTPMAIL, "read");
131         //createField("read", 0x0e69, PropertyType.Boolean);//PR_READ
132 
133         if (Settings.getBooleanProperty("davmail.popCommonDeleted", true)) {
134             // deleted flag, see http://microsoft.public.win32.programmer.messaging.narkive.com/w7Mrsrsx/how-to-detect-deleted-imap-messages-using-mapi-outlook-object-model-api
135             createField("deleted", DistinguishedPropertySetType.Common, 0x8570, "deleted", PropertyType.String);
136         } else {
137             createField("deleted", DistinguishedPropertySetType.PublicStrings);
138         }
139 
140         //createField(URN_SCHEMAS_HTTPMAIL, "date");//PR_CLIENT_SUBMIT_TIME, 0x0039
141         createField("date", 0x0e06, PropertyType.SystemTime);//PR_MESSAGE_DELIVERY_TIME
142         createField(URN_SCHEMAS_MAILHEADER, "bcc");//PS_INTERNET_HEADERS/bcc
143         createField(URN_SCHEMAS_HTTPMAIL, "datereceived");//PR_MESSAGE_DELIVERY_TIME, 0x0E06
144 
145         // unused: force message encoding
146         createField("messageFormat", 0x5909, PropertyType.Integer);//PR_MSG_EDITOR_FORMAT EDITOR_FORMAT_PLAINTEXT = 1 EDITOR_FORMAT_HTML = 2
147         createField("mailOverrideFormat", 0x5902, PropertyType.Integer);//PR_INETMAIL_OVERRIDE_FORMAT ENCODING_PREFERENCE = 2 BODY_ENCODING_TEXT_AND_HTML = 1 ENCODING_MIME = 4
148 
149         // IMAP search
150 
151         createField(URN_SCHEMAS_HTTPMAIL, "subject"); // DistinguishedPropertySetType.InternetHeaders/Subject/String
152         //createField("subject", 0x0037, PropertyType.String);//PR_SUBJECT
153         createField("body", 0x1000, PropertyType.String);//PR_BODY
154         createField("messageheaders", 0x007D, PropertyType.String);// PR_TRANSPORT_MESSAGE_HEADERS
155         createField(URN_SCHEMAS_HTTPMAIL, "from");
156         //createField("from", DistinguishedPropertySetType.PublicStrings, 0x001f);//urn:schemas:httpmail:from
157         createField(URN_SCHEMAS_MAILHEADER, "to"); // DistinguishedPropertySetType.InternetHeaders/To/String
158         createField(URN_SCHEMAS_MAILHEADER, "cc"); // DistinguishedPropertySetType.InternetHeaders/To/String
159         createField(URN_SCHEMAS_MAILHEADER, "message-id"); // DistinguishedPropertySetType.InternetHeaders/message-id/String
160         createField(URN_SCHEMAS_MAILHEADER, "htmldescription"); // DistinguishedPropertySetType.InternetHeaders/htmldescription/String
161 
162         createField("lastmodified", DAV, "getlastmodified"); // PR_LAST_MODIFICATION_TIME 0x3008 SystemTime
163 
164         // failover search
165         createField(DAV, "displayname");
166         createField("urlcompname", 0x10f3, PropertyType.String); //PR_URL_COMP_NAME
167 
168         // items
169         createField("etag", DAV, "getetag");
170 
171         // calendar
172         createField(SCHEMAS_EXCHANGE, "permanenturl");
173         createField(URN_SCHEMAS_CALENDAR, "instancetype"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:instancetype/Integer
174         createField(URN_SCHEMAS_CALENDAR, "dtstart"); // 0x10C3 SystemTime
175         createField(URN_SCHEMAS_CALENDAR, "dtend"); // 0x10C4 SystemTime
176 
177         //createField(URN_SCHEMAS_CALENDAR, "prodid"); //  // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:prodid/String
178         createField("calendarversion", URN_SCHEMAS_CALENDAR, "version"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:version/String
179         createField(URN_SCHEMAS_CALENDAR, "method"); //  // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:method/String
180 
181         createField("calendarlastmodified", URN_SCHEMAS_CALENDAR, "lastmodified"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:isorganizer/Boolean
182         createField(URN_SCHEMAS_CALENDAR, "dtstamp"); // PidLidOwnerCriticalChange
183         createField("calendaruid", URN_SCHEMAS_CALENDAR, "uid"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:uid/String
184         createField(URN_SCHEMAS_CALENDAR, "transparent"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:transparent/String
185 
186         createField(URN_SCHEMAS_CALENDAR, "organizer");
187         createField(URN_SCHEMAS_CALENDAR, "created"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:created/SystemTime
188         createField(URN_SCHEMAS_CALENDAR, "alldayevent"); // DistinguishedPropertySetType.Appointment/0x8215 Boolean
189 
190         createField(URN_SCHEMAS_CALENDAR, "rrule"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:rrule/PtypMultipleString
191         createField(URN_SCHEMAS_CALENDAR, "exdate"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:exdate/PtypMultipleTime
192 
193         createField(SCHEMAS_MAPI, "reminderset"); // PidLidReminderSet
194         createField(SCHEMAS_MAPI, "reminderdelta"); // PidLidReminderDelta         
195 
196         // TODO
197         createField(SCHEMAS_MAPI, "allattendeesstring"); // PidLidAllAttendeesString
198         createField(SCHEMAS_MAPI, "required_attendees"); // PidLidRequiredAttendees
199         createField(SCHEMAS_MAPI, "apptendtime"); // PidLidAppointmentEndTime
200         createField(SCHEMAS_MAPI, "apptstateflags"); // PidLidAppointmentStateFlags 1: Meeting, 2: Received, 4: Cancelled
201 
202         createField(URN_SCHEMAS_CALENDAR, "isorganizer"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:isorganizer/Boolean
203         createField(URN_SCHEMAS_CALENDAR, "location"); // DistinguishedPropertySetType.Appointment/0x8208 String
204         createField(URN_SCHEMAS_CALENDAR, "attendeerole"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:attendeerole/Integer
205         createField(URN_SCHEMAS_CALENDAR, "busystatus"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:busystatus/String
206         createField(URN_SCHEMAS_CALENDAR, "exrule"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:exrule/PtypMultipleString
207         createField(URN_SCHEMAS_CALENDAR, "recurrenceidrange"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:recurrenceidrange/String
208         createField(URN_SCHEMAS_CALENDAR, "rdate"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:rdate/PtypMultipleTime        
209         createField(URN_SCHEMAS_CALENDAR, "reminderoffset"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:reminderoffset/Integer
210         createField(URN_SCHEMAS_CALENDAR, "timezone"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:timezone/String
211 
212 
213         createField(SCHEMAS_EXCHANGE, "sensitivity"); // PR_SENSITIVITY 0x0036 Integer
214         createField(URN_SCHEMAS_CALENDAR, "timezoneid"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:timezoneid/Integer
215         // should use PidLidServerProcessed ?
216         createField("processed", 0x65e8, PropertyType.Boolean);// PR_MESSAGE_PROCESSED
217 
218         createField(DAV, "contentclass");
219         createField("internetContent", 0x6659, PropertyType.Binary);
220 
221         // contact
222 
223         createField(SCHEMAS_EXCHANGE, "outlookmessageclass");
224         createField(URN_SCHEMAS_HTTPMAIL, "subject");
225 
226         createField(URN_SCHEMAS_CONTACTS, "middlename"); // PR_MIDDLE_NAME 0x3A44
227         createField(URN_SCHEMAS_CONTACTS, "fileas"); // urn:schemas:contacts:fileas PS_PUBLIC_STRINGS
228 
229         //createField("id", 0x0ff6, PropertyType.Binary); // PR_INSTANCE_KEY http://support.microsoft.com/kb/320749
230 
231         createField(URN_SCHEMAS_CONTACTS, "homepostaladdress"); // homeAddress DistinguishedPropertySetType.Address/0x0000801A/String
232         createField(URN_SCHEMAS_CONTACTS, "otherpostaladdress"); // otherAddress DistinguishedPropertySetType.Address/0x0000801C/String
233         createField(URN_SCHEMAS_CONTACTS, "mailingaddressid"); // postalAddressId DistinguishedPropertySetType.Address/0x00008022/String
234         createField(URN_SCHEMAS_CONTACTS, "workaddress"); // workAddress DistinguishedPropertySetType.Address/0x0000801B/String
235 
236         createField(URN_SCHEMAS_CONTACTS, "alternaterecipient"); // alternaterecipient DistinguishedPropertySetType.PublicStrings/urn:schemas:contacts:alternaterecipient/String
237 
238         createField(SCHEMAS_EXCHANGE, "extensionattribute1"); // DistinguishedPropertySetType.Address/0x0000804F/String
239         createField(SCHEMAS_EXCHANGE, "extensionattribute2"); // DistinguishedPropertySetType.Address/0x00008050/String
240         createField(SCHEMAS_EXCHANGE, "extensionattribute3"); // DistinguishedPropertySetType.Address/0x00008051/String
241         createField(SCHEMAS_EXCHANGE, "extensionattribute4"); // DistinguishedPropertySetType.Address/0x00008052/String
242 
243         createField(URN_SCHEMAS_CONTACTS, "bday"); // PR_BIRTHDAY 0x3A42 SystemTime
244         createField("anniversary", URN_SCHEMAS_CONTACTS, "weddinganniversary"); // WeddingAnniversary
245         createField(URN_SCHEMAS_CONTACTS, "businesshomepage"); // PR_BUSINESS_HOME_PAGE 0x3A51 String
246         createField(URN_SCHEMAS_CONTACTS, "personalHomePage"); // PR_PERSONAL_HOME_PAGE 0x3A50 String
247         createField(URN_SCHEMAS_CONTACTS, "cn"); // PR_DISPLAY_NAME 0x3001 String
248         createField(URN_SCHEMAS_CONTACTS, "co"); // workAddressCountry DistinguishedPropertySetType.Address/0x00008049/String
249         createField(URN_SCHEMAS_CONTACTS, "department"); // PR_DEPARTMENT_NAME 0x3A18 String
250 
251         // smtp email
252         createField("smtpemail1", DistinguishedPropertySetType.Address, 0x8084, "smtpemail1"); // Email1OriginalDisplayName
253         createField("smtpemail2", DistinguishedPropertySetType.Address, 0x8094, "smtpemail2"); // Email2OriginalDisplayName
254         createField("smtpemail3", DistinguishedPropertySetType.Address, 0x80A4, "smtpemail3"); // Email3OriginalDisplayName
255 
256         // native email
257         createField("email1", DistinguishedPropertySetType.Address, 0x8083, "email1"); // Email1EmailAddress
258         createField("email2", DistinguishedPropertySetType.Address, 0x8093, "email2"); // Email2EmailAddress
259         createField("email3", DistinguishedPropertySetType.Address, 0x80A3, "email3"); // Email3EmailAddress
260 
261         // email type
262         createField("email1type", DistinguishedPropertySetType.Address, 0x8082, "email1type"); // Email1AddressType
263         createField("email2type", DistinguishedPropertySetType.Address, 0x8092, "email2type"); // Email2AddressType
264         createField("email3type", DistinguishedPropertySetType.Address, 0x80A2, "email3type"); // Email3AddressType
265 
266         createField(URN_SCHEMAS_CONTACTS, "facsimiletelephonenumber"); // PR_BUSINESS_FAX_NUMBER 0x3A24 String
267         createField(URN_SCHEMAS_CONTACTS, "givenName"); // PR_GIVEN_NAME 0x3A06 String
268         createField(URN_SCHEMAS_CONTACTS, "homepostofficebox"); // PR_HOME_ADDRESS_POST_OFFICE_BOX 0x3A5E String
269         createField(URN_SCHEMAS_CONTACTS, "homeCity"); // PR_HOME_ADDRESS_CITY 0x3A59 String
270         createField(URN_SCHEMAS_CONTACTS, "homeCountry"); // PR_HOME_ADDRESS_COUNTRY 0x3A5A String
271         createField(URN_SCHEMAS_CONTACTS, "homePhone"); // PR_HOME_TELEPHONE_NUMBER 0x3A09 String
272         createField(URN_SCHEMAS_CONTACTS, "homePostalCode"); // PR_HOME_ADDRESS_POSTAL_CODE 0x3A5B String
273         createField(URN_SCHEMAS_CONTACTS, "homeState"); // PR_HOME_ADDRESS_STATE_OR_PROVINCE 0x3A5C String
274         createField(URN_SCHEMAS_CONTACTS, "homeStreet"); // PR_HOME_ADDRESS_STREET 0x3A5D String
275         createField(URN_SCHEMAS_CONTACTS, "l"); // workAddressCity DistinguishedPropertySetType.Address/0x00008046/String
276         createField(URN_SCHEMAS_CONTACTS, "manager"); // PR_MANAGER_NAME 0x3A4E String
277         createField(URN_SCHEMAS_CONTACTS, "mobile"); // PR_MOBILE_TELEPHONE_NUMBER 0x3A1C String
278         createField(URN_SCHEMAS_CONTACTS, "namesuffix"); // PR_GENERATION 0x3A05 String
279         createField(URN_SCHEMAS_CONTACTS, "nickname"); // PR_NICKNAME 0x3A4F String
280         createField(URN_SCHEMAS_CONTACTS, "o"); // PR_COMPANY_NAME 0x3A16 String
281         createField(URN_SCHEMAS_CONTACTS, "pager"); // PR_PAGER_TELEPHONE_NUMBER 0x3A21 String
282         createField(URN_SCHEMAS_CONTACTS, "personaltitle"); // PR_DISPLAY_NAME_PREFIX 0x3A45 String
283         createField(URN_SCHEMAS_CONTACTS, "postalcode"); // workAddressPostalCode DistinguishedPropertySetType.Address/0x00008048/String
284         createField(URN_SCHEMAS_CONTACTS, "postofficebox"); // workAddressPostOfficeBox DistinguishedPropertySetType.Address/0x0000804A/String
285         createField(URN_SCHEMAS_CONTACTS, "profession"); // PR_PROFESSION 0x3A46 String
286         createField(URN_SCHEMAS_CONTACTS, "roomnumber"); // PR_OFFICE_LOCATION 0x3A19 String
287         createField(URN_SCHEMAS_CONTACTS, "secretarycn"); // PR_ASSISTANT 0x3A30 String
288         createField(URN_SCHEMAS_CONTACTS, "sn"); // PR_SURNAME 0x3A11 String
289         createField(URN_SCHEMAS_CONTACTS, "spousecn"); // PR_SPOUSE_NAME 0x3A48 String
290         createField(URN_SCHEMAS_CONTACTS, "st"); // workAddressState DistinguishedPropertySetType.Address/0x00008047/String
291         createField(URN_SCHEMAS_CONTACTS, "street"); // workAddressStreet DistinguishedPropertySetType.Address/0x00008045/String
292         createField(URN_SCHEMAS_CONTACTS, "telephoneNumber"); // PR_BUSINESS_TELEPHONE_NUMBER 0x3A08 String
293         createField(URN_SCHEMAS_CONTACTS, "title"); // PR_TITLE 0x3A17 String
294         createField("description", URN_SCHEMAS_HTTPMAIL, "textdescription"); // PR_BODY 0x1000 String
295         createField("im", SCHEMAS_MAPI, "InstMsg"); // InstantMessagingAddress DistinguishedPropertySetType.Address/0x00008062/String
296         createField(URN_SCHEMAS_CONTACTS, "othermobile"); // PR_CAR_TELEPHONE_NUMBER 0x3A1E String
297         createField(URN_SCHEMAS_CONTACTS, "internationalisdnnumber"); // PR_ISDN_NUMBER 0x3A2D String        
298 
299         createField(URN_SCHEMAS_CONTACTS, "otherTelephone"); // PR_OTHER_TELEPHONE_NUMBER 0x3A21 String
300         createField(URN_SCHEMAS_CONTACTS, "homefax"); // PR_HOME_FAX_NUMBER 0x3A25 String
301 
302         createField(URN_SCHEMAS_CONTACTS, "otherstreet"); // PR_OTHER_ADDRESS_STREET 0x3A63 String
303         createField(URN_SCHEMAS_CONTACTS, "otherstate"); // PR_OTHER_ADDRESS_STATE_OR_PROVINCE 0x3A62 String
304         createField(URN_SCHEMAS_CONTACTS, "otherpostofficebox"); // PR_OTHER_ADDRESS_POST_OFFICE_BOX 0x3A64 String
305         createField(URN_SCHEMAS_CONTACTS, "otherpostalcode"); // PR_OTHER_ADDRESS_POSTAL_CODE 0x3A61 String
306         createField(URN_SCHEMAS_CONTACTS, "othercountry"); // PR_OTHER_ADDRESS_COUNTRY 0x3A60 String
307         createField(URN_SCHEMAS_CONTACTS, "othercity"); // PR_OTHER_ADDRESS_CITY 0x3A5F String
308 
309         createField(URN_SCHEMAS_CONTACTS, "gender"); // PR_GENDER 0x3A4D Integer16
310 
311         createField("keywords", SCHEMAS_EXCHANGE, "keywords-utf8", PropertyType.StringArray); // PS_PUBLIC_STRINGS Keywords String
312         //createField("keywords", DistinguishedPropertySetType.PublicStrings, "Keywords", ); // PS_PUBLIC_STRINGS Keywords String
313 
314         // contact private flags
315         createField("private", DistinguishedPropertySetType.Common, 0x8506, "private", PropertyType.Boolean); // True/False
316         createField("sensitivity", 0x0036, PropertyType.Integer); // PR_SENSITIVITY SENSITIVITY_PRIVATE = 2, SENSITIVITY_PERSONAL = 1, SENSITIVITY_NONE = 0
317 
318         createField("haspicture", DistinguishedPropertySetType.Address, 0x8015, "haspicture", PropertyType.Boolean); // True/False
319 
320         createField(URN_SCHEMAS_CALENDAR, "fburl"); // freeBusyLocation
321 
322         // OWA settings
323         createField("messageclass", 0x001a, PropertyType.String);
324         createField("roamingxmlstream", 0x7c08, PropertyType.Binary);
325         createField("roamingdictionary", 0x7c07, PropertyType.Binary);
326 
327         createField(DAV, "ishidden");
328 
329         // attachment content
330         createField("attachDataBinary", 0x3701, PropertyType.Binary);
331 
332         createField("attachmentContactPhoto", 0x7FFF, PropertyType.Boolean); // PR_ATTACHMENT_CONTACTPHOTO
333         createField("renderingPosition", 0x370B, PropertyType.Integer);// PR_RENDERING_POSITION
334         //createField("attachFilename", 0x3704, PropertyType.String); // PR_ATTACH_FILENAME
335         createField("attachExtension", 0x3703, PropertyType.String); // PR_ATTACH_EXTENSION
336 
337         createField("xmozlastack", DistinguishedPropertySetType.PublicStrings);
338         createField("xmozsnoozetime", DistinguishedPropertySetType.PublicStrings);
339         createField("xmozsendinvitations", DistinguishedPropertySetType.PublicStrings);
340 
341         // task
342         createField(URN_SCHEMAS_MAILHEADER, "importance");//PS_INTERNET_HEADERS/importance
343         createField("percentcomplete", DistinguishedPropertySetType.Task, 0x8102, "percentcomplete", PropertyType.Double);
344         createField("taskstatus", DistinguishedPropertySetType.Task, 0x8101, "taskstatus", PropertyType.Integer);
345         createField("startdate", DistinguishedPropertySetType.Task, 0x8104, "startdate", PropertyType.SystemTime);
346         createField("duedate", DistinguishedPropertySetType.Task, 0x8105, "duedate", PropertyType.SystemTime);
347         createField("datecompleted", DistinguishedPropertySetType.Task, 0x810F, "datecompleted", PropertyType.SystemTime);
348         createField("iscomplete", DistinguishedPropertySetType.Task, 0x811C, "iscomplete", PropertyType.Boolean);
349 
350 
351         createField("commonstart", DistinguishedPropertySetType.Common, 0x8516, "commonstart", PropertyType.SystemTime);
352         createField("commonend", DistinguishedPropertySetType.Common, 0x8517, "commonend", PropertyType.SystemTime);
353     }
354 
355     protected static String toHexString(int propertyTag) {
356         StringBuilder hexValue = new StringBuilder(Integer.toHexString(propertyTag));
357         while (hexValue.length() < 4) {
358             hexValue.insert(0, '0');
359         }
360         return hexValue.toString();
361     }
362 
363     protected static void createField(String alias, int propertyTag, PropertyType propertyType) {
364         String name = 'x' + toHexString(propertyTag) + propertyTypeMap.get(propertyType);
365         Field field;
366         if (propertyType == PropertyType.Binary) {
367             field = new Field(alias, SCHEMAS_MAPI_PROPTAG, name, propertyType, null, "bin.base64", name);
368         } else {
369             field = new Field(alias, SCHEMAS_MAPI_PROPTAG, name, propertyType);
370         }
371         fieldMap.put(field.alias, field);
372     }
373 
374     protected static void createField(String alias, DistinguishedPropertySetType propertySetType) {
375         Field field = new Field(Namespace.getNamespace(SCHEMAS_MAPI_STRING.getURI() +
376                 '{' + distinguishedPropertySetMap.get(propertySetType) + "}/"), alias);
377         fieldMap.put(field.alias, field);
378     }
379 
380     protected static void createField(String alias, DistinguishedPropertySetType propertySetType, int propertyTag, String responseAlias) {
381         createField(alias, propertySetType, propertyTag, responseAlias, null);
382     }
383 
384     protected static void createField(String alias, DistinguishedPropertySetType propertySetType, int propertyTag, String responseAlias, PropertyType propertyType) {
385         String name;
386         String updateAlias;
387         if (propertySetType == DistinguishedPropertySetType.Address) {
388             // Address namespace expects integer names
389             name = String.valueOf(propertyTag);
390             updateAlias = "_x0030_x" + toHexString(propertyTag);
391         } else if (propertySetType == DistinguishedPropertySetType.Task) {
392             name = "0x" + toHexString(propertyTag);
393             updateAlias = "0x0000" + toHexString(propertyTag);
394         } else {
395             // Common namespace expects hex names
396             name = "0x" + toHexString(propertyTag);
397             updateAlias = "_x0030_x" + toHexString(propertyTag);
398         }
399         Field field = new Field(alias, Namespace.getNamespace(SCHEMAS_MAPI_ID.getURI() +
400                 '{' + distinguishedPropertySetMap.get(propertySetType) + "}/"), name, propertyType, responseAlias, null, updateAlias);
401         fieldMap.put(field.alias, field);
402     }
403 
404     protected static void createField(Namespace namespace, String name) {
405         Field field = new Field(namespace, name);
406         fieldMap.put(field.alias, field);
407     }
408 
409     protected static void createField(String alias, Namespace namespace, String name) {
410         Field field = new Field(alias, namespace, name, null);
411         fieldMap.put(field.alias, field);
412     }
413 
414     protected static void createField(String alias, Namespace namespace, String name, PropertyType propertyType) {
415         Field field = new Field(alias, namespace, name, propertyType);
416         fieldMap.put(field.alias, field);
417     }
418 
419     private final DavPropertyName davPropertyName;
420     protected final String alias;
421     protected final String uri;
422     protected final String requestPropertyString;
423     protected final DavPropertyName responsePropertyName;
424     protected final DavPropertyName updatePropertyName;
425     protected final String cast;
426     protected final boolean isIntValue;
427     protected final boolean isMultivalued;
428     protected final boolean isBooleanValue;
429     protected final boolean isFloatValue;
430     protected final boolean isDateValue;
431 
432     /**
433      * Create field for namespace and name, use name as alias.
434      *
435      * @param namespace Exchange namespace
436      * @param name      Exchange name
437      */
438     protected Field(Namespace namespace, String name) {
439         this(name, namespace, name, null);
440     }
441 
442     /**
443      * Create field for namespace and name of type propertyType.
444      *
445      * @param alias        logical name in DavMail
446      * @param namespace    Exchange namespace
447      * @param name         Exchange name
448      * @param propertyType property type
449      */
450     protected Field(String alias, Namespace namespace, String name, PropertyType propertyType) {
451         this(alias, namespace, name, propertyType, null, null, name);
452     }
453 
454     /**
455      * Create field for namespace and name of type propertyType.
456      *
457      * @param alias         logical name in DavMail
458      * @param namespace     Exchange namespace
459      * @param name          Exchange name
460      * @param propertyType  property type
461      * @param responseAlias property name in SEARCH response (as responsealias in request)
462      * @param cast          response cast type (e.g. bin.base64)
463      * @param updateAlias   some properties use a different alias in PROPPATCH requests
464      */
465     protected Field(String alias, Namespace namespace, String name, PropertyType propertyType, String responseAlias, String cast, String updateAlias) {
466         this.alias = alias;
467 
468         // property name in PROPFIND requests
469         davPropertyName = DavPropertyName.create(name, namespace);
470         // property name in PROPPATCH requests
471         updatePropertyName = DavPropertyName.create(updateAlias, namespace);
472 
473         // a few type based flags
474         isMultivalued = propertyType != null && propertyType.toString().endsWith("Array");
475         isIntValue = propertyType == PropertyType.Long || propertyType == PropertyType.Integer || propertyType == PropertyType.Short;
476         isBooleanValue = propertyType == PropertyType.Boolean;
477         isFloatValue = propertyType == PropertyType.Float || propertyType == PropertyType.Double;
478         isDateValue = propertyType == PropertyType.SystemTime;
479 
480         this.uri = namespace.getURI() + name;
481         if (responseAlias == null) {
482             this.requestPropertyString = '"' + uri + '"';
483             this.responsePropertyName = davPropertyName;
484         } else {
485             this.requestPropertyString = '"' + uri + "\" as " + responseAlias;
486             this.responsePropertyName = DavPropertyName.create(responseAlias, EMPTY);
487         }
488         this.cast = cast;
489     }
490 
491     /**
492      * Property uri.
493      *
494      * @return uri
495      */
496     public String getUri() {
497         return uri;
498     }
499 
500     /**
501      * Integer value property type.
502      *
503      * @return true if the field value is integer
504      */
505     public boolean isIntValue() {
506         return isIntValue;
507     }
508 
509     /**
510      * Get Field by alias.
511      *
512      * @param alias field alias
513      * @return field
514      */
515     public static Field get(String alias) {
516         Field field = fieldMap.get(alias);
517         if (field == null) {
518             throw new IllegalArgumentException("Unknown field: " + alias);
519         }
520         return field;
521     }
522 
523     /**
524      * Get Mime header field.
525      *
526      * @param headerName header name
527      * @return field object
528      */
529     public static Field getHeader(String headerName) {
530         return new Field(SCHEMAS_MAPI_STRING_INTERNET_HEADERS, headerName);
531     }
532 
533     /**
534      * Create DavProperty object for field alias and value.
535      *
536      * @param alias DavMail field alias
537      * @param value field value
538      * @return DavProperty with value or DavPropertyName for null values
539      */
540     public static PropEntry createDavProperty(String alias, String value) {
541         Field field = Field.get(alias);
542         if (value == null) {
543             // return DavPropertyName to remove property
544             return field.updatePropertyName;
545         } else if (field.isMultivalued) {
546             // multivalued field, split values separated by \n
547             List<XmlSerializable> valueList = new ArrayList<XmlSerializable>();
548             String[] values = value.split(",");
549             for (final String singleValue : values) {
550                 valueList.add(new XmlSerializable() {
551                     public Element toXml(Document document) {
552                         return DomUtil.createElement(document, "v", XML, singleValue);
553                     }
554                 });
555             }
556 
557             return new DefaultDavProperty(field.updatePropertyName, valueList);
558         } else if (field.isBooleanValue && !"haspicture".equals(alias)) {
559             if ("true".equals(value)) {
560                 return new DefaultDavProperty(field.updatePropertyName, "1");
561             } else if ("false".equals(value)) {
562                 return new DefaultDavProperty(field.updatePropertyName, "0");
563             } else {
564                 throw new RuntimeException("Invalid value for " + field.alias + ": " + value);
565             }
566         } else {
567             return new DefaultDavProperty(field.updatePropertyName, value);
568         }
569     }
570 
571     /**
572      * Create property value object for field and value.
573      *
574      * @param alias field alias
575      * @param value field value
576      * @return property value object
577      * @see ExchangePropPatchMethod
578      */
579     public static PropertyValue createPropertyValue(String alias, String value) {
580         Field field = Field.get(alias);
581         DavPropertyName davPropertyName = field.davPropertyName;
582         if (value == null) {
583             // return DavPropertyName to remove property
584             return new PropertyValue(davPropertyName.getNamespace().getURI(), davPropertyName.getName());
585         } else if (field.isMultivalued) {
586             StringBuilder buffer = new StringBuilder();
587             // multivalued field, split values separated by \n
588             String[] values = value.split("\n");
589             for (final String singleValue : values) {
590                 buffer.append("<v>");
591                 buffer.append(StringUtil.xmlEncode(singleValue));
592                 buffer.append("</v>");
593             }
594             return new PropertyValue(davPropertyName.getNamespace().getURI(), davPropertyName.getName(), buffer.toString());
595         } else if (field.isBooleanValue) {
596             if ("true".equals(value)) {
597                 return new PropertyValue(davPropertyName.getNamespace().getURI(), davPropertyName.getName(), "1", "boolean");
598             } else if ("false".equals(value)) {
599                 return new PropertyValue(davPropertyName.getNamespace().getURI(), davPropertyName.getName(), "0", "boolean");
600             } else {
601                 throw new RuntimeException("Invalid value for " + field.alias + ": " + value);
602             }
603         } else if (field.isFloatValue) {
604             return new PropertyValue(davPropertyName.getNamespace().getURI(), davPropertyName.getName(), StringUtil.xmlEncode(value), "float");
605         } else if (field.isIntValue) {
606             return new PropertyValue(field.updatePropertyName.getNamespace().getURI(), field.updatePropertyName.getName(), StringUtil.xmlEncode(value), "int");
607         } else if (field.isDateValue) {
608             return new PropertyValue(field.updatePropertyName.getNamespace().getURI(), field.updatePropertyName.getName(), StringUtil.xmlEncode(value), "dateTime.tz");
609         } else {
610             return new PropertyValue(davPropertyName.getNamespace().getURI(), davPropertyName.getName(), StringUtil.xmlEncode(value));
611         }
612     }
613 
614     /**
615      * SEARCH request property name for alias
616      *
617      * @param alias field alias
618      * @return request property string
619      */
620     public static String getRequestPropertyString(String alias) {
621         return Field.get(alias).requestPropertyString;
622     }
623 
624     /**
625      * PROPFIND request property name
626      *
627      * @param alias field alias
628      * @return request property name
629      */
630     public static DavPropertyName getPropertyName(String alias) {
631         return Field.get(alias).davPropertyName;
632     }
633 
634     /**
635      * SEARCH response property name
636      *
637      * @param alias field alias
638      * @return response property name
639      */
640     public static DavPropertyName getResponsePropertyName(String alias) {
641         return Field.get(alias).responsePropertyName;
642     }
643 }