This project has retired. For details please refer to its Attic page.
AbstractAtomPubService xref

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