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