/*
 * #%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 .
 * #L%
 */
package org.alfresco.repo.publishing;
import static org.alfresco.model.ContentModel.ASSOC_CONTAINS;
import static org.alfresco.model.ContentModel.TYPE_FOLDER;
import static org.alfresco.repo.publishing.PublishingModel.ASSOC_PUBLISHING_QUEUE;
import static org.alfresco.repo.publishing.PublishingModel.NAMESPACE;
import static org.alfresco.repo.publishing.PublishingModel.TYPE_PUBLISHING_QUEUE;
import java.util.List;
import org.alfresco.repo.node.NodeUtils;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
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.SearchService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.springframework.beans.BeansException;
/**
 * Returns a properly configured Environment. The factory is multi-tenancy enabled, returning the correct Environment object for the current domain.
 * 
 * @author Nick Smith
 * @since 4.0
 */
public class PublishingRootObject
{
    public static final String NAME = "publishingRootObject";
    protected static final QName CHANNELS_QNAME = QName.createQName(NAMESPACE, "channels");
    
    private NodeService nodeService;
    private PublishingEventHelper publishingEventHelper;
    private NamespaceService namespaceService;
    private SearchService searchService;
    private TransactionService transactionService;
    private PermissionService permissionService;
    
    private StoreRef publishingStore;
    private String publishingRootPath;
    
    
    public void setTransactionService(TransactionService transactionService)
    {
        this.transactionService = transactionService;
    }
    /**
     * @return the approprieate {@link Environment} for the current domain.
     * @throws BeansException
     */
    public Environment getEnvironment() throws BeansException
    {
        return createEnvironment();
    }
    public NodeRef getChannelContainer()
    {
        return getEnvironment().getChannelsContainer();
    }
    
    public PublishingQueueImpl getPublishingQueue()
    {
        return getEnvironment().getPublishingQueue();
    }
    
    private Environment createEnvironment()
    {
        return AuthenticationUtil.runAs(new RunAsWork()
        {
            public Environment doWork() throws Exception
            {
                RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
                txnHelper.setForceWritable(true);
                boolean requiresNew = false;
                if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE)
                {
                    //We can be in a read-only transaction, so force a new transaction 
                    requiresNew = true;
                }
                return txnHelper.doInTransaction(new RetryingTransactionCallback()
                {
                    public Environment execute() throws Exception
                    {
                        NodeRef environmentNode = getEnvironmentNode();
                        PublishingQueueImpl queue = createPublishingQueue(environmentNode);
                        NodeRef channelsContainer = getChannelsContainer(environmentNode);
                        return new Environment(environmentNode, queue, channelsContainer);
                    }
                }, false,requiresNew);
            }
        }, AuthenticationUtil.getSystemUserName());
    }
    private NodeRef getChannelsContainer(NodeRef environmentNode)
    {
        List childAssocs = 
            nodeService.getChildAssocs(environmentNode,ASSOC_CONTAINS, CHANNELS_QNAME);
        NodeRef channels = NodeUtils.getSingleChildAssocNode(childAssocs, true);
        if (channels == null)
        {
            // No channels container.
            channels = nodeService.createNode(environmentNode,
                    ASSOC_CONTAINS,
                    CHANNELS_QNAME,
                    TYPE_FOLDER).getChildRef();
        }
        return channels;
    }
    private PublishingQueueImpl createPublishingQueue(NodeRef environmentNode)
    {
        NodeRef queueNode = getPublishingQueueNode(environmentNode);
        return new PublishingQueueImpl(queueNode, publishingEventHelper);
    }
    private NodeRef getPublishingQueueNode(NodeRef environmentNode)
    {
        List childAssocs = nodeService.getChildAssocs(environmentNode, ASSOC_PUBLISHING_QUEUE, RegexQNamePattern.MATCH_ALL);
        NodeRef queueNode = NodeUtils.getSingleChildAssocNode(childAssocs, true);
        if (queueNode == null)
        {
            // No publishing queue
            queueNode = nodeService.createNode(environmentNode,
                    ASSOC_PUBLISHING_QUEUE,
                    QName.createQName(NAMESPACE, "publishingQueue"),
                    TYPE_PUBLISHING_QUEUE).getChildRef();
            permissionService.setPermission(queueNode, PermissionService.ALL_AUTHORITIES, PermissionService.ADD_CHILDREN, true);
        }
        return queueNode;
    }
    private NodeRef getEnvironmentNode()
    {
        return transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
        {
            public NodeRef execute() throws Exception
            {
                return findEnvrionmentNode();
            }
        }, true);
    }
    private NodeRef findEnvrionmentNode()
    {
        NodeRef rootNode = nodeService.getRootNode(publishingStore);
        List refs = searchService.selectNodes(rootNode, publishingRootPath, null, namespaceService, false);
        if (refs.size() != 1)
        {
            String msg = "Invalid publishing root path: " + publishingRootPath + " - found: " + refs.size();
            throw new IllegalStateException(msg);
        }
        return refs.get(0);
    }
    /**
     * @param publishingStore the publishingStore to set
     */
    public void setPublishingStore(String publishingStore)
    {
        this.publishingStore = new StoreRef(publishingStore);
    }
    
    /**
     * @param publishingRootPath the publishingRootPath to set
     */
    public void setPublishingRootPath(String publishingRootPath)
    {
        this.publishingRootPath = publishingRootPath;
    }
    
    /**
     * @param namespaceService the namespaceService to set
     */
    public void setNamespaceService(NamespaceService namespaceService)
    {
        this.namespaceService = namespaceService;
    }
    
    /**
     * @param nodeService the nodeService to set
     */
    public void setNodeService(NodeService nodeService)
    {
        this.nodeService = nodeService;
    }
    
    /**
     * @param publishingEventHelper the publishingEventHelper to set
     */
    public void setPublishingEventHelper(PublishingEventHelper publishingEventHelper)
    {
        this.publishingEventHelper = publishingEventHelper;
    }
    
    /**
     * @param searchService the searchService to set
     */
    public void setSearchService(SearchService searchService)
    {
        this.searchService = searchService;
    }
    
    /**
     * @param permissionService the permissionService to set
     */
    public void setPermissionService(PermissionService permissionService)
    {
        this.permissionService = permissionService;
    }
}