View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.client.bindings.spi.browser;
20  
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.Locale;
28  import java.util.Map;
29  
30  import org.apache.chemistry.opencmis.client.bindings.impl.CmisBindingsHelper;
31  import org.apache.chemistry.opencmis.client.bindings.spi.BindingSession;
32  import org.apache.chemistry.opencmis.client.bindings.spi.LinkAccess;
33  import org.apache.chemistry.opencmis.client.bindings.spi.http.HttpInvoker;
34  import org.apache.chemistry.opencmis.client.bindings.spi.http.Output;
35  import org.apache.chemistry.opencmis.client.bindings.spi.http.Response;
36  import org.apache.chemistry.opencmis.commons.PropertyIds;
37  import org.apache.chemistry.opencmis.commons.SessionParameter;
38  import org.apache.chemistry.opencmis.commons.data.ObjectData;
39  import org.apache.chemistry.opencmis.commons.data.PropertyData;
40  import org.apache.chemistry.opencmis.commons.data.PropertyString;
41  import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
42  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
43  import org.apache.chemistry.opencmis.commons.enums.DateTimeFormat;
44  import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
45  import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException;
46  import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
47  import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
48  import org.apache.chemistry.opencmis.commons.exceptions.CmisFilterNotValidException;
49  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
50  import org.apache.chemistry.opencmis.commons.exceptions.CmisNameConstraintViolationException;
51  import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
52  import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
53  import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
54  import org.apache.chemistry.opencmis.commons.exceptions.CmisProxyAuthenticationException;
55  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
56  import org.apache.chemistry.opencmis.commons.exceptions.CmisServiceUnavailableException;
57  import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException;
58  import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException;
59  import org.apache.chemistry.opencmis.commons.exceptions.CmisTooManyRequestsException;
60  import org.apache.chemistry.opencmis.commons.exceptions.CmisUnauthorizedException;
61  import org.apache.chemistry.opencmis.commons.exceptions.CmisUpdateConflictException;
62  import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException;
63  import org.apache.chemistry.opencmis.commons.impl.Constants;
64  import org.apache.chemistry.opencmis.commons.impl.IOUtils;
65  import org.apache.chemistry.opencmis.commons.impl.JSONConstants;
66  import org.apache.chemistry.opencmis.commons.impl.JSONConverter;
67  import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
68  import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoBrowserBindingImpl;
69  import org.apache.chemistry.opencmis.commons.impl.json.JSONObject;
70  import org.apache.chemistry.opencmis.commons.impl.json.parser.ContainerFactory;
71  import org.apache.chemistry.opencmis.commons.impl.json.parser.JSONParseException;
72  import org.apache.chemistry.opencmis.commons.impl.json.parser.JSONParser;
73  import org.apache.chemistry.opencmis.commons.spi.Holder;
74  
75  /**
76   * Base class for all Browser Binding client services.
77   */
78  public abstract class AbstractBrowserBindingService implements LinkAccess {
79  
80      protected static final ContainerFactory SIMPLE_CONTAINER_FACTORY = new ContainerFactory() {
81          @Override
82          public Map<String, Object> createObjectContainer() {
83              return new LinkedHashMap<String, Object>();
84          }
85  
86          @Override
87          public List<Object> creatArrayContainer() {
88              return new ArrayList<Object>();
89          }
90      };
91  
92      private BindingSession session;
93      private boolean succint;
94      private DateTimeFormat dateTimeFormat;
95  
96      /**
97       * Sets the current session.
98       */
99      protected void setSession(BindingSession session) {
100         this.session = session;
101 
102         Object succintObj = session.get(SessionParameter.BROWSER_SUCCINCT);
103         this.succint = succintObj == null ? true : Boolean.parseBoolean(succintObj.toString());
104 
105         Object dateTimeFormatObj = session.get(SessionParameter.BROWSER_DATETIME_FORMAT);
106         this.dateTimeFormat = dateTimeFormatObj == null ? DateTimeFormat.SIMPLE : DateTimeFormat
107                 .fromValue(dateTimeFormatObj.toString().toLowerCase(Locale.ENGLISH));
108     }
109 
110     /**
111      * Gets the current session.
112      */
113     protected BindingSession getSession() {
114         return session;
115     }
116 
117     /**
118      * Gets the HTTP Invoker object.
119      */
120     protected HttpInvoker getHttpInvoker() {
121         return CmisBindingsHelper.getHttpInvoker(session);
122     }
123 
124     /**
125      * Returns the service URL of this session.
126      */
127     protected String getServiceUrl() {
128         Object url = session.get(SessionParameter.BROWSER_URL);
129         if (url instanceof String) {
130             return (String) url;
131         }
132 
133         return null;
134     }
135 
136     protected UrlBuilder getRepositoryUrl(String repositoryId, String selector) {
137         UrlBuilder result = getRepositoryUrlCache().getRepositoryUrl(repositoryId, selector);
138 
139         if (result == null) {
140             getRepositoriesInternal(repositoryId);
141             result = getRepositoryUrlCache().getRepositoryUrl(repositoryId, selector);
142         }
143 
144         if (result == null) {
145             throw new CmisObjectNotFoundException("Unknown repository!");
146         }
147 
148         return result;
149     }
150 
151     protected UrlBuilder getRepositoryUrl(String repositoryId) {
152         UrlBuilder result = getRepositoryUrlCache().getRepositoryUrl(repositoryId);
153 
154         if (result == null) {
155             getRepositoriesInternal(repositoryId);
156             result = getRepositoryUrlCache().getRepositoryUrl(repositoryId);
157         }
158 
159         if (result == null) {
160             throw new CmisObjectNotFoundException("Unknown repository!");
161         }
162 
163         return result;
164     }
165 
166     protected UrlBuilder getObjectUrl(String repositoryId, String objectId, String selector) {
167         UrlBuilder result = getRepositoryUrlCache().getObjectUrl(repositoryId, objectId, selector);
168 
169         if (result == null) {
170             getRepositoriesInternal(repositoryId);
171             result = getRepositoryUrlCache().getObjectUrl(repositoryId, objectId, selector);
172         }
173 
174         if (result == null) {
175             throw new CmisObjectNotFoundException("Unknown repository!");
176         }
177 
178         return result;
179     }
180 
181     protected UrlBuilder getObjectUrl(String repositoryId, String objectId) {
182         UrlBuilder result = getRepositoryUrlCache().getObjectUrl(repositoryId, objectId);
183 
184         if (result == null) {
185             getRepositoriesInternal(repositoryId);
186             result = getRepositoryUrlCache().getObjectUrl(repositoryId, objectId);
187         }
188 
189         if (result == null) {
190             throw new CmisObjectNotFoundException("Unknown repository!");
191         }
192 
193         return result;
194     }
195 
196     protected UrlBuilder getPathUrl(String repositoryId, String path, String selector) {
197         UrlBuilder result = getRepositoryUrlCache().getPathUrl(repositoryId, path, selector);
198 
199         if (result == null) {
200             getRepositoriesInternal(repositoryId);
201             result = getRepositoryUrlCache().getPathUrl(repositoryId, path, selector);
202         }
203 
204         if (result == null) {
205             throw new CmisObjectNotFoundException("Unknown repository!");
206         }
207 
208         return result;
209     }
210 
211     protected boolean getSuccinct() {
212         return succint;
213     }
214 
215     protected String getSuccinctParameter() {
216         return succint ? "true" : null;
217     }
218 
219     protected DateTimeFormat getDateTimeFormat() {
220         return dateTimeFormat;
221     }
222 
223     protected String getDateTimeFormatParameter() {
224         return dateTimeFormat == null || dateTimeFormat == DateTimeFormat.SIMPLE ? null : dateTimeFormat.value();
225     }
226 
227     protected void setChangeToken(Holder<String> changeToken, ObjectData obj) {
228         if (changeToken == null) {
229             return;
230         }
231 
232         changeToken.setValue(null);
233 
234         if (obj == null || obj.getProperties() == null || obj.getProperties().getProperties() == null) {
235             return;
236         }
237 
238         PropertyData<?> ct = obj.getProperties().getProperties().get(PropertyIds.CHANGE_TOKEN);
239         if (ct instanceof PropertyString) {
240             changeToken.setValue(((PropertyString) ct).getFirstValue());
241         }
242     }
243 
244     // ---- exceptions ----
245 
246     /**
247      * Converts an error message or a HTTP status code into an Exception.
248      */
249     protected CmisBaseException convertStatusCode(int code, String message, String errorContent, Throwable t) {
250         Object obj = null;
251         try {
252             if (errorContent != null) {
253                 JSONParser parser = new JSONParser();
254                 obj = parser.parse(errorContent);
255             }
256         } catch (JSONParseException pe) {
257             // error content is not valid JSON -> ignore
258         }
259 
260         if (obj instanceof JSONObject) {
261             JSONObject json = (JSONObject) obj;
262             Object jsonError = json.get(JSONConstants.ERROR_EXCEPTION);
263             if (jsonError instanceof String) {
264                 Object jsonMessage = json.get(JSONConstants.ERROR_MESSAGE);
265                 if (jsonMessage != null) {
266                     message = jsonMessage.toString();
267                 }
268 
269                 Map<String, String> additionalData = null;
270                 for (Map.Entry<String, Object> e : json.entrySet()) {
271                     if (JSONConstants.ERROR_EXCEPTION.equalsIgnoreCase(e.getKey())
272                             || JSONConstants.ERROR_MESSAGE.equalsIgnoreCase(e.getKey())) {
273                         continue;
274                     }
275 
276                     if (additionalData == null) {
277                         additionalData = new HashMap<String, String>();
278                     }
279 
280                     additionalData.put(e.getKey(), e.getValue() == null ? null : e.getValue().toString());
281                 }
282 
283                 if (CmisConstraintException.EXCEPTION_NAME.equalsIgnoreCase((String) jsonError)) {
284                     return new CmisConstraintException(message, errorContent, additionalData, t);
285                 } else if (CmisContentAlreadyExistsException.EXCEPTION_NAME.equalsIgnoreCase((String) jsonError)) {
286                     return new CmisContentAlreadyExistsException(message, errorContent, additionalData, t);
287                 } else if (CmisFilterNotValidException.EXCEPTION_NAME.equalsIgnoreCase((String) jsonError)) {
288                     return new CmisFilterNotValidException(message, errorContent, additionalData, t);
289                 } else if (CmisInvalidArgumentException.EXCEPTION_NAME.equalsIgnoreCase((String) jsonError)) {
290                     return new CmisInvalidArgumentException(message, errorContent, additionalData, t);
291                 } else if (CmisNameConstraintViolationException.EXCEPTION_NAME.equalsIgnoreCase((String) jsonError)) {
292                     return new CmisNameConstraintViolationException(message, errorContent, additionalData, t);
293                 } else if (CmisNotSupportedException.EXCEPTION_NAME.equalsIgnoreCase((String) jsonError)) {
294                     return new CmisNotSupportedException(message, errorContent, additionalData, t);
295                 } else if (CmisObjectNotFoundException.EXCEPTION_NAME.equalsIgnoreCase((String) jsonError)) {
296                     return new CmisObjectNotFoundException(message, errorContent, additionalData, t);
297                 } else if (CmisPermissionDeniedException.EXCEPTION_NAME.equalsIgnoreCase((String) jsonError)) {
298                     return new CmisPermissionDeniedException(message, errorContent, additionalData, t);
299                 } else if (CmisStorageException.EXCEPTION_NAME.equalsIgnoreCase((String) jsonError)) {
300                     return new CmisStorageException(message, errorContent, additionalData, t);
301                 } else if (CmisStreamNotSupportedException.EXCEPTION_NAME.equalsIgnoreCase((String) jsonError)) {
302                     return new CmisStreamNotSupportedException(message, errorContent, additionalData, t);
303                 } else if (CmisUpdateConflictException.EXCEPTION_NAME.equalsIgnoreCase((String) jsonError)) {
304                     return new CmisUpdateConflictException(message, errorContent, additionalData, t);
305                 } else if (CmisVersioningException.EXCEPTION_NAME.equalsIgnoreCase((String) jsonError)) {
306                     return new CmisVersioningException(message, errorContent, additionalData, t);
307                 } else if (code == 503) {
308                     return new CmisServiceUnavailableException(message, errorContent, additionalData, t);
309                 }
310             }
311         }
312 
313         // fall back to status code
314         switch (code) {
315         case 301:
316         case 302:
317         case 303:
318         case 307:
319             return new CmisConnectionException("Redirects are not supported (HTTP status code " + code + "): "
320                     + message, errorContent, t);
321         case 400:
322             return new CmisInvalidArgumentException(message, errorContent, t);
323         case 401:
324             return new CmisUnauthorizedException(message, errorContent, t);
325         case 403:
326             return new CmisPermissionDeniedException(message, errorContent, t);
327         case 404:
328             return new CmisObjectNotFoundException(message, errorContent, t);
329         case 405:
330             return new CmisNotSupportedException(message, errorContent, t);
331         case 407:
332             return new CmisProxyAuthenticationException(message, errorContent, t);
333         case 409:
334             return new CmisConstraintException(message, errorContent, t);
335         case 429:
336             return new CmisTooManyRequestsException(message, errorContent, t);
337         case 503:
338             return new CmisServiceUnavailableException(message, errorContent, t);
339         default:
340             return new CmisRuntimeException(message, errorContent, t);
341         }
342     }
343 
344     // ---- helpers ----
345 
346     /**
347      * Parses an object from an input stream.
348      */
349     @SuppressWarnings("unchecked")
350     protected Map<String, Object> parseObject(InputStream stream, String charset) {
351         Object obj = parse(stream, charset, SIMPLE_CONTAINER_FACTORY);
352 
353         if (obj instanceof Map) {
354             return (Map<String, Object>) obj;
355         }
356 
357         throw new CmisConnectionException("Unexpected object!");
358     }
359 
360     /**
361      * Parses an array from an input stream.
362      */
363     @SuppressWarnings("unchecked")
364     protected List<Object> parseArray(InputStream stream, String charset) {
365         Object obj = parse(stream, charset, SIMPLE_CONTAINER_FACTORY);
366 
367         if (obj instanceof List) {
368             return (List<Object>) obj;
369         }
370 
371         throw new CmisConnectionException("Unexpected object!");
372     }
373 
374     /**
375      * Parses an input stream.
376      */
377     protected Object parse(InputStream stream, String charset, ContainerFactory containerFactory) {
378 
379         InputStreamReader reader = null;
380 
381         Object obj = null;
382         try {
383             reader = new InputStreamReader(stream, charset);
384             JSONParser parser = new JSONParser();
385             obj = parser.parse(reader, containerFactory);
386         } catch (JSONParseException e) {
387             throw new CmisConnectionException("Parsing exception: " + e.getMessage(), e);
388         } catch (Exception e) {
389             throw new CmisConnectionException("Parsing exception!", e);
390         } finally {
391             IOUtils.consumeAndClose(reader);
392             if (reader == null) {
393                 IOUtils.closeQuietly(stream);
394             }
395         }
396 
397         return obj;
398     }
399 
400     /**
401      * Performs a GET on an URL, checks the response code and returns the
402      * result.
403      */
404     protected Response read(UrlBuilder url) {
405         // make the call
406         Response resp = getHttpInvoker().invokeGET(url, session);
407 
408         // check response code
409         if (resp.getResponseCode() != 200) {
410             throw convertStatusCode(resp.getResponseCode(), resp.getResponseMessage(), resp.getErrorContent(), null);
411         }
412 
413         return resp;
414     }
415 
416     /**
417      * Performs a POST on an URL, checks the response code and returns the
418      * result.
419      */
420     protected Response post(UrlBuilder url, String contentType, Output writer) {
421         // make the call
422         Response resp = getHttpInvoker().invokePOST(url, contentType, writer, session);
423 
424         // check response code
425         if (resp.getResponseCode() != 200 && resp.getResponseCode() != 201) {
426             throw convertStatusCode(resp.getResponseCode(), resp.getResponseMessage(), resp.getErrorContent(), null);
427         }
428 
429         return resp;
430     }
431 
432     /**
433      * Performs a POST on an URL, checks the response code and returns the
434      * result.
435      */
436     protected void postAndConsume(UrlBuilder url, String contentType, Output writer) {
437         Response resp = post(url, contentType, writer);
438         IOUtils.consumeAndClose(resp.getStream());
439     }
440 
441     // ---- URL ----
442 
443     /**
444      * Returns the repository URL cache or creates a new cache if it doesn't
445      * exist.
446      */
447     protected RepositoryUrlCache getRepositoryUrlCache() {
448         RepositoryUrlCache repositoryUrlCache = (RepositoryUrlCache) getSession().get(
449                 SpiSessionParameter.REPOSITORY_URL_CACHE);
450         if (repositoryUrlCache == null) {
451             repositoryUrlCache = new RepositoryUrlCache();
452             getSession().put(SpiSessionParameter.REPOSITORY_URL_CACHE, repositoryUrlCache);
453         }
454 
455         return repositoryUrlCache;
456     }
457 
458     /**
459      * Retrieves the the repository info objects.
460      */
461     protected List<RepositoryInfo> getRepositoriesInternal(String repositoryId) {
462 
463         UrlBuilder url = null;
464 
465         if (repositoryId == null) {
466             // no repository id provided -> get all
467             url = new UrlBuilder(getServiceUrl());
468         } else {
469             // use URL of the specified repository
470             url = getRepositoryUrlCache().getRepositoryUrl(repositoryId, Constants.SELECTOR_REPOSITORY_INFO);
471             if (url == null) {
472                 // repository infos haven't been fetched yet -> get them all
473                 url = new UrlBuilder(getServiceUrl());
474             }
475         }
476 
477         // read and parse
478         Response resp = read(url);
479         Map<String, Object> json = parseObject(resp.getStream(), resp.getCharset());
480 
481         List<RepositoryInfo> repInfos = new ArrayList<RepositoryInfo>();
482 
483         for (Object jri : json.values()) {
484             if (jri instanceof Map) {
485                 @SuppressWarnings("unchecked")
486                 RepositoryInfo ri = JSONConverter.convertRepositoryInfo((Map<String, Object>) jri);
487                 String id = ri.getId();
488 
489                 if (ri instanceof RepositoryInfoBrowserBindingImpl) {
490                     String repositoryUrl = ((RepositoryInfoBrowserBindingImpl) ri).getRepositoryUrl();
491                     String rootUrl = ((RepositoryInfoBrowserBindingImpl) ri).getRootUrl();
492 
493                     if (id == null || repositoryUrl == null || rootUrl == null) {
494                         throw new CmisConnectionException("Found invalid Repository Info! (id: " + id + ")");
495                     }
496 
497                     getRepositoryUrlCache().addRepository(id, repositoryUrl, rootUrl);
498                 }
499 
500                 repInfos.add(ri);
501             } else {
502                 throw new CmisConnectionException("Found invalid Repository Info!");
503             }
504         }
505 
506         return repInfos;
507     }
508 
509     /**
510      * Retrieves a type definition.
511      */
512     protected TypeDefinition getTypeDefinitionInternal(String repositoryId, String typeId) {
513         // build URL
514         UrlBuilder url = getRepositoryUrl(repositoryId, Constants.SELECTOR_TYPE_DEFINITION);
515         url.addParameter(Constants.PARAM_TYPE_ID, typeId);
516 
517         // read and parse
518         Response resp = read(url);
519         Map<String, Object> json = parseObject(resp.getStream(), resp.getCharset());
520 
521         return JSONConverter.convertTypeDefinition(json);
522     }
523 
524     // ---- LinkAccess interface ----
525 
526     @Override
527     public String loadLink(String repositoryId, String objectId, String rel, String type) {
528         // AtomPub specific -> return null
529         return null;
530     }
531 
532     @Override
533     public String loadContentLink(String repositoryId, String documentId) {
534         UrlBuilder result = getRepositoryUrlCache().getObjectUrl(repositoryId, documentId, Constants.SELECTOR_CONTENT);
535         return result == null ? null : result.toString();
536     }
537 
538     @Override
539     public String loadRenditionContentLink(String repositoryId, String documentId, String streamId) {
540         UrlBuilder result = getRepositoryUrlCache().getObjectUrl(repositoryId, documentId, Constants.SELECTOR_CONTENT);
541         if (result != null) {
542             result.addParameter(Constants.PARAM_STREAM_ID, streamId);
543             return result.toString();
544         }
545 
546         return null;
547     }
548 }