/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * 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 <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.tenant;

import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ApplicationModel;
import org.alfresco.model.ContentModel;
import org.alfresco.query.PagingRequest;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.node.archive.NodeArchiveService;
import org.alfresco.repo.node.archive.RestoreNodeReport;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.impl.AccessPermissionImpl;
import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.admin.RepoAdminService;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.model.FileFolderService;
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.InvalidNodeRefException;
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.search.CategoryService;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.OwnableService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.security.PersonService.PersonInfo;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.site.SiteVisibility;
import org.alfresco.service.cmr.usage.UsageService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.testing.category.LuceneTests;
import org.alfresco.util.testing.category.RedundantTests;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.FixMethodOrder;
import org.junit.experimental.categories.Category;
import org.junit.runners.MethodSorters;
import org.springframework.context.ApplicationContext;

import junit.framework.TestCase;

/**
 * @author janv
 * since 3.0
 */
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Category({OwnJVMTestsCategory.class, LuceneTests.class})
public class MultiTDemoTest extends TestCase
{
    private static Log logger = LogFactory.getLog(MultiTDemoTest.class);
    
    private ApplicationContext ctx; 
    
    private NodeService nodeService;
    private NodeArchiveService nodeArchiveService;
    private NamespaceService namespaceService;
    private MutableAuthenticationService authenticationService;
    private PersonService personService;
    private SiteService siteService;
    private SearchService searchService;
    private ContentService contentService;
    private PermissionService permissionService;
    private OwnableService ownableService;
    private TenantAdminService tenantAdminService;
    private TenantService tenantService;
    private AuthorityService authorityService;
    private CategoryService categoryService;
    private CheckOutCheckInService cociService;
    private RepoAdminService repoAdminService;
    private DictionaryService dictionaryService;
    private UsageService usageService;
    private TransactionService transactionService;
    private FileFolderService fileFolderService;
    private Repository repositoryHelper;
    
    public static int NUM_TENANTS = 2;
    
    public static final String TEST_RUN = System.currentTimeMillis()+"";
    public static final String TEST_TENANT_DOMAIN = TEST_RUN+".my.test";
    public static final String TEST_TENANT_DOMAIN2 = TEST_TENANT_DOMAIN+"2";
    
    public static List<String> tenants;
    
    static 
    {
        tenants = new ArrayList<String>(NUM_TENANTS);
        for (int i = 1; i <= NUM_TENANTS; i++)
        {
            tenants.add(TEST_TENANT_DOMAIN+i);
        }
    }
    
    public static final String ROOT_DIR = "./tenantstores";
    
    public static final String DEFAULT_ADMIN_PW = "admin";
    
    public static final String DEFAULT_GUEST_UN = "guest";
    public static final String DEFAULT_GUEST_PW = "thiscanbeanything";
    
    public static final String TEST_USER1 = "alice";
    public static final String TEST_USER2 = "bob";
    public static final String TEST_USER3 = "eve";
    public static final String TEST_USER4 = "fred";
    
    private static Set<StoreRef> DEFAULT_STORES = new HashSet<StoreRef>(Arrays.asList(new StoreRef[]
    {
        new StoreRef("workspace://lightWeightVersionStore"), new StoreRef("system://system"),
        new StoreRef("workspace://version2Store"), new StoreRef("user://alfrescoUserStore"),
        new StoreRef("workspace://SpacesStore"), new StoreRef("archive://SpacesStore")
    }));
    private static final int DEFAULT_STORE_COUNT = DEFAULT_STORES.size();
        
    public static StoreRef SPACES_STORE = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
    
    
    public MultiTDemoTest()
    {
        super();
    }
    
    
    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        ctx = ApplicationContextHelper.getApplicationContext(new String[] { ApplicationContextHelper.CONFIG_LOCATIONS[0], "classpath:tenant/mt-*context.xml" });

