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

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.inmemory.server;
20  
21  import java.math.BigInteger;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.chemistry.opencmis.commons.PropertyIds;
29  import org.apache.chemistry.opencmis.commons.data.Acl;
30  import org.apache.chemistry.opencmis.commons.data.AllowableActions;
31  import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement;
32  import org.apache.chemistry.opencmis.commons.data.ContentStream;
33  import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
34  import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
35  import org.apache.chemistry.opencmis.commons.data.ObjectData;
36  import org.apache.chemistry.opencmis.commons.data.Properties;
37  import org.apache.chemistry.opencmis.commons.data.PropertyData;
38  import org.apache.chemistry.opencmis.commons.data.RenditionData;
39  import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition;
40  import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
41  import org.apache.chemistry.opencmis.commons.definitions.RelationshipTypeDefinition;
42  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
43  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
44  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
45  import org.apache.chemistry.opencmis.commons.enums.Cardinality;
46  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
47  import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
48  import org.apache.chemistry.opencmis.commons.enums.Updatability;
49  import org.apache.chemistry.opencmis.commons.enums.VersioningState;
50  import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
51  import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
52  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
53  import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
54  import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
55  import org.apache.chemistry.opencmis.commons.exceptions.CmisUpdateConflictException;
56  import org.apache.chemistry.opencmis.commons.impl.dataobjects.CmisExtensionElementImpl;
57  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
58  import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl;
59  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
60  import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl;
61  import org.apache.chemistry.opencmis.commons.server.CallContext;
62  import org.apache.chemistry.opencmis.commons.server.ObjectInfoHandler;
63  import org.apache.chemistry.opencmis.commons.spi.Holder;
64  import org.apache.chemistry.opencmis.inmemory.FilterParser;
65  import org.apache.chemistry.opencmis.inmemory.NameValidator;
66  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Content;
67  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Document;
68  import org.apache.chemistry.opencmis.inmemory.storedobj.api.DocumentVersion;
69  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Filing;
70  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Folder;
71  import org.apache.chemistry.opencmis.inmemory.storedobj.api.ObjectStore;
72  import org.apache.chemistry.opencmis.inmemory.storedobj.api.StoreManager;
73  import org.apache.chemistry.opencmis.inmemory.storedobj.api.StoredObject;
74  import org.apache.chemistry.opencmis.inmemory.storedobj.api.VersionedDocument;
75  import org.apache.chemistry.opencmis.inmemory.types.InMemoryDocumentTypeDefinition;
76  import org.apache.chemistry.opencmis.inmemory.types.InMemoryFolderTypeDefinition;
77  import org.apache.chemistry.opencmis.inmemory.types.InMemoryPolicyTypeDefinition;
78  import org.apache.chemistry.opencmis.inmemory.types.InMemoryRelationshipTypeDefinition;
79  import org.apache.chemistry.opencmis.inmemory.types.PropertyCreationHelper;
80  import org.apache.chemistry.opencmis.server.support.TypeValidator;
81  import org.apache.commons.logging.Log;
82  import org.apache.commons.logging.LogFactory;
83  
84  public class InMemoryObjectServiceImpl extends InMemoryAbstractServiceImpl {
85      private static final Log LOG = LogFactory.getLog(InMemoryServiceFactoryImpl.class.getName());
86  
87      final AtomLinkInfoProvider fAtomLinkProvider;
88  
89      public InMemoryObjectServiceImpl(StoreManager storeManager) {
90          super(storeManager);
91          fAtomLinkProvider = new AtomLinkInfoProvider(fStoreManager);
92      }
93  
94      public String createDocument(CallContext context, String repositoryId, Properties properties, String folderId,
95              ContentStream contentStream, VersioningState versioningState, List<String> policies, Acl addAces,
96              Acl removeAces, ExtensionsData extension) {
97  
98          LOG.debug("start createDocument()");
99          // Attach the CallContext to a thread local context that can be
100         // accessed from everywhere
101 
102         StoredObject so = createDocumentIntern(context, repositoryId, properties, folderId, contentStream, versioningState,
103                 policies, addAces, removeAces, extension);
104         LOG.debug("stop createDocument()");
105         return so.getId();
106     }
107 
108     public String createDocumentFromSource(CallContext context, String repositoryId, String sourceId,
109             Properties properties, String folderId, VersioningState versioningState, List<String> policies,
110             Acl addAces, Acl removeAces, ExtensionsData extension) {
111 
112         LOG.debug("start createDocumentFromSource()");
113         StoredObject so = validator.createDocumentFromSource(context, repositoryId, sourceId, folderId, extension);
114         TypeDefinition td = getTypeDefinition(repositoryId, so);  // type definition may be copied from source object
115 
116         ContentStream content = getContentStream(context, repositoryId, sourceId, null, BigInteger.valueOf(-1),
117                 BigInteger.valueOf(-1), null);
118 
119         if (so == null) {
120             throw new CmisObjectNotFoundException("Unknown object id: " + sourceId);
121         }
122 
123         // build properties collection
124         List<String> requestedIds = FilterParser.getRequestedIdsFromFilter("*");
125 
126         Properties existingProps = PropertyCreationHelper.getPropertiesFromObject(so, td, requestedIds, true);
127 
128         PropertiesImpl newPD = new PropertiesImpl();
129         // copy all existing properties
130         for (PropertyData<?> prop : existingProps.getProperties().values()) {
131             newPD.addProperty(prop);
132         }
133 
134         if (null != properties)
135             // overwrite all new properties
136             for (PropertyData<?> prop : properties.getProperties().values()) {
137                 newPD.addProperty(prop);
138             }
139 
140         String res = createDocument(context, repositoryId, newPD, folderId, content, versioningState, policies,
141                 addAces, removeAces, null);
142         LOG.debug("stop createDocumentFromSource()");
143         return res;
144     }
145 
146     public String createFolder(CallContext context, String repositoryId, Properties properties, String folderId,
147             List<String> policies, Acl addAces, Acl removeAces, ExtensionsData extension) {
148         LOG.debug("start createFolder()");
149 
150         Folder folder = createFolderIntern(context, repositoryId, properties, folderId, policies, addAces, removeAces,
151                 extension);
152         LOG.debug("stop createFolder()");
153         return folder.getId();
154     }
155 
156     public String createPolicy(CallContext context, String repositoryId, Properties properties, String folderId,
157             List<String> policies, Acl addAces, Acl removeAces, ExtensionsData extension) {
158 
159         // TODO to be completed if policies are implemented
160         LOG.debug("start createPolicy()");
161         StoredObject so = createPolicyIntern(context, repositoryId, properties, folderId, policies, addAces, removeAces,
162                 extension);
163         LOG.debug("stop createPolicy()");
164         return so == null ? null : so.getId();
165     }
166 
167     public String createRelationship(CallContext context, String repositoryId, Properties properties,
168             List<String> policies, Acl addAces, Acl removeAces, ExtensionsData extension) {
169 
170         // TODO to be completed if relationships are implemented
171         LOG.debug("start createRelationship()");
172         StoredObject so = createRelationshipIntern(context, repositoryId, properties, policies, addAces, removeAces, extension);
173         LOG.debug("stop createRelationship()");
174         return so == null ? null : so.getId();
175     }
176 
177     @SuppressWarnings("unchecked")
178     public String create(CallContext context, String repositoryId, Properties properties, String folderId,
179             ContentStream contentStream, VersioningState versioningState, List<String> policies,
180             ExtensionsData extension, ObjectInfoHandler objectInfos) {
181 
182         if (null == properties || null == properties.getProperties()) {
183             throw new CmisInvalidArgumentException("Cannot create object, without properties.");
184         }
185 
186         // Find out what kind of object needs to be created
187         PropertyData<String> pd = (PropertyData<String>) properties.getProperties().get(PropertyIds.OBJECT_TYPE_ID);
188         String typeId = pd == null ? null : pd.getFirstValue();
189         if (null == typeId) {
190             throw new CmisInvalidArgumentException(
191                     "Cannot create object, without a type (no property with id CMIS_OBJECT_TYPE_ID).");
192         }
193 
194         TypeDefinitionContainer typeDefC = fStoreManager.getTypeById(repositoryId, typeId);
195         if (typeDefC == null) {
196             throw new CmisInvalidArgumentException("Cannot create object, a type with id " + typeId + " is unknown");
197         }
198 
199         // check if the given type is a document type
200         BaseTypeId typeBaseId = typeDefC.getTypeDefinition().getBaseTypeId();
201         StoredObject so = null;
202         if (typeBaseId.equals(InMemoryDocumentTypeDefinition.getRootDocumentType().getBaseTypeId())) {
203             so = createDocumentIntern(context, repositoryId, properties, folderId, contentStream, versioningState, null, null,
204                     null, null);
205         } else if (typeBaseId.equals(InMemoryFolderTypeDefinition.getRootFolderType().getBaseTypeId())) {
206             so = createFolderIntern(context, repositoryId, properties, folderId, null, null, null, null);
207         } else if (typeBaseId.equals(InMemoryPolicyTypeDefinition.getRootPolicyType().getBaseTypeId())) {
208             so = createPolicyIntern(context, repositoryId, properties, folderId, null, null, null, null);
209         } else if (typeBaseId.equals(InMemoryRelationshipTypeDefinition.getRootRelationshipType().getBaseTypeId())) {
210             so = createRelationshipIntern(context, repositoryId, properties, null, null, null, null);
211         } else {
212             LOG.error("The type contains an unknown base object id, object can't be created");
213         }
214 
215         // Make a call to getObject to convert the resulting id into an
216         // ObjectData
217         TypeDefinition td = typeDefC.getTypeDefinition();
218         ObjectData od = PropertyCreationHelper.getObjectData(td, so, null, context.getUsername(), false,
219                 IncludeRelationships.NONE, null, false, false, extension);
220 
221         if (context.isObjectInfoRequired()) {
222             ObjectInfoImpl objectInfo = new ObjectInfoImpl();
223             fAtomLinkProvider.fillInformationForAtomLinks(repositoryId, so, od, objectInfo);
224             objectInfos.addObjectInfo(objectInfo);
225         }
226         return so != null ? so.getId() : null;
227     }
228 
229     public void deleteContentStream(CallContext context, String repositoryId, Holder<String> objectId,
230             Holder<String> changeToken, ExtensionsData extension) {
231 
232         LOG.debug("start deleteContentStream()");
233         StoredObject so = validator.deleteContentStream(context, repositoryId, objectId, extension);
234 
235         if (so == null) {
236             throw new CmisObjectNotFoundException("Unknown object id: " + objectId);
237         }
238 
239         if ( so.getChangeToken() != null && ( changeToken == null || !so.getChangeToken().equals( changeToken.getValue() ) ) )
240             throw new CmisUpdateConflictException( "deleteContentStream failed, ChangeToken does not match." );
241              
242         if (!(so instanceof Content)) {
243             throw new CmisObjectNotFoundException("Id" + objectId
244                     + " does not refer to a document, but only documents can have content");
245         }
246 
247         ((Content) so).setContent(null, true);
248         LOG.debug("stop deleteContentStream()");
249     }
250 
251     public void deleteObject(CallContext context, String repositoryId, String objectId,
252             Boolean allVersions, ExtensionsData extension) {
253 
254         LOG.debug("start deleteObject()");
255         validator.deleteObject(context, repositoryId, objectId, allVersions, extension);
256         ObjectStore objectStore = fStoreManager.getObjectStore(repositoryId);
257         LOG.debug("delete object for id: " + objectId);
258 
259         // check if it is the root folder
260         if (objectId.equals(objectStore.getRootFolder().getId())) {
261             throw new CmisNotSupportedException("You can't delete a root folder");
262         }
263 
264         objectStore.deleteObject(objectId, allVersions, context.getUsername());
265         LOG.debug("stop deleteObject()");
266     }
267 
268     public FailedToDeleteData deleteTree(CallContext context, String repositoryId, String folderId,
269             Boolean allVersions, UnfileObject unfileObjects, Boolean continueOnFailure, ExtensionsData extension) {
270 
271         LOG.debug("start deleteTree()");
272         StoredObject so = validator.deleteTree(context, repositoryId, folderId, allVersions, unfileObjects, extension);
273         List<String> failedToDeleteIds = new ArrayList<String>();
274         FailedToDeleteDataImpl result = new FailedToDeleteDataImpl();
275 
276         if (null == allVersions) {
277             allVersions = true;
278         }
279         if (null == unfileObjects) {
280             unfileObjects = UnfileObject.DELETE;
281         }
282         if (null == continueOnFailure) {
283             continueOnFailure = false;
284         }
285 
286         ObjectStore objectStore = fStoreManager.getObjectStore(repositoryId);
287 
288         if (null == so) {
289             throw new CmisInvalidArgumentException("Cannot delete object with id  " + folderId + ". Object does not exist.");
290         }
291 
292         if (!(so instanceof Folder)) {
293             throw new CmisInvalidArgumentException("deleteTree can only be invoked on a folder, but id " + folderId
294                     + " does not refer to a folder");
295         }
296 
297         if (unfileObjects == UnfileObject.UNFILE) {
298             throw new CmisNotSupportedException("This repository does not support unfile operations.");
299         }
300 
301         // check if it is the root folder
302         if (folderId.equals(objectStore.getRootFolder().getId())) {
303             throw new CmisNotSupportedException("You can't delete a root folder");
304         }
305 
306         // recursively delete folder
307         deleteRecursive(objectStore, (Folder) so, continueOnFailure, allVersions, failedToDeleteIds, context.getUsername());
308 
309         result.setIds(failedToDeleteIds);
310         LOG.debug("stop deleteTree()");
311         return result;
312     }
313 
314     public AllowableActions getAllowableActions(CallContext context, String repositoryId, String objectId,
315             ExtensionsData extension) {
316 
317         LOG.debug("start getAllowableActions()");
318         StoredObject so = validator.getAllowableActions(context, repositoryId, objectId, extension);
319 
320         fStoreManager.getObjectStore(repositoryId);
321 
322         if (so == null) {
323             throw new CmisObjectNotFoundException("Unknown object id: " + objectId);
324         }
325 
326         String user = context.getUsername();
327 //      AllowableActions allowableActions = DataObjectCreator.fillAllowableActions(so, user);
328         AllowableActions allowableActions = so.getAllowableActions(user);
329         LOG.debug("stop getAllowableActions()");
330         return allowableActions;
331     }
332 
333     public ContentStream getContentStream(CallContext context, String repositoryId, String objectId, String streamId,
334             BigInteger offset, BigInteger length, ExtensionsData extension) {
335 
336         LOG.debug("start getContentStream()");
337         StoredObject so = validator.getContentStream(context, repositoryId, objectId, streamId, extension);
338 
339 
340         if (so == null) {
341             throw new CmisObjectNotFoundException("Unknown object id: " + objectId);
342         }
343 
344         if (!(so instanceof Content)) {
345             throw new CmisConstraintException("Id" + objectId
346                     + " does not refer to a document or version, but only those can have content");
347         }
348 
349         ContentStream csd = getContentStream(so, streamId, offset, length);
350 
351         if (null == csd) {
352             throw new CmisConstraintException("Object " + so.getId() + " does not have content.");
353         }
354 
355         LOG.debug("stop getContentStream()");
356         return csd;
357     }
358 
359     public ObjectData getObject(CallContext context, String repositoryId, String objectId, String filter,
360             Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
361             Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension, ObjectInfoHandler objectInfos) {
362 
363         LOG.debug("start getObject()");
364 
365         StoredObject so = validator.getObject(context, repositoryId, objectId, extension);
366 
367         if (so == null) {
368             throw new CmisObjectNotFoundException("Unknown object id: " + objectId);
369         }
370 
371         String user = context.getUsername();
372         TypeDefinition td = fStoreManager.getTypeById(repositoryId, so.getTypeId()).getTypeDefinition();
373         ObjectData od = PropertyCreationHelper.getObjectData(td, so, filter, user, includeAllowableActions,
374                 includeRelationships, renditionFilter, includePolicyIds, includeAcl, extension);
375 
376         if (context.isObjectInfoRequired()) {
377             ObjectInfoImpl objectInfo = new ObjectInfoImpl();
378             fAtomLinkProvider.fillInformationForAtomLinks(repositoryId, so, objectInfo);
379             objectInfos.addObjectInfo(objectInfo);
380         }
381 
382         // fill an example extension
383         String ns = "http://apache.org/opencmis/inmemory";
384         List<CmisExtensionElement> extElements = new ArrayList<CmisExtensionElement>();
385 
386         Map<String, String> attr = new HashMap<String, String>();
387         attr.put("type", so.getTypeId());
388 
389         extElements.add(new CmisExtensionElementImpl(ns, "objectId", attr, objectId));
390         extElements.add(new CmisExtensionElementImpl(ns, "name", null, so.getName()));
391         od.setExtensions(Collections.singletonList(
392                 (CmisExtensionElement) new CmisExtensionElementImpl(ns, "exampleExtension",null,  extElements)));
393 
394         LOG.debug("stop getObject()");
395 
396         return od;
397     }
398 
399     public ObjectData getObjectByPath(CallContext context, String repositoryId, String path, String filter,
400             Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
401             Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension, ObjectInfoHandler objectInfos) {
402 
403         LOG.debug("start getObjectByPath()");
404         StoredObject so = validator.getObjectByPath(context, repositoryId, path, extension);
405         if (so instanceof VersionedDocument) {
406             VersionedDocument verDoc = (VersionedDocument) so;
407             so = verDoc.getLatestVersion(false);
408         }
409 
410         String user = context.getUsername();
411 
412         TypeDefinition td = fStoreManager.getTypeById(repositoryId, so.getTypeId()).getTypeDefinition();
413         ObjectData od = PropertyCreationHelper.getObjectData(td, so, filter, user, includeAllowableActions,
414                 includeRelationships, renditionFilter, includePolicyIds, includeAcl, extension);
415 
416         LOG.debug("stop getObjectByPath()");
417 
418         // To be able to provide all Atom links in the response we need
419         // additional information:
420         if (context.isObjectInfoRequired()) {
421             ObjectInfoImpl objectInfo = new ObjectInfoImpl();
422             fAtomLinkProvider.fillInformationForAtomLinks(repositoryId, so, objectInfo);
423             objectInfos.addObjectInfo(objectInfo);
424         }
425 
426         return od;
427     }
428 
429     public Properties getProperties(CallContext context, String repositoryId, String objectId, String filter,
430             ExtensionsData extension) {
431 
432         LOG.debug("start getProperties()");
433         StoredObject so = validator.getProperties(context, repositoryId, objectId, extension);
434 
435         if (so == null) {
436             throw new CmisObjectNotFoundException("Unknown object id: " + objectId);
437         }
438 
439         // build properties collection
440         List<String> requestedIds = FilterParser.getRequestedIdsFromFilter(filter);
441         TypeDefinition td = fStoreManager.getTypeById(repositoryId, so.getTypeId()).getTypeDefinition();
442         Properties props = PropertyCreationHelper.getPropertiesFromObject(so, td, requestedIds, true);
443         LOG.debug("stop getProperties()");
444         return props;
445     }
446 
447     public List<RenditionData> getRenditions(CallContext context, String repositoryId, String objectId,
448             String renditionFilter, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
449 
450         // TODO to be completed if renditions are implemented
451         LOG.debug("start getRenditions()");
452         validator.getRenditions(context, repositoryId, objectId, extension);
453 
454         LOG.debug("stop getRenditions()");
455         return null;
456     }
457 
458     public ObjectData moveObject(CallContext context, String repositoryId, Holder<String> objectId,
459             String targetFolderId, String sourceFolderId, ExtensionsData extension, ObjectInfoHandler objectInfos) {
460 
461         LOG.debug("start moveObject()");
462         StoredObject[] sos = validator.moveObject(context, repositoryId, objectId, targetFolderId, sourceFolderId, extension);
463         StoredObject so = sos[0];
464         Folder targetFolder = null;
465         Folder sourceFolder = null;
466         ObjectStore objectStore = fStoreManager.getObjectStore(repositoryId);
467         Filing spo = null;
468         String user = context.getUsername();
469 
470         if (null == so) {
471             throw new CmisObjectNotFoundException("Unknown object: " + objectId.getValue());
472         } else if (so instanceof Filing) {
473             spo = (Filing) so;
474         } else {
475             throw new CmisInvalidArgumentException("Object must be folder or document: " + objectId.getValue());
476         }
477 
478         StoredObject soTarget = objectStore.getObjectById(targetFolderId);
479         if (null == soTarget) {
480             throw new CmisObjectNotFoundException("Unknown target folder: " + targetFolderId);
481         } else if (soTarget instanceof Folder) {
482             targetFolder = (Folder) soTarget;
483         } else {
484             throw new CmisNotSupportedException("Destination " + targetFolderId
485                     + " of a move operation must be a folder");
486         }
487 
488         StoredObject soSource = objectStore.getObjectById(sourceFolderId);
489         if (null == soSource) {
490             throw new CmisObjectNotFoundException("Unknown source folder: " + sourceFolderId);
491         } else if (soSource instanceof Folder) {
492             sourceFolder = (Folder) soSource;
493         } else {
494             throw new CmisNotSupportedException("Source " + sourceFolderId + " of a move operation must be a folder");
495         }
496 
497         boolean foundOldParent = false;
498         for (Folder parent : spo.getParents(user)) {
499             if (parent.getId().equals(soSource.getId())) {
500                 foundOldParent = true;
501                 break;
502             }
503         }
504         if (!foundOldParent) {
505             throw new CmisNotSupportedException("Cannot move object, source folder " + sourceFolderId
506                     + "is not a parent of object " + objectId.getValue());
507         }
508 
509         if (so instanceof Folder && hasDescendant((Folder) so, targetFolder)) {
510             throw new CmisNotSupportedException("Destination of a move cannot be a subfolder of the source");
511         }
512 
513         spo.move(sourceFolder, targetFolder);
514         objectId.setValue(so.getId());
515         LOG.debug("stop moveObject()");
516 
517         TypeDefinition td = fStoreManager.getTypeById(repositoryId, so.getTypeId()).getTypeDefinition();
518         ObjectData od = PropertyCreationHelper.getObjectData(td, so, null, user, false,
519                 IncludeRelationships.NONE, null, false, false, extension);
520 
521         // To be able to provide all Atom links in the response we need
522         // additional information:
523         if (context.isObjectInfoRequired()) {
524             ObjectInfoImpl objectInfo = new ObjectInfoImpl();
525             fAtomLinkProvider.fillInformationForAtomLinks(repositoryId, so, od, objectInfo);
526             objectInfos.addObjectInfo(objectInfo);
527         }
528 
529         return od;
530     }
531 
532     public void setContentStream(CallContext context, String repositoryId, Holder<String> objectId,
533             Boolean overwriteFlag, Holder<String> changeToken, ContentStream contentStream, ExtensionsData extension) {
534 
535         LOG.debug("start setContentStream()");
536         Content content;
537         if ( null == overwriteFlag ) {
538             overwriteFlag = Boolean.TRUE;
539         }
540 
541         StoredObject so = validator.setContentStream(context, repositoryId, objectId, overwriteFlag, extension);
542 
543         if ( so.getChangeToken() != null && ( changeToken == null || !so.getChangeToken().equals( changeToken.getValue() ) ) )
544             throw new CmisUpdateConflictException( "setContentStream failed, ChangeToken does not match." );
545              
546         if (!(so instanceof Document || so instanceof VersionedDocument || so instanceof DocumentVersion)) {
547             throw new CmisObjectNotFoundException("Id" + objectId
548                     + " does not refer to a document, but only documents can have content");
549         }
550 
551         if (so instanceof Document) {
552             content = ((Document) so);
553         } else if (so instanceof DocumentVersion) {
554             // something that is versionable check the proper status of the
555             // object
556             String user = context.getUsername();
557             testHasProperCheckedOutStatus(so, user);
558             content = (DocumentVersion) so;
559         } else {
560             throw new IllegalArgumentException("Content cannot be set on this object (must be document or version)");
561         }
562 
563         if (!overwriteFlag && content.getContent(0, -1) != null) {
564             throw new CmisContentAlreadyExistsException("cannot overwrite existing content if overwrite flag is not set");
565         }
566 
567         content.setContent(contentStream, true);
568         LOG.debug("stop setContentStream()");
569     }
570 
571     public void updateProperties(CallContext context, String repositoryId, Holder<String> objectId,
572             Holder<String> changeToken, Properties properties, Acl acl, ExtensionsData extension,
573             ObjectInfoHandler objectInfos) {
574 
575         LOG.debug("start updateProperties()");
576         StoredObject so = validator.updateProperties(context, repositoryId, objectId, extension);
577         String user = context.getUsername();
578 
579         // Validation
580         TypeDefinition typeDef = getTypeDefinition(repositoryId, so);
581         boolean isCheckedOut = false;
582 
583         isCheckedOut = isCheckedOut(so, user);
584 
585         Map<String, PropertyData<?>> oldProperties = so.getProperties();
586 
587         // check properties for validity
588         TypeValidator.validateProperties(typeDef, properties, false);
589 
590         if (changeToken != null && changeToken.getValue() != null
591                 && Long.valueOf(so.getChangeToken()) > Long.valueOf(changeToken.getValue())) {
592             throw new CmisUpdateConflictException("updateProperties failed: changeToken does not match");
593         }
594 
595         // update properties
596         boolean hasUpdatedName = false;
597         boolean hasUpdatedOtherProps = false;
598 
599         if(properties != null) {
600         	for (String key : properties.getProperties().keySet()) {
601         		if (key.equals(PropertyIds.NAME))
602         		{
603         			continue; // ignore here
604         		}
605 
606         		PropertyData<?> value = properties.getProperties().get(key);
607         		PropertyDefinition<?> propDef = typeDef.getPropertyDefinitions().get(key);
608         		if (value.getValues() == null || value.getFirstValue() == null) {
609         			// delete property
610         			// check if a required a property
611         			if (propDef.isRequired()) {
612         				throw new CmisConstraintException(
613         						"updateProperties failed, following property can't be deleted, because it is required: "
614         								+ key);
615         			}
616         			oldProperties.remove(key);
617         			hasUpdatedOtherProps = true;
618         		} else {
619         			if (propDef.getUpdatability().equals(Updatability.WHENCHECKEDOUT)) {
620         				if (!isCheckedOut)
621         					throw new CmisUpdateConflictException(
622         							"updateProperties failed, following property can't be updated, because it is not checked-out: "
623         									+ key);
624         			} else if (!propDef.getUpdatability().equals(Updatability.READWRITE)) {
625         				throw new CmisConstraintException(
626         						"updateProperties failed, following property can't be updated, because it is not writable: "
627         								+ key);
628         			}
629         			oldProperties.put(key, value);
630         			hasUpdatedOtherProps = true;
631         		}
632         	}
633 
634         	// get name from properties and perform special rename to check if
635         	// path already exists
636         	PropertyData<?> pd = properties.getProperties().get(PropertyIds.NAME);
637         	if (pd != null && so instanceof Filing) {
638         		String newName = (String) pd.getFirstValue();
639         		List<Folder> parents = ((Filing) so).getParents(user);
640         		if (so instanceof Folder && parents.isEmpty()) {
641         			throw new CmisConstraintException("updateProperties failed, you cannot rename the root folder");
642         		}
643         		if (newName == null || newName.equals("")) {
644         			throw new CmisConstraintException("updateProperties failed, name must not be empty.");
645         		}
646 
647         		so.rename((String) pd.getFirstValue()); // note: this does persist
648         		hasUpdatedName = true;
649         	}
650         }
651 
652         if (hasUpdatedOtherProps) {
653             // set user, creation date, etc.
654             if (user == null) {
655                 user = "unknown";
656             }
657             so.updateSystemBasePropertiesWhenModified(properties.getProperties(), user);
658             // set changeToken
659             so.persist();
660         }
661 
662         if (hasUpdatedName || hasUpdatedOtherProps) {
663             objectId.setValue(so.getId()); // might have a new id
664             if (null != changeToken) {
665                 String changeTokenVal = so.getChangeToken();
666                 LOG.debug("updateProperties(), new change token is: " + changeTokenVal);
667                 changeToken.setValue(changeTokenVal);
668             }
669         }
670 
671         if (null != acl) {
672             LOG.warn("Setting ACLs is currently not supported by this implementation, acl is ignored");
673             // if implemented add this call:
674             // fAclService.appyAcl(context, repositoryId, acl, null,
675             // AclPropagation.OBJECTONLY,
676             // extension);
677         }
678 
679         TypeDefinition td = fStoreManager.getTypeById(repositoryId, so.getTypeId()).getTypeDefinition();
680         ObjectData od = PropertyCreationHelper.getObjectData(td, so, null, user, false,
681                 IncludeRelationships.NONE, null, false, false, extension);
682 
683         // To be able to provide all Atom links in the response we need
684         // additional information:
685         if (context.isObjectInfoRequired()) {
686             ObjectInfoImpl objectInfo = new ObjectInfoImpl();
687             fAtomLinkProvider.fillInformationForAtomLinks(repositoryId, so, od, objectInfo);
688             objectInfos.addObjectInfo(objectInfo);
689         }
690 
691         LOG.debug("stop updateProperties()");
692     }
693 
694     // ///////////////////////////////////////////////////////
695     // private helper methods
696 
697     private StoredObject createDocumentIntern(CallContext context, String repositoryId, Properties properties, String folderId,
698             ContentStream contentStream, VersioningState versioningState, List<String> policies, Acl addACEs,
699             Acl removeACEs, ExtensionsData extension) {
700 
701         validator.createDocument(context, repositoryId, folderId, extension);
702 
703         // Validation stuff
704         TypeValidator.validateRequiredSystemProperties(properties);
705 
706         String user = context.getUsername();
707         TypeDefinition typeDef = getTypeDefinition(repositoryId, properties);
708 
709         ObjectStore objectStore = fStoreManager.getObjectStore(repositoryId);
710         Map<String, PropertyData<?>> propMap = properties.getProperties();
711         // get name from properties
712         PropertyData<?> pd = propMap.get(PropertyIds.NAME);
713         String name = (String) pd.getFirstValue();
714 
715 
716         // validate ACL
717         TypeValidator.validateAcl(typeDef, addACEs, removeACEs);
718 
719         Folder folder = null;
720         if (null != folderId) {
721             StoredObject so = objectStore.getObjectById(folderId);
722 
723             if (null == so) {
724                 throw new CmisInvalidArgumentException(" Cannot create document, folderId: " + folderId + " is invalid");
725             }
726 
727             if (so instanceof Folder) {
728                 folder = (Folder) so;
729             } else {
730                 throw new CmisInvalidArgumentException("Can't creat document, folderId does not refer to a folder: "
731                         + folderId);
732             }
733 
734             TypeValidator.validateAllowedChildObjectTypes(typeDef, folder.getAllowedChildObjectTypeIds());
735         }
736 
737         // check if the given type is a document type
738         if (!typeDef.getBaseTypeId().equals(BaseTypeId.CMIS_DOCUMENT)) {
739             throw new CmisInvalidArgumentException("Cannot create a document, with a non-document type: " + typeDef.getId());
740         }
741 
742         // check name syntax
743         if (!NameValidator.isValidName(name)) {
744             throw new CmisInvalidArgumentException(NameValidator.ERROR_ILLEGAL_NAME);
745         }
746 
747         TypeValidator.validateVersionStateForCreate((DocumentTypeDefinition) typeDef, versioningState);
748 
749         // set properties that are not set but have a default:
750         Map<String, PropertyData<?>> propMapNew = setDefaultProperties(typeDef, propMap);
751         if (propMapNew != propMap) {
752             properties = new PropertiesImpl(propMapNew.values());
753             propMap = propMapNew;
754         }
755 
756         TypeValidator.validateProperties(typeDef, properties, true);
757 
758         // set user, creation date, etc.
759         if (user == null) {
760             user = "unknown";
761         }
762 
763         StoredObject so = null;
764 
765         // check if content stream parameters are set and if not set some defaults
766         if (null != contentStream && (contentStream.getFileName() == null || contentStream.getFileName().length() == 0 ||
767             contentStream.getMimeType() == null || contentStream.getMimeType().length() == 0)) {
768             ContentStreamImpl cs = new ContentStreamImpl();
769             cs.setStream(contentStream.getStream());
770             if (contentStream.getFileName() == null || contentStream.getFileName().length() == 0) {
771                 cs.setFileName(name);
772             } else {
773                 cs.setFileName(contentStream.getFileName());
774             }
775             cs.setLength(contentStream.getBigLength());
776             if (contentStream.getMimeType() == null || contentStream.getMimeType().length() == 0) {
777                 cs.setMimeType("application/octet-stream");
778             } else {
779                 cs.setMimeType(contentStream.getMimeType());
780             }
781             cs.setExtensions(contentStream.getExtensions());
782             contentStream = cs;
783         }
784 
785         // Now we are sure to have document type definition:
786         if (((DocumentTypeDefinition) typeDef).isVersionable()) {
787             DocumentVersion version = objectStore.createVersionedDocument(name,  propMap,
788                     user, folder, addACEs, removeACEs, contentStream, versioningState);
789             version.persist();
790             so = version; // return the version and not the version series to caller
791         } else {
792             Document doc = objectStore.createDocument(name, propMap, user, folder, addACEs, removeACEs);
793             doc.setContent(contentStream, false);
794             doc.persist();
795             so = doc;
796         }
797 
798         return so;
799     }
800 
801     private Folder createFolderIntern(CallContext context, String repositoryId, Properties properties, String folderId,
802             List<String> policies, Acl addACEs, Acl removeACEs, ExtensionsData extension) {
803 
804         validator.createFolder(context, repositoryId, folderId, extension);
805         TypeValidator.validateRequiredSystemProperties(properties);
806         String user = context.getUsername();
807 
808         ObjectStore fs = fStoreManager.getObjectStore(repositoryId);
809         StoredObject so = null;
810         Folder parent = null;
811 
812         // get required properties
813         PropertyData<?> pd = properties.getProperties().get(PropertyIds.NAME);
814         String folderName = (String) pd.getFirstValue();
815         if (null == folderName || folderName.length() == 0) {
816             throw new CmisInvalidArgumentException("Cannot create a folder without a name.");
817         }
818 
819         // check name syntax
820         if (!NameValidator.isValidId(folderName)) {
821             throw new CmisInvalidArgumentException(NameValidator.ERROR_ILLEGAL_NAME);
822         }
823 
824 
825         TypeDefinition typeDef = getTypeDefinition(repositoryId, properties);
826 
827         // check if the given type is a folder type
828         if (!typeDef.getBaseTypeId().equals(BaseTypeId.CMIS_FOLDER)) {
829             throw new CmisInvalidArgumentException("Cannot create a folder, with a non-folder type: " + typeDef.getId());
830         }
831 
832         Map<String, PropertyData<?>> propMap = properties.getProperties();
833         Map<String, PropertyData<?>> propMapNew = setDefaultProperties(typeDef, propMap);
834         if (propMapNew != propMap) {
835             properties = new PropertiesImpl(propMapNew.values());
836         }
837 
838         TypeValidator.validateProperties(typeDef, properties, true);
839 
840         // validate ACL
841         TypeValidator.validateAcl(typeDef, addACEs, removeACEs);
842 
843         // create folder
844         try {
845             LOG.debug("get folder for id: " + folderId);
846             so = fs.getObjectById(folderId);
847         } catch (Exception e) {
848             throw new CmisObjectNotFoundException("Failed to retrieve folder.", e);
849         }
850 
851         if (so instanceof Folder) {
852             parent = (Folder) so;
853         } else {
854             throw new CmisInvalidArgumentException("Can't create folder, folderId does not refer to a folder: "
855                     + folderId);
856         }
857 
858         if (user == null) {
859             user = "unknown";
860         }
861 
862         ObjectStore objStore = fStoreManager.getObjectStore(repositoryId);
863         Folder newFolder = objStore.createFolder(folderName, properties.getProperties(), user, parent,
864         addACEs,  removeACEs);
865         LOG.debug("stop createFolder()");
866         newFolder.persist();
867         return newFolder;
868     }
869 
870     private StoredObject createPolicyIntern(CallContext context, String repositoryId, Properties properties, String folderId,
871             List<String> policies, Acl addAces, Acl removeAces, ExtensionsData extension) {
872 
873         validator.createPolicy(context, repositoryId, folderId, extension);
874         throw new CmisNotSupportedException("createPolicy is not supported.");
875     }
876 
877     private StoredObject createRelationshipIntern(CallContext context, String repositoryId,
878             Properties properties, List<String> policies,
879             Acl addACEs, Acl removeACEs, ExtensionsData extension) {
880 
881         TypeValidator.validateRequiredSystemProperties(properties);
882 
883         String user = context.getUsername();
884         
885         // get required properties
886         PropertyData<?> pd = properties.getProperties().get(PropertyIds.SOURCE_ID);
887         String sourceId = (String) pd.getFirstValue();
888         if (null == sourceId || sourceId.length() == 0)
889             throw new CmisInvalidArgumentException("Cannot create a relationship without a sourceId.");
890 
891         pd = properties.getProperties().get(PropertyIds.TARGET_ID);
892         String targetId = (String) pd.getFirstValue();
893         if (null == targetId || targetId.length() == 0)
894             throw new CmisInvalidArgumentException("Cannot create a relationship without a targetId.");
895 
896         RelationshipTypeDefinition typeDef = (RelationshipTypeDefinition) getTypeDefinition(repositoryId, properties);
897 
898         // check if the given type is a relationship type
899         if (!typeDef.getBaseTypeId().equals(BaseTypeId.CMIS_RELATIONSHIP))
900             throw new CmisInvalidArgumentException("Cannot create a relationship, with a non-relationship type: " + typeDef.getId());
901 
902        StoredObject[] relationObjects = validator.createRelationship(context, repositoryId, sourceId, targetId, extension);
903 
904 
905        // set default properties
906        Map<String, PropertyData<?>> propMap = properties.getProperties();
907        Map<String, PropertyData<?>> propMapNew = setDefaultProperties(typeDef, propMap);
908        if (propMapNew != propMap) {
909            properties = new PropertiesImpl(propMapNew.values());
910        }
911 
912        TypeValidator.validateProperties(typeDef, properties, true);
913 
914        // validate ACL
915        TypeValidator.validateAcl(typeDef, addACEs, removeACEs);
916 
917        // validate the allowed types of the relationship
918        ObjectStore objStore = fStoreManager.getObjectStore(repositoryId);
919 
920        TypeDefinition sourceTypeDef = fStoreManager.getTypeById(repositoryId, objStore.getObjectById(sourceId).getTypeId()).getTypeDefinition();
921        TypeDefinition targetTypeDef = fStoreManager.getTypeById(repositoryId, objStore.getObjectById(targetId).getTypeId()).getTypeDefinition();
922        TypeValidator.validateAllowedRelationshipTypes(typeDef,  sourceTypeDef, targetTypeDef);
923 
924         StoredObject storedObject = objStore.createRelationship( relationObjects[0], relationObjects[1],
925                 propMap, user, addACEs,  removeACEs);
926         return storedObject;
927     }
928 
929 
930     private static boolean hasDescendant(Folder sourceFolder, Folder targetFolder) {
931         String sourceId = sourceFolder.getId();
932         String targetId = targetFolder.getId();
933         while (targetId != null) {
934             // log.debug("comparing source id " + sourceId + " with predecessor "
935             // +
936             // targetId);
937             if (targetId.equals(sourceId)) {
938                 return true;
939             }
940             targetFolder = targetFolder.getParent();
941             if (null != targetFolder) {
942                 targetId = targetFolder.getId();
943             } else {
944                 targetId = null;
945             }
946         }
947         return false;
948     }
949 
950     /**
951      * Recursively delete a tree by traversing it and first deleting all
952      * children and then the object itself
953      *
954      * @param folderStore
955      * @param parentFolder
956      * @param continueOnFailure
957      * @param allVersions
958      * @param failedToDeleteIds
959      * @return returns true if operation should continue, false if it should
960      *         stop
961      */
962     private boolean deleteRecursive(ObjectStore folderStore, Folder parentFolder, boolean continueOnFailure,
963             boolean allVersions, List<String> failedToDeleteIds, String user) {
964         List<StoredObject> children = parentFolder.getChildren(-1, -1, "Admin");
965 
966         if (null == children) {
967             return true;
968         }
969 
970         for (StoredObject child : children) {
971             if (child instanceof Folder) {
972                 boolean mustContinue = deleteRecursive(folderStore, (Folder) child, continueOnFailure, allVersions,
973                         failedToDeleteIds, user);
974                 if (!mustContinue && !continueOnFailure)
975                  {
976                     return false; // stop further deletions
977                 }
978             } else {
979                 try {
980                     folderStore.deleteObject(child.getId(), allVersions, user);
981                 } catch (Exception e) {
982                     failedToDeleteIds.add(child.getId());
983                 }
984             }
985         }
986         folderStore.deleteObject(parentFolder.getId(), allVersions, user);
987         return true;
988     }
989 
990     private static ContentStream getContentStream(StoredObject so, String streamId, BigInteger offset, BigInteger length) {
991         if (streamId != null) {
992             return null;
993         }
994         long lOffset = offset == null ? 0 : offset.longValue();
995         long lLength = length == null ? -1 : length.longValue();
996         ContentStream csd = ((Content) so).getContent(lOffset, lLength);
997         return csd;
998     }
999 
1000     private Map<String, PropertyData<?>> setDefaultProperties(TypeDefinition typeDef, Map<String, PropertyData<?>> properties) {
1001         Map<String, PropertyDefinition<?>> propDefs = typeDef.getPropertyDefinitions();
1002         boolean hasCopied = false;
1003 
1004         for ( PropertyDefinition<?> propDef : propDefs.values()) {
1005             String propId = propDef.getId();
1006             List<?> defaultVal = propDef.getDefaultValue();
1007             if (defaultVal != null && null == properties.get(propId)) {
1008                 if (!hasCopied) {
1009                     properties = new HashMap<String, PropertyData<?>>(properties); // copy because it is an unmodified collection
1010                     hasCopied = true;
1011                 }
1012                 Object value = propDef.getCardinality() == Cardinality.SINGLE ? defaultVal.get(0)
1013                         : defaultVal;
1014                 PropertyData<?> pd = fStoreManager.getObjectFactory().createPropertyData(
1015                         propDef, value);
1016                 // set property:
1017                 properties.put(propId, pd);
1018             }
1019         }
1020         return properties;
1021     }
1022 }