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.exchange.ExchangeSession;
24  import davmail.util.IOUtil;
25  import davmail.util.StringUtil;
26  import org.apache.http.client.methods.HttpDelete;
27  import org.apache.http.client.methods.HttpGet;
28  import org.apache.http.client.methods.HttpPatch;
29  import org.apache.http.client.methods.HttpPost;
30  import org.apache.http.client.methods.HttpPut;
31  import org.apache.http.client.methods.HttpRequestBase;
32  import org.apache.http.client.utils.URIBuilder;
33  import org.apache.http.entity.ByteArrayEntity;
34  import org.apache.log4j.Logger;
35  import org.codehaus.jettison.json.JSONException;
36  import org.codehaus.jettison.json.JSONObject;
37  
38  import java.io.IOException;
39  import java.net.URISyntaxException;
40  import java.util.ArrayList;
41  import java.util.HashMap;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.Set;
45  
46  /**
47   * Build Microsoft graph request
48   */
49  public class GraphRequestBuilder {
50      protected static final Logger LOGGER = Logger.getLogger("davmail.exchange.graph.GraphRequestBuilder");
51  
52      String method = "POST";
53      String url;
54  
55      String contentType = "application/json";
56  
57      String baseUrl = Settings.getGraphUrl();
58      String version = Settings.getProperty("davmail.graphVersion", "beta");
59      String mailbox;
60      String objectType;
61  
62      String objectId;
63  
64      String childType;
65      String childId;
66      String childSuffix;
67  
68      String action;
69  
70      String select;
71      String expand;
72  
73      String filter;
74      String orderBy;
75      String search;
76      // sets $top parameter
77      int sizeLimit;
78      // only for messages, sets Prefer: odata.maxpagesize
79      int maxPageSize;
80  
81      String deltaToken;
82  
83      String startDateTime;
84      String endDateTime;
85  
86      String timeZone;
87  
88      Set<GraphField> selectFields;
89  
90      String accessToken;
91  
92      JSONObject jsonBody = null;
93  
94      /**
95       * Custom request headers
96       */
97      HashMap<String, String> headerMap;
98  
99      byte[] mimeContent;
100 
101     /**
102      * Set property in the JSON body.
103      * @param name property name
104      * @param value property value
105      * @throws JSONException on error
106      */
107     public GraphRequestBuilder setProperty(String name, String value) throws JSONException {
108         if (jsonBody == null) {
109             jsonBody = new JSONObject();
110         }
111         jsonBody.put(name, value);
112         return this;
113     }
114 
115     /**
116      * Replace JSON body;
117      * @return this
118      */
119     public GraphRequestBuilder setJsonBody(JSONObject jsonBody) {
120         this.jsonBody = jsonBody;
121         return this;
122     }
123 
124     public GraphRequestBuilder setJsonBody(GraphObject graphObject) {
125         this.jsonBody = graphObject.jsonObject;
126         return this;
127     }
128 
129     public GraphRequestBuilder addHeader(String name, String value) {
130         if (headerMap == null) {
131             headerMap = new HashMap<>();
132         }
133         headerMap.put(name, value);
134         return this;
135     }
136 
137     public GraphRequestBuilder setUrl(String url) {
138         this.url = url;
139         return this;
140     }
141 
142     /**
143      * Set expand fields (returning attributes).
144      * @param selectFields set of fields to return
145      * @return this
146      */
147     public GraphRequestBuilder setSelectFields(Set<GraphField> selectFields) {
148         this.selectFields = selectFields;
149         computeSelectAndExpand();
150         return this;
151     }
152 
153     public GraphRequestBuilder setVersion(String version) {
154         this.version = version;
155         return this;
156     }
157 
158     public GraphRequestBuilder setObjectType(String objectType) {
159         this.objectType = objectType;
160         return this;
161     }
162 
163     public GraphRequestBuilder setChildType(String childType) {
164         this.childType = childType;
165         return this;
166     }
167 
168     public GraphRequestBuilder setChildId(String childId) {
169         this.childId = childId;
170         return this;
171     }
172 
173     public GraphRequestBuilder setChildSuffix(String childSuffix) {
174         this.childSuffix = childSuffix;
175         return this;
176     }
177 
178     public GraphRequestBuilder setAction(String action) {
179         this.action = action;
180         return this;
181     }
182 
183     public GraphRequestBuilder setFilter(String filter) {
184         this.filter = filter;
185         return this;
186     }
187 
188     public GraphRequestBuilder setFilter(ExchangeSession.Condition condition) {
189         if (condition != null && !condition.isEmpty()) {
190             StringBuilder buffer = new StringBuilder();
191             condition.appendTo(buffer);
192             this.filter = buffer.toString();
193             LOGGER.debug("filter: " + filter);
194         }
195         return this;
196     }
197 
198     public GraphRequestBuilder setSearch(String search) {
199         this.search = search;
200         return this;
201     }
202 
203     public GraphRequestBuilder setOrderBy(String orderBy) {
204         this.orderBy = orderBy;
205         return this;
206     }
207 
208     public GraphRequestBuilder setStartDateTime(String startDateTime) {
209         this.startDateTime = startDateTime;
210         return this;
211     }
212 
213     public GraphRequestBuilder setEndDateTime(String endDateTime) {
214         this.endDateTime = endDateTime;
215         return this;
216     }
217 
218     public GraphRequestBuilder setTimezone(String timeZone) {
219         this.timeZone = timeZone;
220         return this;
221     }
222 
223     public GraphRequestBuilder setAccessToken(String accessToken) {
224         this.accessToken = accessToken;
225         return this;
226     }
227 
228     public GraphRequestBuilder setMethod(String method) {
229         this.method = method;
230         return this;
231     }
232 
233     public GraphRequestBuilder setContentType(String contentType) {
234         this.contentType = contentType;
235         return this;
236     }
237 
238     public GraphRequestBuilder setMimeContent(byte[] mimeContent) {
239         this.mimeContent = mimeContent;
240         return this;
241     }
242 
243     public GraphRequestBuilder setMailbox(String mailbox) {
244         this.mailbox = mailbox;
245         return this;
246     }
247 
248     public GraphRequestBuilder setObjectId(String objectId) {
249         this.objectId = objectId;
250         return this;
251     }
252 
253     public GraphRequestBuilder setSelect(String select) {
254         this.select = select;
255         return this;
256     }
257 
258     public GraphRequestBuilder setExpand(String expand) {
259         this.expand = expand;
260         return this;
261     }
262 
263     public GraphRequestBuilder setSizeLimit(int sizeLimit) {
264         this.sizeLimit = sizeLimit;
265         return this;
266     }
267 
268     public GraphRequestBuilder setMaxPageSize(int maxPageSize) {
269         this.maxPageSize = maxPageSize;
270         return this;
271     }
272 
273     public GraphRequestBuilder setDeltaToken(String deltaToken) {
274         this.deltaToken = deltaToken;
275         return this;
276     }
277 
278 
279     /**
280      * Build request path based on version, username, object type and object id.
281      * @return request path
282      */
283     protected String buildPath() {
284         StringBuilder buffer = new StringBuilder();
285         buffer.append("/").append(version);
286         if ("orgcontacts".equals(objectType)) {
287             // global org contact search
288             buffer.append("/contacts");
289         } else if ("users".equals(objectType)) {
290             buffer.append("/users");
291         } else {
292             if (mailbox != null) {
293                 buffer.append("/users/").append(mailbox);
294             } else {
295                 buffer.append("/me");
296             }
297             if (objectType != null) {
298                 buffer.append("/").append(objectType);
299             }
300         }
301         if (objectId != null) {
302             buffer.append("/").append(objectId);
303         }
304         if (childType != null) {
305             buffer.append("/").append(childType);
306         }
307         if (childId != null) {
308             buffer.append("/").append(childId);
309         }
310         if (childSuffix != null) {
311             buffer.append("/").append(childSuffix);
312         }
313         if (action != null) {
314             buffer.append("/").append(action);
315         }
316 
317         return buffer.toString();
318     }
319 
320     /**
321      * Compute expand parameters from properties.
322      */
323     private void computeSelectAndExpand() {
324         ArrayList<String> singleValueProperties = new ArrayList<>();
325         ArrayList<String> multiValueProperties = new ArrayList<>();
326         ArrayList<String> selectProperties = new ArrayList<>();
327         for (GraphField field : selectFields) {
328             if (field.isExtended()) {
329                 if (field.isMultiValued()) {
330                     multiValueProperties.add(field.getGraphId());
331                 } else {
332                     singleValueProperties.add(field.getGraphId());
333                     if (field.getAlias().startsWith("smtpemail")) {
334                         // email fetched, load emailAddresses array
335                         selectProperties.add("emailAddresses");
336                     }
337                 }
338             // etag is always returned, no a select field
339             } else if (!GraphField.getGraphId("@odata.etag").equals(field.getGraphId())){
340                 selectProperties.add(field.getGraphId());
341             }
342         }
343         StringBuilder expandBuffer = new StringBuilder();
344         if (!singleValueProperties.isEmpty()) {
345             expandBuffer.append("singleValueExtendedProperties($filter=");
346             appendExpandProperties(expandBuffer, singleValueProperties);
347             expandBuffer.append(")");
348         }
349         if (!multiValueProperties.isEmpty()) {
350             if (!singleValueProperties.isEmpty()) {
351                 expandBuffer.append(",");
352             }
353             expandBuffer.append("multiValueExtendedProperties($filter=");
354             appendExpandProperties(expandBuffer, multiValueProperties);
355             expandBuffer.append(")");
356         }
357         expand = expandBuffer.toString();
358         if (!selectProperties.isEmpty()) {
359             select = String.join(",", selectProperties);
360         }
361     }
362 
363     /**
364      * Build expand graph parameter to retrieve mapi properties.
365      * @param buffer expand buffer
366      * @param properties MAPI properties list
367      */
368     protected void appendExpandProperties(StringBuilder buffer, List<String> properties) {
369         boolean first = true;
370         for (String id : properties) {
371             if (first) {
372                 first = false;
373             } else {
374                 buffer.append(" or ");
375             }
376             buffer.append("id eq '").append(id).append("'");
377         }
378     }
379 
380 
381     /**
382      * Build http request.
383      * @return Http request
384      * @throws IOException on error
385      */
386     public HttpRequestBase build() throws IOException {
387         try {
388             // url overrides everything else
389             String requestUrl = url;
390             if (requestUrl == null) {
391                 URIBuilder uriBuilder = new URIBuilder(baseUrl).setPath(buildPath());
392                 if (select != null) {
393                     uriBuilder.addParameter("$select", select);
394                 }
395 
396                 if (expand != null && !expand.isEmpty()) {
397                     uriBuilder.addParameter("$expand", expand);
398                 }
399 
400                 if (filter != null) {
401                     uriBuilder.addParameter("$filter", filter);
402                 }
403 
404                 if (orderBy != null) {
405                     uriBuilder.addParameter("$orderby", orderBy);
406                 }
407 
408                 if (search != null) {
409                     uriBuilder.addParameter("$search", "\"" + StringUtil.escapeDoubleQuotes(search) + "\"");
410                 }
411 
412                 if (startDateTime != null) {
413                     uriBuilder.addParameter("startDateTime", startDateTime);
414                 }
415 
416                 if (endDateTime != null) {
417                     uriBuilder.addParameter("endDateTime", endDateTime);
418                 }
419 
420                 if (sizeLimit != 0) {
421                     uriBuilder.addParameter("$top", String.valueOf(sizeLimit));
422                 }
423                 if (deltaToken != null) {
424                     uriBuilder.addParameter("$deltatoken", deltaToken);
425                 }
426                 requestUrl = uriBuilder.toString();
427             }
428 
429             HttpRequestBase httpRequest;
430 
431             if ("POST".equals(method)) {
432                 httpRequest = new HttpPost(requestUrl);
433                 if (mimeContent != null) {
434                     ((HttpPost) httpRequest).setEntity(new ByteArrayEntity(mimeContent));
435                 } else if (jsonBody != null) {
436                     ((HttpPost) httpRequest).setEntity(new ByteArrayEntity(IOUtil.convertToBytes(jsonBody)));
437                 }
438             } else if (HttpPut.METHOD_NAME.equals(method)) {
439                 // contact picture
440                 httpRequest = new HttpPut(requestUrl);
441                 if (mimeContent != null) {
442                     ((HttpPut) httpRequest).setEntity(new ByteArrayEntity(mimeContent));
443                 }
444             } else if (HttpPatch.METHOD_NAME.equals(method)) {
445                 httpRequest = new HttpPatch(requestUrl);
446                 if (jsonBody != null) {
447                     ((HttpPatch) httpRequest).setEntity(new ByteArrayEntity(IOUtil.convertToBytes(jsonBody)));
448                 }
449             } else if ("DELETE".equals(method)) {
450                 httpRequest = new HttpDelete(requestUrl);
451             } else {
452                 // default to GET request
453                 httpRequest = new HttpGet(requestUrl);
454             }
455             httpRequest.setHeader("Content-Type", contentType);
456             httpRequest.setHeader("Authorization", "Bearer " + accessToken);
457 
458             // set custom headers
459             if (headerMap != null) {
460                 for (Map.Entry<String, String> header : headerMap.entrySet()) {
461                     httpRequest.addHeader(header.getKey(), header.getValue());
462                 }
463             }
464 
465             if (maxPageSize != 0) {
466                 httpRequest.addHeader("Prefer", "odata.maxpagesize=" + maxPageSize);
467             }
468 
469             if (timeZone != null) {
470                 httpRequest.addHeader("Prefer", "outlook.timezone=\"" + timeZone + "\"");
471             }
472 
473             httpRequest.addHeader("Prefer", "IdType=\"ImmutableId\"");
474 
475             if (LOGGER.isDebugEnabled()) {
476                 LOGGER.debug(httpRequest.getMethod() + " " + httpRequest.getURI());
477                 if (jsonBody != null) {
478                     LOGGER.debug(jsonBody.toString());
479                 }
480             }
481 
482             return httpRequest;
483         } catch (URISyntaxException e) {
484             throw new IOException(e.getMessage(), e);
485         }
486     }
487 
488 }