/******************************************************************************* * Copyright (c) 2013 aegif. * * This file is part of NemakiWare. * * NemakiWare is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NemakiWare is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public Licensealong with NemakiWare. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * linzhixing(https://github.com/linzhixing) - initial API and implementation ******************************************************************************/ package jp.aegif.nemaki.cmis.service.impl; import java.util.List; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import jp.aegif.nemaki.businesslogic.ContentService; import jp.aegif.nemaki.cmis.aspect.CompileService; import jp.aegif.nemaki.cmis.aspect.ExceptionService; import jp.aegif.nemaki.cmis.aspect.type.TypeManager; import jp.aegif.nemaki.cmis.factory.info.RepositoryInfo; import jp.aegif.nemaki.cmis.factory.info.RepositoryInfoMap; import jp.aegif.nemaki.cmis.service.AclService; import jp.aegif.nemaki.model.Content; import jp.aegif.nemaki.util.cache.NemakiCachePool; import jp.aegif.nemaki.util.constant.DomainType; import jp.aegif.nemaki.util.constant.PrincipalId; import jp.aegif.nemaki.util.lock.ThreadLockService; import org.apache.chemistry.opencmis.commons.data.Ace; import org.apache.chemistry.opencmis.commons.data.Acl; import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement; import org.apache.chemistry.opencmis.commons.data.PermissionMapping; import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition; import org.apache.chemistry.opencmis.commons.enums.AclPropagation; import org.apache.chemistry.opencmis.commons.enums.ChangeType; import org.apache.chemistry.opencmis.commons.server.CallContext; import org.apache.commons.collections.CollectionUtils; /** * Discovery Service implementation for CouchDB. * */ public class AclServiceImpl implements AclService { private ContentService contentService; private CompileService compileService; private ExceptionService exceptionService; private TypeManager typeManager; private ThreadLockService threadLockService; private NemakiCachePool nemakiCachePool; private RepositoryInfoMap repositoryInfoMap; @Override public Acl getAcl(CallContext callContext, String repositoryId, String objectId, Boolean onlyBasicPermissions) { exceptionService.invalidArgumentRequired("objectId", objectId); Lock lock = threadLockService.getReadLock(repositoryId, objectId); try{ lock.lock(); // ////////////////// // General Exception // ////////////////// Content content = contentService.getContent(repositoryId, objectId); exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); exceptionService.permissionDenied(callContext,repositoryId, PermissionMapping.CAN_GET_ACL_OBJECT, content); // ////////////////// // Body of the method // ////////////////// jp.aegif.nemaki.model.Acl acl = contentService.calculateAcl(repositoryId, content); //return compileService.compileAcl(acl, content.isAclInherited(), onlyBasicPermissions); return compileService.compileAcl(acl, contentService.getAclInheritedWithDefault(repositoryId, content), onlyBasicPermissions); }finally{ lock.unlock(); } } @Override public Acl applyAcl(CallContext callContext, String repositoryId, String objectId, Acl acl, AclPropagation aclPropagation) { exceptionService.invalidArgumentRequired("objectId", objectId); Lock lock = threadLockService.getReadLock(repositoryId, objectId); try{ lock.lock(); // ////////////////// // General Exception // ////////////////// Content content = contentService.getContent(repositoryId, objectId); exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); exceptionService.permissionDenied(callContext,repositoryId, PermissionMapping.CAN_APPLY_ACL_OBJECT, content); // ////////////////// // Specific Exception // ////////////////// TypeDefinition td = typeManager.getTypeDefinition(repositoryId, content); if(!td.isControllableAcl()) exceptionService.constraint(objectId, "applyAcl cannot be performed on the object whose controllableAcl = false"); exceptionService.constraintAclPropagationDoesNotMatch(aclPropagation); exceptionService.constraintPermissionDefined(repositoryId, acl, objectId); // ////////////////// // Body of the method // ////////////////// //Check ACL inheritance boolean inherited = true; //Inheritance defaults to true if nothing input List<CmisExtensionElement> exts = acl.getExtensions(); if(!CollectionUtils.isEmpty(exts)){ for(CmisExtensionElement ext : exts){ if(ext.getName().equals("inherited")){ inherited = Boolean.valueOf(ext.getValue()); } } if(!contentService.getAclInheritedWithDefault(repositoryId, content).equals(inherited)) content.setAclInherited(inherited); } jp.aegif.nemaki.model.Acl nemakiAcl = new jp.aegif.nemaki.model.Acl(); //REPOSITORYDETERMINED or PROPAGATE is considered as PROPAGATE boolean objectOnly = (aclPropagation == AclPropagation.OBJECTONLY)? true : false; for(Ace ace : acl.getAces()){ if(ace.isDirect()){ jp.aegif.nemaki.model.Ace nemakiAce = new jp.aegif.nemaki.model.Ace(ace.getPrincipalId(), ace.getPermissions(), objectOnly); nemakiAcl.getLocalAces().add(nemakiAce); } } convertSystemPrinciaplId(repositoryId, nemakiAcl); content.setAcl(nemakiAcl); contentService.update(repositoryId, content); contentService.writeChangeEvent(callContext, repositoryId, content, nemakiAcl, ChangeType.SECURITY ); nemakiCachePool.get(repositoryId).removeCmisCache(objectId); clearCachesRecursively(Executors.newCachedThreadPool(), callContext, repositoryId, content, false); writeChangeEventsRecursively(Executors.newCachedThreadPool(), callContext, repositoryId, content, false); return getAcl(callContext, repositoryId, objectId, false); }finally{ lock.unlock(); } } private void clearCachesRecursively(ExecutorService executorService, CallContext callContext, final String repositoryId, Content content, boolean executeOnParent){ //Call threads for recursive applyAcl if(content.isFolder()){ List<Content> children = contentService.getChildren(repositoryId, content.getId()); if(CollectionUtils.isEmpty(children)){ return; } if(executeOnParent){ executorService.submit(new ClearCacheTask(repositoryId, content.getId())); } for(Content child : children){ if(contentService.getAclInheritedWithDefault(repositoryId, child)){ executorService.submit(new ClearCachesRecursivelyTask(executorService, callContext, repositoryId, child)); } } }else{ executorService.submit(new ClearCacheTask(repositoryId, content.getId())); } } private class ClearCacheTask implements Runnable{ private String repositoryId; private String objectId; public ClearCacheTask(String repositoryId, String objectId) { super(); this.repositoryId = repositoryId; this.objectId = objectId; } @Override public void run() { // TODO Auto-generated method stub nemakiCachePool.get(repositoryId).removeCmisAndContentCache(objectId); } } private class ClearCachesRecursivelyTask implements Runnable{ private ExecutorService executorService; private CallContext callContext; private String repositoryId; private Content content; public ClearCachesRecursivelyTask(ExecutorService executorService, CallContext callContext, String repositoryId, Content content) { super(); this.executorService = executorService; this.callContext = callContext; this.repositoryId = repositoryId; this.content = content; } @Override public void run() { clearCachesRecursively(executorService, callContext, repositoryId, content, true); } } private void writeChangeEventsRecursively(ExecutorService executorService, CallContext callContext, final String repositoryId, Content content, boolean executeOnParent){ //Call threads for recursive applyAcl if(content.isFolder()){ List<Content> children = contentService.getChildren(repositoryId, content.getId()); if(CollectionUtils.isEmpty(children)){ return; } if(executeOnParent){ executorService.submit(new ClearCacheTask(repositoryId, content.getId())); } for(Content child : children){ if(contentService.getAclInheritedWithDefault(repositoryId, child)){ executorService.submit(new WriteChangeEventsRecursivelyTask(executorService, callContext, repositoryId, child)); } } }else{ executorService.submit(new WriteChangeEventTask(callContext, repositoryId, content)); } } private class WriteChangeEventTask implements Runnable{ private CallContext callContext; private String repositoryId; private Content content; public WriteChangeEventTask(CallContext callContext, String repositoryId, Content content) { super(); this.callContext = callContext; this.repositoryId = repositoryId; this.content = content; } @Override public void run() { // TODO content.getAcl()? content.calculateAcl()? contentService.writeChangeEvent(callContext, repositoryId, content, content.getAcl(), ChangeType.SECURITY); } } private class WriteChangeEventsRecursivelyTask implements Runnable{ private ExecutorService executorService; private CallContext callContext; private String repositoryId; private Content content; public WriteChangeEventsRecursivelyTask(ExecutorService executorService, CallContext callContext, String repositoryId, Content content) { super(); this.executorService = executorService; this.callContext = callContext; this.repositoryId = repositoryId; this.content = content; } @Override public void run() { writeChangeEventsRecursively(executorService, callContext, repositoryId, content, true); } } private void convertSystemPrinciaplId(String repositoryId, jp.aegif.nemaki.model.Acl acl){ List<jp.aegif.nemaki.model.Ace> aces = acl.getAllAces(); for (jp.aegif.nemaki.model.Ace ace : aces) { RepositoryInfo info = repositoryInfoMap.get(repositoryId); //Convert anonymous to the form of database String anonymous = info.getPrincipalIdAnonymous(); if (anonymous.equals(ace.getPrincipalId())) { ace.setPrincipalId(PrincipalId.ANONYMOUS_IN_DB); } //Convert anyone to the form of database String anyone = info.getPrincipalIdAnyone(); if (anyone.equals(ace.getPrincipalId())) { ace.setPrincipalId(PrincipalId.ANYONE_IN_DB); } } } public void setContentService(ContentService contentService) { this.contentService = contentService; } public void setExceptionService(ExceptionService exceptionService) { this.exceptionService = exceptionService; } public void setTypeManager(TypeManager typeManager) { this.typeManager = typeManager; } public void setThreadLockService(ThreadLockService threadLockService) { this.threadLockService = threadLockService; } public void setNemakiCachePool(NemakiCachePool nemakiCachePool) { this.nemakiCachePool = nemakiCachePool; } public void setCompileService(CompileService compileService) { this.compileService = compileService; } public void setRepositoryInfoMap(RepositoryInfoMap repositoryInfoMap) { this.repositoryInfoMap = repositoryInfoMap; } }