This project has retired. For details please refer to its Attic page.
SessionImpl 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.runtime;
20  
21  import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNullOrEmpty;
22  
23  import java.math.BigInteger;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.EnumSet;
28  import java.util.HashMap;
29  import java.util.LinkedHashMap;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.concurrent.locks.ReentrantReadWriteLock;
35  
36  import org.apache.chemistry.opencmis.client.api.ChangeEvent;
37  import org.apache.chemistry.opencmis.client.api.ChangeEvents;
38  import org.apache.chemistry.opencmis.client.api.CmisObject;
39  import org.apache.chemistry.opencmis.client.api.Document;
40  import org.apache.chemistry.opencmis.client.api.DocumentType;
41  import org.apache.chemistry.opencmis.client.api.Folder;
42  import org.apache.chemistry.opencmis.client.api.ItemIterable;
43  import org.apache.chemistry.opencmis.client.api.ObjectFactory;
44  import org.apache.chemistry.opencmis.client.api.ObjectId;
45  import org.apache.chemistry.opencmis.client.api.ObjectType;
46  import org.apache.chemistry.opencmis.client.api.OperationContext;
47  import org.apache.chemistry.opencmis.client.api.Policy;
48  import org.apache.chemistry.opencmis.client.api.QueryResult;
49  import org.apache.chemistry.opencmis.client.api.QueryStatement;
50  import org.apache.chemistry.opencmis.client.api.Relationship;
51  import org.apache.chemistry.opencmis.client.api.SecondaryType;
52  import org.apache.chemistry.opencmis.client.api.Session;
53  import org.apache.chemistry.opencmis.client.api.Tree;
54  import org.apache.chemistry.opencmis.client.bindings.cache.TypeDefinitionCache;
55  import org.apache.chemistry.opencmis.client.runtime.cache.Cache;
56  import org.apache.chemistry.opencmis.client.runtime.cache.CacheImpl;
57  import org.apache.chemistry.opencmis.client.runtime.repository.ObjectFactoryImpl;
58  import org.apache.chemistry.opencmis.client.runtime.util.AbstractPageFetcher;
59  import org.apache.chemistry.opencmis.client.runtime.util.CollectionIterable;
60  import org.apache.chemistry.opencmis.client.runtime.util.TreeImpl;
61  import org.apache.chemistry.opencmis.client.util.OperationContextUtils;
62  import org.apache.chemistry.opencmis.commons.PropertyIds;
63  import org.apache.chemistry.opencmis.commons.SessionParameter;
64  import org.apache.chemistry.opencmis.commons.SessionParameterDefaults;
65  import org.apache.chemistry.opencmis.commons.data.Ace;
66  import org.apache.chemistry.opencmis.commons.data.Acl;
67  import org.apache.chemistry.opencmis.commons.data.BulkUpdateObjectIdAndChangeToken;
68  import org.apache.chemistry.opencmis.commons.data.ContentStream;
69  import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
70  import org.apache.chemistry.opencmis.commons.data.ObjectData;
71  import org.apache.chemistry.opencmis.commons.data.ObjectList;
72  import org.apache.chemistry.opencmis.commons.data.PropertyData;
73  import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
74  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
75  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
76  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList;
77  import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
78  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
79  import org.apache.chemistry.opencmis.commons.enums.BindingType;
80  import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
81  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
82  import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection;
83  import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
84  import org.apache.chemistry.opencmis.commons.enums.Updatability;
85  import org.apache.chemistry.opencmis.commons.enums.VersioningState;
86  import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
87  import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
88  import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
89  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
90  import org.apache.chemistry.opencmis.commons.impl.ClassLoaderUtil;
91  import org.apache.chemistry.opencmis.commons.impl.Constants;
92  import org.apache.chemistry.opencmis.commons.impl.dataobjects.BulkUpdateObjectIdAndChangeTokenImpl;
93  import org.apache.chemistry.opencmis.commons.spi.AclService;
94  import org.apache.chemistry.opencmis.commons.spi.AuthenticationProvider;
95  import org.apache.chemistry.opencmis.commons.spi.CmisBinding;
96  import org.apache.chemistry.opencmis.commons.spi.DiscoveryService;
97  import org.apache.chemistry.opencmis.commons.spi.ExtendedAclService;
98  import org.apache.chemistry.opencmis.commons.spi.ExtendedHolder;
99  import org.apache.chemistry.opencmis.commons.spi.ExtendedRepositoryService;
100 import org.apache.chemistry.opencmis.commons.spi.Holder;
101 import org.apache.chemistry.opencmis.commons.spi.NavigationService;
102 import org.apache.chemistry.opencmis.commons.spi.RelationshipService;
103 import org.apache.chemistry.opencmis.commons.spi.RepositoryService;
104 
105 /**
106  * Persistent model session.
107  */
108 public class SessionImpl implements Session {
109 
110     private static final OperationContext DEFAULT_CONTEXT = new OperationContextImpl(null, false, true, false,
111             IncludeRelationships.NONE, null, true, null, true, 100);
112 
113     private static final Set<Updatability> CREATE_UPDATABILITY = EnumSet.noneOf(Updatability.class);
114     private static final Set<Updatability> CREATE_AND_CHECKOUT_UPDATABILITY = EnumSet.noneOf(Updatability.class);
115 
116     static {
117         CREATE_UPDATABILITY.add(Updatability.ONCREATE);
118         CREATE_UPDATABILITY.add(Updatability.READWRITE);
119         CREATE_AND_CHECKOUT_UPDATABILITY.add(Updatability.ONCREATE);
120         CREATE_AND_CHECKOUT_UPDATABILITY.add(Updatability.READWRITE);
121         CREATE_AND_CHECKOUT_UPDATABILITY.add(Updatability.WHENCHECKEDOUT);
122     }
123 
124     // private static Logger log = LoggerFactory.getLogger(SessionImpl.class);
125 
126     private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
127     private transient LinkedHashMap<String, ObjectType> objectTypeCache;
128 
129     /*
130      * default session context (serializable)
131      */
132     private OperationContext defaultContext = DEFAULT_CONTEXT;
133 
134     /*
135      * session parameter (serializable)
136      */
137     private Map<String, String> parameters;
138 
139     /*
140      * CMIS binding (serializable)
141      */
142     private CmisBinding binding;
143 
144     /*
145      * Session Locale, determined from session parameter (serializable)
146      */
147     private Locale locale;
148 
149     /*
150      * Object factory (serializable)
151      */
152     private final ObjectFactory objectFactory;
153 
154     /*
155      * Authentication provider (serializable)
156      */
157     private final AuthenticationProvider authenticationProvider;
158 
159     /*
160      * Object cache (serializable)
161      */
162     private Cache cache;
163     private final boolean cachePathOmit;
164 
165     /*
166      * Type cache.
167      */
168     private TypeDefinitionCache typeDefCache;
169 
170     /*
171      * Repository info (serializable)
172      */
173     private RepositoryInfo repositoryInfo;
174 
175     /**
176      * required for serialization
177      */
178     private static final long serialVersionUID = 1L;
179 
180     /**
181      * Constructor.
182      */
183     public SessionImpl(Map<String, String> parameters, ObjectFactory objectFactory,
184             AuthenticationProvider authenticationProvider, Cache cache, TypeDefinitionCache typeDefCache) {
185         if (parameters == null) {
186             throw new IllegalArgumentException("No parameters provided!");
187         }
188 
189         this.parameters = parameters;
190         this.locale = determineLocale(parameters);
191 
192         this.objectFactory = objectFactory == null ? createObjectFactory() : objectFactory;
193         this.authenticationProvider = authenticationProvider;
194         this.cache = cache == null ? createCache() : cache;
195         this.typeDefCache = typeDefCache;
196 
197         cachePathOmit = Boolean.parseBoolean(parameters.get(SessionParameter.CACHE_PATH_OMIT));
198     }
199 
200     private Locale determineLocale(Map<String, String> parameters) {
201         Locale result = null;
202 
203         String language = parameters.get(SessionParameter.LOCALE_ISO639_LANGUAGE);
204         String country = parameters.get(SessionParameter.LOCALE_ISO3166_COUNTRY);
205         String variant = parameters.get(SessionParameter.LOCALE_VARIANT);
206 
207         if (variant != null) {
208             // all 3 parameter must not be null and valid
209             result = new Locale(language, country, variant);
210         } else {
211             if (country != null) {
212                 // 2 parameter must not be null and valid
213                 result = new Locale(language, country);
214             } else {
215                 if (language != null) {
216                     // 1 parameter must not be null and valid
217                     result = new Locale(language);
218                 } else {
219                     result = Locale.getDefault();
220                 }
221             }
222         }
223 
224         return result;
225     }
226 
227     private ObjectFactory createObjectFactory() {
228         try {
229             String classname = parameters.get(SessionParameter.OBJECT_FACTORY_CLASS);
230 
231             Class<?> objectFactoryClass;
232             if (classname == null) {
233                 objectFactoryClass = ObjectFactoryImpl.class;
234             } else {
235                 objectFactoryClass = ClassLoaderUtil.loadClass(classname);
236             }
237 
238             Object of = objectFactoryClass.newInstance();
239             if (!(of instanceof ObjectFactory)) {
240                 throw new InstantiationException("Class does not implement ObjectFactory!");
241             }
242 
243             ((ObjectFactory) of).initialize(this, parameters);
244 
245             return (ObjectFactory) of;
246         } catch (Exception e) {
247             throw new IllegalArgumentException("Unable to create object factory: " + e, e);
248         }
249     }
250 
251     private Cache createCache() {
252         try {
253             String classname = parameters.get(SessionParameter.CACHE_CLASS);
254 
255             Class<?> cacheClass;
256             if (classname == null) {
257                 cacheClass = CacheImpl.class;
258             } else {
259                 cacheClass = ClassLoaderUtil.loadClass(classname);
260             }
261 
262             Object of = cacheClass.newInstance();
263             if (!(of instanceof Cache)) {
264                 throw new InstantiationException("Class does not implement Cache!");
265             }
266 
267             ((Cache) of).initialize(this, parameters);
268 
269             return (Cache) of;
270         } catch (Exception e) {
271             throw new IllegalArgumentException("Unable to create cache: " + e, e);
272         }
273     }
274 
275     @Override
276     public Map<String, String> getSessionParameters() {
277         return Collections.unmodifiableMap(parameters);
278     }
279 
280     @Override
281     public void clear() {
282         lock.writeLock().lock();
283         try {
284             // create new object cache
285             cache = createCache();
286 
287             // clear object type cache
288             objectTypeCache = null;
289 
290             // clear provider cache
291             getBinding().clearAllCaches();
292         } finally {
293             lock.writeLock().unlock();
294         }
295     }
296 
297     @Override
298     public ObjectFactory getObjectFactory() {
299         assert objectFactory != null;
300         return objectFactory;
301     }
302 
303     @Override
304     public ItemIterable<Document> getCheckedOutDocs() {
305         return getCheckedOutDocs(getDefaultContext());
306     }
307 
308     @Override
309     public ItemIterable<Document> getCheckedOutDocs(OperationContext context) {
310         checkContext(context);
311 
312         final NavigationService navigationService = getBinding().getNavigationService();
313         final ObjectFactory of = getObjectFactory();
314         final OperationContext ctxt = new OperationContextImpl(context);
315 
316         return new CollectionIterable<Document>(new AbstractPageFetcher<Document>(ctxt.getMaxItemsPerPage()) {
317 
318             @Override
319             protected AbstractPageFetcher.Page<Document> fetchPage(long skipCount) {
320 
321                 // get all checked out documents
322                 ObjectList checkedOutDocs = navigationService.getCheckedOutDocs(getRepositoryId(), null,
323                         ctxt.getFilterString(), ctxt.getOrderBy(), ctxt.isIncludeAllowableActions(),
324                         ctxt.getIncludeRelationships(), ctxt.getRenditionFilterString(),
325                         BigInteger.valueOf(this.maxNumItems), BigInteger.valueOf(skipCount), null);
326 
327                 // convert objects
328                 List<Document> page = new ArrayList<Document>();
329                 if (checkedOutDocs.getObjects() != null) {
330                     for (ObjectData objectData : checkedOutDocs.getObjects()) {
331                         CmisObject doc = of.convertObject(objectData, ctxt);
332                         if (!(doc instanceof Document)) {
333                             // should not happen...
334                             continue;
335                         }
336 
337                         page.add((Document) doc);
338                     }
339                 }
340 
341                 return new AbstractPageFetcher.Page<Document>(page, checkedOutDocs.getNumItems(),
342                         checkedOutDocs.hasMoreItems());
343             }
344         });
345     }
346 
347     @Override
348     public ChangeEvents getContentChanges(String changeLogToken, boolean includeProperties, long maxNumItems) {
349         return getContentChanges(changeLogToken, includeProperties, maxNumItems, getDefaultContext());
350     }
351 
352     @Override
353     public ChangeEvents getContentChanges(String changeLogToken, boolean includeProperties, long maxNumItems,
354             OperationContext context) {
355         checkContext(context);
356 
357         Holder<String> changeLogTokenHolder = new Holder<String>(changeLogToken);
358         ObjectList objectList = null;
359 
360         lock.readLock().lock();
361         try {
362             objectList = getBinding().getDiscoveryService().getContentChanges(getRepositoryInfo().getId(),
363                     changeLogTokenHolder, includeProperties, context.getFilterString(), context.isIncludePolicies(),
364                     context.isIncludeAcls(), BigInteger.valueOf(maxNumItems), null);
365         } finally {
366             lock.readLock().unlock();
367         }
368 
369         return objectFactory.convertChangeEvents(changeLogTokenHolder.getValue(), objectList);
370     }
371 
372     @Override
373     public ItemIterable<ChangeEvent> getContentChanges(String changeLogToken, final boolean includeProperties) {
374         return getContentChanges(changeLogToken, includeProperties, getDefaultContext());
375     }
376 
377     @Override
378     public ItemIterable<ChangeEvent> getContentChanges(final String changeLogToken, final boolean includeProperties,
379             OperationContext context) {
380         checkContext(context);
381 
382         final DiscoveryService discoveryService = getBinding().getDiscoveryService();
383         final ObjectFactory of = getObjectFactory();
384         final OperationContext ctxt = new OperationContextImpl(context);
385 
386         return new CollectionIterable<ChangeEvent>(new AbstractPageFetcher<ChangeEvent>(Integer.MAX_VALUE) {
387 
388             private String token = changeLogToken;
389             private String nextLink = null;
390             private boolean firstPage = true;
391 
392             @Override
393             protected AbstractPageFetcher.Page<ChangeEvent> fetchPage(long skipCount) {
394                 assert firstPage || token != null ? (nextLink == null) : true;
395 
396                 // fetch the data
397                 ExtendedHolder<String> changeLogTokenHolder = new ExtendedHolder<String>(token);
398                 if (nextLink != null) {
399                     changeLogTokenHolder.setExtraValue(Constants.REP_REL_CHANGES, nextLink);
400                 }
401 
402                 ObjectList objectList = discoveryService.getContentChanges(getRepositoryInfo().getId(),
403                         changeLogTokenHolder, includeProperties, ctxt.getFilterString(), ctxt.isIncludePolicies(),
404                         ctxt.isIncludeAcls(), BigInteger.valueOf(this.maxNumItems), null);
405 
406                 // convert type definitions
407                 List<ChangeEvent> page = new ArrayList<ChangeEvent>();
408                 for (ObjectData objectData : objectList.getObjects()) {
409                     page.add(of.convertChangeEvent(objectData));
410                 }
411 
412                 if (!firstPage) {
413                     // the last entry of the previous page is repeated
414                     // -> remove the first entry
415                     page.remove(0);
416                 }
417                 firstPage = false;
418 
419                 if (changeLogTokenHolder.getValue() != null) {
420                     // the web services and the browser binding
421                     // return a new token
422                     token = changeLogTokenHolder.getValue();
423                 } else {
424                     // the atompub binding does not return a new token,
425                     // but might return a link to the next Atom feed
426                     token = null;
427                     nextLink = (String) changeLogTokenHolder.getExtraValue(Constants.REP_REL_CHANGES);
428                 }
429 
430                 return new AbstractPageFetcher.Page<ChangeEvent>(page, objectList.getNumItems(),
431                         objectList.hasMoreItems()) {
432                 };
433             }
434         }) {
435             @Override
436             public ItemIterable<ChangeEvent> skipTo(long position) {
437                 throw new CmisNotSupportedException("Skipping not supported!");
438             }
439 
440             @Override
441             public ItemIterable<ChangeEvent> getPage() {
442                 throw new CmisNotSupportedException("Paging not supported!");
443             }
444 
445             @Override
446             public ItemIterable<ChangeEvent> getPage(int maxNumItems) {
447                 throw new CmisNotSupportedException("Paging not supported!");
448             }
449         };
450     }
451 
452     @Override
453     public String getLatestChangeLogToken() {
454         return getBinding().getRepositoryService().getRepositoryInfo(getRepositoryId(), null).getLatestChangeLogToken();
455     }
456 
457     @Override
458     public OperationContext getDefaultContext() {
459         lock.readLock().lock();
460         try {
461             return defaultContext;
462         } finally {
463             lock.readLock().unlock();
464         }
465     }
466 
467     @Override
468     public void setDefaultContext(OperationContext context) {
469         lock.writeLock().lock();
470         try {
471             this.defaultContext = context == null ? DEFAULT_CONTEXT : context;
472         } finally {
473             lock.writeLock().unlock();
474         }
475     }
476 
477     @Override
478     public OperationContext createOperationContext(Set<String> filter, boolean includeAcls,
479             boolean includeAllowableActions, boolean includePolicies, IncludeRelationships includeRelationships,
480             Set<String> renditionFilter, boolean includePathSegments, String orderBy, boolean cacheEnabled,
481             int maxItemsPerPage) {
482         return OperationContextUtils.createOperationContext(filter, includeAcls, includeAllowableActions,
483                 includePolicies, includeRelationships, renditionFilter, includePathSegments, orderBy, cacheEnabled,
484                 maxItemsPerPage);
485     }
486 
487     @Override
488     public OperationContext createOperationContext() {
489         return OperationContextUtils.createOperationContext();
490     }
491 
492     @Override
493     public ObjectId createObjectId(String id) {
494         return new ObjectIdImpl(id);
495     }
496 
497     @Override
498     public Locale getLocale() {
499         return locale;
500     }
501 
502     @Override
503     public CmisObject getObject(ObjectId objectId) {
504         return getObject(objectId, getDefaultContext());
505     }
506 
507     @Override
508     public CmisObject getObject(ObjectId objectId, OperationContext context) {
509         checkObjectId(objectId);
510         return getObject(objectId.getId(), context);
511     }
512 
513     @Override
514     public CmisObject getObject(String objectId) {
515         return getObject(objectId, getDefaultContext());
516     }
517 
518     @Override
519     public CmisObject getObject(String objectId, OperationContext context) {
520         checkObjectId(objectId);
521         checkContext(context);
522 
523         CmisObject result = null;
524 
525         // ask the cache first
526         if (context.isCacheEnabled()) {
527             result = cache.getById(objectId, context.getCacheKey());
528             if (result != null) {
529                 return result;
530             }
531         }
532 
533         // get the object
534         ObjectData objectData = binding.getObjectService().getObject(getRepositoryId(), objectId,
535                 context.getFilterString(), context.isIncludeAllowableActions(), context.getIncludeRelationships(),
536                 context.getRenditionFilterString(), context.isIncludePolicies(), context.isIncludeAcls(), null);
537 
538         result = getObjectFactory().convertObject(objectData, context);
539 
540         // put into cache
541         if (context.isCacheEnabled()) {
542             cache.put(result, context.getCacheKey());
543         }
544 
545         return result;
546     }
547 
548     @Override
549     public CmisObject getObjectByPath(String path) {
550         return getObjectByPath(path, getDefaultContext());
551     }
552 
553     @Override
554     public CmisObject getObjectByPath(String path, OperationContext context) {
555         checkPath(path);
556         checkContext(context);
557 
558         CmisObject result = null;
559 
560         // ask the cache first
561         if (context.isCacheEnabled() && !cachePathOmit) {
562             result = cache.getByPath(path, context.getCacheKey());
563             if (result != null) {
564                 return result;
565             }
566         }
567 
568         // get the object
569         ObjectData objectData = binding.getObjectService().getObjectByPath(getRepositoryId(), path,
570                 context.getFilterString(), context.isIncludeAllowableActions(), context.getIncludeRelationships(),
571                 context.getRenditionFilterString(), context.isIncludePolicies(), context.isIncludeAcls(), null);
572 
573         result = getObjectFactory().convertObject(objectData, context);
574 
575         // put into cache
576         if (context.isCacheEnabled()) {
577             cache.putPath(path, result, context.getCacheKey());
578         }
579 
580         return result;
581     }
582 
583     @Override
584     public CmisObject getObjectByPath(String parentPath, String name) {
585         return getObjectByPath(parentPath, name, getDefaultContext());
586     }
587 
588     @Override
589     public CmisObject getObjectByPath(String parentPath, String name, OperationContext context) {
590         if (parentPath == null || parentPath.length() < 1) {
591             throw new IllegalArgumentException("Parent path must be set!");
592         }
593         if (parentPath.charAt(0) != '/') {
594             throw new IllegalArgumentException("Parent path must start with a '/'!");
595         }
596         if (name == null || name.length() < 1) {
597             throw new IllegalArgumentException("Name must be set!");
598         }
599 
600         StringBuilder path = new StringBuilder(parentPath.length() + name.length() + 2);
601         path.append(parentPath);
602         if (!parentPath.endsWith("/")) {
603             path.append('/');
604         }
605         path.append(name);
606 
607         return getObjectByPath(path.toString(), context);
608     }
609 
610     @Override
611     public Document getLatestDocumentVersion(ObjectId objectId) {
612         return getLatestDocumentVersion(objectId, false, getDefaultContext());
613     }
614 
615     @Override
616     public Document getLatestDocumentVersion(String objectId, OperationContext context) {
617         checkDocumentId(objectId);
618         return getLatestDocumentVersion(createObjectId(objectId), false, context);
619     }
620 
621     @Override
622     public Document getLatestDocumentVersion(String objectId, boolean major, OperationContext context) {
623         checkDocumentId(objectId);
624         return getLatestDocumentVersion(createObjectId(objectId), major, context);
625     }
626 
627     @Override
628     public Document getLatestDocumentVersion(String objectId) {
629         checkDocumentId(objectId);
630         return getLatestDocumentVersion(createObjectId(objectId), false, getDefaultContext());
631     }
632 
633     @Override
634     public Document getLatestDocumentVersion(ObjectId objectId, OperationContext context) {
635         return getLatestDocumentVersion(objectId, false, context);
636     }
637 
638     @Override
639     public Document getLatestDocumentVersion(ObjectId objectId, boolean major, OperationContext context) {
640         checkDocumentId(objectId);
641         checkContext(context);
642 
643         CmisObject result = null;
644 
645         String versionSeriesId = null;
646 
647         // first attempt: if we got a Document object, try getting the version
648         // series ID from it
649         if (objectId instanceof Document) {
650             Document sourceDoc = (Document) objectId;
651 
652             if (!sourceDoc.isVersionable()) {
653                 // if it is not versionable, a getObject() is sufficient
654                 return (Document) getObject(sourceDoc, context);
655             }
656 
657             versionSeriesId = sourceDoc.getVersionSeriesId();
658         }
659 
660         // second attempt: if we have a Document object in the cache, retrieve
661         // the version series ID form there
662         if (versionSeriesId == null) {
663             if (context.isCacheEnabled()) {
664                 CmisObject sourceObj = cache.getById(objectId.getId(), context.getCacheKey());
665                 if (sourceObj instanceof Document) {
666                     Document sourceDoc = (Document) sourceObj;
667 
668                     if (!sourceDoc.isVersionable()) {
669                         // if it is not versionable, a getObject() is sufficient
670                         return (Document) getObject(sourceDoc, context);
671                     }
672 
673                     versionSeriesId = sourceDoc.getVersionSeriesId();
674                 }
675             }
676         }
677 
678         // third attempt (Web Services only): get the version series ID from the
679         // repository
680         // (the AtomPub and Browser binding don't need the version series ID ->
681         // avoid roundtrip)
682         if (versionSeriesId == null) {
683             BindingType bindingType = getBinding().getBindingType();
684             if (bindingType == BindingType.WEBSERVICES || bindingType == BindingType.CUSTOM) {
685 
686                 // get the document to find the version series ID
687                 ObjectData sourceObjectData = binding.getObjectService().getObject(getRepositoryId(), objectId.getId(),
688                         PropertyIds.OBJECT_ID + "," + PropertyIds.OBJECT_TYPE_ID + "," + PropertyIds.VERSION_SERIES_ID,
689                         false, IncludeRelationships.NONE, "cmis:none", false, false, null);
690 
691                 String objectTypeId = null;
692 
693                 if (sourceObjectData.getProperties() != null
694                         && sourceObjectData.getProperties().getProperties() != null) {
695 
696                     PropertyData<?> objectTypeIdProp = sourceObjectData.getProperties().getProperties()
697                             .get(PropertyIds.OBJECT_TYPE_ID);
698                     if (objectTypeIdProp != null && objectTypeIdProp.getFirstValue() instanceof String) {
699                         objectTypeId = (String) objectTypeIdProp.getFirstValue();
700                     }
701 
702                     PropertyData<?> verionsSeriesIdProp = sourceObjectData.getProperties().getProperties()
703                             .get(PropertyIds.VERSION_SERIES_ID);
704                     if (verionsSeriesIdProp != null && verionsSeriesIdProp.getFirstValue() instanceof String) {
705                         versionSeriesId = (String) verionsSeriesIdProp.getFirstValue();
706                     }
707                 }
708 
709                 // the Web Services binding needs the version series ID
710                 if (versionSeriesId == null) {
711 
712                     ObjectType type = getTypeDefinition(objectTypeId);
713                     if (type instanceof DocumentType && Boolean.FALSE.equals(((DocumentType) type).isVersionable())) {
714                         // if the document is not versionable, we don't need a
715                         // version series ID
716                         return (Document) getObject(objectId, context);
717                     }
718 
719                     throw new IllegalArgumentException("Object is not a document or not versionable!");
720                 }
721             }
722         }
723 
724         // get the object
725         ObjectData objectData = binding.getVersioningService().getObjectOfLatestVersion(getRepositoryId(),
726                 objectId.getId(), versionSeriesId, major, context.getFilterString(),
727                 context.isIncludeAllowableActions(), context.getIncludeRelationships(),
728                 context.getRenditionFilterString(), context.isIncludePolicies(), context.isIncludeAcls(), null);
729 
730         result = getObjectFactory().convertObject(objectData, context);
731 
732         // put into cache
733         if (context.isCacheEnabled()) {
734             cache.put(result, context.getCacheKey());
735         }
736 
737         // check result
738         if (!(result instanceof Document)) {
739             throw new IllegalArgumentException("Latest version is not a document!");
740         }
741 
742         return (Document) result;
743     }
744 
745     @Override
746     public boolean exists(ObjectId objectId) {
747         checkObjectId(objectId);
748         return exists(objectId.getId());
749     }
750 
751     @Override
752     public boolean exists(String objectId) {
753         checkObjectId(objectId);
754 
755         try {
756             binding.getObjectService().getObject(getRepositoryId(), objectId, "cmis:objectId", Boolean.FALSE,
757                     IncludeRelationships.NONE, "cmis:none", Boolean.FALSE, Boolean.FALSE, null);
758             return true;
759         } catch (CmisObjectNotFoundException onf) {
760             removeObjectFromCache(objectId);
761             return false;
762         }
763     }
764 
765     @Override
766     public boolean existsPath(String path) {
767         checkPath(path);
768 
769         try {
770             ObjectData object = binding.getObjectService().getObjectByPath(getRepositoryId(), path, "cmis:objectId",
771                     Boolean.FALSE, IncludeRelationships.NONE, "cmis:none", Boolean.FALSE, Boolean.FALSE, null);
772 
773             String cacheObjectId = cache.getObjectIdByPath(path);
774             if (cacheObjectId != null && !cacheObjectId.equals(object.getId())) {
775                 cache.removePath(path);
776             }
777 
778             return true;
779         } catch (CmisObjectNotFoundException onf) {
780             cache.removePath(path);
781             return false;
782         }
783     }
784 
785     @Override
786     public boolean existsPath(String parentPath, String name) {
787         if (parentPath == null || parentPath.length() < 1) {
788             throw new IllegalArgumentException("Parent path must be set!");
789         }
790         if (parentPath.charAt(0) != '/') {
791             throw new IllegalArgumentException("Parent path must start with a '/'!");
792         }
793         if (name == null || name.length() < 1) {
794             throw new IllegalArgumentException("Name must be set!");
795         }
796 
797         StringBuilder path = new StringBuilder(parentPath.length() + name.length() + 2);
798         path.append(parentPath);
799         if (!parentPath.endsWith("/")) {
800             path.append('/');
801         }
802         path.append(name);
803 
804         return existsPath(path.toString());
805     }
806 
807     @Override
808     public void removeObjectFromCache(ObjectId objectId) {
809         checkObjectId(objectId);
810         removeObjectFromCache(objectId.getId());
811     }
812 
813     @Override
814     public void removeObjectFromCache(String objectId) {
815         cache.remove(objectId);
816     }
817 
818     @Override
819     public RepositoryInfo getRepositoryInfo() {
820         lock.readLock().lock();
821         try {
822             return repositoryInfo;
823         } finally {
824             lock.readLock().unlock();
825         }
826     }
827 
828     @Override
829     public Folder getRootFolder() {
830         return getRootFolder(getDefaultContext());
831     }
832 
833     @Override
834     public Folder getRootFolder(OperationContext context) {
835         String rootFolderId = getRepositoryInfo().getRootFolderId();
836 
837         CmisObject rootFolder = getObject(rootFolderId, context);
838         if (!(rootFolder instanceof Folder)) {
839             throw new CmisRuntimeException("Root folder object is not a folder!");
840         }
841 
842         return (Folder) rootFolder;
843     }
844 
845     @Override
846     public ItemIterable<ObjectType> getTypeChildren(final String typeId, final boolean includePropertyDefinitions) {
847         final RepositoryService repositoryService = getBinding().getRepositoryService();
848 
849         return new CollectionIterable<ObjectType>(new AbstractPageFetcher<ObjectType>(getDefaultContext()
850                 .getMaxItemsPerPage()) {
851 
852             @Override
853             protected AbstractPageFetcher.Page<ObjectType> fetchPage(long skipCount) {
854 
855                 // fetch the data
856                 TypeDefinitionList tdl = repositoryService.getTypeChildren(SessionImpl.this.getRepositoryId(), typeId,
857                         includePropertyDefinitions, BigInteger.valueOf(this.maxNumItems),
858                         BigInteger.valueOf(skipCount), null);
859 
860                 // convert type definitions
861                 List<ObjectType> page = new ArrayList<ObjectType>(tdl.getList().size());
862                 for (TypeDefinition typeDefinition : tdl.getList()) {
863                     page.add(convertTypeDefinition(typeDefinition));
864                 }
865 
866                 return new AbstractPageFetcher.Page<ObjectType>(page, tdl.getNumItems(), tdl.hasMoreItems()) {
867                 };
868             }
869         });
870     }
871 
872     @Override
873     public ObjectType getTypeDefinition(String typeId) {
874         TypeDefinition typeDefinition = getBinding().getRepositoryService().getTypeDefinition(getRepositoryId(),
875                 typeId, null);
876 
877         return convertAndCacheTypeDefinition(typeDefinition, true);
878     }
879 
880     @Override
881     public ObjectType getTypeDefinition(String typeId, boolean useCache) {
882         RepositoryService service = getBinding().getRepositoryService();
883         if (!(service instanceof ExtendedRepositoryService)) {
884             throw new CmisRuntimeException(
885                     "Internal error: Repository Service does not implement ExtendedRepositoryService!");
886         }
887 
888         ExtendedRepositoryService extRepSrv = (ExtendedRepositoryService) service;
889         TypeDefinition typeDefinition = extRepSrv.getTypeDefinition(getRepositoryId(), typeId, null, useCache);
890 
891         return convertAndCacheTypeDefinition(typeDefinition, useCache);
892     }
893 
894     @Override
895     public List<Tree<ObjectType>> getTypeDescendants(String typeId, int depth, boolean includePropertyDefinitions) {
896         List<TypeDefinitionContainer> descendants = getBinding().getRepositoryService().getTypeDescendants(
897                 getRepositoryId(), typeId, BigInteger.valueOf(depth), includePropertyDefinitions, null);
898 
899         return convertTypeDescendants(descendants);
900     }
901 
902     /**
903      * Converts binding <code>TypeDefinitionContainer</code> to API
904      * <code>Container</code>.
905      */
906     private List<Tree<ObjectType>> convertTypeDescendants(List<TypeDefinitionContainer> descendantsList) {
907         List<Tree<ObjectType>> result = new ArrayList<Tree<ObjectType>>();
908 
909         for (TypeDefinitionContainer container : descendantsList) {
910             ObjectType objectType = convertTypeDefinition(container.getTypeDefinition());
911             List<Tree<ObjectType>> children = convertTypeDescendants(container.getChildren());
912 
913             result.add(new TreeImpl<ObjectType>(objectType, children));
914         }
915 
916         return result;
917     }
918 
919     private ObjectType convertTypeDefinition(TypeDefinition typeDefinition) {
920         return objectFactory.convertTypeDefinition(typeDefinition);
921     }
922 
923     /**
924      * Converts a type definition into an object type and caches the result.
925      * 
926      * The cache should only be used for type definitions that have been fetched
927      * with getTypeDefinition() because the high level cache should roughly
928      * correspond to the low level type cache. The type definitions returned by
929      * getTypeChildren() and getTypeDescendants() are not cached in the low
930      * level cache and therefore shouldn't be cached here.
931      */
932     private ObjectType convertAndCacheTypeDefinition(TypeDefinition typeDefinition, boolean useCache) {
933         ObjectType result = null;
934 
935         lock.writeLock().lock();
936         try {
937             if (objectTypeCache == null) {
938                 int cacheSize;
939                 try {
940                     cacheSize = Integer.valueOf(parameters.get(SessionParameter.CACHE_SIZE_TYPES));
941                     if (cacheSize < 0) {
942                         cacheSize = SessionParameterDefaults.CACHE_SIZE_TYPES;
943                     }
944                 } catch (NumberFormatException nfe) {
945                     cacheSize = SessionParameterDefaults.CACHE_SIZE_TYPES;
946                 }
947 
948                 final int maxEntries = cacheSize;
949 
950                 objectTypeCache = new LinkedHashMap<String, ObjectType>(maxEntries + 1, 0.70f, true) {
951                     private static final long serialVersionUID = 1L;
952 
953                     @Override
954                     public boolean removeEldestEntry(Map.Entry<String, ObjectType> eldest) {
955                         return size() > maxEntries;
956                     }
957                 };
958             }
959 
960             if (!useCache) {
961                 result = objectFactory.convertTypeDefinition(typeDefinition);
962                 objectTypeCache.put(result.getId(), result);
963             } else {
964                 result = objectTypeCache.get(typeDefinition.getId());
965                 if (result == null) {
966                     result = objectFactory.convertTypeDefinition(typeDefinition);
967                     objectTypeCache.put(result.getId(), result);
968                 }
969             }
970 
971             return result;
972         } finally {
973             lock.writeLock().unlock();
974         }
975     }
976 
977     /**
978      * Removes the object type object with the given type ID from the cache.
979      */
980     private void removeFromObjectTypeCache(String typeId) {
981         lock.writeLock().lock();
982         try {
983             if (objectTypeCache != null) {
984                 objectTypeCache.remove(typeId);
985             }
986         } finally {
987             lock.writeLock().unlock();
988         }
989     }
990 
991     @Override
992     public ObjectType createType(TypeDefinition type) {
993         checkCmisVersion();
994 
995         TypeDefinition newType = getBinding().getRepositoryService().createType(getRepositoryId(), type, null);
996         return convertTypeDefinition(newType);
997     }
998 
999     @Override
1000     public ObjectType updateType(TypeDefinition type) {
1001         checkCmisVersion();
1002 
1003         TypeDefinition updatedType = getBinding().getRepositoryService().updateType(getRepositoryId(), type, null);
1004 
1005         removeFromObjectTypeCache(updatedType.getId());
1006 
1007         return convertTypeDefinition(updatedType);
1008     }
1009 
1010     @Override
1011     public void deleteType(String typeId) {
1012         checkCmisVersion();
1013 
1014         getBinding().getRepositoryService().deleteType(getRepositoryId(), typeId, null);
1015         removeFromObjectTypeCache(typeId);
1016     }
1017 
1018     @Override
1019     public ItemIterable<QueryResult> query(final String statement, final boolean searchAllVersions) {
1020         return query(statement, searchAllVersions, getDefaultContext());
1021     }
1022 
1023     @Override
1024     public ItemIterable<QueryResult> query(final String statement, final boolean searchAllVersions,
1025             OperationContext context) {
1026         checkContext(context);
1027 
1028         final DiscoveryService discoveryService = getBinding().getDiscoveryService();
1029         final ObjectFactory of = getObjectFactory();
1030         final OperationContext ctxt = new OperationContextImpl(context);
1031 
1032         return new CollectionIterable<QueryResult>(new AbstractPageFetcher<QueryResult>(ctxt.getMaxItemsPerPage()) {
1033 
1034             @Override
1035             protected AbstractPageFetcher.Page<QueryResult> fetchPage(long skipCount) {
1036 
1037                 // fetch the data
1038                 ObjectList resultList = discoveryService.query(getRepositoryId(), statement, searchAllVersions,
1039                         ctxt.isIncludeAllowableActions(), ctxt.getIncludeRelationships(),
1040                         ctxt.getRenditionFilterString(), BigInteger.valueOf(this.maxNumItems),
1041                         BigInteger.valueOf(skipCount), null);
1042 
1043                 // convert query results
1044                 List<QueryResult> page = new ArrayList<QueryResult>();
1045                 if (resultList.getObjects() != null) {
1046                     for (ObjectData objectData : resultList.getObjects()) {
1047                         if (objectData == null) {
1048                             continue;
1049                         }
1050 
1051                         page.add(of.convertQueryResult(objectData));
1052                     }
1053                 }
1054 
1055                 return new AbstractPageFetcher.Page<QueryResult>(page, resultList.getNumItems(),
1056                         resultList.hasMoreItems());
1057             }
1058         });
1059     }
1060 
1061     @Override
1062     public ItemIterable<CmisObject> queryObjects(String typeId, String where, final boolean searchAllVersions,
1063             OperationContext context) {
1064         if (typeId == null || typeId.trim().length() == 0) {
1065             throw new IllegalArgumentException("Type ID must be set!");
1066         }
1067 
1068         checkContext(context);
1069 
1070         final DiscoveryService discoveryService = getBinding().getDiscoveryService();
1071         final ObjectFactory of = getObjectFactory();
1072         final OperationContext ctxt = new OperationContextImpl(context);
1073         final StringBuilder statement = new StringBuilder(1024);
1074 
1075         statement.append("SELECT ");
1076 
1077         String select = ctxt.getFilterString();
1078         if (select == null) {
1079             statement.append('*');
1080         } else {
1081             statement.append(select);
1082         }
1083 
1084         final ObjectType type = getTypeDefinition(typeId);
1085         statement.append(" FROM ");
1086         statement.append(type.getQueryName());
1087 
1088         if (where != null && where.trim().length() > 0) {
1089             statement.append(" WHERE ");
1090             statement.append(where);
1091         }
1092 
1093         String orderBy = ctxt.getOrderBy();
1094         if (orderBy != null && orderBy.trim().length() > 0) {
1095             statement.append(" ORDER BY ");
1096             statement.append(orderBy);
1097         }
1098 
1099         return new CollectionIterable<CmisObject>(new AbstractPageFetcher<CmisObject>(ctxt.getMaxItemsPerPage()) {
1100 
1101             @Override
1102             protected AbstractPageFetcher.Page<CmisObject> fetchPage(long skipCount) {
1103 
1104                 // fetch the data
1105                 ObjectList resultList = discoveryService.query(getRepositoryId(), statement.toString(),
1106                         searchAllVersions, ctxt.isIncludeAllowableActions(), ctxt.getIncludeRelationships(),
1107                         ctxt.getRenditionFilterString(), BigInteger.valueOf(this.maxNumItems),
1108                         BigInteger.valueOf(skipCount), null);
1109 
1110                 // convert query results
1111                 List<CmisObject> page = new ArrayList<CmisObject>();
1112                 if (resultList.getObjects() != null) {
1113                     for (ObjectData objectData : resultList.getObjects()) {
1114                         if (objectData == null) {
1115                             continue;
1116                         }
1117 
1118                         page.add(of.convertObject(objectData, ctxt));
1119                     }
1120                 }
1121 
1122                 return new AbstractPageFetcher.Page<CmisObject>(page, resultList.getNumItems(),
1123                         resultList.hasMoreItems());
1124             }
1125         });
1126     }
1127 
1128     @Override
1129     public QueryStatement createQueryStatement(final String statement) {
1130         return new QueryStatementImpl(this, statement);
1131     }
1132 
1133     @Override
1134     public QueryStatement createQueryStatement(final Collection<String> selectPropertyIds,
1135             final Map<String, String> fromTypes, final String whereClause, final List<String> orderByPropertyIds) {
1136         return new QueryStatementImpl(this, selectPropertyIds, fromTypes, whereClause, orderByPropertyIds);
1137     }
1138 
1139     /**
1140      * Connect session object to the provider. This is the very first call after
1141      * a session is created.
1142      * <p>
1143      * In dependency of the parameter set an {@code AtomPub}, a
1144      * {@code WebService} or an {@code InMemory} provider is selected.
1145      */
1146     public void connect() {
1147         lock.writeLock().lock();
1148         try {
1149             binding = CmisBindingHelper.createBinding(parameters, authenticationProvider, typeDefCache);
1150 
1151             /* get initial repository ID from session parameter */
1152             String repositoryId = parameters.get(SessionParameter.REPOSITORY_ID);
1153             if (repositoryId == null) {
1154                 throw new IllegalStateException("Repository ID is not set!");
1155             }
1156 
1157             repositoryInfo = objectFactory.convertRepositoryInfo(getBinding().getRepositoryService().getRepositoryInfo(
1158                     repositoryId, null));
1159         } finally {
1160             lock.writeLock().unlock();
1161         }
1162     }
1163 
1164     @Override
1165     public CmisBinding getBinding() {
1166         lock.readLock().lock();
1167         try {
1168             return binding;
1169         } finally {
1170             lock.readLock().unlock();
1171         }
1172     }
1173 
1174     public Cache getCache() {
1175         lock.readLock().lock();
1176         try {
1177             return cache;
1178         } finally {
1179             lock.readLock().unlock();
1180         }
1181     }
1182 
1183     /**
1184      * Returns the repository id.
1185      */
1186     public String getRepositoryId() {
1187         return getRepositoryInfo().getId();
1188     }
1189 
1190     // --- creates ---
1191 
1192     @Override
1193     public ObjectId createDocument(Map<String, ?> properties, ObjectId folderId, ContentStream contentStream,
1194             VersioningState versioningState, List<Policy> policies, List<Ace> addAces, List<Ace> removeAces) {
1195         checkProperties(properties);
1196 
1197         String newId = getBinding().getObjectService().createDocument(getRepositoryId(),
1198                 objectFactory.convertProperties(properties, null, null, CREATE_AND_CHECKOUT_UPDATABILITY),
1199                 (folderId == null ? null : folderId.getId()), objectFactory.convertContentStream(contentStream),
1200                 versioningState, objectFactory.convertPolicies(policies), objectFactory.convertAces(addAces),
1201                 objectFactory.convertAces(removeAces), null);
1202 
1203         if (newId == null) {
1204             return null;
1205         }
1206 
1207         return createObjectId(newId);
1208     }
1209 
1210     @Override
1211     public ObjectId createDocumentFromSource(ObjectId source, Map<String, ?> properties, ObjectId folderId,
1212             VersioningState versioningState, List<Policy> policies, List<Ace> addAces, List<Ace> removeAces) {
1213         if (source == null || source.getId() == null) {
1214             throw new IllegalArgumentException("Source must be set!");
1215         }
1216 
1217         // get the type of the source document
1218         ObjectType type = null;
1219         List<SecondaryType> secondaryTypes = null;
1220         if (source instanceof CmisObject) {
1221             type = ((CmisObject) source).getType();
1222             secondaryTypes = ((CmisObject) source).getSecondaryTypes();
1223         } else {
1224             CmisObject sourceObj = getObject(source);
1225             type = sourceObj.getType();
1226             secondaryTypes = sourceObj.getSecondaryTypes();
1227         }
1228 
1229         if (type.getBaseTypeId() != BaseTypeId.CMIS_DOCUMENT) {
1230             throw new IllegalArgumentException("Source object must be a document!");
1231         }
1232 
1233         String newId = getBinding().getObjectService().createDocumentFromSource(getRepositoryId(), source.getId(),
1234                 objectFactory.convertProperties(properties, type, secondaryTypes, CREATE_AND_CHECKOUT_UPDATABILITY),
1235                 (folderId == null ? null : folderId.getId()), versioningState, objectFactory.convertPolicies(policies),
1236                 objectFactory.convertAces(addAces), objectFactory.convertAces(removeAces), null);
1237 
1238         if (newId == null) {
1239             return null;
1240         }
1241 
1242         return createObjectId(newId);
1243     }
1244 
1245     @Override
1246     public ObjectId createFolder(Map<String, ?> properties, ObjectId folderId, List<Policy> policies,
1247             List<Ace> addAces, List<Ace> removeAces) {
1248         checkFolderId(folderId);
1249         checkProperties(properties);
1250 
1251         String newId = getBinding().getObjectService().createFolder(getRepositoryId(),
1252                 objectFactory.convertProperties(properties, null, null, CREATE_UPDATABILITY), folderId.getId(),
1253                 objectFactory.convertPolicies(policies), objectFactory.convertAces(addAces),
1254                 objectFactory.convertAces(removeAces), null);
1255 
1256         if (newId == null) {
1257             return null;
1258         }
1259 
1260         return createObjectId(newId);
1261     }
1262 
1263     @Override
1264     public ObjectId createPolicy(Map<String, ?> properties, ObjectId folderId, List<Policy> policies,
1265             List<Ace> addAces, List<Ace> removeAces) {
1266         checkProperties(properties);
1267 
1268         String newId = getBinding().getObjectService().createPolicy(getRepositoryId(),
1269                 objectFactory.convertProperties(properties, null, null, CREATE_UPDATABILITY),
1270                 (folderId == null ? null : folderId.getId()), objectFactory.convertPolicies(policies),
1271                 objectFactory.convertAces(addAces), objectFactory.convertAces(removeAces), null);
1272 
1273         if (newId == null) {
1274             return null;
1275         }
1276 
1277         return createObjectId(newId);
1278     }
1279 
1280     @Override
1281     public ObjectId createItem(Map<String, ?> properties, ObjectId folderId, List<Policy> policies, List<Ace> addAces,
1282             List<Ace> removeAces) {
1283         checkProperties(properties);
1284 
1285         String newId = getBinding().getObjectService().createItem(getRepositoryId(),
1286                 objectFactory.convertProperties(properties, null, null, CREATE_UPDATABILITY),
1287                 (folderId == null ? null : folderId.getId()), objectFactory.convertPolicies(policies),
1288                 objectFactory.convertAces(addAces), objectFactory.convertAces(removeAces), null);
1289 
1290         if (newId == null) {
1291             return null;
1292         }
1293 
1294         return createObjectId(newId);
1295     }
1296 
1297     @Override
1298     public ObjectId createRelationship(Map<String, ?> properties, List<Policy> policies, List<Ace> addAces,
1299             List<Ace> removeAces) {
1300         checkProperties(properties);
1301 
1302         String newId = getBinding().getObjectService().createRelationship(getRepositoryId(),
1303                 objectFactory.convertProperties(properties, null, null, CREATE_UPDATABILITY),
1304                 objectFactory.convertPolicies(policies), objectFactory.convertAces(addAces),
1305                 objectFactory.convertAces(removeAces), null);
1306 
1307         if (newId == null) {
1308             return null;
1309         }
1310 
1311         return createObjectId(newId);
1312     }
1313 
1314     @Override
1315     public ObjectId createDocument(Map<String, ?> properties, ObjectId folderId, ContentStream contentStream,
1316             VersioningState versioningState) {
1317         return createDocument(properties, folderId, contentStream, versioningState, null, null, null);
1318     }
1319 
1320     @Override
1321     public ObjectId createDocumentFromSource(ObjectId source, Map<String, ?> properties, ObjectId folderId,
1322             VersioningState versioningState) {
1323         return createDocumentFromSource(source, properties, folderId, versioningState, null, null, null);
1324     }
1325 
1326     @Override
1327     public ObjectId createFolder(Map<String, ?> properties, ObjectId folderId) {
1328         return createFolder(properties, folderId, null, null, null);
1329     }
1330 
1331     @Override
1332     public ObjectId createPolicy(Map<String, ?> properties, ObjectId folderId) {
1333         return createPolicy(properties, folderId, null, null, null);
1334     }
1335 
1336     @Override
1337     public ObjectId createItem(Map<String, ?> properties, ObjectId folderId) {
1338         return createItem(properties, folderId, null, null, null);
1339     }
1340 
1341     // --- relationships ---
1342 
1343     @Override
1344     public ObjectId createRelationship(Map<String, ?> properties) {
1345         return createRelationship(properties, null, null, null);
1346     }
1347 
1348     @Override
1349     public ItemIterable<Relationship> getRelationships(ObjectId objectId, final boolean includeSubRelationshipTypes,
1350             final RelationshipDirection relationshipDirection, ObjectType type, OperationContext context) {
1351         checkObjectId(objectId);
1352         checkContext(context);
1353 
1354         final String id = objectId.getId();
1355         final String typeId = type == null ? null : type.getId();
1356         final RelationshipService relationshipService = getBinding().getRelationshipService();
1357         final OperationContext ctxt = new OperationContextImpl(context);
1358 
1359         return new CollectionIterable<Relationship>(new AbstractPageFetcher<Relationship>(ctxt.getMaxItemsPerPage()) {
1360 
1361             @Override
1362             protected AbstractPageFetcher.Page<Relationship> fetchPage(long skipCount) {
1363 
1364                 // fetch the relationships
1365                 ObjectList relList = relationshipService.getObjectRelationships(getRepositoryId(), id,
1366                         includeSubRelationshipTypes, relationshipDirection, typeId, ctxt.getFilterString(),
1367                         ctxt.isIncludeAllowableActions(), BigInteger.valueOf(this.maxNumItems),
1368                         BigInteger.valueOf(skipCount), null);
1369 
1370                 // convert relationship objects
1371                 List<Relationship> page = new ArrayList<Relationship>();
1372                 if (relList.getObjects() != null) {
1373                     for (ObjectData rod : relList.getObjects()) {
1374                         CmisObject relationship = getObject(rod.getId(), ctxt);
1375                         if (!(relationship instanceof Relationship)) {
1376                             throw new CmisRuntimeException("Repository returned an object that is not a relationship!");
1377                         }
1378 
1379                         page.add((Relationship) relationship);
1380                     }
1381                 }
1382 
1383                 return new AbstractPageFetcher.Page<Relationship>(page, relList.getNumItems(), relList.hasMoreItems());
1384             }
1385         });
1386     }
1387 
1388     // --- bulk update ---
1389 
1390     @Override
1391     public List<BulkUpdateObjectIdAndChangeToken> bulkUpdateProperties(List<CmisObject> objects,
1392             Map<String, ?> properties, List<String> addSecondaryTypeIds, List<String> removeSecondaryTypeIds) {
1393         checkCmisVersion();
1394         checkProperties(properties);
1395 
1396         ObjectType objectType = null;
1397         Map<String, SecondaryType> secondaryTypes = new HashMap<String, SecondaryType>();
1398 
1399         // gather secondary types
1400         if (addSecondaryTypeIds != null) {
1401             for (String stid : addSecondaryTypeIds) {
1402                 ObjectType secondaryType = getTypeDefinition(stid);
1403 
1404                 if (!(secondaryType instanceof SecondaryType)) {
1405                     throw new IllegalArgumentException("Secondary types contains a type that is not a secondary type: "
1406                             + secondaryType.getId());
1407                 }
1408 
1409                 secondaryTypes.put(secondaryType.getId(), (SecondaryType) secondaryType);
1410             }
1411         }
1412 
1413         // gather IDs and change tokens
1414         List<BulkUpdateObjectIdAndChangeToken> objectIdsAndChangeTokens = new ArrayList<BulkUpdateObjectIdAndChangeToken>();
1415         for (CmisObject object : objects) {
1416             if (object == null) {
1417                 continue;
1418             }
1419 
1420             objectIdsAndChangeTokens.add(new BulkUpdateObjectIdAndChangeTokenImpl(object.getId(), object
1421                     .getChangeToken()));
1422 
1423             if (objectType == null) {
1424                 objectType = object.getType();
1425             }
1426 
1427             if (object.getSecondaryTypes() != null) {
1428                 for (SecondaryType secondaryType : object.getSecondaryTypes()) {
1429                     secondaryTypes.put(secondaryType.getId(), secondaryType);
1430                 }
1431             }
1432         }
1433 
1434         Set<Updatability> updatebility = EnumSet.noneOf(Updatability.class);
1435         updatebility.add(Updatability.READWRITE);
1436 
1437         return getBinding().getObjectService().bulkUpdateProperties(getRepositoryId(), objectIdsAndChangeTokens,
1438                 objectFactory.convertProperties(properties, objectType, secondaryTypes.values(), updatebility),
1439                 addSecondaryTypeIds, removeSecondaryTypeIds, null);
1440     }
1441 
1442     // --- delete ---
1443 
1444     @Override
1445     public void delete(ObjectId objectId) {
1446         delete(objectId, true);
1447     }
1448 
1449     @Override
1450     public void delete(ObjectId objectId, boolean allVersions) {
1451         checkObjectId(objectId);
1452 
1453         getBinding().getObjectService().deleteObject(getRepositoryId(), objectId.getId(), allVersions, null);
1454         removeObjectFromCache(objectId);
1455     }
1456 
1457     @Override
1458     public List<String> deleteTree(ObjectId folderId, boolean allVersions, UnfileObject unfile,
1459             boolean continueOnFailure) {
1460         checkFolderId(folderId);
1461 
1462         FailedToDeleteData failed = getBinding().getObjectService().deleteTree(getRepositoryId(), folderId.getId(),
1463                 allVersions, unfile, continueOnFailure, null);
1464 
1465         if (failed == null || isNullOrEmpty(failed.getIds())) {
1466             removeObjectFromCache(folderId);
1467         }
1468 
1469         return (failed != null ? failed.getIds() : null);
1470     }
1471 
1472     // --- content stream ---
1473 
1474     @Override
1475     public ContentStream getContentStream(ObjectId docId) {
1476         return getContentStream(docId, null, null, null);
1477     }
1478 
1479     @Override
1480     public ContentStream getContentStream(ObjectId docId, String streamId, BigInteger offset, BigInteger length) {
1481         checkDocumentId(docId);
1482 
1483         // get the stream
1484         ContentStream contentStream = null;
1485         try {
1486             contentStream = getBinding().getObjectService().getContentStream(getRepositoryId(), docId.getId(),
1487                     streamId, offset, length, null);
1488         } catch (CmisConstraintException e) {
1489             // no content stream
1490             return null;
1491         }
1492 
1493         return contentStream;
1494     }
1495 
1496     // --- ACL ---
1497 
1498     @Override
1499     public Acl getAcl(ObjectId objectId, boolean onlyBasicPermissions) {
1500         checkObjectId(objectId);
1501         return getBinding().getAclService().getAcl(getRepositoryId(), objectId.getId(), onlyBasicPermissions, null);
1502     }
1503 
1504     @Override
1505     public Acl applyAcl(ObjectId objectId, List<Ace> addAces, List<Ace> removeAces, AclPropagation aclPropagation) {
1506         checkObjectId(objectId);
1507 
1508         ObjectFactory of = getObjectFactory();
1509 
1510         return getBinding().getAclService().applyAcl(getRepositoryId(), objectId.getId(), of.convertAces(addAces),
1511                 of.convertAces(removeAces), aclPropagation, null);
1512     }
1513 
1514     @Override
1515     public Acl setAcl(ObjectId objectId, List<Ace> aces) {
1516         checkObjectId(objectId);
1517 
1518         if (aces == null) {
1519             aces = Collections.emptyList();
1520         }
1521 
1522         AclService aclService = getBinding().getAclService();
1523         if (!(aclService instanceof ExtendedAclService)) {
1524             throw new CmisNotSupportedException("setAcl() is not supported by the binding implementation.");
1525         }
1526 
1527         ObjectFactory of = getObjectFactory();
1528 
1529         return ((ExtendedAclService) aclService).setAcl(getRepositoryId(), objectId.getId(), of.convertAces(aces));
1530     }
1531 
1532     // --- Policies ---
1533 
1534     @Override
1535     public void applyPolicy(ObjectId objectId, ObjectId... policyIds) {
1536         checkObjectId(objectId);
1537 
1538         if (policyIds == null || policyIds.length == 0) {
1539             throw new IllegalArgumentException("No Policies provided!");
1540         }
1541 
1542         String[] ids = new String[policyIds.length];
1543         for (int i = 0; i < policyIds.length; i++) {
1544             if (policyIds[i] == null || policyIds[i].getId() == null) {
1545                 throw new IllegalArgumentException("A Policy ID is not set!");
1546             }
1547 
1548             ids[i] = policyIds[i].getId();
1549         }
1550 
1551         for (String id : ids) {
1552             getBinding().getPolicyService().applyPolicy(getRepositoryId(), id, objectId.getId(), null);
1553         }
1554     }
1555 
1556     @Override
1557     public void removePolicy(ObjectId objectId, ObjectId... policyIds) {
1558         checkObjectId(objectId);
1559 
1560         if (policyIds == null || policyIds.length == 0) {
1561             throw new IllegalArgumentException("No Policies provided!");
1562         }
1563 
1564         String[] ids = new String[policyIds.length];
1565         for (int i = 0; i < policyIds.length; i++) {
1566             if (policyIds[i] == null || policyIds[i].getId() == null) {
1567                 throw new IllegalArgumentException("A Policy ID is not set!");
1568             }
1569 
1570             ids[i] = policyIds[i].getId();
1571         }
1572 
1573         for (String id : ids) {
1574             getBinding().getPolicyService().removePolicy(getRepositoryId(), id, objectId.getId(), null);
1575         }
1576     }
1577 
1578     // ----
1579 
1580     protected final void checkObjectId(ObjectId objectId) {
1581         if (objectId == null || objectId.getId() == null) {
1582             throw new IllegalArgumentException("Invalid object ID!");
1583         }
1584     }
1585 
1586     protected final void checkObjectId(String objectId) {
1587         if (objectId == null) {
1588             throw new IllegalArgumentException("Invalid object ID!");
1589         }
1590     }
1591 
1592     protected final void checkDocumentId(ObjectId docId) {
1593         if (docId == null || docId.getId() == null) {
1594             throw new IllegalArgumentException("Invalid document ID!");
1595         }
1596     }
1597 
1598     protected final void checkDocumentId(String docId) {
1599         if (docId == null) {
1600             throw new IllegalArgumentException("Invalid document ID!");
1601         }
1602     }
1603 
1604     protected final void checkFolderId(ObjectId folderId) {
1605         if (folderId == null || folderId.getId() == null) {
1606             throw new IllegalArgumentException("Invalid folder ID!");
1607         }
1608     }
1609 
1610     protected final void checkPath(String path) {
1611         if (path == null || path.length() < 1) {
1612             throw new IllegalArgumentException("Invalid path!");
1613         }
1614         if (path.charAt(0) != '/') {
1615             throw new IllegalArgumentException("Path must start with a '/'!");
1616         }
1617     }
1618 
1619     protected final void checkContext(OperationContext context) {
1620         if (context == null) {
1621             throw new IllegalArgumentException("Invalid Operation Context!");
1622         }
1623     }
1624 
1625     protected final void checkProperties(Map<String, ?> properties) {
1626         if (isNullOrEmpty(properties)) {
1627             throw new IllegalArgumentException("Properties must not be empty!");
1628         }
1629     }
1630 
1631     protected final void checkCmisVersion() {
1632         if (repositoryInfo.getCmisVersion() == CmisVersion.CMIS_1_0) {
1633             throw new CmisNotSupportedException("This method is not supported for CMIS 1.0 repositories.");
1634         }
1635     }
1636 
1637     // ----
1638 
1639     @Override
1640     public String toString() {
1641         return "Session " + getBinding().getSessionId();
1642     }
1643 }