This project has retired. For details please refer to its Attic page.
AbstractAtomPubService xref
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.atompub;
20  
21  import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNotEmpty;
22  
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  import java.math.BigInteger;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import javax.xml.stream.XMLStreamWriter;
34  
35  import org.apache.chemistry.opencmis.client.bindings.impl.CmisBindingsHelper;
36  import org.apache.chemistry.opencmis.client.bindings.impl.RepositoryInfoCache;
37  import org.apache.chemistry.opencmis.client.bindings.spi.BindingSession;
38  import org.apache.chemistry.opencmis.client.bindings.spi.LinkAccess;
39  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomAcl;
40  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomBase;
41  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomElement;
42  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomEntry;
43  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomLink;
44  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.RepositoryWorkspace;
45  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.ServiceDoc;
46  import org.apache.chemistry.opencmis.client.bindings.spi.http.HttpInvoker;
47  import org.apache.chemistry.opencmis.client.bindings.spi.http.Output;
48  import org.apache.chemistry.opencmis.client.bindings.spi.http.Response;
49  import org.apache.chemistry.opencmis.commons.PropertyIds;
50  import org.apache.chemistry.opencmis.commons.SessionParameter;
51  import org.apache.chemistry.opencmis.commons.data.Ace;
52  import org.apache.chemistry.opencmis.commons.data.Acl;
53  import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
54  import org.apache.chemistry.opencmis.commons.data.ObjectData;
55  import org.apache.chemistry.opencmis.commons.data.Properties;
56  import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
57  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
58  import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
59  import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
60  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
61  import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
62  import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException;
63  import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
64  import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
65  import org.apache.chemistry.opencmis.commons.exceptions.CmisFilterNotValidException;
66  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
67  import org.apache.chemistry.opencmis.commons.exceptions.CmisNameConstraintViolationException;
68  import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
69  import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
70  import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
71  import org.apache.chemistry.opencmis.commons.exceptions.CmisProxyAuthenticationException;
72  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
73  import org.apache.chemistry.opencmis.commons.exceptions.CmisServiceUnavailableException;
74  import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException;
75  import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException;
76  import org.apache.chemistry.opencmis.commons.exceptions.CmisUnauthorizedException;
77  import org.apache.chemistry.opencmis.commons.exceptions.CmisUpdateConflictException;
78  import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException;
79  import org.apache.chemistry.opencmis.commons.impl.Constants;
80  import org.apache.chemistry.opencmis.commons.impl.ReturnVersion;
81  import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
82  import org.apache.chemistry.opencmis.commons.impl.XMLConverter;
83  import org.apache.chemistry.opencmis.commons.impl.XMLUtils;
84  import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl;
85  import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
86  import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl;
87  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl;
88  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PolicyIdListImpl;
89  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
90  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl;
91  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
92  
93  /**
94   * Base class for all AtomPub client services.
95   */
96  public abstract class AbstractAtomPubService implements LinkAccess {
97  
98      protected enum IdentifierType {
99          ID, PATH
100     }
101 
102     protected static final String NAME_COLLECTION = "collection";
103     protected static final String NAME_URI_TEMPLATE = "uritemplate";
104     protected static final String NAME_PATH_SEGMENT = "pathSegment";
105     protected static final String NAME_RELATIVE_PATH_SEGMENT = "relativePathSegment";
106     protected static final String NAME_NUM_ITEMS = "numItems";
107 
108     private BindingSession session;
109 
110     /**
111      * Sets the current session.
112      */
113     protected void setSession(BindingSession session) {
114         this.session = session;
115     }
116 
117     /**
118      * Gets the current session.
119      */
120     protected BindingSession getSession() {
121         return session;
122     }
123 
124     /**
125      * Gets the HTTP Invoker object.
126      */
127     protected HttpInvoker getHttpInvoker() {
128         return CmisBindingsHelper.getHttpInvoker(session);
129     }
130 
131     /**
132      * Returns the service document URL of this session.
133      */
134     protected String getServiceDocURL() {
135         Object url = session.get(SessionParameter.ATOMPUB_URL);
136         if (url instanceof String) {
137             return (String) url;
138         }
139 
140         return null;
141     }
142 
143     /**
144      * Return the CMIS version of the given repository.
145      */
146     protected CmisVersion getCmisVersion(String repositoryId) {
147         if (CmisBindingsHelper.getForcedCmisVersion(session) != null) {
148             return CmisBindingsHelper.getForcedCmisVersion(session);
149         }
150 
151         RepositoryInfoCache cache = CmisBindingsHelper.getRepositoryInfoCache(session);
152         RepositoryInfo info = cache.get(repositoryId);
153 
154         if (info == null) {
155             List<RepositoryInfo> infoList = getRepositoriesInternal(repositoryId);
156             if (isNotEmpty(infoList)) {
157                 info = infoList.get(0);
158                 cache.put(info);
159             }
160         }
161 
162         return (info == null ? CmisVersion.CMIS_1_0 : info.getCmisVersion());
163     }
164 
165     // ---- link cache ----
166 
167     /**
168      * Returns the link cache or creates a new cache if it doesn't exist.
169      */
170     protected LinkCache getLinkCache() {
171         LinkCache linkCache = (LinkCache) getSession().get(SpiSessionParameter.LINK_CACHE);
172         if (linkCache == null) {
173             linkCache = new LinkCache(getSession());
174             getSession().put(SpiSessionParameter.LINK_CACHE, linkCache);
175         }
176 
177         return linkCache;
178     }
179 
180     /**
181      * Gets a link from the cache.
182      */
183     protected String getLink(String repositoryId, String id, String rel, String type) {
184         if (repositoryId == null) {
185             throw new CmisInvalidArgumentException("Repository ID must be set!");
186         }
187 
188         if (id == null) {
189             throw new CmisInvalidArgumentException("Object ID must be set!");
190         }
191 
192         return getLinkCache().getLink(repositoryId, id, rel, type);
193     }
194 
195     /**
196      * Gets a link from the cache.
197      */
198     protected String getLink(String repositoryId, String id, String rel) {
199         return getLink(repositoryId, id, rel, null);
200     }
201 
202     /**
203      * Gets a link from the cache if it is there or loads it into the cache if
204      * it is not there.
205      */
206     public String loadLink(String repositoryId, String id, String rel, String type) {
207         String link = getLink(repositoryId, id, rel, type);
208         if (link == null) {
209             getObjectInternal(repositoryId, IdentifierType.ID, id, ReturnVersion.THIS, "cmis:objectId", Boolean.FALSE,
210                     IncludeRelationships.NONE, "cmis:none", Boolean.FALSE, Boolean.FALSE, null);
211             link = getLink(repositoryId, id, rel, type);
212         }
213 
214         return link;
215     }
216 
217     /**
218      * Gets the content link from the cache if it is there or loads it into the
219      * cache if it is not there.
220      */
221     public String loadContentLink(String repositoryId, String id) {
222         return loadLink(repositoryId, id, AtomPubParser.LINK_REL_CONTENT, null);
223     }
224 
225     /**
226      * Gets a rendition content link from the cache if it is there or loads it
227      * into the cache if it is not there.
228      */
229     public String loadRenditionContentLink(String repositoryId, String id, String streamId) {
230         return loadLink(repositoryId, id, Constants.REL_ALTERNATE, streamId);
231     }
232 
233     /**
234      * Adds a link to the cache.
235      */
236     protected void addLink(String repositoryId, String id, String rel, String type, String link) {
237         getLinkCache().addLink(repositoryId, id, rel, type, link);
238     }
239 
240     /**
241      * Adds a link to the cache.
242      */
243     protected void addLink(String repositoryId, String id, AtomLink link) {
244         getLinkCache().addLink(repositoryId, id, link.getRel(), link.getType(), link.getHref());
245     }
246 
247     /**
248      * Removes all links of an object.
249      */
250     protected void removeLinks(String repositoryId, String id) {
251         getLinkCache().removeLinks(repositoryId, id);
252     }
253 
254     /**
255      * Locks the link cache.
256      */
257     protected void lockLinks() {
258         getLinkCache().lockLinks();
259     }
260 
261     /**
262      * Unlocks the link cache.
263      */
264     protected void unlockLinks() {
265         getLinkCache().unlockLinks();
266     }
267 
268     /**
269      * Checks a link throw an appropriate exception.
270      */
271     protected void throwLinkException(String repositoryId, String id, String rel, String type) {
272         int index = getLinkCache().checkLink(repositoryId, id, rel, type);
273 
274         switch (index) {
275         case 0:
276             throw new CmisObjectNotFoundException("Unknown repository!");
277         case 1:
278             throw new CmisObjectNotFoundException("Unknown object!");
279         case 2:
280             throw new CmisNotSupportedException("Operation not supported by the repository for this object!");
281         case 3:
282             throw new CmisNotSupportedException("No link with matching media type!");
283         case 4:
284             throw new CmisRuntimeException("Nothing wrong! Either this is a bug or a threading issue.");
285         default:
286             throw new CmisRuntimeException("Unknown error!");
287         }
288     }
289 
290     /**
291      * Gets a type link from the cache.
292      */
293     protected String getTypeLink(String repositoryId, String typeId, String rel, String type) {
294         if (repositoryId == null) {
295             throw new CmisInvalidArgumentException("Repository ID must be set!");
296         }
297 
298         if (typeId == null) {
299             throw new CmisInvalidArgumentException("Type ID must be set!");
300         }
301 
302         return getLinkCache().getTypeLink(repositoryId, typeId, rel, type);
303     }
304 
305     /**
306      * Gets a type link from the cache.
307      */
308     protected String getTypeLink(String repositoryId, String typeId, String rel) {
309         return getTypeLink(repositoryId, typeId, rel, null);
310     }
311 
312     /**
313      * Gets a link from the cache if it is there or loads it into the cache if
314      * it is not there.
315      */
316     protected String loadTypeLink(String repositoryId, String typeId, String rel, String type) {
317         String link = getTypeLink(repositoryId, typeId, rel, type);
318         if (link == null) {
319             getTypeDefinitionInternal(repositoryId, typeId);
320             link = getTypeLink(repositoryId, typeId, rel, type);
321         }
322 
323         return link;
324     }
325 
326     /**
327      * Adds a type link to the cache.
328      */
329     protected void addTypeLink(String repositoryId, String typeId, String rel, String type, String link) {
330         getLinkCache().addTypeLink(repositoryId, typeId, rel, type, link);
331     }
332 
333     /**
334      * Adds a type link to the cache.
335      */
336     protected void addTypeLink(String repositoryId, String typeId, AtomLink link) {
337         getLinkCache().addTypeLink(repositoryId, typeId, link.getRel(), link.getType(), link.getHref());
338     }
339 
340     /**
341      * Removes all links of a type.
342      */
343     protected void removeTypeLinks(String repositoryId, String id) {
344         getLinkCache().removeTypeLinks(repositoryId, id);
345     }
346 
347     /**
348      * Locks the type link cache.
349      */
350     protected void lockTypeLinks() {
351         getLinkCache().lockTypeLinks();
352     }
353 
354     /**
355      * Unlocks the type link cache.
356      */
357     protected void unlockTypeLinks() {
358         getLinkCache().unlockTypeLinks();
359     }
360 
361     /**
362      * Gets a collection from the cache.
363      */
364     protected String getCollection(String repositoryId, String collection) {
365         return getLinkCache().getCollection(repositoryId, collection);
366     }
367 
368     /**
369      * Gets a collection from the cache if it is there or loads it into the
370      * cache if it is not there.
371      */
372     protected String loadCollection(String repositoryId, String collection) {
373         String link = getCollection(repositoryId, collection);
374         if (link == null) {
375             // cache repository info
376             getRepositoriesInternal(repositoryId);
377             link = getCollection(repositoryId, collection);
378         }
379 
380         return link;
381     }
382 
383     /**
384      * Adds a collection to the cache.
385      */
386     protected void addCollection(String repositoryId, String collection, String link) {
387         getLinkCache().addCollection(repositoryId, collection, link);
388     }
389 
390     /**
391      * Gets a repository link from the cache.
392      */
393     protected String getRepositoryLink(String repositoryId, String rel) {
394         return getLinkCache().getRepositoryLink(repositoryId, rel);
395     }
396 
397     /**
398      * Gets a repository link from the cache if it is there or loads it into the
399      * cache if it is not there.
400      */
401     protected String loadRepositoryLink(String repositoryId, String rel) {
402         String link = getRepositoryLink(repositoryId, rel);
403         if (link == null) {
404             // cache repository info
405             getRepositoriesInternal(repositoryId);
406             link = getRepositoryLink(repositoryId, rel);
407         }
408 
409         return link;
410     }
411 
412     /**
413      * Adds a repository link to the cache.
414      */
415     protected void addRepositoryLink(String repositoryId, String rel, String link) {
416         getLinkCache().addRepositoryLink(repositoryId, rel, link);
417     }
418 
419     /**
420      * Adds a repository link to the cache.
421      */
422     protected void addRepositoryLink(String repositoryId, AtomLink link) {
423         addRepositoryLink(repositoryId, link.getRel(), link.getHref());
424     }
425 
426     /**
427      * Gets an URI template from the cache.
428      */
429     protected String getTemplateLink(String repositoryId, String type, Map<String, Object> parameters) {
430         return getLinkCache().getTemplateLink(repositoryId, type, parameters);
431     }
432 
433     /**
434      * Gets a template link from the cache if it is there or loads it into the
435      * cache if it is not there.
436      */
437     protected String loadTemplateLink(String repositoryId, String type, Map<String, Object> parameters) {
438         String link = getTemplateLink(repositoryId, type, parameters);
439         if (link == null) {
440             // cache repository info
441             getRepositoriesInternal(repositoryId);
442             link = getTemplateLink(repositoryId, type, parameters);
443         }
444 
445         return link;
446     }
447 
448     /**
449      * Adds an URI template to the cache.
450      */
451     protected void addTemplate(String repositoryId, String type, String link) {
452         getLinkCache().addTemplate(repositoryId, type, link);
453     }
454 
455     // ---- exceptions ----
456 
457     /**
458      * Converts a HTTP status code into an Exception.
459      */
460     protected CmisBaseException convertStatusCode(int code, String message, String errorContent, Throwable t) {
461         String exception = extractException(errorContent);
462         message = extractErrorMessage(message, errorContent);
463 
464         switch (code) {
465         case 301:
466         case 302:
467         case 303:
468         case 307:
469             return new CmisConnectionException("Redirects are not supported (HTTP status code " + code + "): "
470                     + message, errorContent, t);
471         case 400:
472             if (CmisFilterNotValidException.EXCEPTION_NAME.equals(exception)) {
473                 return new CmisFilterNotValidException(message, errorContent, t);
474             }
475             return new CmisInvalidArgumentException(message, errorContent, t);
476         case 401:
477             return new CmisUnauthorizedException(message, errorContent, t);
478         case 403:
479             if (CmisStreamNotSupportedException.EXCEPTION_NAME.equals(exception)) {
480                 return new CmisStreamNotSupportedException(message, errorContent, t);
481             }
482             return new CmisPermissionDeniedException(message, errorContent, t);
483         case 404:
484             return new CmisObjectNotFoundException(message, errorContent, t);
485         case 405:
486             return new CmisNotSupportedException(message, errorContent, t);
487         case 407:
488             return new CmisProxyAuthenticationException(message, errorContent, t);
489         case 409:
490             if (CmisContentAlreadyExistsException.EXCEPTION_NAME.equals(exception)) {
491                 return new CmisContentAlreadyExistsException(message, errorContent, t);
492             } else if (CmisVersioningException.EXCEPTION_NAME.equals(exception)) {
493                 return new CmisVersioningException(message, errorContent, t);
494             } else if (CmisUpdateConflictException.EXCEPTION_NAME.equals(exception)) {
495                 return new CmisUpdateConflictException(message, errorContent, t);
496             } else if (CmisNameConstraintViolationException.EXCEPTION_NAME.equals(exception)) {
497                 return new CmisNameConstraintViolationException(message, errorContent, t);
498             }
499             return new CmisConstraintException(message, errorContent, t);
500         case 503:
501             return new CmisServiceUnavailableException(message, errorContent, t);
502         default:
503             if (CmisStorageException.EXCEPTION_NAME.equals(exception)) {
504                 return new CmisStorageException(message, errorContent, t);
505             }
506             return new CmisRuntimeException(message, errorContent, t);
507         }
508     }
509 
510     protected String extractException(String errorContent) {
511         if (errorContent == null) {
512             return null;
513         }
514 
515         int begin = errorContent.indexOf("<!--exception-->");
516         int end = errorContent.indexOf("<!--/exception-->");
517 
518         if (begin == -1 || end == -1 || begin > end) {
519             return null;
520         }
521 
522         return errorContent.substring(begin + "<!--exception-->".length(), end);
523     }
524 
525     protected String extractErrorMessage(String message, String errorContent) {
526         if (errorContent == null) {
527             return message;
528         }
529 
530         int begin = errorContent.indexOf("<!--message-->");
531         int end = errorContent.indexOf("<!--/message-->");
532 
533         if (begin == -1 || end == -1 || begin > end) {
534             return message;
535         }
536 
537         return errorContent.substring(begin + "<!--message-->".length(), end);
538     }
539 
540     // ---- helpers ----
541 
542     protected boolean is(String name, AtomElement element) {
543         return name.equals(element.getName().getLocalPart());
544     }
545 
546     protected boolean isStr(String name, AtomElement element) {
547         return is(name, element) && (element.getObject() instanceof String);
548     }
549 
550     protected boolean isInt(String name, AtomElement element) {
551         return is(name, element) && (element.getObject() instanceof BigInteger);
552     }
553 
554     protected boolean isNextLink(AtomElement element) {
555         return Constants.REL_NEXT.equals(((AtomLink) element.getObject()).getRel());
556     }
557 
558     /**
559      * Creates a CMIS object with properties and policy IDs.
560      */
561     protected ObjectDataImpl createObject(Properties properties, String changeToken, List<String> policies) {
562         ObjectDataImpl object = new ObjectDataImpl();
563 
564         boolean omitChangeToken = getSession().get(SessionParameter.OMIT_CHANGE_TOKENS, false);
565 
566         if (properties == null) {
567             properties = new PropertiesImpl();
568             if (changeToken != null && !omitChangeToken) {
569                 ((PropertiesImpl) properties)
570                         .addProperty(new PropertyStringImpl(PropertyIds.CHANGE_TOKEN, changeToken));
571             }
572         } else {
573             if (omitChangeToken) {
574                 if (properties.getProperties().containsKey(PropertyIds.CHANGE_TOKEN)) {
575                     properties = new PropertiesImpl(properties);
576                     ((PropertiesImpl) properties).removeProperty(PropertyIds.CHANGE_TOKEN);
577                 }
578             } else {
579                 if (changeToken != null && !properties.getProperties().containsKey(PropertyIds.CHANGE_TOKEN)) {
580                     properties = new PropertiesImpl(properties);
581                     ((PropertiesImpl) properties).addProperty(new PropertyStringImpl(PropertyIds.CHANGE_TOKEN,
582                             changeToken));
583                 }
584             }
585         }
586 
587         object.setProperties(properties);
588 
589         if (isNotEmpty(policies)) {
590             PolicyIdListImpl policyIdList = new PolicyIdListImpl();
591             policyIdList.setPolicyIds(policies);
592             object.setPolicyIds(policyIdList);
593         }
594 
595         return object;
596     }
597 
598     /**
599      * Creates a CMIS object that only contains an ID in the property list.
600      */
601     protected ObjectData createIdObject(String objectId) {
602         ObjectDataImpl object = new ObjectDataImpl();
603 
604         PropertiesImpl properties = new PropertiesImpl();
605         object.setProperties(properties);
606 
607         properties.addProperty(new PropertyIdImpl(PropertyIds.OBJECT_ID, objectId));
608 
609         return object;
610     }
611 
612     /**
613      * Parses an input stream.
614      */
615     @SuppressWarnings("unchecked")
616     protected <T extends AtomBase> T parse(InputStream stream, Class<T> clazz) {
617         AtomPubParser parser = new AtomPubParser(stream);
618 
619         try {
620             parser.parse();
621         } catch (Exception e) {
622             throw new CmisConnectionException("Parsing exception!", e);
623         }
624 
625         AtomBase parseResult = parser.getResults();
626 
627         if (!clazz.isInstance(parseResult)) {
628             throw new CmisConnectionException("Unexpected document! Received: "
629                     + (parseResult == null ? "something unknown" : parseResult.getType()));
630         }
631 
632         return (T) parseResult;
633     }
634 
635     /**
636      * Performs a GET on an URL, checks the response code and returns the
637      * result.
638      */
639     protected Response read(UrlBuilder url) {
640         // make the call
641         Response resp = getHttpInvoker().invokeGET(url, session);
642 
643         // check response code
644         if (resp.getResponseCode() != 200) {
645             throw convertStatusCode(resp.getResponseCode(), resp.getResponseMessage(), resp.getErrorContent(), null);
646         }
647 
648         return resp;
649     }
650 
651     /**
652      * Performs a POST on an URL, checks the response code and returns the
653      * result.
654      */
655     protected Response post(UrlBuilder url, String contentType, Output writer) {
656         // make the call
657         Response resp = getHttpInvoker().invokePOST(url, contentType, writer, session);
658 
659         // check response code
660         if (resp.getResponseCode() != 201) {
661             throw convertStatusCode(resp.getResponseCode(), resp.getResponseMessage(), resp.getErrorContent(), null);
662         }
663 
664         return resp;
665     }
666 
667     /**
668      * Performs a PUT on an URL, checks the response code and returns the
669      * result.
670      */
671     protected Response put(UrlBuilder url, String contentType, Output writer) {
672         return put(url, contentType, null, writer);
673     }
674 
675     /**
676      * Performs a PUT on an URL, checks the response code and returns the
677      * result.
678      */
679     protected Response put(UrlBuilder url, String contentType, Map<String, String> headers, Output writer) {
680         // make the call
681         Response resp = getHttpInvoker().invokePUT(url, contentType, headers, writer, session);
682 
683         // check response code
684         if ((resp.getResponseCode() < 200) || (resp.getResponseCode() > 299)) {
685             throw convertStatusCode(resp.getResponseCode(), resp.getResponseMessage(), resp.getErrorContent(), null);
686         }
687 
688         return resp;
689     }
690 
691     /**
692      * Performs a DELETE on an URL, checks the response code and returns the
693      * result.
694      */
695     protected void delete(UrlBuilder url) {
696         // make the call
697         Response resp = getHttpInvoker().invokeDELETE(url, session);
698 
699         // check response code
700         if (resp.getResponseCode() != 204) {
701             throw convertStatusCode(resp.getResponseCode(), resp.getResponseMessage(), resp.getErrorContent(), null);
702         }
703     }
704 
705     // ---- common operations ----
706 
707     /**
708      * Checks if at least one ACE list is not empty.
709      */
710     protected boolean isAclMergeRequired(Acl addAces, Acl removeAces) {
711         return (addAces != null && isNotEmpty(addAces.getAces()))
712                 || (removeAces != null && isNotEmpty(removeAces.getAces()));
713     }
714 
715     /**
716      * Merges the new ACL from original, add and remove ACEs lists.
717      */
718     protected Acl mergeAcls(Acl originalAces, Acl addAces, Acl removeAces) {
719         Map<String, Set<String>> originals = convertAclToMap(originalAces);
720         Map<String, Set<String>> adds = convertAclToMap(addAces);
721         Map<String, Set<String>> removes = convertAclToMap(removeAces);
722         List<Ace> newAces = new ArrayList<Ace>();
723 
724         // iterate through the original ACEs
725         for (Map.Entry<String, Set<String>> ace : originals.entrySet()) {
726 
727             // add permissions
728             Set<String> addPermissions = adds.get(ace.getKey());
729             if (addPermissions != null) {
730                 ace.getValue().addAll(addPermissions);
731             }
732 
733             // remove permissions
734             Set<String> removePermissions = removes.get(ace.getKey());
735             if (removePermissions != null) {
736                 ace.getValue().removeAll(removePermissions);
737             }
738 
739             // create new ACE
740             if (!ace.getValue().isEmpty()) {
741                 newAces.add(new AccessControlEntryImpl(new AccessControlPrincipalDataImpl(ace.getKey()),
742                         new ArrayList<String>(ace.getValue())));
743             }
744         }
745 
746         // find all ACEs that should be added but are not in the original ACE
747         // list
748         for (Map.Entry<String, Set<String>> ace : adds.entrySet()) {
749             if (!originals.containsKey(ace.getKey()) && !ace.getValue().isEmpty()) {
750                 newAces.add(new AccessControlEntryImpl(new AccessControlPrincipalDataImpl(ace.getKey()),
751                         new ArrayList<String>(ace.getValue())));
752             }
753         }
754 
755         return new AccessControlListImpl(newAces);
756     }
757 
758     /**
759      * Converts a list of ACEs into Map for better handling.
760      */
761     private static Map<String, Set<String>> convertAclToMap(Acl acl) {
762         Map<String, Set<String>> result = new HashMap<String, Set<String>>();
763 
764         if (acl == null || acl.getAces() == null) {
765             return result;
766         }
767 
768         for (Ace ace : acl.getAces()) {
769             // don't consider indirect ACEs - we can't change them
770             if (!ace.isDirect()) {
771                 // ignore
772                 continue;
773             }
774 
775             // although a principal must not be null, check it
776             if (ace.getPrincipal() == null || ace.getPrincipal().getId() == null) {
777                 // ignore
778                 continue;
779             }
780 
781             Set<String> permissions = result.get(ace.getPrincipal().getId());
782             if (permissions == null) {
783                 permissions = new HashSet<String>();
784                 result.put(ace.getPrincipal().getId(), permissions);
785             }
786 
787             if (ace.getPermissions() != null) {
788                 permissions.addAll(ace.getPermissions());
789             }
790         }
791 
792         return result;
793     }
794 
795     /**
796      * Retrieves the Service Document from the server and caches the repository
797      * info objects, collections, links, URI templates, etc.
798      */
799     @SuppressWarnings("unchecked")
800     protected List<RepositoryInfo> getRepositoriesInternal(String repositoryId) {
801         List<RepositoryInfo> repInfos = new ArrayList<RepositoryInfo>();
802 
803         // retrieve service doc
804         UrlBuilder url = new UrlBuilder(getServiceDocURL());
805         url.addParameter(Constants.PARAM_REPOSITORY_ID, repositoryId);
806 
807         // read and parse
808         Response resp = read(url);
809         ServiceDoc serviceDoc = parse(resp.getStream(), ServiceDoc.class);
810 
811         // walk through the workspaces
812         for (RepositoryWorkspace ws : serviceDoc.getWorkspaces()) {
813             if (ws.getId() == null) {
814                 // found a non-CMIS workspace
815                 continue;
816             }
817 
818             for (AtomElement element : ws.getElements()) {
819                 if (is(NAME_COLLECTION, element)) {
820                     Map<String, String> colMap = (Map<String, String>) element.getObject();
821                     addCollection(ws.getId(), colMap.get("collectionType"), colMap.get("href"));
822                 } else if (element.getObject() instanceof AtomLink) {
823                     addRepositoryLink(ws.getId(), (AtomLink) element.getObject());
824                 } else if (is(NAME_URI_TEMPLATE, element)) {
825                     Map<String, String> tempMap = (Map<String, String>) element.getObject();
826                     addTemplate(ws.getId(), tempMap.get("type"), tempMap.get("template"));
827                 } else if (element.getObject() instanceof RepositoryInfo) {
828                     repInfos.add((RepositoryInfo) element.getObject());
829                 }
830             }
831         }
832 
833         return repInfos;
834     }
835 
836     /**
837      * Retrieves an object from the server and caches the links.
838      */
839     protected ObjectData getObjectInternal(String repositoryId, IdentifierType idOrPath, String objectIdOrPath,
840             ReturnVersion returnVersion, String filter, Boolean includeAllowableActions,
841             IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
842             Boolean includeAcl, ExtensionsData extension) {
843 
844         Map<String, Object> parameters = new HashMap<String, Object>();
845         parameters.put(Constants.PARAM_ID, objectIdOrPath);
846         parameters.put(Constants.PARAM_PATH, objectIdOrPath);
847         parameters.put(Constants.PARAM_RETURN_VERSION, returnVersion);
848         parameters.put(Constants.PARAM_FILTER, filter);
849         parameters.put(Constants.PARAM_ALLOWABLE_ACTIONS, includeAllowableActions);
850         parameters.put(Constants.PARAM_ACL, includeAcl);
851         parameters.put(Constants.PARAM_POLICY_IDS, includePolicyIds);
852         parameters.put(Constants.PARAM_RELATIONSHIPS, includeRelationships);
853         parameters.put(Constants.PARAM_RENDITION_FILTER, renditionFilter);
854 
855         String link = loadTemplateLink(repositoryId, (idOrPath == IdentifierType.ID ? Constants.TEMPLATE_OBJECT_BY_ID
856                 : Constants.TEMPLATE_OBJECT_BY_PATH), parameters);
857         if (link == null) {
858             throw new CmisObjectNotFoundException("Unknown repository!");
859         }
860 
861         UrlBuilder url = new UrlBuilder(link);
862         // workaround for missing template parameter in the CMIS spec
863         if (returnVersion != null && returnVersion != ReturnVersion.THIS) {
864             url.addParameter(Constants.PARAM_RETURN_VERSION, returnVersion);
865         }
866 
867         // read and parse
868         Response resp = read(url);
869         AtomEntry entry = parse(resp.getStream(), AtomEntry.class);
870 
871         // we expect a CMIS entry
872         if (entry.getId() == null) {
873             throw new CmisConnectionException("Received Atom entry is not a CMIS entry!");
874         }
875 
876         lockLinks();
877         ObjectData result = null;
878         try {
879             // clean up cache
880             removeLinks(repositoryId, entry.getId());
881 
882             // walk through the entry
883             for (AtomElement element : entry.getElements()) {
884                 if (element.getObject() instanceof AtomLink) {
885                     addLink(repositoryId, entry.getId(), (AtomLink) element.getObject());
886                 } else if (element.getObject() instanceof ObjectData) {
887                     result = (ObjectData) element.getObject();
888                 }
889             }
890         } finally {
891             unlockLinks();
892         }
893 
894         return result;
895     }
896 
897     /**
898      * Retrieves a type definition.
899      */
900     protected TypeDefinition getTypeDefinitionInternal(String repositoryId, String typeId) {
901 
902         Map<String, Object> parameters = new HashMap<String, Object>();
903         parameters.put(Constants.PARAM_ID, typeId);
904 
905         String link = loadTemplateLink(repositoryId, Constants.TEMPLATE_TYPE_BY_ID, parameters);
906         if (link == null) {
907             throw new CmisObjectNotFoundException("Unknown repository!");
908         }
909 
910         // read and parse
911         Response resp = read(new UrlBuilder(link));
912         AtomEntry entry = parse(resp.getStream(), AtomEntry.class);
913 
914         // we expect a CMIS entry
915         if (entry.getId() == null) {
916             throw new CmisConnectionException("Received Atom entry is not a CMIS entry!");
917         }
918 
919         lockTypeLinks();
920         TypeDefinition result = null;
921         try {
922             // clean up cache
923             removeTypeLinks(repositoryId, entry.getId());
924 
925             // walk through the entry
926             for (AtomElement element : entry.getElements()) {
927                 if (element.getObject() instanceof AtomLink) {
928                     addTypeLink(repositoryId, entry.getId(), (AtomLink) element.getObject());
929                 } else if (element.getObject() instanceof TypeDefinition) {
930                     result = (TypeDefinition) element.getObject();
931                 }
932             }
933         } finally {
934             unlockTypeLinks();
935         }
936 
937         return result;
938     }
939 
940     /**
941      * Retrieves the ACL of an object.
942      */
943     public Acl getAclInternal(String repositoryId, String objectId, Boolean onlyBasicPermissions,
944             ExtensionsData extension) {
945 
946         // find the link
947         String link = loadLink(repositoryId, objectId, Constants.REL_ACL, Constants.MEDIATYPE_ACL);
948 
949         if (link == null) {
950             throwLinkException(repositoryId, objectId, Constants.REL_ACL, Constants.MEDIATYPE_ACL);
951         }
952 
953         UrlBuilder url = new UrlBuilder(link);
954         url.addParameter(Constants.PARAM_ONLY_BASIC_PERMISSIONS, onlyBasicPermissions);
955 
956         // read and parse
957         Response resp = read(url);
958         AtomAcl acl = parse(resp.getStream(), AtomAcl.class);
959 
960         return acl.getACL();
961     }
962 
963     /**
964      * Updates the ACL of an object.
965      */
966     protected AtomAcl updateAcl(String repositoryId, String objectId, final Acl acl, AclPropagation aclPropagation) {
967 
968         // find the link
969         String link = loadLink(repositoryId, objectId, Constants.REL_ACL, Constants.MEDIATYPE_ACL);
970 
971         if (link == null) {
972             throwLinkException(repositoryId, objectId, Constants.REL_ACL, Constants.MEDIATYPE_ACL);
973         }
974 
975         UrlBuilder aclUrl = new UrlBuilder(link);
976         aclUrl.addParameter(Constants.PARAM_ACL_PROPAGATION, aclPropagation);
977 
978         final CmisVersion cmisVersion = getCmisVersion(repositoryId);
979 
980         // update
981         Response resp = put(aclUrl, Constants.MEDIATYPE_ACL, new Output() {
982             public void write(OutputStream out) throws Exception {
983                 XMLStreamWriter writer = XMLUtils.createWriter(out);
984                 XMLUtils.startXmlDocument(writer);
985                 XMLConverter.writeAcl(writer, cmisVersion, true, acl);
986                 XMLUtils.endXmlDocument(writer);
987             }
988         });
989 
990         // parse new entry
991         return parse(resp.getStream(), AtomAcl.class);
992     }
993 
994 }