This project has retired. For details please refer to its Attic page.
ObjectStoreImpl 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.storedobj.impl;
20  
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.concurrent.locks.Lock;
27  import java.util.concurrent.locks.ReentrantLock;
28  
29  import org.apache.chemistry.opencmis.commons.data.Ace;
30  import org.apache.chemistry.opencmis.commons.data.Acl;
31  import org.apache.chemistry.opencmis.commons.data.ContentStream;
32  import org.apache.chemistry.opencmis.commons.data.PropertyData;
33  import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
34  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
35  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
36  import org.apache.chemistry.opencmis.commons.enums.VersioningState;
37  import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
38  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
39  import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
40  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Document;
41  import org.apache.chemistry.opencmis.inmemory.storedobj.api.DocumentVersion;
42  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Folder;
43  import org.apache.chemistry.opencmis.inmemory.storedobj.api.MultiFiling;
44  import org.apache.chemistry.opencmis.inmemory.storedobj.api.ObjectStore;
45  import org.apache.chemistry.opencmis.inmemory.storedobj.api.SingleFiling;
46  import org.apache.chemistry.opencmis.inmemory.storedobj.api.StoredObject;
47  import org.apache.chemistry.opencmis.inmemory.storedobj.api.VersionedDocument;
48  
49  /**
50   * The object store is the central core of the in-memory repository. It is based on huge HashMap
51   * map mapping ids to objects in memory. To allow access from multiple threads a Java concurrent
52   * HashMap is used that allows parallel access methods.
53   * <p>
54   * Certain methods in the in-memory repository must guarantee constraints. For example a folder
55   * enforces that each child has a unique name. Therefore certain operations must occur in an
56   * atomic manner. In the example it must be guaranteed that no write access occurs to the
57   * map between acquiring the iterator to find the children and finishing the add operation when
58   * no name conflicts can occur. For this purpose this class has methods to lock an unlock the
59   * state of the repository. It is very important that the caller acquiring the lock enforces an
60   * unlock under all circumstances. Typical code is:
61   * <p>
62   * <pre>
63   * ObjectStoreImpl os = ... ;
64   * try {
65   *     os.lock();
66   * } finally {
67   *     os.unlock();
68   * }
69   * </pre>
70   *
71   * The locking is very coarse-grained. Productive implementations would probably implement finer
72   * grained locks on a folder or document rather than the complete repository.
73   */
74  public class ObjectStoreImpl implements ObjectStore {
75  
76      
77      /**
78       * user id for administrator always having all rights
79       */
80      public static final String ADMIN_PRINCIPAL_ID = "Admin";
81      
82      /**
83       * Simple id generator that uses just an integer
84       */
85      private static int NEXT_UNUSED_ID = 100;
86  
87      /**
88       * a concurrent HashMap as core element to hold all objects in the repository
89       */
90      private final Map<String, StoredObject> fStoredObjectMap = new ConcurrentHashMap<String, StoredObject>();
91  
92      /**
93       * a concurrent HashMap to hold all Acls in the repository
94       */
95      private int nextUnusedAclId = 0;
96      
97      private final List<InMemoryAcl> fAcls = new ArrayList<InMemoryAcl>();
98  
99      private final Lock fLock = new ReentrantLock();
100 
101     final String fRepositoryId;
102     FolderImpl fRootFolder = null;
103 
104     public ObjectStoreImpl(String repositoryId) {
105         fRepositoryId = repositoryId;
106         createRootFolder();
107     }
108 
109     private static synchronized Integer getNextId() {
110         return NEXT_UNUSED_ID++;
111     }
112 
113     private synchronized Integer getNextAclId() {
114         return nextUnusedAclId++;
115     }
116     
117    public void lock() {
118       fLock.lock();
119     }
120 
121     public void unlock() {
122       fLock.unlock();
123     }
124 
125     public Folder getRootFolder() {
126         return fRootFolder;
127     }
128 
129     public StoredObject getObjectByPath(String path, String user) {
130         for (StoredObject so : fStoredObjectMap.values()) {
131             if (so instanceof SingleFiling) {
132                 String soPath = ((SingleFiling) so).getPath();
133                 if (soPath.equals(path)) {
134                     return so;
135                 }
136             } else if (so instanceof MultiFiling) {
137                 MultiFiling mfo = (MultiFiling) so;
138                 List<Folder> parents = mfo.getParents(user);
139                 for (Folder parent : parents) {
140                     String parentPath = parent.getPath();
141                     String mfPath = parentPath.equals(Folder.PATH_SEPARATOR) ? parentPath + mfo.getPathSegment()
142                             : parentPath + Folder.PATH_SEPARATOR + mfo.getPathSegment();
143                     if (mfPath.equals(path)) {
144                         return so;
145                     }
146                 }
147             } else {
148                 return null;
149             }
150         }
151         return null;
152     }
153 
154     public StoredObject getObjectById(String objectId) {
155         // we use path as id so we just can look it up in the map
156         StoredObject so = fStoredObjectMap.get(objectId);
157         return so;
158     }
159 
160     public void deleteObject(String objectId, Boolean allVersions, String user) {
161         StoredObject obj = fStoredObjectMap.get(objectId);
162 
163         if (null == obj) {
164             throw new RuntimeException("Cannot delete object with id  " + objectId + ". Object does not exist.");
165         }
166 
167         if (obj instanceof FolderImpl) {
168         	deleteFolder(objectId, user);
169         } else if (obj instanceof DocumentVersion) {
170             DocumentVersion vers = (DocumentVersion) obj;
171             VersionedDocument parentDoc = vers.getParentDocument();
172             boolean otherVersionsExists;
173             if (allVersions != null && allVersions) {
174                 otherVersionsExists = false;
175                 List<DocumentVersion> allVers = parentDoc.getAllVersions();
176                 for (DocumentVersion ver : allVers) {
177                     fStoredObjectMap.remove(ver.getId());
178                 }
179             } else {
180                 fStoredObjectMap.remove(objectId);
181                 otherVersionsExists = parentDoc.deleteVersion(vers);
182             }
183             
184             if (!otherVersionsExists) {
185                 fStoredObjectMap.remove(parentDoc.getId());
186             }
187         } else {
188             fStoredObjectMap.remove(objectId);
189         }
190     }
191 
192     public void removeVersion(DocumentVersion vers) {
193         StoredObject found = fStoredObjectMap.remove(vers.getId());
194 
195         if (null == found) {
196             throw new CmisInvalidArgumentException("Cannot delete object with id  " + vers.getId() + ". Object does not exist.");
197         }
198     }
199 
200     public String storeObject(StoredObject so) {
201         String id = so.getId();
202         // check if update or create
203         if (null == id) {
204             id = getNextId().toString();
205         }
206         fStoredObjectMap.put(id, so);
207         return id;
208     }
209 
210     StoredObject getObject(String id) {
211         return fStoredObjectMap.get(id);
212     }
213 
214     void removeObject(String id) {
215         fStoredObjectMap.remove(id);
216     }
217 
218     public Set<String> getIds() {
219         Set<String> entries = fStoredObjectMap.keySet();
220         return entries;
221     }
222 
223     /**
224      * Clear repository and remove all data.
225      */
226     public void clear() {
227         lock();
228         fStoredObjectMap.clear();
229         storeObject(fRootFolder);
230         unlock();
231     }
232 
233     public long getObjectCount() {
234         return fStoredObjectMap.size();
235     }
236 
237     // /////////////////////////////////////////
238     // private helper methods
239 
240     private void createRootFolder() {
241         FolderImpl rootFolder = new FolderImpl(this);
242         rootFolder.setName("RootFolder");
243         rootFolder.setParent(null);
244         rootFolder.setTypeId(BaseTypeId.CMIS_FOLDER.value());
245         rootFolder.setCreatedBy("Admin");
246         rootFolder.setModifiedBy("Admin");
247         rootFolder.setModifiedAtNow();
248         rootFolder.setRepositoryId(fRepositoryId);
249         rootFolder.setAclId(addAcl(InMemoryAcl.getDefaultAcl()));
250         rootFolder.persist();
251         fRootFolder = rootFolder;
252     }
253 
254     public Document createDocument(String name,
255 			Map<String, PropertyData<?>> propMap, String user, Folder folder,
256 			Acl addACEs, Acl removeACEs)  {
257     	DocumentImpl doc = new DocumentImpl(this);
258         doc.createSystemBasePropertiesWhenCreated(propMap, user);
259         doc.setCustomProperties(propMap);
260         doc.setRepositoryId(fRepositoryId);
261         doc.setName(name);
262         if (null != folder) {
263             ((FolderImpl)folder).addChildDocument(doc); // add document to folder and
264         }
265         int aclId = getAclId(((FolderImpl)folder), addACEs, removeACEs);
266         doc.setAclId(aclId);
267         return doc;
268     }
269 
270     public DocumentVersion createVersionedDocument(String name,
271     		Map<String, PropertyData<?>> propMap, String user, Folder folder,
272 			Acl addACEs, Acl removeACEs, ContentStream contentStream, VersioningState versioningState) {
273     	VersionedDocumentImpl doc = new VersionedDocumentImpl(this);
274         doc.createSystemBasePropertiesWhenCreated(propMap, user);
275         doc.setCustomProperties(propMap);
276         doc.setRepositoryId(fRepositoryId);
277         doc.setName(name);
278         DocumentVersion version = doc.addVersion(contentStream, versioningState, user);
279         if (null != folder) {
280         	((FolderImpl)folder).addChildDocument(doc); // add document to folder and set
281         }
282         version.createSystemBasePropertiesWhenCreated(propMap, user);
283         version.setCustomProperties(propMap);
284         int aclId = getAclId(((FolderImpl)folder), addACEs, removeACEs);
285         doc.setAclId(aclId);
286         doc.persist();
287         return version;
288     }
289 
290     public Folder createFolder(String name,
291 			Map<String, PropertyData<?>> propMap, String user, Folder parent,
292 			Acl addACEs, Acl removeACEs) {
293     	
294     	FolderImpl folder = new FolderImpl(this, name, null);
295         if (null != propMap) {
296         	folder.createSystemBasePropertiesWhenCreated(propMap, user);
297         	folder.setCustomProperties(propMap);
298         }
299         folder.setRepositoryId(fRepositoryId);
300         if (null != parent) {
301         	((FolderImpl)parent).addChildFolder(folder); // add document to folder and set
302         }
303 
304         int aclId = getAclId(((FolderImpl)parent), addACEs, removeACEs);
305         folder.setAclId(aclId);
306         
307         return folder;
308     }
309 
310     public Folder createFolder(String name) {
311   	  Folder folder = new FolderImpl(this, name, null);
312         folder.setRepositoryId(fRepositoryId);
313         return folder;
314 	}
315     
316 	public List<StoredObject> getCheckedOutDocuments(String orderBy,
317 			String user, IncludeRelationships includeRelationships) {
318 	    List<StoredObject> res = new ArrayList<StoredObject>();
319 
320         for (StoredObject so : fStoredObjectMap.values()) {
321             if (so instanceof VersionedDocument) {
322                 VersionedDocument verDoc = (VersionedDocument) so;
323                 if (verDoc.isCheckedOut() && hasReadAccess(user, verDoc)) {
324                     res.add(verDoc.getPwc());
325                 }
326             }
327         }
328 
329         return res;
330     }
331 
332 	public StoredObject createRelationship(StoredObject sourceObject,
333 			StoredObject targetObject, Map<String, PropertyData<?>> propMap,
334 			String user, Acl addACEs, Acl removeACEs) {
335 		// TODO Auto-generated method stub
336 		return null;
337 	}
338 
339     public Acl applyAcl(StoredObject so, Acl addAces, Acl removeAces, AclPropagation aclPropagation, String principalId) {
340         if (aclPropagation==AclPropagation.OBJECTONLY || !(so instanceof Folder)) {
341             return applyAcl(so, addAces, removeAces);
342         } else {
343             return applyAclRecursive(((Folder)so), addAces, removeAces, principalId);            
344         }
345     }
346     
347     public Acl applyAcl(StoredObject so, Acl acl, AclPropagation aclPropagation, String principalId) {
348         if (aclPropagation==AclPropagation.OBJECTONLY || !(so instanceof Folder)) {
349             return applyAcl(so, acl);
350         } else {
351             return applyAclRecursive(((Folder)so), acl, principalId);
352         }
353     }
354 
355     public List<Integer> getAllAclsForUser(String principalId, Permission permission) {
356         List<Integer> acls = new ArrayList<Integer>();
357         for (InMemoryAcl acl: fAcls) {
358             if (acl.hasPermission(principalId, permission))
359                 acls.add(acl.getId());
360         }
361         return acls;
362     }
363     
364     public Acl getAcl(int aclId) {
365         InMemoryAcl acl = getInMemoryAcl(aclId);
366         return acl==null ? InMemoryAcl.getDefaultAcl().toCommonsAcl() : acl.toCommonsAcl();
367     }
368     
369     public int getAclId(StoredObjectImpl so, Acl addACEs, Acl removeACEs) {
370         InMemoryAcl newAcl;
371         boolean removeDefaultAcl = false;
372         int aclId = 0;
373         
374         if (so == null) {
375             newAcl = new InMemoryAcl();
376         } else {
377             aclId = so.getAclId();
378             newAcl = getInMemoryAcl(aclId);
379             if (null == newAcl)
380                 newAcl = new InMemoryAcl();
381             else
382                 // copy list so that we can safely change it without effecting the original
383                 newAcl = new InMemoryAcl(newAcl.getAces()); 
384         }
385 
386         if (newAcl.size() == 0 && addACEs == null && removeACEs == null)
387             return 0;
388 
389         if (null != removeACEs)
390             for (Ace ace: removeACEs.getAces()) {
391             InMemoryAce inMemAce = new InMemoryAce(ace);
392             if (inMemAce.equals(InMemoryAce.getDefaultAce()))
393                 removeDefaultAcl = true;
394             }
395         
396         if ( so!= null && 0 == aclId  && !removeDefaultAcl)
397             return 0; // if object grants full access to everyone and it will not be removed we do nothing
398 
399         // add ACEs
400         if (null != addACEs)
401             for (Ace ace: addACEs.getAces()) {
402                 InMemoryAce inMemAce = new InMemoryAce(ace);
403                 if (inMemAce.equals(InMemoryAce.getDefaultAce()))
404                     return 0; // if everyone has full access there is no need to add additional ACLs.
405                 newAcl.addAce(inMemAce);
406             }
407         
408         // remove ACEs
409         if (null != removeACEs)
410             for (Ace ace: removeACEs.getAces()) {
411                 InMemoryAce inMemAce = new InMemoryAce(ace);
412                 newAcl.removeAce(inMemAce);
413             }
414 
415         if (newAcl.size() > 0)
416             return addAcl(newAcl);
417         else
418             return 0;
419     }
420     
421     private void deleteFolder(String folderId, String user) {
422         StoredObject folder = fStoredObjectMap.get(folderId);
423         if (folder == null) {
424             throw new CmisInvalidArgumentException("Unknown object with id:  " + folderId);
425         }
426 
427         if (!(folder instanceof FolderImpl)) {
428             throw new CmisInvalidArgumentException("Cannot delete folder with id:  " + folderId
429                     + ". Object exists but is not a folder.");
430         }
431 
432         // check if children exist
433         List<StoredObject> children = ((Folder) folder).getChildren(-1, -1, user);
434         if (children != null && !children.isEmpty()) {
435             throw new CmisConstraintException("Cannot delete folder with id:  " + folderId + ". Folder is not empty.");
436         }
437 
438         fStoredObjectMap.remove(folderId);
439     }
440 
441     public boolean hasReadAccess(String principalId, StoredObject so) {       
442         return hasAccess(principalId, so, Permission.READ);
443     }
444 
445     
446     public boolean hasWriteAccess(String principalId, StoredObject so) {       
447         return hasAccess(principalId, so, Permission.WRITE);
448     }
449 
450     
451     public boolean hasAllAccess(String principalId, StoredObject so) {       
452         return hasAccess(principalId, so, Permission.ALL);
453     }
454     
455 
456     public void checkReadAccess(String principalId, StoredObject so) {
457         checkAccess(principalId, so, Permission.READ);
458     }
459     
460     public void checkWriteAccess(String principalId, StoredObject so) {
461         checkAccess(principalId, so, Permission.WRITE);
462     }
463     
464     public void checkAllAccess(String principalId, StoredObject so) {
465         checkAccess(principalId, so, Permission.ALL);
466     }
467  
468     private void checkAccess(String principalId, StoredObject so, Permission permission) {
469         if (!hasAccess(principalId, so, permission))
470             throw new CmisPermissionDeniedException("Object with id " + so.getId() + " and name " + so.getName()
471                     + " does not grant " + permission.toString() + " access to principal " + principalId);
472     }
473 
474     private boolean hasAccess(String principalId, StoredObject so, Permission permission) {
475         if (null != principalId && principalId.equals(ADMIN_PRINCIPAL_ID))
476             return true;
477         List<Integer> aclIds = getAllAclsForUser(principalId, permission);        
478         return aclIds.contains(((StoredObjectImpl)so).getAclId());
479     }
480 
481     private InMemoryAcl getInMemoryAcl(int aclId) {
482         
483         for (InMemoryAcl acl : fAcls) {
484             if (aclId == acl.getId())
485                 return acl;
486         }
487         return null;
488     }
489 
490     private int setAcl(StoredObjectImpl so, Acl acl) {
491         int aclId;
492         if (null == acl || acl.getAces().isEmpty())
493             aclId = 0;
494         else {
495             aclId = getAclId(null, acl, null);
496         }
497         so.setAclId(aclId);
498         return aclId;
499     }
500     
501 	/**
502 	 * check if an Acl is already known
503 	 * @param acl
504 	 *     acl to be checked
505 	 * @return
506 	 *     0 if Acl is not known, id of Acl otherwise
507 	 */
508 	private int hasAcl(InMemoryAcl acl) {
509 	    for (InMemoryAcl acl2: fAcls) {
510 	        if (acl2.equals(acl))
511 	            return acl2.getId();
512 	    }
513 	    return -1;
514 	}
515 
516     private int addAcl(InMemoryAcl acl) {
517         int aclId = -1;
518         
519         if (null == acl)
520             return 0;
521         
522         lock();
523         try {
524             aclId = hasAcl(acl);
525             if (aclId < 0) {
526                 aclId = getNextAclId();
527                 acl.setId(aclId);
528                 fAcls.add(acl);
529             }
530         } finally {
531             unlock();
532         }
533         return aclId;
534     }
535     
536     private Acl applyAcl(StoredObject so, Acl acl) {
537         int aclId = setAcl((StoredObjectImpl) so, acl);
538         return getAcl(aclId);
539     }
540 
541     private Acl applyAcl(StoredObject so, Acl addAces, Acl removeAces) {
542         int aclId = getAclId((StoredObjectImpl) so, addAces, removeAces);
543         ((StoredObjectImpl) so).setAclId(aclId);
544         return getAcl(aclId);
545     }
546 
547     private Acl applyAclRecursive(Folder folder, Acl addAces, Acl removeAces, String principalId) {
548         List<StoredObject> children = folder.getChildren(-1, -1, ADMIN_PRINCIPAL_ID);
549         
550         Acl result = applyAcl(folder, addAces, removeAces);  
551 
552         if (null == children) {
553             return result;
554         }
555         
556         for (StoredObject child : children) {
557             if (hasAllAccess(principalId, child)) {
558                 if (child instanceof Folder) {
559                     applyAclRecursive((Folder) child, addAces, removeAces, principalId);                
560                 } else {
561                     applyAcl(child, addAces, removeAces);               
562                 }
563             }
564         }
565         
566         return result;
567     }
568     
569     private Acl applyAclRecursive(Folder folder, Acl acl, String principalId) {
570         List<StoredObject> children = folder.getChildren(-1, -1, ADMIN_PRINCIPAL_ID);
571 
572         Acl result = applyAcl(folder, acl);  
573 
574         if (null == children) {
575             return result;
576         }
577 
578         for (StoredObject child : children) {
579             if (hasAllAccess(principalId, child)) {
580                 if (child instanceof Folder) {
581                     applyAclRecursive((Folder) child, acl, principalId);                
582                 } else {
583                     applyAcl(child, acl);               
584                 }
585             }
586         }
587         
588         return result;
589     }
590 
591     public boolean isTypeInUse(String typeId) {
592         // iterate over all the objects and check for each if the type matches
593         for (String objectId : getIds()) {
594             StoredObject so = getObjectById(objectId);
595             if (so.getTypeId().equals(typeId))
596                 return true;
597         }
598         return false;
599     }
600 
601 }