content: /*******************************************************************************
   * 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 License along 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 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.PermissionService;
  import jp.aegif.nemaki.cmis.service.NavigationService;
  import jp.aegif.nemaki.model.Content;
  import jp.aegif.nemaki.model.Document;
  import jp.aegif.nemaki.model.Folder;
  import jp.aegif.nemaki.util.DataUtil;
  import jp.aegif.nemaki.util.constant.DomainType;
  import jp.aegif.nemaki.util.lock.ThreadLockService;
  import org.apache.chemistry.opencmis.commons.PropertyIds;
  import org.apache.chemistry.opencmis.commons.data.*;
  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderContainerImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderDataImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderListImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectParentDataImpl;
  import org.apache.chemistry.opencmis.commons.server.CallContext;
  import org.apache.chemistry.opencmis.commons.spi.Holder;
  import org.apache.commons.collections4.CollectionUtils;
  import org.apache.commons.lang3.StringUtils;
  
  import java.math.BigInteger;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.concurrent.locks.Lock;
  
  public class NavigationServiceImpl implements NavigationService {
  
  	private ContentService contentService;
  	private ExceptionService exceptionService;
  	private CompileService compileService;
  	private PermissionService permissionService;
  	private ThreadLockService threadLockService;
  
  	@Override
  	public ObjectInFolderList getChildren(CallContext callContext,
  			String repositoryId, String folderId, String filter,
  			String orderBy,
  			Boolean includeAllowableActions, IncludeRelationships includeRelationships,
  			String renditionFilter, Boolean includePathSegments,
  			BigInteger maxItems, BigInteger skipCount,
  			Holder<ObjectData> parentObjectData, ExtensionsData extension) {
  
  		exceptionService.invalidArgumentRequiredString("folderId", folderId);
  		
  		Lock parentLock = threadLockService.getReadLock(repositoryId, folderId);
  		
  		try{
  			parentLock.lock();
  			
  			// //////////////////
  			// General Exception
  			// //////////////////
  			Folder folder = contentService.getFolder(repositoryId, folderId);
  			exceptionService.invalidArgumentFolderId(folder, folderId);
  			exceptionService.permissionDenied(callContext,
  					repositoryId, PermissionMapping.CAN_GET_CHILDREN_FOLDER, folder);
  
  			// //////////////////
  			// Body of the method
  			// //////////////////
  			// Set ObjectData of parent folder for ObjectInfo
  			ObjectData _parent = compileService.compileObjectData(
  					callContext, repositoryId, folder, filter,
  					includeAllowableActions, includeRelationships, renditionFilter, false);
  			parentObjectData.setValue(_parent);
  
  			return getChildrenInternal(callContext, repositoryId, folderId, filter,
  					orderBy, includeAllowableActions, includeRelationships,
  					renditionFilter, includePathSegments, maxItems, skipCount, false);
  		}finally{
  			parentLock.unlock();
  		}
  	}
  
  	private ObjectInFolderList getChildrenInternal(CallContext callContext,
  			String repositoryId, String folderId, String filter,
  			String orderBy,
  			Boolean includeAllowableActions, IncludeRelationships includeRelationships,
  			String renditionFilter, Boolean includePathSegments,
  			BigInteger maxItems, BigInteger skipCount, boolean folderOnly) {
  
  		// Prepare
  		ObjectInFolderListImpl result = new ObjectInFolderListImpl();
  		result.setObjects(new ArrayList<ObjectInFolderData>());
  		result.setHasMoreItems(false);
  
  		// Build ObjectList
  		List<Content> contents = contentService.getChildren(repositoryId, folderId);
  		
  		
  		List<Lock> locks = threadLockService.readLocks(repositoryId, contents);
  		
  		try{
  			threadLockService.bulkLock(locks);
  			
  			contents = permissionService.getFiltered(callContext, repositoryId, contents);
  
  			ObjectList ol = compileService.compileObjectDataList(callContext,
  					repositoryId, contents, filter,
  					includeAllowableActions, includeRelationships, renditionFilter, false,
  					maxItems, skipCount, folderOnly, orderBy);
  			
  			
  			// Build ObjectInFolderList
  			for (ObjectData od : ol.getObjects()) {
  				ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl();
  				objectInFolder.setObject(od);
  				if (includePathSegments) {
  					String name = DataUtil.getStringProperty(od.getProperties(),
  							PropertyIds.NAME);
  					objectInFolder.setPathSegment(name);
  				}
  				result.getObjects().add(objectInFolder);
  			}
  			result.setNumItems(ol.getNumItems());
  			result.setHasMoreItems(ol.hasMoreItems());
  
  			return result;
  		}finally{
  			threadLockService.bulkUnlock(locks);
  		}
  	}
  	
  	@Override
  	public List<ObjectInFolderContainer> getDescendants(
  			CallContext callContext, String repositoryId, String folderId,
  			BigInteger depth, String filter,
  			Boolean includeAllowableActions, IncludeRelationships includeRelationships,
  			String renditionFilter, Boolean includePathSegment,
  			boolean foldersOnly, Holder<ObjectData> anscestorObjectData, ExtensionsData extension) {
  
  		exceptionService.invalidArgumentRequiredString("folderId", folderId);
  		
  		Lock parentLock = threadLockService.getReadLock(repositoryId, folderId);
  		
  		try{
  			parentLock.lock();
  			
  			// //////////////////
  			// General Exception
  			// //////////////////
  			Folder folder = contentService.getFolder(repositoryId, folderId);
  			exceptionService.permissionDenied(callContext,
  					repositoryId, PermissionMapping.CAN_GET_DESCENDENTS_FOLDER, folder);
  
  			// //////////////////
  			// Specific Exception
  			// //////////////////
  			exceptionService.invalidArgumentFolderId(folder, folderId);
  			exceptionService.invalidArgumentDepth(depth);
  
  			// //////////////////
  			// Body of the method
  			// //////////////////
  			// check depth
  			int d = (depth == null ? 2 : depth.intValue());
  
  			// set defaults if values not set
  			boolean iaa = (includeAllowableActions == null ? false
  					: includeAllowableActions.booleanValue());
  			boolean ips = (includePathSegment == null ? false : includePathSegment
  					.booleanValue());
  
  			// Set ObjectData of the starting folder for ObjectInfo
  			ObjectData _folder = compileService.compileObjectData(
  					callContext, repositoryId, folder, filter,
  					includeAllowableActions, includeRelationships, renditionFilter, false);
  			anscestorObjectData.setValue(_folder);
  
  			// get the tree.
  			return getDescendantsInternal(callContext, repositoryId, _folder, filter, iaa,
  					false, includeRelationships, null, ips, 0, d, foldersOnly);
  			
  		}finally{
  			parentLock.unlock();
  		}
  	}
  
  	private List<ObjectInFolderContainer> getDescendantsInternal(
  			CallContext callContext, String repositoryId, ObjectData node,
  			String filter, Boolean includeAllowableActions,
  			Boolean includeAcl, IncludeRelationships includeRelationships,
  			String renditionFilter, Boolean includePathSegments, int level,
  			int maxLevels, boolean folderOnly) {
  
  		List<ObjectInFolderContainer> childrenOfFolder = new ArrayList<ObjectInFolderContainer>();
  		// Check specified folderId is folder(if not, it's a leaf node)
  		if (node.getBaseTypeId() != BaseTypeId.CMIS_FOLDER) {
  			return childrenOfFolder;
  		}
  
  		String folderId = node.getId();
  		if (maxLevels == -1 || level < maxLevels) {
  			ObjectInFolderList children = getChildrenInternal(callContext,
  					repositoryId, folderId, filter, null,
  					includeAllowableActions, includeRelationships, renditionFilter,
  					includePathSegments,
  					BigInteger.valueOf(Integer.MAX_VALUE), BigInteger.valueOf(0), folderOnly);
  
  			childrenOfFolder = new ArrayList<ObjectInFolderContainer>();
  			if (null != children
  					&& CollectionUtils.isNotEmpty(children.getObjects())) {
  				for (ObjectInFolderData child : children.getObjects()) {
  					ObjectInFolderContainerImpl oifc = new ObjectInFolderContainerImpl();
  					List<ObjectInFolderContainer> subChildren = getDescendantsInternal(
  							callContext, repositoryId, child.getObject(),
  							filter, includeAllowableActions,
  							includeAcl, includeRelationships,
  							renditionFilter, includePathSegments, level + 1,
  							maxLevels, folderOnly);
  
  					oifc.setObject(child);
  					if (CollectionUtils.isNotEmpty(subChildren))
  						oifc.setChildren(subChildren);
  					childrenOfFolder.add(oifc);
  				}
  			}
  		}
  		return childrenOfFolder;
  	}
  
  	@Override
  	public ObjectData getFolderParent(CallContext callContext, String repositoryId,
  			String folderId, String filter, ExtensionsData extension) {
  		
  		exceptionService.invalidArgumentRequiredString("folderId", folderId);
  		
  		Lock childLock = threadLockService.getReadLock(repositoryId, folderId);
  		
  		try{
  			childLock.lock();
  
  			// //////////////////
  			// General Exception
  			// //////////////////
  			Folder folder = (Folder) contentService.getContent(repositoryId, folderId);
  			exceptionService.objectNotFound(DomainType.OBJECT, folder, folderId);
  			exceptionService.permissionDenied(callContext,
  					repositoryId, PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT, folder);
  
  			// //////////////////
  			// Specific Exception
  			// //////////////////
  			Folder parent = contentService.getParent(repositoryId, folderId);
  			
  			Lock parentLock = threadLockService.getReadLock(repositoryId, parent.getId());
  			try{
  				parentLock.lock();
  				
  				exceptionService.objectNotFoundParentFolder(repositoryId, folderId, parent);
  				exceptionService.invalidArgumentRootFolder(repositoryId, folder);
  
  				// //////////////////
  				// Body of the method
  				// //////////////////
  				return compileService.compileObjectData(callContext, repositoryId,
  						parent, filter, true, IncludeRelationships.NONE, null, true);
  				
  			}finally{
  				parentLock.unlock();
  			}
  		}finally{
  			childLock.unlock();
  		}
  	}
  
  	@Override
  	public List<ObjectParentData> getObjectParents(CallContext callContext,
  			String repositoryId, String objectId, String filter,
  			Boolean includeAllowableActions, IncludeRelationships includeRelationships,
  			String renditionFilter, Boolean includeRelativePathSegment, ExtensionsData extension) {
  
  		exceptionService.invalidArgumentRequired("objectId", objectId);
  		
  		Lock childLock = threadLockService.getReadLock(repositoryId, objectId);
  		
  		try{
  			childLock.lock();
  			
  			// //////////////////
  			// General Exception
  			// //////////////////
  			Content content = contentService.getContent(repositoryId, objectId);
  			exceptionService.objectNotFound(DomainType.OBJECT, content, objectId);
  			exceptionService.permissionDenied(callContext,
  					repositoryId, PermissionMapping.CAN_GET_PARENTS_FOLDER, content);
  
  
  			//Get parent
  			Folder parent = contentService.getParent(repositoryId, objectId);
  			Lock parentLock = threadLockService.getReadLock(repositoryId, parent.getId());
  
  			try{
  				parentLock.lock();
  				
  				// //////////////////
  				// Specific Exception
  				// //////////////////
  				exceptionService.objectNotFoundParentFolder(repositoryId, objectId, parent);
  				exceptionService.invalidArgumentRootFolder(repositoryId, content);
  
  				// //////////////////
  				// Body of the method
  				// //////////////////
  				ObjectParentDataImpl result = new ObjectParentDataImpl();
  				ObjectData o = compileService.compileObjectData(callContext,
  						repositoryId, parent, filter, includeAllowableActions,
  						includeRelationships, null, true);
  				result.setObject(o);
  				boolean irps = (includeRelativePathSegment == null ? false
  						: includeRelativePathSegment.booleanValue());
  				if (irps) {
  					result.setRelativePathSegment(content.getName());
  				}
  
  				return Collections.singletonList((ObjectParentData) result);
  				
  			}finally{
  				parentLock.unlock();
  			}
  			
  		}finally{
  			childLock.unlock();
  		}
  	}
  
  	@Override
  	public ObjectList getCheckedOutDocs(CallContext callContext,
  			String repositoryId, String folderId, String filter,
  			String orderBy,
  			Boolean includeAllowableActions, IncludeRelationships includeRelationships,
  			String renditionFilter, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
  		// //////////////////
  		// General Exception
  		// //////////////////
  		// NONE
  
  		// //////////////////
  		// Specific Exception
  		// //////////////////
  		//Folder ID can be null, which means all PWCs are returned.
  		if(StringUtils.isNotBlank(folderId)){
  			Folder folder = contentService.getFolder(repositoryId, folderId);
  			exceptionService.objectNotFoundParentFolder(repositoryId, folderId, folder);
  		}
  		exceptionService.invalidArgumentOrderBy(repositoryId, orderBy);
  
  		// //////////////////
  		// Body of the method
  		// //////////////////
  		//Folder ID can be null, which means all PWCs are returned.
  		List<Document> checkedOuts = contentService.getCheckedOutDocs(repositoryId,
  				folderId, orderBy, extension);
  		
  		List<Lock> locks = threadLockService.readLocks(repositoryId, checkedOuts);
  		
  		try{
  			threadLockService.bulkLock(locks);
  			
  			ObjectList list = compileService.compileObjectDataList(
  					callContext, repositoryId, checkedOuts, filter,
  					includeAllowableActions, includeRelationships, renditionFilter, false,
  					maxItems, skipCount, false, orderBy);
  
  			return list;
  			
  		}finally{
  			threadLockService.bulkUnlock(locks);
  		}
  	}
  
  	public void setContentService(ContentService contentService) {
  		this.contentService = contentService;
  	}
  
  	public void setExceptionService(ExceptionService exceptionService) {
  		this.exceptionService = exceptionService;
  	}
  
  	public void setCompileService(CompileService compileService) {
  		this.compileService = compileService;
  	}
  
  	public void setPermissionService(PermissionService permissionService) {
  		this.permissionService = permissionService;
  	}
  
  	public void setThreadLockService(ThreadLockService threadLockService) {
  		this.threadLockService = threadLockService;
  	}
  }
  