        nodeService = (NodeService) ctx.getBean("NodeService");
        nodeArchiveService = (NodeArchiveService) ctx.getBean("nodeArchiveService");
        namespaceService = (NamespaceService) ctx.getBean("NamespaceService");
        authenticationService = (MutableAuthenticationService) ctx.getBean("AuthenticationService");
        tenantAdminService = (TenantAdminService) ctx.getBean("tenantAdminService");
        tenantService = (TenantService) ctx.getBean("tenantService");
        personService = (PersonService) ctx.getBean("PersonService");
        searchService = (SearchService) ctx.getBean("SearchService");
        contentService = (ContentService) ctx.getBean("ContentService");
        permissionService = (PermissionService) ctx.getBean("PermissionService");
        ownableService = (OwnableService) ctx.getBean("OwnableService");
        authorityService = (AuthorityService) ctx.getBean("AuthorityService");
        categoryService = (CategoryService) ctx.getBean("CategoryService");
        cociService = (CheckOutCheckInService) ctx.getBean("CheckoutCheckinService");
        repoAdminService = (RepoAdminService) ctx.getBean("RepoAdminService");
        dictionaryService = (DictionaryService) ctx.getBean("DictionaryService");
        usageService = (UsageService) ctx.getBean("usageService");
        transactionService = (TransactionService) ctx.getBean("TransactionService");
        fileFolderService = (FileFolderService) ctx.getBean("FileFolderService");
        ownableService = (OwnableService) ctx.getBean("OwnableService");
        repositoryHelper = (Repository) ctx.getBean("repositoryHelper");
        siteService = (SiteService) ctx.getBean("SiteService");
        
        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); // authenticate as super-admin
        
        createTenants();
        createUsers();
    }
    
    @Override
    protected void tearDown() throws Exception
    {
        super.tearDown();
    }
    
    private void createTenants()
    {
        for (final String tenantDomain : tenants)
        {
            createTenant(tenantDomain);
        }
    }
    
    private void createTenant(final String tenantDomain)
    {
        // create tenants (if not already created)
        TenantUtil.runAsSystemTenant(new TenantRunAsWork<Object>()
        {
            public Object doWork() throws Exception
            {
                if (! tenantAdminService.existsTenant(tenantDomain))
                {
                    //tenantAdminService.createTenant(tenantDomain, DEFAULT_ADMIN_PW.toCharArray(), ROOT_DIR + "/" + tenantDomain);
                    tenantAdminService.createTenant(tenantDomain, (DEFAULT_ADMIN_PW+" "+tenantDomain).toCharArray(), null); // use default root dir
                    
                    logger.info("Created tenant " + tenantDomain);
                }
                
                return null;
            }
        }, TenantService.DEFAULT_DOMAIN);
    }
    
    private void deleteTenant(final String tenantDomain)
    {
        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Object>()
        {
            public Object execute() throws Throwable
            {
                // delete tenant (if it exists)
                TenantUtil.runAsSystemTenant(new TenantRunAsWork<Object>()
                {
                    public Object doWork() throws Exception
                    {
                        if (tenantAdminService.existsTenant(tenantDomain))
                        {
                            // TODO: WARNING: HACK for ALF-19155: MT deleteTenant does not work
                            //       PersonService prevents 'guest' authorities from being deleted
                            {
                                BehaviourFilter behaviourFilter = (BehaviourFilter) ctx.getBean("policyBehaviourFilter");
                                behaviourFilter.disableBehaviour(ContentModel.TYPE_PERSON);
                                behaviourFilter.disableBehaviour(ContentModel.ASPECT_UNDELETABLE);
                            }
                            tenantAdminService.deleteTenant(tenantDomain);
                            
                            logger.info("Deleted tenant " + tenantDomain);
                        }
                        
                        return null;
                    }
                }, TenantService.DEFAULT_DOMAIN);
                return null;
            }
        });
    }
    
    private void createUsers()
    {
        for (final String tenantDomain : tenants)
        {
            String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain);
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    createUser(TEST_USER1, tenantDomain, TEST_USER1+" "+tenantDomain);
                    createUser(TEST_USER2, tenantDomain, TEST_USER2+" "+tenantDomain);
                    
                    if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
                    {
                        createUser(TEST_USER3, tenantDomain, TEST_USER3+" "+tenantDomain);
                    }
                    
                    return null;
                }
            }, tenantAdminName, tenantDomain);
        }
    }
    
    /*
    public void test00_Setup() throws Throwable
    {
        // test common setup (see "setUp")
    }
    */
    
    public void test01CreateTenants() throws Throwable
    {
        // ignore common setup - test here explicitly
        
        logger.info("Create tenants");
        
        final String tenantDomain1 = TEST_RUN+".one.createTenant";
        final String tenantDomain2 = TEST_RUN+".two.createTenant";
        
        String[] tenantDomains = new String[] {tenantDomain1, tenantDomain2 };
        
        for (final String tenantDomain : tenantDomains)
        {
            createTenant(tenantDomain);
        }
        
        // check default (super-tenant) domain
        
        List<PersonInfo> persons = personService.getPeople(null, true, null, new PagingRequest(0, Integer.MAX_VALUE, null)).getPage();
        
        //assertEquals(2, personRefs.size()); // super-tenant: admin, guest (note: checking for 2 assumes that this test is run in a fresh bootstrap env)
        
        for (PersonInfo person : persons)
        {
            String userName = person.getUserName();
            for (final String tenantDomain : tenantDomains)
            {
                assertFalse("Unexpected (tenant) user: "+userName, userName.endsWith(tenantDomain));
            }
        }
    }
    
    
    private void deleteTestAuthoritiesForTenant(final String[] uniqueGroupNames, final String userName)
    {
        String tenantDomain = tenantService.getUserDomain(userName);
        
        // Check deletion for tenant1
        TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
        {
            public Object doWork() throws Exception
            {
                // find person
                NodeRef personNodeRef = personService.getPerson(userName);
                NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER);
                assertNotNull(homeSpaceRef);
                
                // Delete authorities
                for (int i = 0; i < uniqueGroupNames.length; i++)
                {
                    authorityService.deleteAuthority("GROUP_" + uniqueGroupNames[i]);
                }
                return null;
            }
        }, userName, tenantDomain);
    }
    
    private void createTestAuthoritiesForTenant(final String[] uniqueGroupNames, final String userName)
    {
        String tenantDomain = tenantService.getUserDomain(userName);

        // Create groups for tenant
        TenantUtil.runAsUserTenant(new TenantRunAsWork<Void>()
        {
            public Void doWork() throws Exception
            {
                transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Void>()
                {
                    @Override
                    public Void execute() throws Throwable
                    {
                        // find person
                        NodeRef personNodeRef = personService.getPerson(userName);
                        NodeRef homeSpaceRef = (NodeRef) nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER);
                        assertNotNull(homeSpaceRef);

                        for (int i = 0; i < uniqueGroupNames.length; i++)
                        {
                            authorityService.createAuthority(AuthorityType.GROUP, uniqueGroupNames[i]);
                            permissionService.setPermission(homeSpaceRef, "GROUP_" + uniqueGroupNames[i], "Consumer", true);
                        }

                        return null;
                    }
                }, false);
                return null;
            }
        }, userName, tenantDomain);
    }
    
    private void checkTestAuthoritiesPresence(final String[] uniqueGroupNames, final String userName, final boolean shouldPresent)
    {
        String tenantDomain = tenantService.getUserDomain(userName);
        
        // Check that created permissions are not visible to tenant 2
        TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
        {
            public Object doWork() throws Exception
            {
                NodeRef personNodeRef = personService.getPerson(userName);
                NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER);
                Set<AccessPermission> perms = permissionService.getAllSetPermissions(homeSpaceRef);
                Set<String> auths = authorityService.getAllAuthorities(AuthorityType.GROUP);
                
                for (int i = 0; i < uniqueGroupNames.length; i++)
                {
                    AccessPermission toCheck = new AccessPermissionImpl("Consumer", AccessStatus.ALLOWED, "GROUP_" + uniqueGroupNames[i], 0);
                    if (shouldPresent)
                    {
                        assertTrue(auths.contains("GROUP_" + uniqueGroupNames[i]));
                        assertTrue(perms.contains(toCheck));
                    }
                    else
                    {
                        assertTrue(!auths.contains("GROUP_" + uniqueGroupNames[i]));
                        assertTrue(!perms.contains(toCheck));
                    }
                }
                
                return null;
            }
        }, userName, tenantDomain);
    }
    
    private void createGroup(String shortName, String parentShortName)
    {
        // create new Group using authority Service
        String groupName = this.authorityService.getName(AuthorityType.GROUP, shortName);
        if (this.authorityService.authorityExists(groupName) == false)
        {
           String parentGroupName = null;
           if (parentShortName != null)
           {
               parentGroupName = this.authorityService.getName(AuthorityType.GROUP, parentShortName);
               if (this.authorityService.authorityExists(parentGroupName) == false)
               {
                   logger.warn("Parent group does not exist: " + parentShortName);
                   return;
               }
           }
           
           this.authorityService.createAuthority(AuthorityType.GROUP, shortName);
           
           if (parentGroupName != null)
           {
               addToGroup(parentShortName, groupName);
           }
        }
        else
        {
            logger.warn("Group already exists: " + shortName);
        }
    }
    
    private void addToGroup(String parentGroupShortName, String authorityName)
    {
        String parentGroupName = this.authorityService.getName(AuthorityType.GROUP, parentGroupShortName);
        authorityService.addAuthority(parentGroupName, authorityName);
    }
    
    private NodeRef createUser(String baseUserName, String tenantDomain, String password)
    {
        String userName = tenantService.getDomainUser(baseUserName, tenantDomain);
        
        NodeRef personNodeRef = null;
        
        if (! this.authenticationService.authenticationExists(userName))
        {
            NodeRef baseHomeFolder = getUserHomesNodeRef(SPACES_STORE);
            
            // Create the users home folder
            NodeRef homeFolder = createHomeSpaceFolderNode(
                                                baseHomeFolder,
                                                baseUserName,
                                                userName);
            
            // Create the authentication
            this.authenticationService.createAuthentication(userName, password.toCharArray());
            
            // Create the person
            Map<QName, Serializable> personProperties = new HashMap<QName, Serializable>();
            personProperties.put(ContentModel.PROP_USERNAME, userName);
            personProperties.put(ContentModel.PROP_HOMEFOLDER, homeFolder);
            personProperties.put(ContentModel.PROP_FIRSTNAME, baseUserName);
            personProperties.put(ContentModel.PROP_LASTNAME, baseUserName+"-"+tenantDomain); // add domain suffix here for demo only
            personProperties.put(ContentModel.PROP_EMAIL, userName);
            
            personNodeRef = this.personService.createPerson(personProperties);
            
            // ensure the user can access their own Person object
            this.permissionService.setPermission(personNodeRef, userName, permissionService.getAllPermission(), true);
            
            NodeRef checkHomeSpaceRef = (NodeRef)nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER);
            assertNotNull(checkHomeSpaceRef);
            
            logger.info("Created user " + userName);
        }
        else
        {
            personNodeRef = personService.getPerson(userName);
            
            logger.info("Found existing user " + userName);
        }
        
        return personNodeRef;
    }
    
    private void loginLogoutUser(String username, String password)
    {
        // authenticate via the authentication service
        authenticationService.authenticate(username, password.toCharArray());
        
        // set the user name as stored by the back end 
        username = authenticationService.getCurrentUserName();
        
        NodeRef personRef = personService.getPerson(username);
        NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER);
        
        // check that the home space node exists - else user cannot login
        if (nodeService.exists(homeSpaceRef) == false)
        {
           throw new InvalidNodeRefException(homeSpaceRef);
        }
        
        // logout
        authenticationService.clearCurrentSecurityContext();
    }
    
    private NodeRef getUserHomesNodeRef(StoreRef storeRef)
    {
        // get the "User Homes" location
        return findFolderNodeRef(storeRef, "/app:company_home/app:user_homes");
    }
    
    private NodeRef getWebClientExtensionNodeRef(StoreRef storeRef)
    {
        // get the "Web Client Extensions" location
        return findFolderNodeRef(storeRef, "/app:company_home/app:dictionary/app:webclient_extension");
    }
    
    private NodeRef findFolderNodeRef(StoreRef storeRef, String folderXPath)
    {
        NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef);
        
        List<NodeRef> nodeRefs = searchService.selectNodes(storeRootNodeRef, folderXPath, null, namespaceService, false);
        
        NodeRef folderNodeRef = null;
        if (nodeRefs.size() != 1)
        {
            throw new AlfrescoRuntimeException("Cannot find folder location: " + folderXPath);
        }
        else
        {
            folderNodeRef = nodeRefs.get(0);
        }
        return folderNodeRef;
    }
    
    private NodeRef createFolderNode(NodeRef parentFolderNodeRef, String nameValue)
    {
        if (nameValue != null)
        {       
            Map<QName, Serializable> folderProps = new HashMap<QName, Serializable>();
            folderProps.put(ContentModel.PROP_NAME, nameValue);
            
            return this.nodeService.createNode(
                    parentFolderNodeRef, 
                    ContentModel.ASSOC_CONTAINS, 
                    QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, nameValue),
                    ContentModel.TYPE_FOLDER,
                    folderProps).getChildRef();
        }
        
        return null;
    }
    
    private NodeRef createCategory(StoreRef storeRef, NodeRef parentCategoryRef, String name, String description)
    {
        // create category using categoryservice
        NodeRef ref;
        if (parentCategoryRef == null)
        {
           ref = this.categoryService.createRootCategory(storeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE, name);
        }
        else
        {
           ref = categoryService.createCategory(parentCategoryRef, name);
        }
        
        // apply the titled aspect - for description
        Map<QName, Serializable> titledProps = new HashMap<QName, Serializable>(1, 1.0f);
        titledProps.put(ContentModel.PROP_DESCRIPTION, description);
        this.nodeService.addAspect(ref, ContentModel.ASPECT_TITLED, titledProps);
        
        return ref;
    }
    
    private NodeRef createHomeSpaceFolderNode(NodeRef folderNodeRef, String spaceName, String userName)
    {
        if (spaceName != null)
        {       
            Map<QName, Serializable> folderProps = new HashMap<QName, Serializable>();
            folderProps.put(ContentModel.PROP_NAME, spaceName);
            
            NodeRef nodeRef = this.nodeService.createNode(
                                                folderNodeRef, 
                                                ContentModel.ASSOC_CONTAINS, 
                                                QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, spaceName),
                                                ContentModel.TYPE_FOLDER,
                                                folderProps).getChildRef();
            
            // apply the uifacets aspect - icon and title props
            Map<QName, Serializable> uiFacetsProps = new HashMap<QName, Serializable>(3);
            uiFacetsProps.put(ApplicationModel.PROP_ICON, "space-icon-default");
            uiFacetsProps.put(ContentModel.PROP_TITLE, spaceName);
            this.nodeService.addAspect(nodeRef, ApplicationModel.ASPECT_UIFACETS, uiFacetsProps);
            
            setupHomeSpacePermissions(nodeRef, userName);
            
            return nodeRef;
        }
        
        return null;
    }
    
    private void setupHomeSpacePermissions(NodeRef homeSpaceRef, String userName)
    {
       // Admin Authority has full permissions by default (automatic - set in the permission config)
       // give full permissions to the new user
       this.permissionService.setPermission(homeSpaceRef, userName, permissionService.getAllPermission(), true);
       
       // by default other users will only have GUEST access to the space contents
       String permission = "Consumer";
       
       if (permission != null && permission.length() != 0)
       {
          this.permissionService.setPermission(homeSpaceRef, permissionService.getAllAuthorities(), permission, true);
       }
       
       // the new user is the OWNER of their own space and always has full permissions
       this.ownableService.setOwner(homeSpaceRef, userName);
       this.permissionService.setPermission(homeSpaceRef, permissionService.getOwnerAuthority(), permissionService.getAllPermission(), true);
       
       // now detach (if we did this first we could not set any permissions!)
       this.permissionService.setInheritParentPermissions(homeSpaceRef, false);
    }
    
    private NodeRef getHomeSpaceFolderNode(String userName)
    {
        return (NodeRef)this.nodeService.getProperty(personService.getPerson(userName), ContentModel.PROP_HOMEFOLDER);
    }
    
    private NodeRef addContent(NodeRef spaceRef, String name, String textData, String mimeType)
    {
        Map<QName, Serializable> contentProps = new HashMap<QName, Serializable>();
        contentProps.put(ContentModel.PROP_NAME, name);
        
        ChildAssociationRef association = nodeService.createNode(spaceRef,
                ContentModel.ASSOC_CONTAINS,
                QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name),
                ContentModel.TYPE_CONTENT,
                contentProps);
        
        NodeRef content = association.getChildRef();
        
        // add titled aspect (for Web Client display)
        Map<QName, Serializable> titledProps = new HashMap<QName, Serializable>();
        titledProps.put(ContentModel.PROP_TITLE, name);
        titledProps.put(ContentModel.PROP_DESCRIPTION, name);
        this.nodeService.addAspect(content, ContentModel.ASPECT_TITLED, titledProps);
        
        ContentWriter writer = contentService.getWriter(content, ContentModel.PROP_CONTENT, true);
        
        writer.setMimetype(mimeType);
        writer.setEncoding("UTF-8");
        
        writer.putContent(textData);
        
        return content;
    }
    
    private NodeRef addContent(NodeRef spaceRef, String name, InputStream is, String mimeType)
    {
        Map<QName, Serializable> contentProps = new HashMap<QName, Serializable>();
        contentProps.put(ContentModel.PROP_NAME, name);
        
        ChildAssociationRef association = nodeService.createNode(spaceRef,
                ContentModel.ASSOC_CONTAINS, 
                QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), 
                ContentModel.TYPE_CONTENT,
                contentProps);
        
        NodeRef content = association.getChildRef();
        
        // add titled aspect (for Web Client display)
        Map<QName, Serializable> titledProps = new HashMap<QName, Serializable>();
        titledProps.put(ContentModel.PROP_TITLE, name);
        titledProps.put(ContentModel.PROP_DESCRIPTION, name);
        this.nodeService.addAspect(content, ContentModel.ASPECT_TITLED, titledProps);
        
        ContentWriter writer = contentService.getWriter(content, ContentModel.PROP_CONTENT, true);
        
        writer.setMimetype(mimeType);
        writer.setEncoding("UTF-8");
        
        writer.putContent(is);
        
        return content;
    }
    
    private SiteInfo createSite(String siteId)
    {
        SiteInfo siteInfo = siteService.createSite(null, siteId, "title - "+siteId, "description - "+siteId, SiteVisibility.PRIVATE);
        
        // ensure that the Document Library folder is pre-created so that test code can start creating content straight away.
        // At the time of writing V4.1 does not create this folder automatically, but Thor does.
        NodeRef result = siteService.getContainer(siteId, SiteService.DOCUMENT_LIBRARY);
        if (result == null)
        {
            result = siteService.createContainer(siteId, SiteService.DOCUMENT_LIBRARY, ContentModel.TYPE_FOLDER, null);
        }
        
        return siteInfo;
    }
    
    public void test02NonSharedGroupDeletion()
    {
        // ignore common setup - test here explicitly
        
        final String tenantDomain1 = TEST_RUN+".groupdel1";
        final String tenantDomain2 = TEST_RUN+".groupdel2";
        
        try
        {
            final String[] tenantUniqueGroupNames = new String[10];
            final String[] superadminUniqueGroupNames = new String[10];
            for (int i = 0; i < tenantUniqueGroupNames.length; i++)
            {
                tenantUniqueGroupNames[i] = TEST_RUN + "test_group" + i;
                superadminUniqueGroupNames[i] = TEST_RUN + "test_group_sa" + i;
            }
            
            clearUsage(AuthenticationUtil.getAdminUserName());
            
            createTenant(tenantDomain1);
            createTenant(tenantDomain2);
            
            final String tenantAdminName1 = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain1);
            final String tenantAdminName2 = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain2);
            final String superAdmin = "admin";
            
            // Create test authorities that are visible only to tenant1
            clearUsage(tenantDomain1);
            createTestAuthoritiesForTenant(tenantUniqueGroupNames, tenantAdminName1);
            // Check that tenant1's authorities are visible to tenant1
            clearUsage(tenantDomain1);
            checkTestAuthoritiesPresence(tenantUniqueGroupNames, tenantAdminName1, true);
            // Check that tenant1's authorities are not visible to tenant2
            clearUsage(tenantDomain2);
            checkTestAuthoritiesPresence(tenantUniqueGroupNames, tenantAdminName2, false);
            // Check that tenant1's authorities are not visible to super-admin
            checkTestAuthoritiesPresence(tenantUniqueGroupNames, superAdmin, false);
            
            
            // Create test authorities that are visible only to super-admin
            createTestAuthoritiesForTenant(superadminUniqueGroupNames, superAdmin);
            // Check that super-admin's authorities are not visible to tenant1
            clearUsage(tenantDomain1);
            checkTestAuthoritiesPresence(superadminUniqueGroupNames, tenantAdminName1, false);
            // Check that super-admin's authorities are not visible to tenant2
            clearUsage(tenantDomain2);
            checkTestAuthoritiesPresence(superadminUniqueGroupNames, tenantAdminName2, false);
            // Check that super-admin's authorities are visible to super-admin
            checkTestAuthoritiesPresence(superadminUniqueGroupNames, superAdmin, true);
            
            
            // Delete tenant1's authorities
            clearUsage(tenantDomain1);
            deleteTestAuthoritiesForTenant(tenantUniqueGroupNames, tenantAdminName1);
            // Check that tenant1's authorities are not visible to tenant1
            checkTestAuthoritiesPresence(tenantUniqueGroupNames, tenantAdminName1, false);
            
            // Delete super-admin's authorities
            deleteTestAuthoritiesForTenant(superadminUniqueGroupNames, superAdmin);
            // Check that super-admin's authorities are not visible to super-admin
            checkTestAuthoritiesPresence(superadminUniqueGroupNames, superAdmin, false);
        }
        finally
        {
            deleteTenant(tenantDomain1);
            deleteTenant(tenantDomain2);
        }
    }
    
    public void test03SharedGroupDeletion()
    {
        // ignore common setup - test here explicitly
        
        final String tenantDomain1 = TEST_RUN+".groupdel3";
        final String tenantDomain2 = TEST_RUN+".groupdel4";
        
        try
        {
            final String[] commonTenantUniqueGroupNames = new String[10];
            for (int i = 0; i < commonTenantUniqueGroupNames.length; i++)
            {
                commonTenantUniqueGroupNames[i] = TEST_RUN + "test_group" + i;
            }
            
            clearUsage(AuthenticationUtil.getAdminUserName());
            
            createTenant(tenantDomain1);
            createTenant(tenantDomain2);
            
            final String tenantAdminName1 = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain1);
            final String tenantAdminName2 = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain2);
            final String superAdmin = "admin";
            
            // Create test common authorities for tenant1
            clearUsage(tenantDomain1);
            createTestAuthoritiesForTenant(commonTenantUniqueGroupNames, tenantAdminName1);
            // Create test common authorities for tenant2
            clearUsage(tenantDomain2);
            createTestAuthoritiesForTenant(commonTenantUniqueGroupNames, tenantAdminName2);
            // Create test common authorities for super-admin
            createTestAuthoritiesForTenant(commonTenantUniqueGroupNames, superAdmin);
            
            // Check that authorities are visible to tenant1
            clearUsage(tenantDomain1);
            checkTestAuthoritiesPresence(commonTenantUniqueGroupNames, tenantAdminName1, true);
            // Check that authorities are visible to tenant2
            clearUsage(tenantDomain2);
            checkTestAuthoritiesPresence(commonTenantUniqueGroupNames, tenantAdminName2, true);
            // Check that authorities are visible to super-admin
            checkTestAuthoritiesPresence(commonTenantUniqueGroupNames, superAdmin, true);
            
            // Delete tenant1's authorities
            clearUsage(tenantDomain1);
            deleteTestAuthoritiesForTenant(commonTenantUniqueGroupNames, tenantAdminName1);
            // Check that authorities are not visible to tenant1
            clearUsage(tenantDomain1);
            checkTestAuthoritiesPresence(commonTenantUniqueGroupNames, tenantAdminName1, false);
            // Check that authorities are visible to tenant2
            clearUsage(tenantDomain2);
            checkTestAuthoritiesPresence(commonTenantUniqueGroupNames, tenantAdminName2, true);
            // Check that authorities are visible to super-admin
            checkTestAuthoritiesPresence(commonTenantUniqueGroupNames, superAdmin, true);
            
            // Create test common authorities for tenant1
            clearUsage(tenantDomain1);
            createTestAuthoritiesForTenant(commonTenantUniqueGroupNames, tenantAdminName1);
            // Delete tenant2's authorities
            clearUsage(tenantDomain2);
            deleteTestAuthoritiesForTenant(commonTenantUniqueGroupNames, tenantAdminName2);
            // Check that authorities are visible to tenant1
            clearUsage(tenantDomain1);
            checkTestAuthoritiesPresence(commonTenantUniqueGroupNames, tenantAdminName1, true);
            // Check that authorities are not visible to tenant2
            clearUsage(tenantDomain2);
            checkTestAuthoritiesPresence(commonTenantUniqueGroupNames, tenantAdminName2, false);
            // Check that authorities are visible to super-admin
            checkTestAuthoritiesPresence(commonTenantUniqueGroupNames, superAdmin, true);
            
            // Create test common authorities for tenant2
            clearUsage(tenantDomain2);
            createTestAuthoritiesForTenant(commonTenantUniqueGroupNames, tenantAdminName2);
            // Delete super-admin's authorities
            deleteTestAuthoritiesForTenant(commonTenantUniqueGroupNames, superAdmin);
            // Check that authorities are visible to tenant1
            clearUsage(tenantDomain1);
            checkTestAuthoritiesPresence(commonTenantUniqueGroupNames, tenantAdminName1, true);
            // Check that authorities are visible to tenant2
            clearUsage(tenantDomain2);
            checkTestAuthoritiesPresence(commonTenantUniqueGroupNames, tenantAdminName2, true);
            // Check that authorities are not visible to super-admin
            checkTestAuthoritiesPresence(commonTenantUniqueGroupNames, superAdmin, false);
        }
        finally
        {
            AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
            
            deleteTenant(tenantDomain1);
            deleteTenant(tenantDomain2);
        }
    }
    
    
    
    public void test04_ETHREEOH_2015()
    {
        // ignore common setup - test here explicitly
        
        final String tenantDomain1 = TEST_RUN+".one.ethreeoh2015";
        final String tenantDomain2 = TEST_RUN+".two.ethreeoh2015";
        
        clearUsage(AuthenticationUtil.getAdminUserName());
        
        createTenant(tenantDomain1);
        
        String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain1);
        TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
        {
            public Object doWork() throws Exception
            {
                createUser(TEST_USER1, tenantDomain1, TEST_USER1+" "+tenantDomain1);
                
                return null;
            }
        }, tenantAdminName, tenantDomain1);
        
        createTenant(tenantDomain2);
    }
    
    private void clearUsage(String userName)
    {
        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); // authenticate as super-admin
        
        // find person
        final NodeRef personNodeRef = personService.getPerson(userName);
        // clear user usage
        nodeService.setProperty(personNodeRef, ContentModel.PROP_SIZE_CURRENT, new Long(-1L));
        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Void>()
        {
            @Override
            public Void execute() throws Throwable
            {
                usageService.deleteDeltas(personNodeRef);
                return null;
            }
        }, false);
    }
    
    
    public void test05ValidateUsers() throws Throwable
    {
        // use common setup
        logger.info("Validate demo users");
        
        AuthenticationUtil.runAs(new RunAsWork<Object>()
        {
            public Object doWork() throws Exception
            {
                List<PersonInfo> persons = personService.getPeople(null, true, null, new PagingRequest(0, Integer.MAX_VALUE, null)).getPage();
                //assertEquals(2, personRefs.size()); // super-tenant: admin, guest (note: checking for 2 assumes that this test is run in a fresh bootstrap env)
                for (PersonInfo person : persons)
                {
                    String userName = person.getUserName();
                    for (final String tenantDomain : tenants)
                    {
                        assertFalse("Unexpected (tenant) user: "+userName, userName.endsWith(tenantDomain));
                    }
                }
                
                return null;
            }
        }, AuthenticationUtil.getAdminUserName());
        
        try
        {
            for (final String tenantDomain : tenants)
            {
                String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain);
                
                TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
                {
                    public Object doWork() throws Exception
                    {
                        List<PersonInfo> persons = personService.getPeople(null, null, null, new PagingRequest(0, Integer.MAX_VALUE, null)).getPage();
                        
                        for (PersonInfo person : persons)
                        {
                            NodeRef personRef = person.getNodeRef();
                            
                            String userName = (String)nodeService.getProperty(personRef, ContentModel.PROP_USERNAME); 
                            assertTrue(userName.endsWith(tenantDomain));
                            
                            logger.info("Validate users: get all people - found user: "+userName);
                            
                            NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER);
                            assertNotNull(homeSpaceRef);
                        }
                        
                        if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
                        {
                            assertEquals(5, persons.size()); // admin@tenant, guest@tenant, alice@tenant, bob@tenant, eve@tenant
                        }
                        else
                        {
                            assertEquals(4, persons.size()); // admin@tenant, guest@tenant, alice@tenant, bob@tenant
                        }
                        
                        return null;
                    }
                }, tenantAdminName, tenantDomain);
            }
        }   
        catch (Throwable t)
        {
            StringWriter stackTrace = new StringWriter();
            t.printStackTrace(new PrintWriter(stackTrace));
            System.err.println(stackTrace.toString());
            throw t;
        } 
    }
    
    public void test06LoginTenantUsers() throws Throwable
    {
        // use common setup
        logger.info("Login tenant users");
        
        try
        {
            AuthenticationUtil.clearCurrentSecurityContext();
            
            for (final String tenantDomain : tenants)
            {
                loginLogoutUser(tenantService.getDomainUser(TEST_USER1, tenantDomain), TEST_USER1+" "+tenantDomain);
                
                loginLogoutUser(tenantService.getDomainUser(TEST_USER2, tenantDomain), TEST_USER2+" "+tenantDomain);
                
                if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
                {
                    loginLogoutUser(tenantService.getDomainUser(TEST_USER3, tenantDomain), TEST_USER3+" "+tenantDomain);
                }
            }
        }   
        catch (Throwable t)
        {
            StringWriter stackTrace = new StringWriter();
            t.printStackTrace(new PrintWriter(stackTrace));
            System.err.println(stackTrace.toString());
            throw t;
        }
    }
    
    public void test07LoginTenantGuests() throws Throwable
    {
        logger.info("Login tenant guests");
        
        try
        {
            AuthenticationUtil.clearCurrentSecurityContext();
            
            for (final String tenantDomain : tenants)
            {
                loginLogoutUser(tenantService.getDomainUser(DEFAULT_GUEST_UN, tenantDomain), DEFAULT_GUEST_UN);
            }
        }   
        catch (Throwable t)
        {
            StringWriter stackTrace = new StringWriter();
            t.printStackTrace(new PrintWriter(stackTrace));
            System.err.println(stackTrace.toString());
            throw t;
        }
    }
    
    public void test08LoginTenantAdmin() throws Throwable
    {
        logger.info("Login tenant admins");
        
        try
        {
            AuthenticationUtil.clearCurrentSecurityContext();
            
            for (final String tenantDomain : tenants)
            {
                loginLogoutUser(tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain), DEFAULT_ADMIN_PW+" "+tenantDomain);
            }
        }   
        catch (Throwable t)
        {
            StringWriter stackTrace = new StringWriter();
            t.printStackTrace(new PrintWriter(stackTrace));
            System.err.println(stackTrace.toString());
            throw t;
        }
    }
    
    public void test09CreateGroups()
    {
        logger.info("Create demo groups");
        
        assertTrue(tenants.size() > 0);
        
        final int rootGrpsOrigCnt = TenantUtil.runAsUserTenant(new TenantRunAsWork<Integer>()
        {
            public Integer doWork() throws Exception
            {
                return authorityService.getAllRootAuthorities(AuthorityType.GROUP).size();
            }
        }, tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenants.get(0)), tenants.get(0));
        
        // create groups and add users
        for (final String tenantDomain : tenants)
        {
            final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain);
            
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    createGroup("GrpA", null);
                    createGroup("SubGrpA", "GrpA");
                    
                    createGroup("GrpB", null);
                    
                    createGroup("GrpC", null);
                    
                    if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
                    {
                        createGroup("SubGrpC", "GrpC");
                    }
                    
                    createGroup("GrpD", null);
                    addToGroup("GrpD", tenantAdminName);
                    
                    return null;
                }
            }, tenantAdminName, tenantDomain);
        }
        
        // check groups/users
        for (final String tenantDomain : tenants)
        {
            final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain);
            
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    Set<String> rootGrps = authorityService.getAllRootAuthorities(AuthorityType.GROUP);
                    assertEquals(rootGrpsOrigCnt+4, rootGrps.size());
                    
                    Set<String> auths = authorityService.getContainedAuthorities(null, "GROUP_GrpA", true);
                    assertEquals(1, auths.size());
                    
                    auths = authorityService.getContainedAuthorities(null, "GROUP_GrpB", true);
                    assertEquals(0, auths.size());
                    
                    auths = authorityService.getContainedAuthorities(null, "GROUP_GrpC", true);
                    if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
                    {
                        assertEquals(1, auths.size());
                    }
                    else
                    {
                        assertEquals(0, auths.size());
                    }
                    
                    auths = authorityService.getContainedAuthorities(null, "GROUP_GrpD", true);
                    assertEquals(1, auths.size());
                    assertTrue(auths.toArray()[0].equals(tenantAdminName));
                    
                    return null;
                }
            }, tenantAdminName, tenantDomain);
        }
    }

    @Category(RedundantTests.class)
    public void test10CreateCategories()
    {
        logger.info("Create demo categories");
        
        // super admin
        TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
        {
            public Object doWork() throws Exception
            {
                logger.info("Create demo categories - super tenant");
                createCategoriesImpl("");
                
                return null;
            }
        }, AuthenticationUtil.getAdminUserName(), TenantService.DEFAULT_DOMAIN);
        
        for (final String tenantDomain : tenants)
        {
            String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain);
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    logger.info("Create demo categories - "+tenantDomain);
                    
                    createCategoriesImpl(tenantDomain);
                    
                    return null;
                }
            }, tenantAdminName, tenantDomain);
            
        }
    }
    
    @SuppressWarnings("unchecked")
    private void createCategoriesImpl(String tenantDomain)
    {
        if (tenantDomain.equals(TenantService.DEFAULT_DOMAIN))
        {
            Collection<ChildAssociationRef> childAssocs = categoryService.getRootCategories(SPACES_STORE, ContentModel.ASPECT_GEN_CLASSIFIABLE);
            
            for (ChildAssociationRef childAssoc : childAssocs)
            {
                if (nodeService.getProperty(childAssoc.getChildRef(), ContentModel.PROP_NAME).equals("CatA"))
                {
                    return; // re-runnable, else we need to delete the created categories
                }
            }
        }
        
        // Find all root categories
        String query = "PATH:\"/cm:generalclassifiable/*\"";
        ResultSet resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, query);
        int cnt = resultSet.length();
        
        NodeRef catA = createCategory(SPACES_STORE, null, "CatA", "CatA-"+tenantDomain);
        createCategory(SPACES_STORE, catA, "SubCatA", "SubCatA-"+tenantDomain); // ignore return
        
        NodeRef catB = createCategory(SPACES_STORE, null, "CatB", "CatB-"+tenantDomain);
        createCategory(SPACES_STORE, catB, "SubCatB", "SubCatB-"+tenantDomain); // ignore return
        
        cnt = cnt + 2;
        
        if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
        {
            NodeRef catC = createCategory(SPACES_STORE, null, "CatC", "CatC-"+tenantDomain);
            createCategory(SPACES_STORE, catC, "SubCatC", "SubCatC-"+tenantDomain); // ignore return
            
            cnt = cnt + 1;
        }
        resultSet.close();
        
        // Find all root categories
        resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, query);
        assertEquals(cnt, resultSet.length());
        resultSet.close();
        
        String queryMembers = "PATH:\"/cm:generalclassifiable//cm:catA/member\"";
        resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, queryMembers);
        assertEquals(0, resultSet.length());
        resultSet.close();
        
        NodeRef homeSpaceRef = getHomeSpaceFolderNode(AuthenticationUtil.getRunAsUser());
        NodeRef contentRef = addContent(homeSpaceRef, "tqbfjotld.txt", "The quick brown fox jumps over the lazy dog (tenant " + tenantDomain + ")", MimetypeMap.MIMETYPE_TEXT_PLAIN);
        
        assertFalse(nodeService.hasAspect(contentRef, ContentModel.ASPECT_GEN_CLASSIFIABLE));
        
        List<NodeRef> categories = (List<NodeRef>)nodeService.getProperty(contentRef, ContentModel.PROP_CATEGORIES);
        assertNull(categories);
        
        // Classify the node (ie. assign node to a particular category in a classification)
        categories = new ArrayList<NodeRef>(1);
        categories.add(catA);
        
        HashMap<QName, Serializable> catProps = new HashMap<QName, Serializable>();
        catProps.put(ContentModel.PROP_CATEGORIES, (Serializable)categories);
        nodeService.addAspect(contentRef, ContentModel.ASPECT_GEN_CLASSIFIABLE, catProps);
        
        assertTrue(nodeService.hasAspect(contentRef, ContentModel.ASPECT_GEN_CLASSIFIABLE));
        
        categories = (List<NodeRef>)nodeService.getProperty(contentRef, ContentModel.PROP_CATEGORIES);
        assertEquals(1, categories.size());
        
        // test ETHREEOH-210
        queryMembers = "PATH:\"/cm:generalclassifiable//cm:CatA/member\"";
        resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, queryMembers);
        assertEquals(1, resultSet.length());
        resultSet.close();
    }
    
    public void test11CreateFolders()
    {
        logger.info("Create demo folders");
        
        List<String> users = new ArrayList<String>(3);
        users.add(TEST_USER1);
        users.add(TEST_USER2);
        users.add(TEST_USER3);
        
        for (final String tenantDomain : tenants)
        {
            for (String baseUserName : users)
            {
                if ((! baseUserName.equals(TEST_USER3)) || (tenantDomain.equals(TEST_TENANT_DOMAIN2)))
                {
                    final String tenantUserName = tenantService.getDomainUser(baseUserName, tenantDomain);
                    
                    TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
                    {
                        public Object doWork() throws Exception
                        {
                            NodeRef homeSpaceRef = getHomeSpaceFolderNode(tenantUserName);
                            
                            NodeRef folderRef = createFolderNode(homeSpaceRef, "myfolder1");
                            createFolderNode(folderRef, "mysubfolder1"); // ignore return
                            
                            folderRef = createFolderNode(homeSpaceRef, "myfolder2"); 
                            createFolderNode(folderRef, "mysubfolder2"); // ignore return
                            
                            if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
                            {
                                folderRef = createFolderNode(homeSpaceRef, "myfolder3"); 
                                createFolderNode(folderRef, "mysubfolder3"); // ignore return
                            }
                            
                            return null;
                        }
                    }, tenantUserName, tenantDomain);
                }
            }
        }
    }
    
    public void test12CreateVersionableUserContent()
    {
        logger.info("Create demo content");
        
        List<String> users = new ArrayList<String>(3);
        users.add(TEST_USER1);
        users.add(TEST_USER2);
        users.add(TEST_USER3);
        
        for (final String tenantDomain : tenants)
        {
            for (String baseUserName : users)
            {
                if ((! baseUserName.equals(TEST_USER3)) || (tenantDomain.equals(TEST_TENANT_DOMAIN2)))
                {
                    final String tenantUserName = tenantService.getDomainUser(baseUserName, tenantDomain);
                    
                    TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
                    {
                        public Object doWork() throws Exception
                        {
                            NodeRef homeSpaceRef = getHomeSpaceFolderNode(tenantUserName);
                            
                            NodeRef contentRef = addContent(homeSpaceRef, tenantUserName+" quick brown fox.txt", "The quick brown fox jumps over the lazy dog (tenant " + tenantDomain + ")", MimetypeMap.MIMETYPE_TEXT_PLAIN);
                            nodeService.addAspect(contentRef, ContentModel.ASPECT_VERSIONABLE, null);
                            
                            if (tenantDomain.equals(TEST_TENANT_DOMAIN2))
                            {
                                contentRef = addContent(homeSpaceRef, tenantUserName+" quick brown fox ANO.txt", "The quick brown fox jumps over the lazy dog ANO (tenant " + tenantDomain + ")", MimetypeMap.MIMETYPE_TEXT_PLAIN);
                                nodeService.addAspect(contentRef, ContentModel.ASPECT_VERSIONABLE, null);
                            }
                            
                            return null;
                        }
                    }, tenantUserName, tenantDomain);
                }
            }
        }
    }
    
    public void test13GetStores()
    {
        logger.info("Get tenant stores");
        
        // system
        TenantUtil.runAsSystemTenant(new TenantRunAsWork<Object>()
        {
            public Object doWork() throws Exception
            {
                for (StoreRef storeRef : nodeService.getStores())
                {
                    System.out.println("StoreRef: "+storeRef);
                }
                
                assertTrue("System: "+nodeService.getStores().size()+", "+(tenants.size()+1), (nodeService.getStores().size() >= (DEFAULT_STORE_COUNT * (tenants.size()+1))));
                return null;
            }
        }, TenantService.DEFAULT_DOMAIN);
        
        // super admin
        TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
        {
            public Object doWork() throws Exception
            {
                assertTrue("Super admin: "+nodeService.getStores().size(), (nodeService.getStores().size() >= DEFAULT_STORE_COUNT));
                return null;
            }
        }, AuthenticationUtil.getAdminUserName(), TenantService.DEFAULT_DOMAIN);
        
        for (final String tenantDomain : tenants)
        {
            String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain);
            
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    Set<StoreRef> stores = new HashSet<StoreRef>(nodeService.getStores());
                    assertEquals("Tenant: "+tenantDomain, DEFAULT_STORES, stores);
                    
                    return null;
                }
            }, tenantAdminName, tenantDomain);
        }
    }
    
    public void test14GetProperty()
    {
        logger.info("Test get property");
        
        for (final String tenantDomain : tenants)
        {
            String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain);
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    NodeRef personNodeRef = createUser(TEST_USER4, tenantDomain, TEST_USER4+" "+tenantDomain);
                    
                    // Test nodeRef property
                    NodeRef homeFolderNodeRef = (NodeRef)nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER);
                    assertFalse(homeFolderNodeRef.toString().contains(tenantDomain));
                    
                    Map<QName, Serializable> props = nodeService.getProperties(personNodeRef);
                    assertFalse(props.get(ContentModel.PROP_HOMEFOLDER).toString().contains(tenantDomain));
                    
                    // Test "store-identifier" property
                    String storeId = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_STORE_IDENTIFIER);
                    assertFalse(storeId.contains(tenantDomain));
                    
                    assertFalse(props.get(ContentModel.PROP_STORE_IDENTIFIER).toString().contains(tenantDomain));
                    
                    return null;
                }
            }, tenantAdminName, tenantDomain);
        }
    }

    @Category(RedundantTests.class)
    public void test15COCIandSearch()
    {
        logger.info("Test checkout/checkin and search");
        
        for (final String tenantDomain : tenants)
        {
            final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain);
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    // search for local copy of bootstrapped file 'invite_user_email.ftl' (see email_templates.acp)
                    String origText = "You have been invited to";
                    String query = "+PATH:\"/app:company_home/app:dictionary/app:email_templates/app:invite_email_templates/*\" +TEXT:\""+origText+"\"";
                    ResultSet resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, query);
                    assertEquals(1, resultSet.length());
                    
                    NodeRef nodeRef = resultSet.getNodeRef(0);
                    resultSet.close();
                    
                    // checkout, update and checkin
                    
                    NodeRef workingCopyNodeRef = cociService.checkout(nodeRef);
                    
                    ContentWriter writer = contentService.getWriter(workingCopyNodeRef, ContentModel.PROP_CONTENT, true);
                    
                    writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
                    writer.setEncoding("UTF-8");
                    
                    String updateText = "Updated by "+tenantAdminName;
                    writer.putContent(updateText);
                    
                    cociService.checkin(workingCopyNodeRef, null);
                    
                    query = "+PATH:\"/app:company_home/app:dictionary/app:email_templates/app:invite_email_templates/*\" +TEXT:\""+origText+"\"";
                    resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, query);
                    assertEquals(0, resultSet.length());
                    resultSet.close();
                    
                    query = "+PATH:\"/app:company_home/app:dictionary/app:email_templates/app:invite_email_templates/*\" +TEXT:\""+updateText+"\"";
                    resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, query);
                    assertEquals(1, resultSet.length());
                    resultSet.close();
                    
                    return null;
                }
            }, tenantAdminName, tenantDomain);
        }
    }
    
    public void test16DeleteArchiveAndRestoreContent()
    {
        logger.info("test delete/archive & restore content");
        
        // note: CLOUD-1349 - ownership is based on fully authenticated user (else restoreNode fails for non-Admin user)
        AuthenticationUtil.clearCurrentSecurityContext();
        
        final String superAdmin = AuthenticationUtil.getAdminUserName();
        
        AuthenticationUtil.runAs(new RunAsWork<Void>()
        {
            public Void doWork() throws Exception
            {
                // super tenant - admin user
                deleteArchiveAndRestoreContent(superAdmin, TenantService.DEFAULT_DOMAIN);
                return null;
            }
            
        }, superAdmin);
        
        final String superAnoUser = "superAnoUser";
        
        AuthenticationUtil.runAs(new RunAsWork<Void>()
        {
            public Void doWork() throws Exception
            {
                createUser(superAnoUser, TenantService.DEFAULT_DOMAIN, superAnoUser);
                return null;
            }
        }, superAdmin);
        
        AuthenticationUtil.runAs(new RunAsWork<Void>()
        {
            public Void doWork() throws Exception
            {
                // super tenant - ano user
                deleteArchiveAndRestoreContent(superAnoUser, TenantService.DEFAULT_DOMAIN);
                
                return null;
            }
        }, superAnoUser);
        
        for (final String tenantDomain : tenants)
        {
            final String tenantUserName = tenantService.getDomainUser(TEST_USER1, tenantDomain);
            
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    deleteArchiveAndRestoreContent(tenantUserName, tenantDomain);
                    
                    return null;
                }
            }, tenantUserName, tenantDomain);
        }
    }
    
    private void deleteArchiveAndRestoreContent(String userName, String tenantDomain)
    {
        NodeRef homeSpaceRef = getHomeSpaceFolderNode(userName);
        NodeRef contentRef = addContent(homeSpaceRef, userName+" "+System.currentTimeMillis()+" tqbfjotld.txt", "The quick brown fox jumps over the lazy dog (tenant " + tenantDomain + ")", MimetypeMap.MIMETYPE_TEXT_PLAIN);
        
        assertEquals(userName, ownableService.getOwner(contentRef));
        permissionService.hasPermission(contentRef, PermissionService.DELETE_NODE);
        
        NodeRef storeArchiveNode = nodeService.getStoreArchiveNode(contentRef.getStoreRef());
        
        nodeService.deleteNode(contentRef);
        
        // deduce archived nodeRef
        StoreRef archiveStoreRef = storeArchiveNode.getStoreRef();
        NodeRef archivedContentRef = new NodeRef(archiveStoreRef, contentRef.getId());
        
        assertEquals(userName, ownableService.getOwner(archivedContentRef));
        permissionService.hasPermission(archivedContentRef, PermissionService.DELETE_NODE);
        
        //nodeService.restoreNode(archivedContentRef, null, null, null);
        RestoreNodeReport report = nodeArchiveService.restoreArchivedNode(archivedContentRef);
        assertNotNull(report);
        
        NodeRef restoredNodeRef = report.getRestoredNodeRef();
        assertNotNull(restoredNodeRef);
        
        archivedContentRef = new NodeRef(archiveStoreRef, restoredNodeRef.getId());
        
        nodeService.deleteNode(restoredNodeRef);
        
        nodeArchiveService.purgeArchivedNode(archivedContentRef);
    }
    
    public void test17CustomModels()
    {
        logger.info("test custom models");
        
        final int defaultModelCnt = dictionaryService.getAllModels().size();
        
        for (final String tenantDomain : tenants)
        {
            final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain);
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    // no custom models should be deployed yet (note: will fail if re-run)
                    assertEquals(0, repoAdminService.getModels().size());
                    assertEquals(defaultModelCnt, dictionaryService.getAllModels().size());
                    assertNull(dictionaryService.getClass(QName.createQName("{my.new.model}sop")));
                    
                    // deploy custom model
                    InputStream modelStream = getClass().getClassLoader().getResourceAsStream("tenant/exampleModel.xml");
                    repoAdminService.deployModel(modelStream, "exampleModel.xml");
                    
                    assertEquals(1, repoAdminService.getModels().size());
                    assertEquals(defaultModelCnt+1, dictionaryService.getAllModels().size());
                    
                    ClassDefinition myType = dictionaryService.getClass(QName.createQName("{my.new.model}sop"));
                    assertNotNull(myType);
                    assertEquals(QName.createQName("{my.new.model}mynewmodel"),myType.getModel().getName());
                    
                    // deactivate model
                    repoAdminService.deactivateModel("exampleModel.xml");
                    
                    assertEquals(1, repoAdminService.getModels().size()); // still deployed, although not active
                    assertEquals(defaultModelCnt, dictionaryService.getAllModels().size());
                    assertNull(dictionaryService.getClass(QName.createQName("{my.new.model}sop")));
                    
                    // re-activate model
                    repoAdminService.activateModel("exampleModel.xml");
                    
                    assertEquals(1, repoAdminService.getModels().size());
                    assertEquals(defaultModelCnt+1, dictionaryService.getAllModels().size());
                    
                    myType = dictionaryService.getClass(QName.createQName("{my.new.model}sop"));
                    assertNotNull(myType);
                    assertEquals(QName.createQName("{my.new.model}mynewmodel"),myType.getModel().getName());
                    
                    return null;
                }
            }, tenantAdminName, tenantDomain);
        }
    }
    
    public void test18AddCustomWebClient()
    {
        // note: add as demo files - need to re-start Alfresco to see custom web client config / messages 
        logger.info("test add custom web client config");
        
        for (final String tenantDomain : tenants)
        {    
            final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain);
            
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    NodeRef webClientExtFolder = getWebClientExtensionNodeRef(SPACES_STORE);
                    
                    InputStream is = getClass().getClassLoader().getResourceAsStream("tenant/webclient.properties");
                    addContent(webClientExtFolder, "webclient.properties", is, MimetypeMap.MIMETYPE_TEXT_PLAIN);
                    
                    is = getClass().getClassLoader().getResourceAsStream("tenant/web-client-config-custom.xml");
                    addContent(webClientExtFolder, "web-client-config-custom.xml", is, MimetypeMap.MIMETYPE_XML);
                    
                    return null;
                }
            }, tenantAdminName, tenantDomain);
        }
    }
    
    public void test19FileFolder()
    {
        logger.info("test file/folder list");
        
        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
        
        for (final String tenantDomain : tenants)
        {
            final String tenantUserName = tenantService.getDomainUser(TEST_USER1, tenantDomain);
            
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    List<String> pathElements = new ArrayList<String>(2);
                    pathElements.add("Data Dictionary");
                    pathElements.add("Presentation Templates");
                    
                    NodeRef chRef = repositoryHelper.getCompanyHome();
                    NodeRef ddRef = fileFolderService.resolveNamePath(chRef, pathElements).getNodeRef();
                    
                    assertTrue(fileFolderService.list(ddRef).size() > 0);
                    
                    return null;
                }
            }, tenantUserName, tenantDomain);
        }
    }
    
    public void test20_ALF_12732()
    {
        final String tenantDomain1 = TEST_RUN+".one.alf12732";
        
        createTenant(tenantDomain1);
        
        String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain1);
        
        try
        {
            AuthenticationUtil.setFullyAuthenticatedUser(tenantAdminName); // note: since SiteServiceImpl.setupSitePermissions currently uses getCurrentUserName (rather than runAs)
            
            TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
            {
                public Object doWork() throws Exception
                {
                    createSite("site1");
                    
                    NodeRef docLib1Ref = siteService.getContainer("site1", SiteService.DOCUMENT_LIBRARY);
                    NodeRef contentRef = addContent(docLib1Ref, "tqbfjotld.txt", "The quick brown fox jumps over the lazy dog", MimetypeMap.MIMETYPE_TEXT_PLAIN);
                    
                    createSite("site2");
                    
                    NodeRef docLib2Ref = siteService.getContainer("site2", SiteService.DOCUMENT_LIBRARY);
                    
                    nodeService.moveNode(contentRef, docLib2Ref, ContentModel.ASSOC_CONTAINS, QName.createQName("tqbfjotld.txt"));
                    
                    // for Share, called via "move-to.post.json.js" -> ScriptSiteService.cleanSitePermissions
                    siteService.cleanSitePermissions(contentRef, null);
                    
                    return null;
                }
            }, tenantAdminName, tenantDomain1);
        }
        finally
        {
            AuthenticationUtil.clearCurrentSecurityContext();
        }
    }
    
    public void test21_ALF_14354()
    {
        final String tenantDomain1 = TEST_RUN+".one.alf14354";
        final String tenantDomain2 = TEST_RUN+".two.alf14354";
        
        createTenant(tenantDomain1);
        createTenant(tenantDomain2);
        
        String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain1);
        
        TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
        {
            public Object doWork() throws Exception
            {
                NodeRef rootNodeRef = repositoryHelper.getRootHome();
                
                assertTrue(nodeService.exists(rootNodeRef));
                
                return null;
            }
        }, tenantAdminName, tenantDomain1);
        
        tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain2);
        
        TenantUtil.runAsUserTenant(new TenantRunAsWork<Object>()
        {
            public Object doWork() throws Exception
            {
                NodeRef rootNodeRef = repositoryHelper.getRootHome();
                
                assertTrue(nodeService.exists(rootNodeRef));
                
                return null;
            }
        }, tenantAdminName, tenantDomain2);
    }
    

    private int searchForDataDictionary(String tenantAdminName, final String query)
    {
        return AuthenticationUtil.runAs(new RunAsWork<Integer>()
        {
            public Integer doWork() throws Exception
            {
                ResultSet resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, query, null);
                return resultSet.length();
            }
        }, tenantAdminName);
    }
    
    // pseudo cleanup - if this test runs last
    public void test22DeleteAllTenants()
    {
        logger.info("test delete tenants");
        
        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
        
        List<Tenant> allTenants = tenantAdminService.getAllTenants();
        for (final Tenant tenant : allTenants)
        {    
            deleteTenant(tenant.getTenantDomain());
        }
    }
    
    /*
    public static void main(String args[]) 
    {
        System.out.println(new Date());
        junit.textui.TestRunner.run(MultiTDemoTest.class);
        System.out.println(new Date());
    }
    */
}
