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