/*
 * Copyright (C) 2005-2015 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see .
 */
package org.alfresco.opencmis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.opencmis.dictionary.CMISDictionaryService;
import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper;
import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper;
import org.alfresco.opencmis.search.CMISQueryOptions;
import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode;
import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator;
import org.alfresco.repo.action.executer.AddFeaturesActionExecuter;
import org.alfresco.repo.audit.AuditComponent;
import org.alfresco.repo.audit.AuditServiceImpl;
import org.alfresco.repo.audit.UserAuditFilter;
import org.alfresco.repo.audit.model.AuditModelRegistryImpl;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.domain.audit.AuditDAO;
import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.node.archive.NodeArchiveService;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.version.VersionableAspect;
import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.lock.LockService;
import org.alfresco.service.cmr.lock.LockType;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.rule.Rule;
import org.alfresco.service.cmr.rule.RuleService;
import org.alfresco.service.cmr.rule.RuleType;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.tagging.TaggingService;
import org.alfresco.service.cmr.version.Version;
import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.cmr.version.VersionType;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.Pair;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.Ace;
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement;
import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
import org.apache.chemistry.opencmis.commons.data.ObjectData;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderData;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList;
import org.apache.chemistry.opencmis.commons.data.ObjectList;
import org.apache.chemistry.opencmis.commons.data.Properties;
import org.apache.chemistry.opencmis.commons.data.PropertyData;
import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
import org.apache.chemistry.opencmis.commons.enums.Action;
import org.apache.chemistry.opencmis.commons.enums.ChangeType;
import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisUpdateConflictException;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.CmisExtensionElementImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ExtensionDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerDefinitionImpl;
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.server.AbstractServiceFactory;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.commons.server.CmisService;
import org.apache.chemistry.opencmis.commons.spi.Holder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.extensions.webscripts.GUID;
/**
 * OpenCMIS tests.
 * 
 * @author steveglover
 *
 */
public class CMISTest
{
    private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(new String[]{ApplicationContextHelper.CONFIG_LOCATIONS[0],"classpath:test-cmisinteger_modell-context.xml"});
    private FileFolderService fileFolderService;
    private TransactionService transactionService;
    private NodeService nodeService;
    private ContentService contentService;
    private Repository repositoryHelper;
    private VersionService versionService;
    private LockService lockService;
    private TaggingService taggingService;
    private NamespaceService namespaceService;
    private AuthorityService authorityService;
    private AuditModelRegistryImpl auditSubsystem;
    private PermissionService permissionService;
	private DictionaryDAO dictionaryDAO;
    private CMISDictionaryService cmisDictionaryService;
    private AuditDAO auditDAO;
    private ActionService actionService;
    private RuleService ruleService;
    private NodeArchiveService nodeArchiveService;
    private DictionaryService dictionaryService;
    private VersionableAspect versionableAspect;
    private AlfrescoCmisServiceFactory factory;
	
    private CMISConnector cmisConnector;
    
    private NodeDAO nodeDAO;
    
    /**
     * Test class to provide the service factory
     * 
     * @author Derek Hulley
     * @since 4.0
     */
    public static class TestCmisServiceFactory extends AbstractServiceFactory
    {
        private static AlfrescoCmisServiceFactory serviceFactory = (AlfrescoCmisServiceFactory) ctx.getBean("CMISServiceFactory");
        @Override
        public void init(Map parameters)
        {
        	serviceFactory.init(parameters);
        }
        @Override
        public void destroy()
        {
        }
        @Override
        public CmisService getService(CallContext context)
        {
            return serviceFactory.getService(context);
        }
    }
    
