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.aspect.impl;
  
  import java.math.BigInteger;
  import java.text.MessageFormat;
  import java.text.ParseException;
  import java.util.ArrayList;
  import java.util.GregorianCalendar;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.Set;
  import java.util.stream.Stream;
  
  import org.apache.chemistry.opencmis.commons.PropertyIds;
  import org.apache.chemistry.opencmis.commons.data.AllowableActions;
  import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement;
  import org.apache.chemistry.opencmis.commons.data.ObjectData;
  import org.apache.chemistry.opencmis.commons.data.ObjectList;
  import org.apache.chemistry.opencmis.commons.data.PermissionMapping;
  import org.apache.chemistry.opencmis.commons.data.Principal;
  import org.apache.chemistry.opencmis.commons.data.Properties;
  import org.apache.chemistry.opencmis.commons.data.PropertyData;
  import org.apache.chemistry.opencmis.commons.data.RenditionData;
  import org.apache.chemistry.opencmis.commons.data.RepositoryCapabilities;
  import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition;
  import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
  import org.apache.chemistry.opencmis.commons.definitions.SecondaryTypeDefinition;
  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
  import org.apache.chemistry.opencmis.commons.enums.Action;
  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
  import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions;
  import org.apache.chemistry.opencmis.commons.enums.ChangeType;
  import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed;
  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
  import org.apache.chemistry.opencmis.commons.enums.PropertyType;
  import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyData;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.AllowableActionsImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ChangeEventInfoDataImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.CmisExtensionElementImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PolicyIdListImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
  import org.apache.chemistry.opencmis.commons.impl.dataobjects.RenditionDataImpl;
  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 org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  import com.rits.cloning.Cloner;
  
  import jp.aegif.nemaki.businesslogic.ContentService;
  import jp.aegif.nemaki.cmis.aspect.CompileService;
  import jp.aegif.nemaki.cmis.aspect.PermissionService;
  import jp.aegif.nemaki.cmis.aspect.SortUtil;
  import jp.aegif.nemaki.cmis.aspect.type.TypeManager;
  import jp.aegif.nemaki.cmis.factory.info.AclCapabilities;
  import jp.aegif.nemaki.cmis.factory.info.RepositoryInfoMap;
  import jp.aegif.nemaki.cmis.service.RepositoryService;
  import jp.aegif.nemaki.model.Ace;
  import jp.aegif.nemaki.model.Acl;
  import jp.aegif.nemaki.model.Aspect;
  import jp.aegif.nemaki.model.AttachmentNode;
  import jp.aegif.nemaki.model.Change;
  import jp.aegif.nemaki.model.Content;
  import jp.aegif.nemaki.model.Document;
  import jp.aegif.nemaki.model.Folder;
  import jp.aegif.nemaki.model.Item;
  import jp.aegif.nemaki.model.Policy;
  import jp.aegif.nemaki.model.Property;
  import jp.aegif.nemaki.model.Relationship;
  import jp.aegif.nemaki.model.Rendition;
  import jp.aegif.nemaki.model.VersionSeries;
  import jp.aegif.nemaki.util.DataUtil;
  import jp.aegif.nemaki.util.PropertyManager;
  import jp.aegif.nemaki.util.cache.NemakiCachePool;
  import jp.aegif.nemaki.util.constant.CmisExtensionToken;
  import jp.aegif.nemaki.util.constant.PropertyKey;
  import net.sf.ehcache.Element;
  
  public class CompileServiceImpl implements CompileService {
  
  	private static final Log log = LogFactory.getLog(CompileServiceImpl.class);
  
  	private RepositoryInfoMap repositoryInfoMap;
  	private RepositoryService repositoryService;
  	private ContentService contentService;
  	private PermissionService permissionService;
  	private TypeManager typeManager;
  	private AclCapabilities aclCapabilities;
  	private NemakiCachePool nemakiCachePool;
  	private SortUtil sortUtil;
  	private PropertyManager propertyManager;
  
  	private boolean includeRelationshipsEnabled() {
  		return propertyManager.readBoolean(PropertyKey.CAPABILITY_EXTENDED_INCLUDE_RELATIONSHIPS);
  	}
  
  	/**
  	 * Builds a CMIS ObjectData from the given CouchDB content.
  	 */
  	@Override
  	public ObjectData compileObjectData(CallContext callContext, String repositoryId, Content content, String filter,
  			Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
  			Boolean includeAcl) {
  
  		ObjectDataImpl objData = getRawObjectData(callContext, repositoryId, content, filter, includeAllowableActions,
  				includeRelationships, renditionFilter, includeAcl);
  		ObjectData result = filterObjectData(repositoryId, objData, filter, includeAllowableActions,
  				includeRelationships, renditionFilter, includeAcl);
  		return result;
  	}
  
  	private ObjectDataImpl getRawObjectData(CallContext callContext, String repositoryId, Content content,
  			String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
  			String renditionFilter, Boolean includeAcl) {
  		ObjectDataImpl rawObjectData;
  		ObjectData cachedObjectData = nemakiCachePool.get(repositoryId).getObjectDataCache().get(content.getId());
  		if (cachedObjectData == null) {
  			rawObjectData = compileObjectDataWithFullAttributes(callContext, repositoryId, content, filter,
  					includeAllowableActions, includeRelationships, renditionFilter, includeAcl);
  		} else {
  			rawObjectData = (ObjectDataImpl) cachedObjectData;
  
  			// Recalcurate
  			setAclAndAllowableActionsInternal(rawObjectData, callContext, repositoryId, content,
  					includeAllowableActions, includeAcl);
  			setRelationshipInternal(rawObjectData, callContext, repositoryId, content, includeRelationships);
  		}
  		return rawObjectData;
  	}
  
  	private ObjectDataImpl compileObjectDataWithFullAttributes(CallContext callContext, String repositoryId,
  			Content content, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
  			String renditionFilter, Boolean includeAcl) {
  
  		log.info(MessageFormat.format("CompileService#compileObjectDataWithFullAttributes START : Repo={0}, Id={1}", repositoryId, content.getId()));
  
  
  		ObjectDataImpl result = new ObjectDataImpl();
  
  		// Filter(any property filter MUST be done here
  		// TODO filtering ? (for performance)
  		PropertiesImpl properties = compileProperties(callContext, repositoryId, content);
  		result.setProperties(properties);
  
  		log.info(MessageFormat.format("CompileService#compileObjectDataWithFullAttributes compileProperties success : Repo={0}, Id={1}", repositoryId, content.getId()));
  
  		// Set acl and allowable actions
  		setAclAndAllowableActionsInternal(result, callContext, repositoryId, content, includeAllowableActions,
  				includeAcl);
  
  		log.info(MessageFormat.format("CompileService#compileObjectDataWithFullAttributes setAclAndAllowableActionsInternal success : Repo={0}, Id={1}", repositoryId, content.getId()));
  
  
  		// Set relationship
  		setRelationshipInternal(result, callContext, repositoryId, content, includeRelationships);
  
  		log.info(MessageFormat.format("CompileService#compileObjectDataWithFullAttributes setRelationshipInternal success : Repo={0}, Id={1}", repositoryId, content.getId()));
  
  
  		// Set Renditions
  		if (content.isDocument()) {
  			result.setRenditions(compileRenditions(callContext, repositoryId, content));
  		}
  		log.info(MessageFormat.format("CompileService#compileObjectDataWithFullAttributes setRenditions if document success : Repo={0}, Id={1}", repositoryId, content.getId()));
  
  		nemakiCachePool.get(repositoryId).getObjectDataCache().put(new Element(content.getId(), result));
  
  		log.info(MessageFormat.format("CompileService#compileObjectDataWithFullAttributes END : Repo={0}, Id={1}", repositoryId, content.getId()));
  
  
  		return result;
  	}
  
  	private void setRelationshipInternal(ObjectDataImpl objectData, CallContext callContext, String repositoryId,
  			Content content, IncludeRelationships includeRelationships) {
  
  		if (!content.isRelationship() && includeRelationships != IncludeRelationships.NONE
  				&& includeRelationshipsEnabled()) {
  			objectData.setRelationships(compileRelationships(callContext, repositoryId, content, includeRelationships));
  		}else{
  			objectData.setRelationships(new ArrayList<ObjectData>());
  		}
  	}
  
  	private void setAclAndAllowableActionsInternal(ObjectDataImpl objectData, CallContext callContext,
  			String repositoryId, Content content, Boolean includeAllowableActions, Boolean includeAcl) {
  
  		// Set Acl and Set Allowable actions
  		org.apache.chemistry.opencmis.commons.data.Acl cmisAcl = null;
  		Acl nemakiAcl = null;
  		AllowableActions allowableActions = null;
  		if (includeAcl || includeAllowableActions) {
  			nemakiAcl = contentService.calculateAcl(repositoryId, content);
  			objectData.setIsExactAcl(true);
  			cmisAcl = compileAcl(nemakiAcl, contentService.getAclInheritedWithDefault(repositoryId, content), false);
  		}
  		if (includeAllowableActions) {
  			allowableActions = compileAllowableActions(callContext, repositoryId, content, nemakiAcl);
  		}
  		objectData.setAcl(cmisAcl);
  		objectData.setAllowableActions(allowableActions);
  	}
  
  	private ObjectData filterObjectData(String repositoryId, ObjectData fullObjectData, String filter,
  			Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
  			Boolean includeAcl) {
  
  		// Deep copy ObjectData
  		// Shallow copy will cause a destructive effect after filtering some
  		// attributes
  		Cloner cloner = new Cloner();
  		ObjectDataImpl result = DataUtil.convertObjectDataImpl(cloner.deepClone(fullObjectData));
  
  		Properties filteredProperties = filterProperties(result.getProperties(), splitFilter(filter));
  		result.setProperties(filteredProperties);
  
  		// Filter Allowable actions
  		Boolean iaa = includeAllowableActions == null ? false : includeAllowableActions.booleanValue();
  		if (!iaa) {
  			result.setAllowableActions(null);
  		}
  
  		// Filter Acl
  		Boolean iacl = includeAcl == null ? false : includeAcl.booleanValue();
  		if (!iacl) {
  			result.setAcl(null);
  		}
  
  		// Filter Relationships
  		IncludeRelationships irl = includeRelationships == null ? IncludeRelationships.SOURCE : includeRelationships;
  		if (fullObjectData.getBaseTypeId() == BaseTypeId.CMIS_RELATIONSHIP) {
  			result.setRelationships(filterRelationships(result.getId(), result.getRelationships(), irl));
  		}
  
  		// Filter Renditions
  		if (fullObjectData.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT && repositoryInfoMap.get(repositoryId)
  				.getCapabilities().getRenditionsCapability() == CapabilityRenditions.NONE) {
  			result.setRenditions(null);
  		}
  
  		return result;
  	}
  
  	private ObjectData filterObjectDataInList(CallContext callContext, String repositoryId, ObjectData fullObjectData,
  			String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
  			String renditionFilter, Boolean includeAcl) {
  		boolean allowed = permissionService.checkPermission(callContext, Action.CAN_GET_PROPERTIES, fullObjectData);
  		if (allowed) {
  			return filterObjectData(repositoryId, fullObjectData, filter, includeAllowableActions, includeRelationships,
  					renditionFilter, includeAcl);
  		} else {
  			return null;
  		}
  	}
  
  	private Properties filterProperties(Properties properties, Set<String> filter) {
  		PropertiesImpl result = new PropertiesImpl();
  
  		// null filter as NO FILTER: do nothing
  		if (filter == null) {
  			return properties;
  		} else {
  			for (String key : properties.getProperties().keySet()) {
  				PropertyData<?> pd = properties.getProperties().get(key);
  				if (filter.contains(pd.getQueryName())) {
  					result.addProperty(pd);
  				}
  			}
  		}
  		return result;
  	}
  
  	private List<ObjectData> filterRelationships(String objectId, List<ObjectData> bothRelationships,
  			IncludeRelationships includeRelationships) {
  		String propertyId;
  		switch (includeRelationships) {
  		case NONE:
  			return null;
  		case SOURCE:
  			propertyId = PropertyIds.SOURCE_ID;
  		case TARGET:
  			propertyId = PropertyIds.TARGET_ID;
  		default:
  		}
  
  		List<ObjectData> filtered = new ArrayList<ObjectData>();
  		if (CollectionUtils.isNotEmpty(bothRelationships)) {
  			for (ObjectData rel : bothRelationships) {
  				PropertyData<?> filterId = rel.getProperties().getProperties().get(PropertyIds.SOURCE_ID);
  				if (objectId.equals(filterId)) {
  					filtered.add(rel);
  				}
  			}
  
  			return filtered;
  		} else {
  			return null;
  		}
  	}
  
  	@Override
  	public <T extends Content> ObjectList compileObjectDataList(CallContext callContext, String repositoryId,
  			List<T> contents, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
  			String renditionFilter, Boolean includeAcl, BigInteger maxItems, BigInteger skipCount, boolean folderOnly,
  			String orderBy) {
  		if (CollectionUtils.isEmpty(contents)) {
  			// Empty list
  			ObjectListImpl list = new ObjectListImpl();
  			list.setObjects(new ArrayList<ObjectData>());
  			list.setNumItems(BigInteger.ZERO);
  			list.setHasMoreItems(false);
  			return list;
  		} else {
  			List<ObjectData> objectDataList = new ArrayList<ObjectData>();
  			for (T content : contents) {
  				// Filter by folderOnly
  				if (folderOnly && !content.isFolder())
  					continue;
  
  				// Get each ObjectData
  				ObjectDataImpl rawObjectData = getRawObjectData(callContext, repositoryId, content, filter,
  						includeAllowableActions, includeRelationships, renditionFilter, includeAcl);
  				ObjectData filteredObjectData = filterObjectDataInList(callContext, repositoryId, rawObjectData, filter,
  						includeAllowableActions, includeRelationships, renditionFilter, includeAcl);
  
  				if (filteredObjectData != null) {
  					objectDataList.add(filteredObjectData);
  				}
  			}
  
  			// Sort
  			sortUtil.sort(repositoryId, objectDataList, orderBy);
  
  			// Set metadata
  			ObjectListImpl list = new ObjectListImpl();
  			Integer _skipCount = skipCount.intValue();
  			Integer _maxItems = maxItems.intValue();
  
  			if (_skipCount >= objectDataList.size()) {
  				list.setHasMoreItems(false);
  				list.setObjects(new ArrayList<ObjectData>());
  			} else {
  				// hasMoreItems
  				Boolean hasMoreItems = _skipCount + _maxItems < objectDataList.size();
  				list.setHasMoreItems(hasMoreItems);
  				// paged list
  				Integer toIndex = Math.min(_skipCount + _maxItems, objectDataList.size());
  				list.setObjects(new ArrayList<>(objectDataList.subList(_skipCount, toIndex)));
  			}
  			// totalNumItem
  			list.setNumItems(BigInteger.valueOf(objectDataList.size()));
  
  			return list;
  		}
  	}
  	@Override
  	public <T extends Content> ObjectList compileObjectDataListForSearchResult(CallContext callContext, String repositoryId,
  			List<T> contents, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
  			String renditionFilter, Boolean includeAcl, BigInteger maxItems, BigInteger skipCount, boolean folderOnly,
  			String orderBy, long numFound) {
  		if (CollectionUtils.isEmpty(contents)) {
  			// Empty list
  			ObjectListImpl list = new ObjectListImpl();
  			list.setObjects(new ArrayList<ObjectData>());
  			list.setNumItems(BigInteger.ZERO);
  			list.setHasMoreItems(false);
  			return list;
  		} else {
  			List<ObjectData> objectDataList = new ArrayList<ObjectData>();
  			for (T content : contents) {
  				// Filter by folderOnly
  				if (folderOnly && !content.isFolder())
  					continue;
  
  				// Get each ObjectData
  				ObjectDataImpl rawObjectData = getRawObjectData(callContext, repositoryId, content, filter,
  						includeAllowableActions, includeRelationships, renditionFilter, includeAcl);
  				ObjectData filteredObjectData = filterObjectDataInList(callContext, repositoryId, rawObjectData, filter,
  						includeAllowableActions, includeRelationships, renditionFilter, includeAcl);
  
  				if (filteredObjectData != null) {
  					objectDataList.add(filteredObjectData);
  				}
  			}
  
  			// Sort
  			sortUtil.sort(repositoryId, objectDataList, orderBy);
  
  			// Set metadata
  			ObjectListImpl list = new ObjectListImpl();
  			Integer _skipCount = skipCount.intValue();
  			Integer _maxItems = maxItems.intValue();
  
  			if (_skipCount >= numFound) {
  				list.setHasMoreItems(false);
  				list.setObjects(new ArrayList<ObjectData>());
  			} else {
  				// hasMoreItems
  				Boolean hasMoreItems = _skipCount + _maxItems < numFound;
  				list.setHasMoreItems(hasMoreItems);
  				// paged list
  				list.setObjects(new ArrayList<>(objectDataList));
  			}
  			// totalNumItem
  			list.setNumItems(BigInteger.valueOf(numFound));
  
  			return list;
  		}
  	}
  
  	@Override
  	public ObjectList compileChangeDataList(CallContext context, String repositoryId, List<Change> changes,
  			Holder<String> changeLogToken, Boolean includeProperties, String filter, Boolean includePolicyIds,
  			Boolean includeAcl) {
  		ObjectListImpl results = new ObjectListImpl();
  		results.setObjects(new ArrayList<ObjectData>());
  
  		Map<String, Content> cachedContents = new HashMap<String, Content>();
  		if (changes != null && CollectionUtils.isNotEmpty(changes)) {
  			for (Change change : changes) {
  
  				// Retrieve the content(using caches)
  				String objectId = change.getId();
  				Content content = new Content();
  
  				if (cachedContents.containsKey(objectId)) {
  					content = cachedContents.get(objectId);
  				} else {
  					content = contentService.getContent(repositoryId, objectId);
  					cachedContents.put(objectId, content);
  				}
  				// Compile a change object data depending on its type
  				results.getObjects()
  						.add(compileChangeObjectData(repositoryId, change, content, includePolicyIds, includeAcl));
  			}
  		}
  
  		results.setNumItems(BigInteger.valueOf(results.getObjects().size()));
  
  		String latestInRepository = repositoryService.getRepositoryInfo(repositoryId).getLatestChangeLogToken();
  		String latestInResults = changeLogToken.getValue();
  		if (latestInResults != null && latestInResults.equals(latestInRepository)) {
  			results.setHasMoreItems(false);
  		} else {
  			results.setHasMoreItems(true);
  		}
  
  		log.info(MessageFormat.format("compileChangeDataList : END M:{0}", Runtime.getRuntime().freeMemory() / 1024));
  
  		return results;
  	}
  
  	private ObjectData compileChangeObjectData(String repositoryId, Change change, Content content,
  			Boolean includePolicyIds, Boolean includeAcl) {
  		ObjectDataImpl o = new ObjectDataImpl();
  
  		// Set Properties
  		PropertiesImpl properties = new PropertiesImpl();
  		setCmisBasicChangeProperties(properties, change);
  		o.setProperties(properties);
  		// Set PolicyIds
  		setPolcyIds(o, change, includePolicyIds);
  		// Set Acl
  		if (!change.getChangeType().equals(ChangeType.DELETED)) {
  			setAcl(repositoryId, o, content, includeAcl);
  		}
  		// Set Change Event
  		setChangeEvent(o, change);
  
  		return o;
  	}
  
  	private void setCmisBasicChangeProperties(PropertiesImpl props, Change change) {
  		props.addProperty(new PropertyIdImpl(PropertyIds.OBJECT_ID, change.getObjectId()));
  		props.addProperty(new PropertyIdImpl(PropertyIds.BASE_TYPE_ID, change.getBaseType()));
  		props.addProperty(new PropertyIdImpl(PropertyIds.OBJECT_TYPE_ID, change.getObjectType()));
  		props.addProperty(new PropertyIdImpl(PropertyIds.NAME, change.getName()));
  		if (change.isOnDocument()) {
  			props.addProperty(new PropertyIdImpl(PropertyIds.VERSION_SERIES_ID, change.getVersionSeriesId()));
  			props.addProperty(new PropertyStringImpl(PropertyIds.VERSION_LABEL, change.getVersionLabel()));
  		}
  	}
  
  	private void setPolcyIds(ObjectDataImpl object, Change change, Boolean includePolicyids) {
  		boolean iplc = (includePolicyids == null ? false : includePolicyids.booleanValue());
  		if (iplc) {
  			List<String> policyIds = change.getPolicyIds();
  			PolicyIdListImpl plist = new PolicyIdListImpl();
  			plist.setPolicyIds(policyIds);
  			object.setPolicyIds(plist);
  		}
  	}
  
  	private void setAcl(String repositoryId, ObjectDataImpl object, Content content, Boolean includeAcl) {
  		boolean iacl = (includeAcl == null ? false : includeAcl.booleanValue());
  		if (iacl) {
  			if (content != null) {
  				Acl acl = contentService.calculateAcl(repositoryId, content);
  				object.setAcl(compileAcl(acl, contentService.getAclInheritedWithDefault(repositoryId, content), false));
  			}
  		}
  	}
  
  	private void setChangeEvent(ObjectDataImpl object, Change change) {
  		// Set ChangeEventInfo
  		ChangeEventInfoDataImpl ce = new ChangeEventInfoDataImpl();
  		ce.setChangeType(change.getChangeType());
  		ce.setChangeTime(change.getTime());
  		object.setChangeEventInfo(ce);
  	}
  
  	/**
  	 * Sets allowable action for the content
  	 *
  	 * @param content
  	 */
  	@Override
  	public AllowableActions compileAllowableActions(CallContext callContext, String repositoryId, Content content) {
  		Acl acl = contentService.calculateAcl(repositoryId, content);
  		return compileAllowableActions(callContext, repositoryId, content, acl);
  	}
  
  	/**
  	 * Sets allowable action for the content
  	 *
  	 * @param content
  	 */
  	@Override
  	public AllowableActions compileAllowableActions(CallContext callContext, String repositoryId, Content content,
  			Acl acl) {
  		// Get parameters to calculate AllowableActions
  		TypeDefinition tdf = typeManager.getTypeDefinition(repositoryId, content.getObjectType());
  		Acl contentAcl = content.getAcl();
  		if (tdf.isControllableAcl() && contentAcl == null)
  			return null;
  
  		Map<String, PermissionMapping> permissionMap = repositoryInfoMap.get(repositoryId).getAclCapabilities()
  				.getPermissionMapping();
  		String baseType = content.getType();
  
  		// Calculate AllowableActions
  		Set<Action> actionSet = new HashSet<Action>();
  		VersionSeries versionSeries = null;
  		if (content.isDocument()) {
  			Document d = (Document) content;
  			versionSeries = contentService.getVersionSeries(repositoryId, d);
  		}
  
  
  		String userName = callContext.getUsername();
  		Set<String> groups = contentService.getGroupIdsContainingUser(repositoryId, userName);
  
  		for (Entry<String, PermissionMapping> mappingEntry : permissionMap.entrySet()) {
  			String key = mappingEntry.getValue().getKey();
  			// TODO WORKAROUND. implement class cast check
  
  			// FIXME WORKAROUND: skip canCreatePolicy.Folder
  			if (PermissionMapping.CAN_CREATE_POLICY_FOLDER.equals(key)) {
  				continue;
  			}
  
  			// Additional check
  			if (!isAllowableByCapability(repositoryId, key)) {
  				continue;
  			}
  			if (!isAllowableByType(key, content, tdf)) {
  				continue;
  			}
  			if (contentService.isRoot(repositoryId, content)) {
  				if (Action.CAN_MOVE_OBJECT == convertKeyToAction(key)) {
  					continue;
  				}
  			}
  			if (versionSeries != null) {
  				Document d = (Document) content;
  				DocumentTypeDefinition dtdf = (DocumentTypeDefinition) tdf;
  				if (!isAllowableActionForVersionableDocument(callContext, mappingEntry.getKey(), d, versionSeries,
  						dtdf)) {
  					continue;
  				}
  			}
  
  			// Add an allowable action
  			boolean allowable = permissionService.checkPermissionWithGivenList(callContext, repositoryId, mappingEntry.getKey(), acl,
  					baseType, content, userName, groups);
  
  			if (allowable) {
  				actionSet.add(convertKeyToAction(key));
  			}
  		}
  		AllowableActionsImpl allowableActions = new AllowableActionsImpl();
  		allowableActions.setAllowableActions(actionSet);
  		return allowableActions;
  	}
  
  	private boolean isAllowableActionForVersionableDocument(CallContext callContext, String permissionMappingKey,
  			Document document, VersionSeries versionSeries, DocumentTypeDefinition dtdf) {
  
  		// Versioning action(checkOut / checkIn)
  		if (permissionMappingKey.equals(PermissionMapping.CAN_CHECKOUT_DOCUMENT)) {
  			return dtdf.isVersionable() && !versionSeries.isVersionSeriesCheckedOut() && document.isLatestVersion();
  		} else if (permissionMappingKey.equals(PermissionMapping.CAN_CHECKIN_DOCUMENT)) {
  			return dtdf.isVersionable() && versionSeries.isVersionSeriesCheckedOut() && document.isPrivateWorkingCopy();
  		} else if (permissionMappingKey.equals(PermissionMapping.CAN_CANCEL_CHECKOUT_DOCUMENT)) {
  			return dtdf.isVersionable() && versionSeries.isVersionSeriesCheckedOut() && document.isPrivateWorkingCopy();
  		}
  
  		// Lock as an effect of checkOut
  		if (dtdf.isVersionable()) {
  			if (isLockableAction(permissionMappingKey)) {
  				if (document.isLatestVersion()) {
  					// LocK only when checked out
  					return !versionSeries.isVersionSeriesCheckedOut();
  				} else if (document.isPrivateWorkingCopy()) {
  					// Only owner can do actions on pwc
  					return callContext.getUsername().equals(versionSeries.getVersionSeriesCheckedOutBy());
  				} else {
  					return false;
  				}
  			}
  		}
  
  		return true;
  	}
  
  	private boolean isLockableAction(String key) {
  		return key.equals(PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT)
  				|| key.equals(PermissionMapping.CAN_SET_CONTENT_DOCUMENT)
  				|| key.equals(PermissionMapping.CAN_ADD_POLICY_OBJECT)
  				|| key.equals(PermissionMapping.CAN_ADD_TO_FOLDER_OBJECT)
  				|| key.equals(PermissionMapping.CAN_APPLY_ACL_OBJECT)
  				|| key.equals(PermissionMapping.CAN_DELETE_CONTENT_DOCUMENT)
  				|| key.equals(PermissionMapping.CAN_DELETE_OBJECT) || key.equals(PermissionMapping.CAN_MOVE_OBJECT)
  				|| key.equals(PermissionMapping.CAN_REMOVE_FROM_FOLDER_OBJECT)
  				|| key.equals(PermissionMapping.CAN_REMOVE_POLICY_OBJECT);
  	}
  
  	private boolean isAllowableByCapability(String repositoryId, String key) {
  		RepositoryCapabilities capabilities = repositoryInfoMap.get(repositoryId).getCapabilities();
  
  		// Multifiling or Unfiling Capabilities
  		if (PermissionMapping.CAN_ADD_TO_FOLDER_OBJECT.equals(key)
  				|| PermissionMapping.CAN_ADD_TO_FOLDER_FOLDER.equals(key)) {
  			// This is not a explicit spec, but it's plausible.
  			return capabilities.isUnfilingSupported() || capabilities.isMultifilingSupported();
  		} else if (PermissionMapping.CAN_REMOVE_FROM_FOLDER_OBJECT.equals(key)
  				|| PermissionMapping.CAN_REMOVE_FROM_FOLDER_FOLDER.equals(key)) {
  			return capabilities.isUnfilingSupported();
  
  			// GetDescendents or GetFolderTree Capabilities
  		} else if (PermissionMapping.CAN_GET_DESCENDENTS_FOLDER.equals(key)) {
  			return capabilities.isGetDescendantsSupported() || capabilities.isGetFolderTreeSupported();
  		} else {
  			return true;
  		}
  	}
  
  	private boolean isAllowableByType(String key, Content content, TypeDefinition tdf) {
  		// ControllableACL
  		if (PermissionMapping.CAN_APPLY_ACL_OBJECT.equals(key)) {
  			// Default to FALSE
  			boolean ctrlAcl = (tdf.isControllableAcl() == null) ? false : tdf.isControllableAcl();
  			return ctrlAcl;
  
  			// ControllablePolicy
  		} else if (PermissionMapping.CAN_ADD_POLICY_OBJECT.equals(key)
  				|| PermissionMapping.CAN_ADD_POLICY_POLICY.equals(key)
  				|| PermissionMapping.CAN_REMOVE_POLICY_OBJECT.equals(key)
  				|| PermissionMapping.CAN_REMOVE_POLICY_POLICY.equals(key)) {
  			// Default to FALSE
  			boolean ctrlPolicy = (tdf.isControllablePolicy() == null) ? false : tdf.isControllablePolicy();
  			return ctrlPolicy;
  
  			// setContent
  		} else if (PermissionMapping.CAN_SET_CONTENT_DOCUMENT.equals(key)) {
  			if (BaseTypeId.CMIS_DOCUMENT != tdf.getBaseTypeId())
  				return true;
  
  			DocumentTypeDefinition _tdf = (DocumentTypeDefinition) tdf;
  			// Default to REQUIRED
  			ContentStreamAllowed csa = (_tdf.getContentStreamAllowed() == null) ? ContentStreamAllowed.REQUIRED
  					: _tdf.getContentStreamAllowed();
  			return !(csa == ContentStreamAllowed.NOTALLOWED);
  
  			// deleteContent
  		} else if (PermissionMapping.CAN_DELETE_CONTENT_DOCUMENT.equals(key)) {
  			if (BaseTypeId.CMIS_DOCUMENT != tdf.getBaseTypeId())
  				return true;
  
  			DocumentTypeDefinition _tdf = (DocumentTypeDefinition) tdf;
  			// Default to REQUIRED
  			ContentStreamAllowed csa = (_tdf.getContentStreamAllowed() == null) ? ContentStreamAllowed.REQUIRED
  					: _tdf.getContentStreamAllowed();
  			return !(csa == ContentStreamAllowed.REQUIRED);
  
  		} else {
  			return true;
  		}
  	}
  
  	private List<ObjectData> compileRelationships(CallContext context, String repositoryId, Content content,
  			IncludeRelationships irl) {
  
  		log.info(MessageFormat.format("CompileService#compileRelationships START: Repo={0}, Id={1}", repositoryId, content.getId()));
  
  
  		if (IncludeRelationships.NONE == irl) {
  			return null;
  		}
  
  		RelationshipDirection rd;
  		switch (irl) {
  		case SOURCE:
  			rd = RelationshipDirection.SOURCE;
  			break;
  		case TARGET:
  			rd = RelationshipDirection.TARGET;
  			break;
  		case BOTH:
  			rd = RelationshipDirection.EITHER;
  			break;
  		default:
  			rd = RelationshipDirection.SOURCE;
  			break;
  		}
  		List<Relationship> _rels = contentService.getRelationsipsOfObject(repositoryId, content.getId(), rd);
  
  		List<ObjectData> rels = new ArrayList<ObjectData>();
  		if (CollectionUtils.isNotEmpty(_rels)) {
  			for (Relationship _rel : _rels) {
  
  				ObjectData rel = compileObjectData(context, repositoryId, _rel, null, false, IncludeRelationships.NONE,
  						null, false);
  				rels.add(rel);
  			}
  		}
  
  		return rels;
  	}
  
  	private List<RenditionData> compileRenditions(CallContext callContext, String repositoryId, Content content) {
  		List<RenditionData> renditions = new ArrayList<RenditionData>();
  
  		List<Rendition> _renditions = contentService.getRenditions(repositoryId, content.getId());
  		if (CollectionUtils.isNotEmpty(_renditions)) {
  			for (Rendition _rd : _renditions) {
  				RenditionDataImpl rd = new RenditionDataImpl();
  				rd.setStreamId(_rd.getId());
  				rd.setMimeType(_rd.getMimetype());
  				rd.setBigLength(BigInteger.valueOf(_rd.getLength()));
  				rd.setKind(_rd.getKind());
  				rd.setTitle(_rd.getTitle());
  				rd.setBigHeight(BigInteger.valueOf(_rd.getHeight()));
  				rd.setBigWidth(BigInteger.valueOf(_rd.getWidth()));
  				rd.setRenditionDocumentId(_rd.getRenditionDocumentId());
  
  				renditions.add(rd);
  			}
  		}
  
  		return renditions;
  	}
  
  	/**
  	 * Compiles properties of a piece of content.
  	 */
  	@Override
  	public PropertiesImpl compileProperties(CallContext callContext, String repositoryId, Content content) {
  		TypeDefinitionContainer tdfc = typeManager.getTypeById(repositoryId, content.getObjectType());
  		TypeDefinition tdf = tdfc.getTypeDefinition();
  
  		PropertiesImpl properties = new PropertiesImpl();
  		if (content.isFolder()) {
  			Folder folder = (Folder) content;
  			// Root folder
  			if (contentService.isRoot(repositoryId, folder)) {
  				properties = compileRootFolderProperties(repositoryId, folder, properties, tdf);
  				// Other than root folder
  			} else {
  				properties = compileFolderProperties(repositoryId, folder, properties, tdf);
  			}
  		} else if (content.isDocument()) {
  			Document document = (Document) content;
  			properties = compileDocumentProperties(callContext, repositoryId, document, properties, tdf);
  		} else if (content.isRelationship()) {
  			Relationship relationship = (Relationship) content;
  			properties = compileRelationshipProperties(repositoryId, relationship, properties, tdf);
  		} else if (content.isPolicy()) {
  			Policy policy = (Policy) content;
  			properties = compilePolicyProperties(repositoryId, policy, properties, tdf);
  		} else if (content.isItem()) {
  			Item item = (Item) content;
  			properties = compileItemProperties(repositoryId, item, properties, tdf);
  		}
  
  		return properties;
  	}
  
  	private PropertiesImpl compileRootFolderProperties(String repositoryId, Folder folder, PropertiesImpl properties,
  			TypeDefinition tdf) {
  		setCmisBaseProperties(repositoryId, properties, tdf, folder);
  
  		// Add parentId property without value
  		String _null = null;
  		PropertyIdImpl parentId = new PropertyIdImpl(PropertyIds.PARENT_ID, _null);
  		properties.addProperty(parentId);
  		setCmisFolderProperties(repositoryId, properties, tdf, folder);
  
  		return properties;
  	}
  
  	private PropertiesImpl compileFolderProperties(String repositoryId, Folder folder, PropertiesImpl properties,
  			TypeDefinition tdf) {
  		setCmisBaseProperties(repositoryId, properties, tdf, folder);
  		addProperty(properties, tdf, PropertyIds.PARENT_ID, folder.getParentId());
  		setCmisFolderProperties(repositoryId, properties, tdf, folder);
  		return properties;
  	}
  
  	private PropertiesImpl compileDocumentProperties(CallContext callContext, String repositoryId, Document document,
  			PropertiesImpl properties, TypeDefinition tdf) {
  		setCmisBaseProperties(repositoryId, properties, tdf, document);
  		setCmisDocumentProperties(callContext, repositoryId, properties, tdf, document);
  		setCmisAttachmentProperties(repositoryId, properties, tdf, document);
  		return properties;
  	}
  
  	private PropertiesImpl compileRelationshipProperties(String repositoryId, Relationship relationship,
  			PropertiesImpl properties, TypeDefinition tdf) {
  		setCmisBaseProperties(repositoryId, properties, tdf, relationship);
  		setCmisRelationshipProperties(properties, tdf, relationship);
  		return properties;
  	}
  
  	private PropertiesImpl compilePolicyProperties(String repositoryId, Policy policy, PropertiesImpl properties,
  			TypeDefinition tdf) {
  		setCmisBaseProperties(repositoryId, properties, tdf, policy);
  		setCmisPolicyProperties(properties, tdf, policy);
  		return properties;
  	}
  
  	private PropertiesImpl compileItemProperties(String repositoryId, Item item, PropertiesImpl properties,
  			TypeDefinition tdf) {
  		setCmisBaseProperties(repositoryId, properties, tdf, item);
  		setCmisItemProperties(properties, tdf, item);
  		return properties;
  	}
  
  	private void setCmisBaseProperties(String repositoryId, PropertiesImpl properties, TypeDefinition tdf,
  			Content content) {
  		addProperty(properties, tdf, PropertyIds.NAME, content.getName());
  
  		addProperty(properties, tdf, PropertyIds.DESCRIPTION, content.getDescription());
  
  		addProperty(properties, tdf, PropertyIds.OBJECT_ID, content.getId());
  
  		addProperty(properties, tdf, PropertyIds.OBJECT_TYPE_ID, content.getObjectType());
  
  		if (content.getCreated() != null)
  			addProperty(properties, tdf, PropertyIds.CREATION_DATE, content.getCreated());
  
  		if (content.getCreator() != null)
  			addProperty(properties, tdf, PropertyIds.CREATED_BY, content.getCreator());
  
  		if (content.getModified() != null) {
  			addProperty(properties, tdf, PropertyIds.LAST_MODIFICATION_DATE, content.getModified());
  		} else {
  			addProperty(properties, tdf, PropertyIds.LAST_MODIFICATION_DATE, content.getCreated());
  		}
  
  		if (content.getModifier() != null) {
  			addProperty(properties, tdf, PropertyIds.LAST_MODIFIED_BY, content.getModifier());
  		} else {
  			addProperty(properties, tdf, PropertyIds.LAST_MODIFIED_BY, content.getCreator());
  		}
  
  		addProperty(properties, tdf, PropertyIds.CHANGE_TOKEN, String.valueOf(content.getChangeToken()));
  
  		// TODO If subType properties is not registered in DB, return void
  		// properties via CMIS
  		// SubType properties
  		List<PropertyDefinition<?>> specificPropertyDefinitions = typeManager
  				.getSpecificPropertyDefinitions(tdf.getId());
  		if (!CollectionUtils.isEmpty(specificPropertyDefinitions)) {
  			for (PropertyDefinition<?> propertyDefinition : specificPropertyDefinitions) {
  				Property property = extractSubTypeProperty(content, propertyDefinition.getId());
  				Object value = (property == null) ? null : property.getValue();
  				addProperty(properties, tdf, propertyDefinition.getId(), value);
  			}
  		}
  
  		// Secondary properties
  		setCmisSecondaryTypes(repositoryId, properties, content, tdf);
  	}
  
  	private Property extractSubTypeProperty(Content content, String propertyId) {
  		List<Property> subTypeProperties = content.getSubTypeProperties();
  		if (CollectionUtils.isNotEmpty(subTypeProperties)) {
  			for (Property subTypeProperty : subTypeProperties) {
  				if (subTypeProperty.getKey().equals(propertyId)) {
  					return subTypeProperty;
  				}
  			}
  		}
  
  		return null;
  	}
  
  	private void setCmisFolderProperties(String repositoryId, PropertiesImpl properties, TypeDefinition tdf,
  			Folder folder) {
  
  		addProperty(properties, tdf, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_FOLDER.value());
  		addProperty(properties, tdf, PropertyIds.PATH, contentService.calculatePath(repositoryId, folder));
  
  		if (checkAddProperty(properties, tdf, PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS)) {
  			List<String> values = new ArrayList<String>();
  			// If not specified, all child types are allowed.
  			if (!CollectionUtils.isEmpty(folder.getAllowedChildTypeIds())) {
  				values = folder.getAllowedChildTypeIds();
  			}
  			PropertyData<String> pd = new PropertyIdImpl(PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS, values);
  			properties.addProperty(pd);
  		}
  	}
  
  	private void setCmisDocumentProperties(CallContext callContext, String repositoryId, PropertiesImpl properties,
  			TypeDefinition tdf, Document document) {
  		addProperty(properties, tdf, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_DOCUMENT.value());
  
  		Boolean isImmutable = (document.isImmutable() == null)
  				? (Boolean) typeManager.getSingleDefaultValue(PropertyIds.IS_IMMUTABLE, tdf.getId(), repositoryId)
  				: document.isImmutable();
  		addProperty(properties, tdf, PropertyIds.IS_IMMUTABLE, isImmutable);
  
  		DocumentTypeDefinition type = (DocumentTypeDefinition) typeManager.getTypeDefinition(repositoryId, tdf.getId());
  		if (type.isVersionable()) {
  			addProperty(properties, tdf, PropertyIds.IS_PRIVATE_WORKING_COPY, document.isPrivateWorkingCopy());
  			addProperty(properties, tdf, PropertyIds.IS_LATEST_VERSION, document.isLatestVersion());
  			addProperty(properties, tdf, PropertyIds.IS_MAJOR_VERSION, document.isMajorVersion());
  			addProperty(properties, tdf, PropertyIds.IS_LATEST_MAJOR_VERSION, document.isLatestMajorVersion());
  			addProperty(properties, tdf, PropertyIds.VERSION_LABEL, document.getVersionLabel());
  			addProperty(properties, tdf, PropertyIds.VERSION_SERIES_ID, document.getVersionSeriesId());
  			addProperty(properties, tdf, PropertyIds.CHECKIN_COMMENT, document.getCheckinComment());
  
  			VersionSeries vs = contentService.getVersionSeries(repositoryId, document);
  			addProperty(properties, tdf, PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, vs.isVersionSeriesCheckedOut());
  			if (vs.isVersionSeriesCheckedOut()) {
  				String userId = callContext.getUsername();
  				String checkedOutBy = vs.getVersionSeriesCheckedOutBy();
  
  				if (userId.equals(checkedOutBy)) {
  					addProperty(properties, tdf, PropertyIds.VERSION_SERIES_CHECKED_OUT_ID,
  							vs.getVersionSeriesCheckedOutId());
  					addProperty(properties, tdf, PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, checkedOutBy);
  				} else {
  					addProperty(properties, tdf, PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, null);
  					addProperty(properties, tdf, PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, checkedOutBy);
  				}
  
  			} else {
  				addProperty(properties, tdf, PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, null);
  				addProperty(properties, tdf, PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, null);
  			}
  
  			// TODO comment
  		} else {
  			addProperty(properties, tdf, PropertyIds.IS_PRIVATE_WORKING_COPY, false);
  			addProperty(properties, tdf, PropertyIds.IS_LATEST_VERSION, false);
  			addProperty(properties, tdf, PropertyIds.IS_MAJOR_VERSION, false);
  			addProperty(properties, tdf, PropertyIds.IS_LATEST_MAJOR_VERSION, false);
  			addProperty(properties, tdf, PropertyIds.VERSION_LABEL, "");
  			addProperty(properties, tdf, PropertyIds.VERSION_SERIES_ID, "");
  			addProperty(properties, tdf, PropertyIds.CHECKIN_COMMENT, "");
  			addProperty(properties, tdf, PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, false);
  
  			addProperty(properties, tdf, PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, "");
  			addProperty(properties, tdf, PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, "");
  		}
  	}
  
  	private void setCmisAttachmentProperties(String repositoryId, PropertiesImpl properties, TypeDefinition tdf,
  			Document document) {
  		Long length = null;
  		String mimeType = null;
  		String fileName = null;
  		String streamId = null;
  
  		// Check if ContentStream is attached
  		DocumentTypeDefinition dtdf = (DocumentTypeDefinition) tdf;
  		ContentStreamAllowed csa = dtdf.getContentStreamAllowed();
  		if (ContentStreamAllowed.REQUIRED == csa
  				|| ContentStreamAllowed.ALLOWED == csa && StringUtils.isNotBlank(document.getAttachmentNodeId())) {
  
  			AttachmentNode attachment = contentService.getAttachmentRef(repositoryId, document.getAttachmentNodeId());
  
  			if (attachment == null) {
  				String attachmentId = (document.getAttachmentNodeId() == null) ? "" : document.getAttachmentNodeId();
  				log.warn("[objectId=" + document.getId() + " has no file (" + attachmentId + ")");
  			} else {
  				length = attachment.getLength();
  				mimeType = attachment.getMimeType();
  				if(attachment.getName() == null || attachment.getName().isEmpty()){
  					fileName = document.getName();
  				}else{
  					fileName = attachment.getName();
  				}
  				streamId = attachment.getId();
  			}
  		}
  
  		// Add ContentStream properties to Document object
  		addProperty(properties, dtdf, PropertyIds.CONTENT_STREAM_LENGTH, length);
  		addProperty(properties, dtdf, PropertyIds.CONTENT_STREAM_MIME_TYPE, mimeType);
  		addProperty(properties, dtdf, PropertyIds.CONTENT_STREAM_FILE_NAME, fileName);
  		addProperty(properties, dtdf, PropertyIds.CONTENT_STREAM_ID, streamId);
  	}
  
  	private void setCmisRelationshipProperties(PropertiesImpl properties, TypeDefinition typeId,
  			Relationship relationship) {
  		addProperty(properties, typeId, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_RELATIONSHIP.value());
  		addProperty(properties, typeId, PropertyIds.SOURCE_ID, relationship.getSourceId());
  		addProperty(properties, typeId, PropertyIds.TARGET_ID, relationship.getTargetId());
  	}
  
  	private void setCmisPolicyProperties(PropertiesImpl properties, TypeDefinition tdf, Policy policy) {
  		addProperty(properties, tdf, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_POLICY.value());
  		addProperty(properties, tdf, PropertyIds.POLICY_TEXT, policy.getPolicyText());
  	}
  
  	private void setCmisItemProperties(PropertiesImpl properties, TypeDefinition tdf, Item item) {
  		addProperty(properties, tdf, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_ITEM.value());
  	}
  
  	private void setCmisSecondaryTypes(String repositoryId, PropertiesImpl props, Content content, TypeDefinition tdf) {
  		List<Aspect> aspects = content.getAspects();
  		List<String> secondaryIds = new ArrayList<String>();
  
  		// cmis:secondaryObjectTypeIds
  		if (CollectionUtils.isNotEmpty(content.getSecondaryIds())) {
  			for (String secondaryId : content.getSecondaryIds()) {
  				secondaryIds.add(secondaryId);
  			}
  		}
  
  		new PropertyIdImpl(PropertyIds.SECONDARY_OBJECT_TYPE_IDS, secondaryIds);
  		addProperty(props, tdf, PropertyIds.SECONDARY_OBJECT_TYPE_IDS, secondaryIds);
  
  		// each secondary properties
  		for (String secondaryId : secondaryIds) {
  			List<PropertyDefinition<?>> secondaryPropertyDefinitions = typeManager
  					.getSpecificPropertyDefinitions(secondaryId);
  			if (CollectionUtils.isEmpty(secondaryPropertyDefinitions))
  				continue;
  
  			Aspect aspect = extractAspect(aspects, secondaryId);
  			List<Property> properties = (aspect == null) ? new ArrayList<Property>() : aspect.getProperties();
  
  			SecondaryTypeDefinition stdf = (SecondaryTypeDefinition) typeManager.getTypeDefinition(repositoryId,
  					secondaryId);
  			for (PropertyDefinition<?> secondaryPropertyDefinition : secondaryPropertyDefinitions) {
  				Property property = extractProperty(properties, secondaryPropertyDefinition.getId());
  				Object value = (property == null) ? null : property.getValue();
  				addProperty(props, stdf, secondaryPropertyDefinition.getId(), value);
  			}
  		}
  	}
  
  	private Aspect extractAspect(List<Aspect> aspects, String aspectId) {
  		for (Aspect aspect : aspects) {
  			if (aspect.getName().equals(aspectId)) {
  				return aspect;
  			}
  		}
  		return null;
  	}
  
  	private Property extractProperty(List<Property> properties, String propertyId) {
  		for (Property property : properties) {
  			if (property.getKey().equals(propertyId)) {
  				return property;
  			}
  		}
  		return null;
  	}
  
  	/**
  	 * Verifies that parameters are safe.
  	 */
  	private boolean checkAddProperty(Properties properties, TypeDefinition tdf, String id) {
  
  		if ((properties == null) || (properties.getProperties() == null))
  			throw new IllegalArgumentException("Properties must not be null!");
  
  		if (StringUtils.isEmpty(id))
  			throw new IllegalArgumentException("ID must not be null!");
  
  		// TODO :performance
  		if (!tdf.getPropertyDefinitions().containsKey(id))
  			throw new IllegalArgumentException("Unknown property: " + id);
  
  		// String queryName =
  		// tdf.getPropertyDefinitions().get(id).getQueryName();
  		/*
  		 * if ((queryName != null) && (filter != null)) { if
  		 * (!filter.contains(queryName)) { return false; } else {
  		 * filter.remove(queryName); } }
  		 */
  		return true;
  	}
  
  	/**
  	 * Adds specified property in property set.
  	 *
  	 * @param props
  	 *            property set
  	 * @param tdf
  	 *            object type (e.g. cmis:document)
  	 * @param id
  	 *            property ID
  	 * @param value
  	 *            actual property value
  	 */
  	// TODO if cast fails, continue the operation
  	private void addProperty(PropertiesImpl props, TypeDefinition tdf, String id, Object value) {
  		try {
  			PropertyDefinition<?> pdf = tdf.getPropertyDefinitions().get(id);
  			if (!checkAddProperty(props, tdf, id))
  				return;
  
  			switch (pdf.getPropertyType()) {
  			case BOOLEAN:
  				PropertyBooleanImpl propBoolean;
  				if (value instanceof List<?>) {
  					propBoolean = new PropertyBooleanImpl(id, (List<Boolean>) value);
  				} else if (value instanceof Boolean) {
  					propBoolean = new PropertyBooleanImpl(id, (Boolean) value);
  				} else {
  					Boolean _null = null;
  					propBoolean = new PropertyBooleanImpl(id, _null);
  					if (value != null) {
  						String msg = buildCastErrMsg(tdf.getId(), id, pdf.getPropertyType(), value.getClass().getName(),
  								Boolean.class.getName());
  						log.warn("ObjectId=" + id + " Value=" + value.toString() + " Message=" + msg);
  					}
  				}
  				addPropertyBase(props, id, propBoolean, pdf);
  				break;
  			case INTEGER:
  				PropertyIntegerImpl propInteger;
  				if (value instanceof List<?>) {
  					propInteger = new PropertyIntegerImpl(id, (List<BigInteger>) value);
  				} else if (value instanceof Long || value instanceof Integer) {
  					propInteger = new PropertyIntegerImpl(id, BigInteger.valueOf((Long) value));
  				} else {
  					BigInteger _null = null;
  					propInteger = new PropertyIntegerImpl(id, _null);
  					if (value != null) {
  						String msg = buildCastErrMsg(tdf.getId(), id, pdf.getPropertyType(), value.getClass().getName(),
  								Long.class.getName());
  						log.warn("ObjectId=" + id + " Value=" + value.toString() + " Message=" + msg);
  					}
  				}
  				addPropertyBase(props, id, propInteger, pdf);
  				break;
  			case DATETIME:
  				PropertyDateTimeImpl propDate;
  				if (value instanceof List<?>) {
  					Stream<?> values = ((List<?>) value).stream();
  					List<GregorianCalendar> calList = new ArrayList<GregorianCalendar>();
  					values.forEach(p -> {
  						if (p instanceof GregorianCalendar) {
  							calList.add((GregorianCalendar) p);
  						} else if (value instanceof String) {
  							try {
  								GregorianCalendar cal = DataUtil.convertToCalender((String) value);
  								calList.add(cal);
  							} catch (ParseException ex) {
  								// skip
  							}
  						}
  					});
  					propDate = new PropertyDateTimeImpl(id, calList);
  				} else if (value instanceof GregorianCalendar) {
  					propDate = new PropertyDateTimeImpl(id, (GregorianCalendar) value);
  				} else if (value instanceof String) {
  					try {
  						propDate = new PropertyDateTimeImpl(id, DataUtil.convertToCalender((String) value));
  					} catch (ParseException ex) {
  						propDate = createNullDateTimeProperty(tdf, id, value, pdf);
  					}
  				} else {
  					propDate = createNullDateTimeProperty(tdf, id, value, pdf);
  				}
  				addPropertyBase(props, id, propDate, pdf);
  				break;
  			case STRING:
  				PropertyStringImpl propString = new PropertyStringImpl();
  				propString.setId(id);
  				if (value instanceof List<?>) {
  					propString.setValues((List<String>) value);
  				} else if (value instanceof String) {
  					propString.setValue(String.valueOf(value));
  				} else {
  					String _null = null;
  					propString = new PropertyStringImpl(id, _null);
  					if (value != null) {
  						String msg = buildCastErrMsg(tdf.getId(), id, pdf.getPropertyType(), value.getClass().getName(),
  								String.class.getName());
  						log.warn("ObjectId=" + id + " Value=" + value.toString() + " Message=" + msg);
  					}
  				}
  				addPropertyBase(props, id, propString, pdf);
  				break;
  			case ID:
  				PropertyIdImpl propId = new PropertyIdImpl();
  				propId.setId(id);
  				if (value instanceof List<?>) {
  					propId.setValues((List<String>) value);
  				} else if (value instanceof String) {
  					propId.setValue(String.valueOf(value));
  
  				} else {
  					String _null = null;
  					propId = new PropertyIdImpl(id, _null);
  					if (value != null) {
  						String msg = buildCastErrMsg(tdf.getId(), id, pdf.getPropertyType(), value.getClass().getName(),
  								String.class.getName());
  						log.warn("ObjectId=" + id + " Value=" + value.toString() + " Message=" + msg);
  					}
  				}
  				addPropertyBase(props, id, propId, pdf);
  				break;
  			default:
  			}
  		} catch (Exception e) {
  			log.warn("typeId:" + tdf + ", propertyId:" + id + " Fail to add a property!", e);
  		}
  	}
  
  	private PropertyDateTimeImpl createNullDateTimeProperty(TypeDefinition tdf, String id, Object value,
  			PropertyDefinition<?> pdf) {
  		PropertyDateTimeImpl propDate;
  		GregorianCalendar _null = null;
  		propDate = new PropertyDateTimeImpl(id, _null);
  		if (value != null) {
  			String msg = buildCastErrMsg(tdf.getId(), id, pdf.getPropertyType(), value.getClass().getName(),
  					GregorianCalendar.class.getName());
  			log.warn("ObjectId=" + id + " Value=" + value.toString() + " Message=" + msg);
  		}
  		return propDate;
  	}
  
  	private String buildCastErrMsg(String typeId, String propertyId, PropertyType propertyType, String sourceClass,
  			String targetClass) {
  		return "[typeId:" + typeId + ", propertyId:" + propertyId + ", propertyType:" + propertyType.value()
  				+ "]Cannot convert " + sourceClass + " to " + targetClass;
  	}
  
  	private <T> void addPropertyBase(PropertiesImpl props, String id, AbstractPropertyData<T> p,
  			PropertyDefinition<?> pdf) {
  		p.setDisplayName(pdf.getDisplayName());
  		p.setLocalName(id);
  		p.setQueryName(pdf.getQueryName());
  		props.addProperty(p);
  	}
  
  	/**
  	 * Separates filter string with ','. If filter is null or empty, it means
  	 * all properties can go.
  	 */
  	// TODO implement CMIS filterNotValid exception?
  	// NOTE: "not set" can mean "all properties" and invalid queryName should be
  	// ignored.
  	// NOTE: So, filterNotValid exception might not be needed.
  	@Override
  	public Set<String> splitFilter(String filter) {
  		final String ASTERISK = "*";
  		final String COMMA = ",";
  
  		if (filter == null || filter.trim().length() == 0) {
  			return null;
  		}
  		Set<String> filters = new HashSet<String>();
  		for (String s : filter.split(COMMA)) {
  			s = s.trim();
  			if (s.equals(ASTERISK)) {
  				return null;
  			} else if (s.length() > 0) {
  				filters.add(s);
  			}
  		}
  		// set a few base properties for ObjetInfo
  		// query name == id (for base type properties)
  		filters.add(PropertyIds.OBJECT_ID);
  		filters.add(PropertyIds.OBJECT_TYPE_ID);
  		filters.add(PropertyIds.BASE_TYPE_ID);
  		return filters;
  	}
  
  	private Action convertKeyToAction(String key) {
  		// NavigationServices
  		if (PermissionMapping.CAN_GET_DESCENDENTS_FOLDER.equals(key))
  			return Action.CAN_GET_DESCENDANTS;
  		if (PermissionMapping.CAN_GET_CHILDREN_FOLDER.equals(key))
  			return Action.CAN_GET_CHILDREN;
  		if (PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT.equals(key))
  			return Action.CAN_GET_FOLDER_PARENT;
  		if (PermissionMapping.CAN_GET_PARENTS_FOLDER.equals(key))
  			return Action.CAN_GET_OBJECT_PARENTS;
  		// Object Services
  		if (PermissionMapping.CAN_CREATE_DOCUMENT_FOLDER.equals(key))
  			return Action.CAN_CREATE_DOCUMENT;
  		if (PermissionMapping.CAN_CREATE_FOLDER_FOLDER.equals(key))
  			return Action.CAN_CREATE_FOLDER;
  		// FIXME the constant already implemented?
  		// if (PermissionMapping.CAN_CREATE_POLICY_FOLDER.equals(key))
  		// return null;
  		if (PermissionMapping.CAN_CREATE_RELATIONSHIP_SOURCE.equals(key))
  			return Action.CAN_CREATE_RELATIONSHIP;
  		if (PermissionMapping.CAN_CREATE_RELATIONSHIP_TARGET.equals(key))
  			return Action.CAN_CREATE_RELATIONSHIP;
  		if (PermissionMapping.CAN_GET_PROPERTIES_OBJECT.equals(key))
  			return Action.CAN_GET_PROPERTIES;
  		if (PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT.equals(key))
  			return Action.CAN_UPDATE_PROPERTIES;
  		if (PermissionMapping.CAN_MOVE_OBJECT.equals(key))
  			return Action.CAN_MOVE_OBJECT;
  		if (PermissionMapping.CAN_MOVE_TARGET.equals(key))
  			return Action.CAN_MOVE_OBJECT;
  		if (PermissionMapping.CAN_MOVE_SOURCE.equals(key))
  			return Action.CAN_MOVE_OBJECT;
  		if (PermissionMapping.CAN_DELETE_OBJECT.equals(key))
  			return Action.CAN_DELETE_OBJECT;
  		if (PermissionMapping.CAN_VIEW_CONTENT_OBJECT.equals(key))
  			return Action.CAN_GET_CONTENT_STREAM;
  		if (PermissionMapping.CAN_SET_CONTENT_DOCUMENT.equals(key))
  			return Action.CAN_SET_CONTENT_STREAM;
  		if (PermissionMapping.CAN_DELETE_CONTENT_DOCUMENT.equals(key))
  			return Action.CAN_DELETE_CONTENT_STREAM;
  		if (PermissionMapping.CAN_DELETE_TREE_FOLDER.equals(key))
  			return Action.CAN_DELETE_TREE;
  		// Filing Services
  		if (PermissionMapping.CAN_ADD_TO_FOLDER_OBJECT.equals(key))
  			return Action.CAN_ADD_OBJECT_TO_FOLDER;
  		if (PermissionMapping.CAN_ADD_TO_FOLDER_FOLDER.equals(key))
  			return Action.CAN_ADD_OBJECT_TO_FOLDER;
  		if (PermissionMapping.CAN_REMOVE_FROM_FOLDER_OBJECT.equals(key))
  			return Action.CAN_REMOVE_OBJECT_FROM_FOLDER;
  		if (PermissionMapping.CAN_REMOVE_FROM_FOLDER_FOLDER.equals(key))
  			return Action.CAN_REMOVE_OBJECT_FROM_FOLDER;
  		// Versioning Services
  		if (PermissionMapping.CAN_CHECKOUT_DOCUMENT.equals(key))
  			return Action.CAN_CHECK_OUT;
  		if (PermissionMapping.CAN_CANCEL_CHECKOUT_DOCUMENT.equals(key))
  			return Action.CAN_CANCEL_CHECK_OUT;
  		if (PermissionMapping.CAN_CHECKIN_DOCUMENT.equals(key))
  			return Action.CAN_CHECK_IN;
  		if (PermissionMapping.CAN_GET_ALL_VERSIONS_VERSION_SERIES.equals(key))
  			return Action.CAN_GET_ALL_VERSIONS;
  		// Relationship Services
  		if (PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT.equals(key))
  			return Action.CAN_GET_OBJECT_RELATIONSHIPS;
  		// Policy Services
  		if (PermissionMapping.CAN_ADD_POLICY_OBJECT.equals(key))
  			return Action.CAN_APPLY_POLICY;
  		if (PermissionMapping.CAN_ADD_POLICY_POLICY.equals(key))
  			return Action.CAN_APPLY_POLICY;
  		if (PermissionMapping.CAN_REMOVE_POLICY_OBJECT.equals(key))
  			return Action.CAN_REMOVE_POLICY;
  		if (PermissionMapping.CAN_REMOVE_POLICY_POLICY.equals(key))
  			return Action.CAN_REMOVE_POLICY;
  		if (PermissionMapping.CAN_GET_APPLIED_POLICIES_OBJECT.equals(key))
  			return Action.CAN_GET_APPLIED_POLICIES;
  		// ACL Services
  		if (PermissionMapping.CAN_GET_ACL_OBJECT.equals(key))
  			return Action.CAN_GET_ACL;
  		if (PermissionMapping.CAN_APPLY_ACL_OBJECT.equals(key))
  			return Action.CAN_APPLY_ACL;
  
  		return null;
  	}
  
  	@Override
  	public org.apache.chemistry.opencmis.commons.data.Acl compileAcl(Acl acl, Boolean isInherited,
  			Boolean onlyBasicPermissions) {
  		// Default to FALSE
  		boolean obp = (onlyBasicPermissions == null) ? false : onlyBasicPermissions;
  
  		AccessControlListImpl cmisAcl = new AccessControlListImpl();
  		cmisAcl.setAces(new ArrayList<org.apache.chemistry.opencmis.commons.data.Ace>());
  		if (acl != null) {
  			// Set local ACEs
  			buildCmisAce(cmisAcl, true, acl.getLocalAces(), obp);
  
  			// Set inherited ACEs
  			buildCmisAce(cmisAcl, false, acl.getInheritedAces(), obp);
  		}
  
  		// Set "exact" property
  		cmisAcl.setExact(true);
  
  		// Set "inherited" property, which is out of bounds to CMIS
  		String namespace = CmisExtensionToken.ACL_INHERITANCE_NAMESPACE;
  		// boolean iht = (isInherited == null)? false : isInherited;
  		CmisExtensionElementImpl inherited = new CmisExtensionElementImpl(namespace,
  				CmisExtensionToken.ACL_INHERITANCE_INHERITED, null, String.valueOf(isInherited));
  		List<CmisExtensionElement> exts = new ArrayList<CmisExtensionElement>();
  		exts.add(inherited);
  		cmisAcl.setExtensions(exts);
  
  		return cmisAcl;
  	}
  
  	private void buildCmisAce(AccessControlListImpl cmisAcl, boolean direct, List<Ace> aces,
  			boolean onlyBasicPermissions) {
  		if (CollectionUtils.isNotEmpty(aces)) {
  			for (Ace ace : aces) {
  				// Set principal
  				Principal principal = new AccessControlPrincipalDataImpl(ace.getPrincipalId());
  
  				// Set permissions
  				List<String> permissions = new ArrayList<String>();
  				if (onlyBasicPermissions && CollectionUtils.isNotEmpty(ace.getPermissions())) {
  					HashMap<String, String> map = aclCapabilities.getBasicPermissionConversion();
  
  					// Translate permissions as CMIS Basic permissions
  					for (String p : ace.getPermissions()) {
  						permissions.add(map.get(p));
  					}
  				} else {
  					permissions = ace.getPermissions();
  				}
  
  				// Build CMIS ACE
  				AccessControlEntryImpl cmisAce = new AccessControlEntryImpl(principal, permissions);
  
  				// Set direct flag
  				cmisAce.setDirect(direct);
  
  				cmisAcl.getAces().add(cmisAce);
  			}
  		}
  	}
  
  	public void setRepositoryInfoMap(RepositoryInfoMap repositoryInfoMap) {
  		this.repositoryInfoMap = repositoryInfoMap;
  	}
  
  	public void setRepositoryService(RepositoryService repositoryService) {
  		this.repositoryService = repositoryService;
  	}
  
  	public void setContentService(ContentService contentService) {
  		this.contentService = contentService;
  	}
  
  	public void setPermissionService(PermissionService permissionService) {
  		this.permissionService = permissionService;
  	}
  
  	public void setAclCapabilities(AclCapabilities aclCapabilities) {
  		this.aclCapabilities = aclCapabilities;
  	}
  
  	public void setTypeManager(TypeManager typeManager) {
  		this.typeManager = typeManager;
  	}
  
  	public void setNemakiCachePool(NemakiCachePool nemakiCachePool) {
  		this.nemakiCachePool = nemakiCachePool;
  	}
  
  	public void setPropertyManager(PropertyManager propertyManager) {
  		this.propertyManager = propertyManager;
  	}
  
  	public void setSortUtil(SortUtil sortUtil) {
  		this.sortUtil = sortUtil;
  	}
  
  }