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 davmail.Settings;
23  import davmail.exception.HttpNotFoundException;
24  import davmail.exchange.ExchangeSession;
25  import davmail.exchange.auth.O365Token;
26  import davmail.exchange.ews.EwsExchangeSession;
27  import davmail.exchange.ews.ExtendedFieldURI;
28  import davmail.exchange.ews.Field;
29  import davmail.exchange.ews.FieldURI;
30  import davmail.exchange.ews.IndexedFieldURI;
31  import davmail.http.HttpClientAdapter;
32  import davmail.util.IOUtil;
33  import org.apache.http.HttpStatus;
34  import org.apache.http.client.methods.CloseableHttpResponse;
35  import org.apache.http.client.methods.HttpDelete;
36  import org.apache.http.client.methods.HttpGet;
37  import org.apache.http.client.methods.HttpPatch;
38  import org.apache.http.client.methods.HttpPost;
39  import org.apache.http.client.methods.HttpRequestBase;
40  import org.apache.http.client.utils.URIBuilder;
41  import org.apache.http.entity.ByteArrayEntity;
42  import org.codehaus.jettison.json.JSONArray;
43  import org.codehaus.jettison.json.JSONException;
44  import org.codehaus.jettison.json.JSONObject;
45  
46  import javax.mail.MessagingException;
47  import javax.mail.internet.MimeMessage;
48  import javax.mail.util.SharedByteArrayInputStream;
49  import java.io.ByteArrayOutputStream;
50  import java.io.IOException;
51  import java.io.InputStream;
52  import java.net.URI;
53  import java.net.URISyntaxException;
54  import java.nio.charset.StandardCharsets;
55  import java.util.ArrayList;
56  import java.util.Date;
57  import java.util.HashMap;
58  import java.util.HashSet;
59  import java.util.List;
60  import java.util.Map;
61  import java.util.Set;
62  import java.util.zip.GZIPInputStream;
63  
64  public class GraphExchangeSessionDraft extends ExchangeSession {
65      HttpClientAdapter httpClient;
66      O365Token token;
67  
68      /**
69       * API version
70       */
71      String apiVersion = "beta";
72  
73      String baseUrl;
74  
75      protected class Folder extends ExchangeSession.Folder {
76          public String lastModified;
77          public String id;
78      }
79  
80      protected static final HashSet<FieldURI> FOLDER_PROPERTIES = new HashSet<>();
81  
82      static {
83          FOLDER_PROPERTIES.add(Field.get("urlcompname"));
84          FOLDER_PROPERTIES.add(Field.get("folderDisplayName"));
85          FOLDER_PROPERTIES.add(Field.get("lastmodified"));
86          FOLDER_PROPERTIES.add(Field.get("folderclass"));
87          FOLDER_PROPERTIES.add(Field.get("ctag"));
88          //FOLDER_PROPERTIES.add(Field.get("count"));
89          //FOLDER_PROPERTIES.add(Field.get("unread"));
90          //FOLDER_PROPERTIES.add(Field.get("hassubs"));
91          FOLDER_PROPERTIES.add(Field.get("uidNext"));
92          FOLDER_PROPERTIES.add(Field.get("highestUid"));
93      }
94  
95  
96      public GraphExchangeSessionDraft(HttpClientAdapter httpClient, O365Token token, String userName) {
97          this.httpClient = httpClient;
98          this.token = token;
99          this.userName = userName;
100         // TODO: build url from settings for .us and other tenants
101         this.baseUrl = Settings.GRAPH_URL;
102     }
103 
104     // get folder id, well known folders
105     // https://learn.microsoft.com/en-us/graph/api/resources/mailfolder?view=graph-rest-1.0
106     // /me/mailFolders/msgfolderroot
107 
108     public Folder getFolderByName(String folderName) throws URISyntaxException, IOException, JSONException {
109         Folder folder = null;
110 
111         HttpRequestBase httpRequest = new GraphRequestBuilder()
112                 .setMethod("GET")
113                 .setAccessToken(token.getAccessToken())
114                 .setObjectType("mailFolders")
115                 .setObjectId(folderName)
116                 .setExpandFields(FOLDER_PROPERTIES).build();
117 
118         JSONObject jsonResponse = executeRequest(httpRequest);
119 
120 
121         folder = new Folder();
122         folder.folderPath = folderName;
123         folder.displayName = jsonResponse.optString("displayName");
124 
125         LOGGER.debug("urlcompname " + Field.get("urlcompname").getGraphId());
126         LOGGER.debug("folderDisplayName " + jsonResponse.optString("displayName"));
127         LOGGER.debug("lastmodified " + Field.get("lastmodified").getGraphId());
128         LOGGER.debug("folderclass " + Field.get("folderclass").getGraphId());
129         LOGGER.debug("ctag " + Field.get("ctag").getGraphId());
130         LOGGER.debug("count " + Field.get("count").getGraphId());
131         LOGGER.debug("unread " + Field.get("unread").getGraphId());
132         LOGGER.debug("hassubs " + Field.get("hassubs").getGraphId());
133         LOGGER.debug("uidNext " + Field.get("uidNext").getGraphId());
134         LOGGER.debug("highestUid " + Field.get("highestUid").getGraphId());
135 
136         // retrieve property values
137         JSONArray singleValueExtendedProperties = jsonResponse.optJSONArray("singleValueExtendedProperties");
138         if (singleValueExtendedProperties != null) {
139             for (int i = 0; i < singleValueExtendedProperties.length(); i++) {
140                 JSONObject singleValueProperty = singleValueExtendedProperties.getJSONObject(i);
141                 String singleValueId = singleValueProperty.getString("id");
142                 String singleValue = singleValueProperty.getString("value");
143                 if (Field.get("lastmodified").getGraphId().equals(singleValueId)) {
144                     // TODO parse date ?
145                     folder.lastModified = singleValue;
146                 } else if (Field.get("folderclass").getGraphId().equals(singleValueId)) {
147                     folder.folderClass = singleValue;
148                 } else if (Field.get("uidNext").getGraphId().equals(singleValueId)) {
149                     folder.uidNext = Long.parseLong(singleValue);
150                 } else if (Field.get("ctag").getGraphId().equals(singleValueId)) {
151                     folder.ctag = singleValue;
152                     // replaced with native properties
153                     //} else if (Field.get("count").getGraphId().equals(singleValueId)) {
154                     //    folder.count = Integer.parseInt(singleValue);
155                     //} else if (Field.get("hassubs").getGraphId().equals(singleValueId)) {
156                     //    folder.hasChildren = "true".equals(singleValue);
157                     //} else if (Field.get("unread").getGraphId().equals(singleValueId)) {
158                     //    folder.unreadCount = Integer.parseInt(singleValue);
159                 } else {
160                     LOGGER.warn("Unknown property " + singleValueId);
161                 }
162 
163             }
164         }
165         folder.count = jsonResponse.getInt("totalItemCount");
166         folder.unreadCount = jsonResponse.getInt("unreadItemCount");
167         folder.hasChildren = jsonResponse.getInt("childFolderCount") > 0;
168 
169         return folder;
170     }
171 
172     /**
173      * Compute expand parameters from properties
174      * @param fields
175      * @return $expand value
176      */
177     private String buildExpand(HashSet<FieldURI> fields) {
178         ArrayList<String> singleValueProperties = new ArrayList<>();
179         ArrayList<String> multiValueProperties = new ArrayList<>();
180         for (FieldURI fieldURI : fields) {
181             if (fieldURI instanceof ExtendedFieldURI) {
182                 singleValueProperties.add(fieldURI.getGraphId());
183             } else if (fieldURI instanceof IndexedFieldURI) {
184                 multiValueProperties.add(fieldURI.getGraphId());
185             }
186         }
187         StringBuilder expand = new StringBuilder();
188         if (!singleValueProperties.isEmpty()) {
189             expand.append("singleValueExtendedProperties($filter=");
190             appendExpandProperties(expand, singleValueProperties);
191             expand.append(")");
192         }
193         if (!multiValueProperties.isEmpty()) {
194             if (!singleValueProperties.isEmpty()) {
195                 expand.append(",");
196             }
197             expand.append("multiValueExtendedProperties($filter=");
198             appendExpandProperties(expand, multiValueProperties);
199             expand.append(")");
200         }
201         return expand.toString();
202     }
203 
204     public void appendExpandProperties(StringBuilder buffer, List<String> properties) {
205         boolean first = true;
206         for (String id : properties) {
207             if (first) {
208                 first = false;
209             } else {
210                 buffer.append(" or ");
211             }
212             buffer.append("id eq '").append(id).append("'");
213         }
214     }
215 
216 
217     @Override
218     public void close() {
219 
220     }
221 
222     @Override
223     public String formatSearchDate(Date date) {
224         return null;
225     }
226 
227     @Override
228     protected void buildSessionInfo(URI uri) throws IOException {
229 
230     }
231 
232     @Override
233     public Message createMessage(String folderPath, String messageName, HashMap<String, String> properties, MimeMessage mimeMessage) throws IOException {
234         ByteArrayOutputStream baos = new ByteArrayOutputStream();
235         try {
236             mimeMessage.writeTo(baos);
237         } catch (MessagingException e) {
238             throw new IOException(e.getMessage());
239         }
240         baos.close();
241         byte[] mimeContent = IOUtil.encodeBase64(baos.toByteArray());
242         // https://learn.microsoft.com/en-us/graph/api/user-post-messages
243 
244         try {
245             String folderId = getFolderIdIfExists(folderPath);
246             String path = "/beta/me/mailFolders/" + folderId + "/messages";
247             path = "/beta/me/messages";
248             HttpPost httpPost = new HttpPost(new URIBuilder(baseUrl).setPath(path).build());
249             httpPost.setHeader("Content-Type", "text/plain");
250 
251 
252             httpPost.setEntity(new ByteArrayEntity(mimeContent));
253             JSONObject response = executeRequest(httpPost);
254 
255             //path = "/beta/me/mailFolders/"+response.get("id")+"/messages";
256             path = "/beta/me/messages/" + response.get("id");
257             HttpPatch httpPatch = new HttpPatch(new URIBuilder(baseUrl).setPath(path).build());
258             httpPatch.setHeader("Content-Type", "application/json");
259 
260             // TODO: map properties
261             response.put("singleValueExtendedProperties", new JSONArray().put(
262                     new JSONObject()
263                             .put("id", Field.get("messageFlags").getGraphId())
264                             .put("value", "4")
265             ));
266 
267 
268             httpPatch.setEntity(new ByteArrayEntity(response.toString().getBytes(StandardCharsets.UTF_8)));
269             response = executeRequest(httpPatch);
270             response = moveMessage(response.getString("id"), folderId);
271             getMessage(response.getString("id"));
272 
273             /*
274 
275             getMessage(response.getString("id"));
276             response = updateMessage(response.getString("id"), properties);
277             getMessage(response.getString("id"));
278             getMessageBody(response.getString("id"));
279             System.out.println(response.toString(4));*/
280 
281 
282         } catch (URISyntaxException | JSONException e) {
283             throw new IOException(e);
284         }
285 
286         return null;
287         /*
288           // TODO: fields
289         List<FieldUpdate> fieldUpdates = buildProperties(properties);
290         if (!properties.containsKey("draft")) {
291             // need to force draft flag to false
292             if (properties.containsKey("read")) {
293                 fieldUpdates.add(Field.createFieldUpdate("messageFlags", "1"));
294             } else {
295                 fieldUpdates.add(Field.createFieldUpdate("messageFlags", "0"));
296             }
297         }
298         fieldUpdates.add(Field.createFieldUpdate("urlcompname", messageName));
299         item.setFieldUpdates(fieldUpdates);
300         */
301 
302 
303         /*
304         CreateItemMethod createItemMethod = new CreateItemMethod(MessageDisposition.SaveOnly, getFolderId(folderPath), item);
305         executeMethod(createItemMethod);
306 
307         ItemId newItemId = new ItemId(createItemMethod.getResponseItem());
308         GetItemMethod getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, newItemId, false);
309         for (String attribute : IMAP_MESSAGE_ATTRIBUTES) {
310             getItemMethod.addAdditionalProperty(Field.get(attribute));
311         }
312         executeMethod(getItemMethod);
313 
314         return buildMessage(getItemMethod.getResponseItem());
315 
316          */
317     }
318 
319     private JSONObject updateMessage(String id, HashMap<String, String> properties) throws IOException {
320         try {
321             String path = "/beta/me/messages/" + id;
322             HttpPatch httpPatch = new HttpPatch(new URIBuilder(baseUrl).setPath(path).build());
323             httpPatch.setHeader("Content-Type", "application/json");
324 
325             JSONObject jsonObject = new JSONObject();
326             // TODO: map properties
327             jsonObject.put("isDraft", false);
328             jsonObject.put("singleValueExtendedProperties", new JSONArray().put(
329                     new JSONObject()
330                             .put("id", Field.get("messageFlags").getGraphId())
331                             .put("value", "4")
332             ));
333 
334 
335             httpPatch.setEntity(new ByteArrayEntity(jsonObject.toString().getBytes(StandardCharsets.UTF_8)));
336             JSONObject response = executeRequest(httpPatch);
337             return response;
338 
339         } catch (URISyntaxException | JSONException e) {
340             throw new IOException(e);
341         }
342     }
343 
344     private JSONObject moveMessage(String id, String folderId) throws IOException {
345         try {
346             String path = "/beta/me/messages/" + id + "/move/";
347             HttpPost httpPost = new HttpPost(new URIBuilder(baseUrl).setPath(path).build());
348             httpPost.setHeader("Content-Type", "application/json");
349 
350             JSONObject jsonObject = new JSONObject();
351             jsonObject.put("destinationId", folderId);
352 
353             httpPost.setEntity(new ByteArrayEntity(jsonObject.toString().getBytes(StandardCharsets.UTF_8)));
354             JSONObject response = executeRequest(httpPost);
355             return response;
356 
357         } catch (URISyntaxException | JSONException e) {
358             throw new IOException(e);
359         }
360     }
361 
362     private Message getMessage(String id) throws URISyntaxException, IOException, JSONException {
363         HashSet<FieldURI> messageProperties = new HashSet<>();
364         messageProperties.add(Field.get("messageFlags"));
365         messageProperties.add(Field.get("imapUid"));
366         Message message = null;
367         URIBuilder uriBuilder = new URIBuilder(baseUrl)
368                 .setPath("/beta/me/messages/" + id)
369                 .addParameter("$expand", buildExpand(messageProperties));
370         HttpGet httpGet = new HttpGet(uriBuilder.build());
371         httpGet.setHeader("Authorization", "Bearer " + token.getAccessToken());
372         try (
373                 CloseableHttpResponse response = httpClient.execute(httpGet);
374         ) {
375             JSONObject jsonResponse = new JsonResponseHandler().handleResponse(response);
376 
377             JSONArray singleValueExtendedProperties = jsonResponse.optJSONArray("singleValueExtendedProperties");
378             if (singleValueExtendedProperties != null) {
379                 for (int i = 0; i < singleValueExtendedProperties.length(); i++) {
380                     JSONObject singleValueProperty = singleValueExtendedProperties.getJSONObject(i);
381                     String singleValueId = singleValueProperty.getString("id");
382                     String singleValue = singleValueProperty.getString("value");
383                     if (Field.get("messageFlags").getGraphId().equals(singleValueId)) {
384                         System.out.println("messageFlags: " + singleValue);
385                     } else if (Field.get("imapUid").getGraphId().equals(singleValueId)) {
386                         System.out.println("imapUid: " + singleValue);
387                     } else {
388                         LOGGER.warn("Unknown property " + singleValueId);
389                     }
390 
391                 }
392 
393 
394             }
395         }
396         return message;
397     }
398 
399     public void getMessageBody(String id) throws URISyntaxException, IOException, MessagingException {
400 
401 
402         MimeMessage mimeMessage = null;
403         URIBuilder uriBuilder = new URIBuilder(baseUrl)
404                 .setPath("/beta/me/messages/" + id + "/$value");
405         HttpGet httpGet = new HttpGet(uriBuilder.build());
406         httpGet.setHeader("Authorization", "Bearer " + token.getAccessToken());
407         try (
408                 CloseableHttpResponse response = httpClient.execute(httpGet);
409         ) {
410             try (InputStream inputStream = response.getEntity().getContent()) {
411                 if (HttpClientAdapter.isGzipEncoded(response)) {
412                     mimeMessage = new MimeMessage(null, new SharedByteArrayInputStream(IOUtil.readFully(new GZIPInputStream(inputStream))));
413                 } else {
414                     mimeMessage = new MimeMessage(null, new SharedByteArrayInputStream(IOUtil.readFully(inputStream)));
415                 }
416             } catch (MessagingException e) {
417                 throw new IOException(e.getMessage(), e);
418             }
419             ByteArrayOutputStream baos = new ByteArrayOutputStream();
420             mimeMessage.writeTo(baos);
421             System.out.println(baos.toString("UTF-8"));
422 
423         }
424     }
425 
426     @Override
427     public void updateMessage(Message message, Map<String, String> properties) throws IOException {
428 
429     }
430 
431     @Override
432     public void deleteMessage(Message message) throws IOException {
433 
434     }
435 
436     @Override
437     protected byte[] getContent(Message message) throws IOException {
438         return new byte[0];
439     }
440 
441     @Override
442     public MessageList searchMessages(String folderName, Set<String> attributes, Condition condition) throws IOException {
443         return null;
444     }
445 
446     @Override
447     public MultiCondition and(Condition... condition) {
448         return null;
449     }
450 
451     @Override
452     public MultiCondition or(Condition... condition) {
453         return null;
454     }
455 
456     @Override
457     public Condition not(Condition condition) {
458         return null;
459     }
460 
461     @Override
462     public Condition isEqualTo(String attributeName, String value) {
463         return null;
464     }
465 
466     @Override
467     public Condition isEqualTo(String attributeName, int value) {
468         return null;
469     }
470 
471     @Override
472     public Condition headerIsEqualTo(String headerName, String value) {
473         return null;
474     }
475 
476     @Override
477     public Condition gte(String attributeName, String value) {
478         return null;
479     }
480 
481     @Override
482     public Condition gt(String attributeName, String value) {
483         return null;
484     }
485 
486     @Override
487     public Condition lt(String attributeName, String value) {
488         return null;
489     }
490 
491     @Override
492     public Condition lte(String attributeName, String value) {
493         return null;
494     }
495 
496     @Override
497     public Condition contains(String attributeName, String value) {
498         return null;
499     }
500 
501     @Override
502     public Condition startsWith(String attributeName, String value) {
503         return null;
504     }
505 
506     @Override
507     public Condition isNull(String attributeName) {
508         return null;
509     }
510 
511     @Override
512     public Condition exists(String attributeName) {
513         return null;
514     }
515 
516     @Override
517     public Condition isTrue(String attributeName) {
518         return null;
519     }
520 
521     @Override
522     public Condition isFalse(String attributeName) {
523         return null;
524     }
525 
526     @Override
527     public List<ExchangeSession.Folder> getSubFolders(String folderName, Condition condition, boolean recursive) throws IOException {
528         // TODO implement conditions and recursive search
529         ArrayList<ExchangeSession.Folder> folders = new ArrayList<>();
530         try {
531             String folderId = getFolderId(folderName);
532             HttpGet httpGet = new HttpGet(new URIBuilder(baseUrl).setPath("/beta/me/mailFolders/" + folderId + "/childFolders")
533                     .addParameter("$expand", buildExpand(FOLDER_PROPERTIES))
534                     .build());
535             JSONObject jsonResponse = executeRequest(httpGet);
536             JSONArray jsonValues = jsonResponse.optJSONArray("value");
537             for (int i = 0; i < jsonValues.length(); i++) {
538                 Folder folder = buildFolder(jsonValues.getJSONObject(i));
539                 folder.folderPath = folderName + '/' + EwsExchangeSession.encodeFolderName(folder.displayName);
540                 folders.add(folder);
541             }
542         } catch (JSONException | URISyntaxException e) {
543             throw new IOException(e);
544         }
545         return folders;
546     }
547 
548     @Override
549     public void sendMessage(MimeMessage mimeMessage) throws IOException, MessagingException {
550 
551     }
552 
553     @Override
554     protected Folder internalGetFolder(String folderPath) throws IOException {
555         String folderId = getFolderId(folderPath);
556 
557         JSONObject jsonResponse = null;
558         try {
559             URIBuilder uriBuilder = new URIBuilder(baseUrl)
560                     .setPath("/beta/me/mailFolders/" + folderId)
561                     .addParameter("$expand", buildExpand(FOLDER_PROPERTIES));
562             HttpGet httpGet = new HttpGet(uriBuilder.build());
563             httpGet.setHeader("Authorization", "Bearer " + token.getAccessToken());
564             try (
565                     CloseableHttpResponse response = httpClient.execute(httpGet);
566             ) {
567                 jsonResponse = new JsonResponseHandler().handleResponse(response);
568             }
569         } catch (URISyntaxException e) {
570             throw new IOException(e);
571         }
572 
573         GraphExchangeSessionDraft.Folder folder;
574         if (jsonResponse != null) {
575             folder = buildFolder(jsonResponse);
576             folder.folderPath = folderPath;
577         } else {
578             throw new HttpNotFoundException("Folder " + folderPath + " not found");
579         }
580         return folder;
581     }
582 
583     private String internalGetFolderId(String folderName) throws IOException {
584         String folderId;
585         try {
586             URIBuilder uriBuilder = new URIBuilder(baseUrl)
587                     .setPath("/beta/me/mailFolders/" + folderName)
588                     .addParameter("$select", "id");
589             HttpGet httpGet = new HttpGet(uriBuilder.build());
590             httpGet.setHeader("Authorization", "Bearer " + token.getAccessToken());
591             try (
592                     CloseableHttpResponse response = httpClient.execute(httpGet);
593             ) {
594                 JSONObject jsonResponse = new JsonResponseHandler().handleResponse(response);
595                 folderId = jsonResponse.getString("id");
596             }
597         } catch (URISyntaxException | JSONException e) {
598             throw new IOException(e);
599         }
600         return folderId;
601     }
602 
603     protected String getFolderIdIfExists(String folderPath) throws IOException {
604         // assume start from root folder
605         // TODO: implement access to shared mailbox
606         String parentFolderId = internalGetFolderId("msgfolderroot");
607         if ("msgfolderroot".equals(folderPath)) {
608             return parentFolderId;
609         }
610         String folderId = null;
611         String[] pathElements = folderPath.split("/");
612         for (String pathElement : pathElements) {
613             try {
614                 String displayName = EwsExchangeSession.decodeFolderName(pathElement);
615                 URIBuilder uriBuilder = new URIBuilder(baseUrl)
616                         .setPath("/beta/me/mailFolders/" + parentFolderId + "/childFolders")
617                         .addParameter("$select", "id, displayName")
618                         // TODO escape quotes
619                         .addParameter("$filter", "displayName eq '" + displayName + "'");
620                 HttpGet httpGet = new HttpGet(uriBuilder.build());
621                 httpGet.setHeader("Authorization", "Bearer " + token.getAccessToken());
622                 try (
623                         CloseableHttpResponse response = httpClient.execute(httpGet);
624                 ) {
625                     JSONObject jsonResponse = new JsonResponseHandler().handleResponse(response);
626                     JSONArray jsonFolders = jsonResponse.getJSONArray("value");
627                     String currentFolderId = null;
628                     for (int i = 0; i < jsonFolders.length(); i++) {
629                         JSONObject jsonFolder = jsonFolders.getJSONObject(i);
630                         if (displayName.equals(jsonFolder.optString("displayName"))) {
631                             // found folder
632                             currentFolderId = jsonFolder.getString("id");
633                         }
634                     }
635                     parentFolderId = currentFolderId;
636                     if (currentFolderId == null) {
637                         // not found
638                         break;
639                     }
640                 }
641             } catch (URISyntaxException | JSONException e) {
642                 throw new IOException(e);
643             }
644         }
645         return parentFolderId;
646     }
647 
648     protected String getFolderId(String folderPath) throws IOException {
649         String folderId = getFolderIdIfExists(folderPath);
650         if (folderId == null) {
651             throw new HttpNotFoundException("Folder '" + folderPath + "' not found");
652         }
653         return folderId;
654     }
655 
656     private Folder buildFolder(JSONObject jsonResponse) throws IOException {
657         Folder folder = new Folder();
658         try {
659             folder.displayName = jsonResponse.optString("displayName");
660             folder.count = jsonResponse.getInt("totalItemCount");
661             folder.unreadCount = jsonResponse.getInt("unreadItemCount");
662             folder.hasChildren = jsonResponse.getInt("childFolderCount") > 0;
663 
664             folder.id = jsonResponse.getString("id");
665 
666             // retrieve property values
667             JSONArray singleValueExtendedProperties = jsonResponse.optJSONArray("singleValueExtendedProperties");
668             if (singleValueExtendedProperties != null) {
669                 for (int i = 0; i < singleValueExtendedProperties.length(); i++) {
670                     JSONObject singleValueProperty = singleValueExtendedProperties.getJSONObject(i);
671                     String singleValueId = singleValueProperty.getString("id");
672                     String singleValue = singleValueProperty.getString("value");
673                     if (Field.get("lastmodified").getGraphId().equals(singleValueId)) {
674                         // TODO parse date ?
675                         folder.lastModified = singleValue;
676                     } else if (Field.get("folderclass").getGraphId().equals(singleValueId)) {
677                         folder.folderClass = singleValue;
678                     } else if (Field.get("uidNext").getGraphId().equals(singleValueId)) {
679                         folder.uidNext = Long.parseLong(singleValue);
680                     } else if (Field.get("ctag").getGraphId().equals(singleValueId)) {
681                         folder.ctag = singleValue;
682                     } else {
683                         LOGGER.warn("Unknown property " + singleValueId);
684                     }
685 
686                 }
687             }
688         } catch (JSONException e) {
689             throw new IOException(e);
690         }
691         return folder;
692     }
693 
694     /**
695      * @inheritDoc
696      */
697     @Override
698     public int createFolder(String folderPath, String folderClass, Map<String, String> properties) throws IOException {
699         // TODO: handle path, decodeFolderName
700         if ("IPF.Appointment".equals(folderClass) && folderPath.startsWith("calendars")) {
701             // create calendar
702             try {
703                 HttpPost httpPost = new HttpPost(new URIBuilder(baseUrl).setPath("/beta/me/calendars").build());
704                 httpPost.setHeader("Content-Type", "application/json");
705                 JSONObject jsonObject = new JSONObject();
706                 jsonObject.put("name", folderPath);
707 
708                 httpPost.setEntity(new ByteArrayEntity(jsonObject.toString().getBytes(StandardCharsets.UTF_8)));
709                 executeRequest(httpPost);
710 
711             } catch (URISyntaxException | JSONException e) {
712                 throw new IOException(e);
713             }
714         } else {
715             String parentFolderId;
716             String folderName;
717             if (folderPath.contains("/")) {
718                 String parentFolderPath = folderPath.substring(0, folderPath.lastIndexOf('/'));
719                 parentFolderId = getFolderId(parentFolderPath);
720                 folderName = EwsExchangeSession.decodeFolderName(folderPath.substring(folderPath.lastIndexOf('/') + 1));
721             } else {
722                 parentFolderId = getFolderId("msgfolderroot");
723                 folderName = EwsExchangeSession.decodeFolderName(folderPath);
724             }
725 
726             try {
727                 HttpPost httpPost = new HttpPost(new URIBuilder(baseUrl).setPath("/beta/me/mailFolders/" + parentFolderId + "/childFolders").build());
728                 httpPost.setHeader("Content-Type", "application/json");
729                 JSONObject jsonObject = new JSONObject();
730                 jsonObject.put("displayName", folderName);
731                 if (folderClass != null) {
732                     JSONArray singleValueExtendedProperties = new JSONArray();
733                     singleValueExtendedProperties.put(new JSONObject().put("id", Field.get("folderclass").getGraphId()).put("value", folderClass));
734                     jsonObject.put("singleValueExtendedProperties", singleValueExtendedProperties);
735                 }
736 
737                 httpPost.setEntity(new ByteArrayEntity(jsonObject.toString().getBytes(StandardCharsets.UTF_8)));
738                 executeRequest(httpPost);
739 
740             } catch (URISyntaxException | JSONException e) {
741                 throw new IOException(e);
742             }
743         }
744 
745         return HttpStatus.SC_CREATED;
746     }
747 
748     @Override
749     public int updateFolder(String folderName, Map<String, String> properties) throws IOException {
750         return 0;
751     }
752 
753     @Override
754     public void deleteFolder(String folderPath) throws IOException {
755         String folderId = getFolderId(folderPath);
756 
757         try {
758             HttpDelete httpDelete = new HttpDelete(new URIBuilder(baseUrl).setPath("/beta/me/mailFolders/" + folderId).build());
759             executeRequest(httpDelete);
760 
761         } catch (URISyntaxException e) {
762             throw new IOException(e);
763         }
764     }
765 
766     @Override
767     public void copyMessage(Message message, String targetFolder) throws IOException {
768 
769     }
770 
771     @Override
772     public void moveMessage(Message message, String targetFolder) throws IOException {
773 
774     }
775 
776     @Override
777     public void moveFolder(String folderName, String targetName) throws IOException {
778 
779     }
780 
781     @Override
782     public void moveItem(String sourcePath, String targetPath) throws IOException {
783 
784     }
785 
786     @Override
787     protected void moveToTrash(Message message) throws IOException {
788 
789     }
790 
791     @Override
792     protected Set<String> getItemProperties() {
793         return null;/*EwsExchangeSession.ITEM_PROPERTIES;*/
794     }
795 
796     @Override
797     public List<Contact> searchContacts(String folderPath, Set<String> attributes, Condition condition, int maxCount) throws IOException {
798         return null;
799     }
800 
801     @Override
802     public List<Event> getEventMessages(String folderPath) throws IOException {
803         return null;
804     }
805 
806     @Override
807     protected Condition getCalendarItemCondition(Condition dateCondition) {
808         return null;
809     }
810 
811     @Override
812     public List<Event> searchEvents(String folderPath, Set<String> attributes, Condition condition) throws IOException {
813         return null;
814     }
815 
816     @Override
817     public Item getItem(String folderPath, String itemName) throws IOException {
818         return null;
819     }
820 
821     @Override
822     public ContactPhoto getContactPhoto(Contact contact) throws IOException {
823         return null;
824     }
825 
826     @Override
827     public void deleteItem(String folderPath, String itemName) throws IOException {
828 
829     }
830 
831     @Override
832     public void processItem(String folderPath, String itemName) throws IOException {
833 
834     }
835 
836     @Override
837     public int sendEvent(String icsBody) throws IOException {
838         return 0;
839     }
840 
841     @Override
842     protected Contact buildContact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) throws IOException {
843         return null;
844     }
845 
846     @Override
847     protected ItemResult internalCreateOrUpdateEvent(String folderPath, String itemName, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
848         return null;
849     }
850 
851     @Override
852     public boolean isSharedFolder(String folderPath) {
853         return false;
854     }
855 
856     @Override
857     public boolean isMainCalendar(String folderPath) throws IOException {
858         return false;
859     }
860 
861     @Override
862     public Map<String, Contact> galFind(Condition condition, Set<String> returningAttributes, int sizeLimit) throws IOException {
863         return null;
864     }
865 
866     @Override
867     protected String getFreeBusyData(String attendee, String start, String end, int interval) throws IOException {
868         return null;
869     }
870 
871 
872     @Override
873     protected void loadVtimezone() {
874         try {
875             String timezoneId = null;
876             // Outlook token does not have access to mailboxSettings, use old EWS call
877             JSONObject mailboxSettings = getMailboxSettings();
878             if (mailboxSettings != null) {
879                 timezoneId = mailboxSettings.optString("timeZone", null);
880             }
881             // failover: use timezone id from settings file
882             if (timezoneId == null) {
883                 timezoneId = Settings.getProperty("davmail.timezoneId");
884             }
885             // last failover: use GMT
886             if (timezoneId == null) {
887                 LOGGER.warn("Unable to get user timezone, using GMT Standard Time. Set davmail.timezoneId setting to override this.");
888                 timezoneId = "GMT Standard Time";
889             }
890 
891             // delete existing temp folder first to avoid errors
892             deleteFolder("davmailtemp");
893             createCalendarFolder("davmailtemp", null);
894             /*
895 
896             createCalendarFolder("davmailtemp", null);
897             EWSMethod.Item item = new EWSMethod.Item();
898             item.type = "CalendarItem";
899             if (!"Exchange2007_SP1".equals(serverVersion)) {
900                 SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH);
901                 dateFormatter.setTimeZone(GMT_TIMEZONE);
902                 Calendar cal = Calendar.getInstance();
903                 item.put("Start", dateFormatter.format(cal.getTime()));
904                 cal.add(Calendar.DAY_OF_MONTH, 1);
905                 item.put("End", dateFormatter.format(cal.getTime()));
906                 item.put("StartTimeZone", timezoneId);
907             } else {
908                 item.put("MeetingTimeZone", timezoneId);
909             }
910             CreateItemMethod createItemMethod = new CreateItemMethod(MessageDisposition.SaveOnly, SendMeetingInvitations.SendToNone, getFolderId("davmailtemp"), item);
911             executeMethod(createItemMethod);
912             item = createItemMethod.getResponseItem();
913             if (item == null) {
914                 throw new IOException("Empty timezone item");
915             }
916             VCalendar vCalendar = new VCalendar(getContent(new ItemId(item)), email, null);
917             this.vTimezone = vCalendar.getVTimezone();
918             // delete temporary folder
919             deleteFolder("davmailtemp");*/
920         } catch (IOException e) {
921             LOGGER.warn("Unable to get VTIMEZONE info: " + e, e);
922         } catch (URISyntaxException e) {
923             LOGGER.warn("Unable to get VTIMEZONE info: " + e, e);
924         }
925     }
926 
927     public JSONObject getMailboxSettings() throws IOException, URISyntaxException {
928         URIBuilder uriBuilder = new URIBuilder(baseUrl)
929                 .setPath("/beta/me/mailboxsettings");
930         return executeRequest(new HttpGet(uriBuilder.build()));
931     }
932 
933     /**
934      * Return supported timezone list
935      * @param timeZoneStandard Windows or Iana
936      * @return timezone list
937      * @throws IOException on error
938      * @throws URISyntaxException on error
939      */
940     public JSONArray getSupportedTimeZones(String timeZoneStandard) throws IOException, URISyntaxException, JSONException {
941         if (timeZoneStandard == null) {
942             timeZoneStandard = "Windows";
943         }
944         URIBuilder uriBuilder = new URIBuilder(baseUrl)
945                 .setPath("/beta/me/outlook/supportedTimeZones(TimeZoneStandard=microsoft.graph.timeZoneStandard'" + timeZoneStandard + "')");
946         JSONObject response = executeRequest(new HttpGet(uriBuilder.build()));
947         return response.getJSONArray("value");
948     }
949 
950     private JSONObject executeRequest(HttpRequestBase request) throws IOException {
951         JSONObject jsonResponse;
952         request.setHeader("Authorization", "Bearer " + token.getAccessToken());
953         // disable gzip
954         //httpGet.setHeader("Accept-Encoding", "");
955         try (
956                 CloseableHttpResponse response = httpClient.execute(request);
957         ) {
958             jsonResponse = new JsonResponseHandler().handleResponse(response);
959         }
960         return jsonResponse;
961     }
962 
963 }