    /**
     * Test class to provide the service factory
     * 
     * @author Derek Hulley
     * @since 4.0
     */
    public static class TestCmisServiceFactory11 extends AbstractServiceFactory
    {
        private static AlfrescoCmisServiceFactory serviceFactory = (AlfrescoCmisServiceFactory) ctx.getBean("CMISServiceFactory1.1");
        @Override
        public void init(Map parameters)
        {
            serviceFactory.init(parameters);
        }
        @Override
        public void destroy()
        {
        }
        @Override
        public CmisService getService(CallContext context)
        {
            return serviceFactory.getService(context);
        }
    }
    public static class SimpleCallContext implements CallContext
    {
    	private final Map contextMap = new HashMap();
    	private CmisVersion cmisVersion;
    	public SimpleCallContext(String user, String password, CmisVersion cmisVersion)
    	{
    		contextMap.put(USERNAME, user);
    		contextMap.put(PASSWORD, password);
    		this.cmisVersion = cmisVersion;
    	}
    	public String getBinding()
    	{
    		return BINDING_LOCAL;
    	}
    	public Object get(String key)
    	{
    		return contextMap.get(key);
    	}
    	public String getRepositoryId()
    	{
    		return (String) get(REPOSITORY_ID);
    	}
    	public String getUsername()
    	{
    		return (String) get(USERNAME);
    	}
    	public String getPassword()
    	{
    		return (String) get(PASSWORD);
    	}
    	public String getLocale()
    	{
    		return null;
    	}
    	public BigInteger getOffset()
    	{
    		return (BigInteger) get(OFFSET);
    	}
    	public BigInteger getLength()
    	{
    		return (BigInteger) get(LENGTH);
    	}
    	public boolean isObjectInfoRequired()
    	{
    		return false;
    	}
    	public File getTempDirectory()
    	{
    		return null;
    	}
    	public int getMemoryThreshold()
    	{
    		return 0;
    	}
        public long getMaxContentSize()
        {
            return Long.MAX_VALUE;
        }
        @Override
        public boolean encryptTempFiles()
        {
            return false;
        }
        @Override
        public CmisVersion getCmisVersion()
        {
            return cmisVersion;
        }
    }
    @Before
    public void before()
    {
        this.actionService = (ActionService)ctx.getBean("actionService");
        this.ruleService = (RuleService)ctx.getBean("ruleService");
    	this.fileFolderService = (FileFolderService)ctx.getBean("FileFolderService");
    	this.transactionService = (TransactionService)ctx.getBean("transactionService");
    	this.nodeService = (NodeService)ctx.getBean("NodeService");
    	this.contentService = (ContentService)ctx.getBean("ContentService");
        this.versionService = (VersionService) ctx.getBean("versionService");
        this.lockService = (LockService) ctx.getBean("lockService");
        this.taggingService = (TaggingService) ctx.getBean("TaggingService");
        this.namespaceService = (NamespaceService) ctx.getBean("namespaceService");
        this.repositoryHelper = (Repository)ctx.getBean("repositoryHelper");
    	this.factory = (AlfrescoCmisServiceFactory)ctx.getBean("CMISServiceFactory");
        this.versionService = (VersionService) ctx.getBean("versionService");
    	this.cmisConnector = (CMISConnector) ctx.getBean("CMISConnector");
        this.nodeDAO = (NodeDAO) ctx.getBean("nodeDAO");
        this.authorityService = (AuthorityService)ctx.getBean("AuthorityService");
        this.auditSubsystem = (AuditModelRegistryImpl) ctx.getBean("Audit");
        this.permissionService = (PermissionService) ctx.getBean("permissionService");
    	this.dictionaryDAO = (DictionaryDAO)ctx.getBean("dictionaryDAO");
    	this.cmisDictionaryService = (CMISDictionaryService)ctx.getBean("OpenCMISDictionaryService1.1");
        this.auditDAO = (AuditDAO) ctx.getBean("auditDAO");
        this.nodeArchiveService = (NodeArchiveService) ctx.getBean("nodeArchiveService");
        this.dictionaryService = (DictionaryService) ctx.getBean("dictionaryService");
        
        this.versionableAspect = (VersionableAspect) ctx.getBean("versionableAspect");
        this.versionableAspect.setEnableAutoVersionOnUpdateProps(true);
    }
    
    @After
    public void after()
    {
        this.versionableAspect.setEnableAutoVersionOnUpdateProps(false);
    }
    
    /**
     * MNT-10868 CMIS: Incorrect value of Latest Major version on Versions and Properties tabs.
     */
    @Test
    public void testIsLatestMajorVersionMNT10868()
    {
    	 CallContext context = new SimpleCallContext("admin", "admin", CmisVersion.CMIS_1_0);
    	
    	 String repositoryId = null;
    	 
    	 AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
         CmisService cmisService = factory.getService(context);
         try
         {
             // get repository id
             List repositories = cmisService.getRepositoryInfos(null);
             assertTrue(repositories.size() > 0);
             RepositoryInfo repo = repositories.get(0);
             repositoryId = repo.getId();
             final String folderName = "testfolder" + GUID.generate();
             final String docName = "testdoc.txt" + GUID.generate();
             final FileInfo fileInfo = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
             {
                 @Override
                 public FileInfo execute() throws Throwable
                 {
                     NodeRef companyHomeNodeRef = repositoryHelper.getCompanyHome();
                     FileInfo folderInfo = fileFolderService.create(companyHomeNodeRef, folderName, ContentModel.TYPE_FOLDER);
                     nodeService.setProperty(folderInfo.getNodeRef(), ContentModel.PROP_NAME, folderName);
                    
                     FileInfo fileInfo = fileFolderService.create(folderInfo.getNodeRef(), docName, ContentModel.TYPE_CONTENT);
                     nodeService.setProperty(fileInfo.getNodeRef(), ContentModel.PROP_NAME, docName);
                     nodeService.addAspect(fileInfo.getNodeRef(), ContentModel.ASPECT_VERSIONABLE, null);
                     return fileInfo;
                 }
             });
             
             ObjectData objectData = cmisService.getObjectByPath(repositoryId, "/" + folderName + "/" + docName, null, true, IncludeRelationships.NONE, null, false, true, null);
             
             PropertyData> pd = getPropIsLatestMajorVersion(objectData);
             
             if (pd != null)
             {
                 assertTrue("The CMISDictionaryModel.PROP_IS_LATEST_MAJOR_VERSION should be true as major version was created", (Boolean) pd.getValues().get(0));
             }
                
             nodeService.setProperty(fileInfo.getNodeRef(), ContentModel.PROP_TITLE, docName);
             
             // Create minor version   
             transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback