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