/*******************************************************************************
* 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;
}
}