/*
 * #%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.remotecredentials;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.alfresco.query.EmptyPagingResults;
import org.alfresco.query.ListBackedPagingResults;
import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.node.SystemNodeUtils;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.remotecredentials.BaseCredentialsInfo;
import org.alfresco.service.cmr.remotecredentials.RemoteCredentialsService;
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.datatype.TypeConversionException;
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.util.GUID;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * An Implementation of the {@link RemoteCredentialsService}
 * 
 * @author Nick Burch
 * @since Odin
 */
public class RemoteCredentialsServiceImpl implements RemoteCredentialsService
{
    /**
     * The logger
     */
    private static Log logger = LogFactory.getLog(RemoteCredentialsServiceImpl.class);
    
    /**
     * The name of the System Container used to hold Shared Credentials.
     * This isn't final, as unit tests change it to avoid trampling on
     *  the real credentials.
     */
    private static String SHARED_CREDENTIALS_CONTAINER_NAME = "remote_credentials";

    private Repository repositoryHelper;
    private NodeService nodeService;
    private NamespaceService namespaceService;
    private PermissionService permissionService;
    private DictionaryService dictionaryService;
    
    /**
     * Controls which Factory will be used to create {@link BaseCredentialsInfo}
     *  instances for a given node, based on the type.
     * eg rc:passwordCredentials -> PasswordCredentialsFactory
     */
    private Map<QName,RemoteCredentialsInfoFactory> credentialsFactories = new HashMap<QName, RemoteCredentialsInfoFactory>();

    
    public void setNodeService(NodeService nodeService)
    {
        this.nodeService = nodeService;
    }

    public void setNamespaceService(NamespaceService namespaceService)
    {
        this.namespaceService = namespaceService;
    }

    public void setPermissionService(PermissionService permissionService)
    {
        this.permissionService = permissionService;
    }

    public void setDictionaryService(DictionaryService dictionaryService)
    {
        this.dictionaryService = dictionaryService;
    }
    
    public void setRepositoryHelper(Repository repositoryHelper)
    {
        this.repositoryHelper = repositoryHelper;
    }

    /**
     * Registers a number of new factories
     */
    public void setCredentialsFactories(Map<String,RemoteCredentialsInfoFactory> factories)
    {
        // Convert, eg rc:passwordCredentials -> qname version, then register
        for (String type : factories.keySet())
        {
            RemoteCredentialsInfoFactory factory = factories.get(type);
            QName typeQ = QName.createQName(type, namespaceService);
            registerCredentialsFactory(typeQ, factory);
        }
    }
    
    /**
     * Registers a new Factory to produce {@link BaseCredentialsInfo} objects
     *  for a given data type.
     * This provides an alternative to {@link #setCredentialsFactories(Map)}
     *  to allow the registering of a new type without overriding all of them.
     *  
     * @param credentialsType The object type
     * @param factory The Factory to use to create this type with
     */
    public void registerCredentialsFactory(QName credentialsType, RemoteCredentialsInfoFactory factory)
    {
        // Check the hierarchy is valid
        if (! dictionaryService.isSubClass(credentialsType, RemoteCredentialsModel.TYPE_CREDENTIALS_BASE))
        {
            logger.warn("Unable to register credentials factory for " + credentialsType + 
                        " as that type doesn't inherit from " + RemoteCredentialsModel.TYPE_CREDENTIALS_BASE);
            return;
        }
        
        // Log the new type
        if (logger.isDebugEnabled())
            logger.debug("Registering credentials factory for " + credentialsType + " of " + factory);

        // Store it
        credentialsFactories.put(credentialsType, factory);
    }
    
    /**
     * Provides a read only copy of the credentials factories, useful in unit tests
     */
    protected Map<QName,RemoteCredentialsInfoFactory> getCredentialsFactories()
    {
        return Collections.unmodifiableMap(credentialsFactories);
    }
    
    // --------------------------------------------------------
    
    private static QName SHARED_CREDENTIALS_CONTAINER_QNAME = 
        QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, SHARED_CREDENTIALS_CONTAINER_NAME); 
    /**
     * Gets the NodeRef of the holder of shared credentials remote systems.
     * 
     * This is stored under system
     * 
     * Protected, so that unit tests can make use of it
     */
    protected NodeRef getSharedContainerNodeRef(boolean required)
    {
        // Get the container, if available
        NodeRef container = SystemNodeUtils.getSystemChildContainer(SHARED_CREDENTIALS_CONTAINER_QNAME, nodeService, repositoryHelper);
        
        // If it's needed, have it created
        if (container == null && required)
        {
            // Lock and create
            Pair<NodeRef,Boolean> details = null;
            synchronized (this)
            {
                details = SystemNodeUtils.getOrCreateSystemChildContainer(SHARED_CREDENTIALS_CONTAINER_QNAME, nodeService, repositoryHelper);
            }
            container = details.getFirst();
            
            // If created, set permissions
            // Note - these must be kept in sync with the bootstrap file
            if (details.getSecond())
            {
                final NodeRef containerF = container;
                AuthenticationUtil.runAsSystem(new RunAsWork<Void>() {
                    @Override
                    public Void doWork() throws Exception
                    {
                        // Add the aspect
                        nodeService.addAspect(containerF, RemoteCredentialsModel.ASPECT_REMOTE_CREDENTIALS_SYSTEM_CONTAINER, null);

                        // Set up the default permissions on the container
                        // By default, anyone can add children, and read, but not edit other's credentials
                        // (These can be changed later if needed by an administrator)
                        permissionService.setInheritParentPermissions(containerF, false);
                        permissionService.setPermission(
                                containerF, PermissionService.ALL_AUTHORITIES,
                                PermissionService.ADD_CHILDREN, true);
                        permissionService.setPermission(
                                containerF, PermissionService.ALL_AUTHORITIES,
                                PermissionService.READ, true);

                        permissionService.setPermission(
                                containerF, PermissionService.OWNER_AUTHORITY,
                                PermissionService.FULL_CONTROL, true);
                        
                        return null;
                    }
                });
            }
        }

        if (container == null)
        {
            if (logger.isInfoEnabled())
                logger.info("Required System Folder " + SHARED_CREDENTIALS_CONTAINER_QNAME + " not yet created, will be lazy created on write");
            return null;
        }
        return container;
    }
    
    /**
     * Gets, creating as needed, the person credentials container for the given system
     */
    private NodeRef getPersonContainer(String remoteSystem, boolean lazyCreate)
    {
        // Get the person node
        NodeRef person = repositoryHelper.getPerson();
        if (person == null)
        {
            // Something's rather broken, the service security ought to prevent this 
            throw new IllegalStateException("Person details required but none found! Running as " + AuthenticationUtil.getRunAsUser());
        }
        
        // If we're in edit mode, ensure the correct aspect is applied
        if (lazyCreate)
        {
            ensureCredentialsSystemContainer(person);
        }
        
        // Find the container
        return findRemoteSystemContainer(person, remoteSystem, lazyCreate);
    }
    /**
     * Gets, creating as needed, the shared credentials container for the given system
     */
    private NodeRef getSharedContainer(String remoteSystem, boolean lazyCreate)
    {
        // Find the shared credentials container, under system
        NodeRef systemContainer = getSharedContainerNodeRef(lazyCreate);
        if (systemContainer == null) return null;
        
        // If we're in edit mode, ensure the correct aspect is applied
        if (lazyCreate)
        {
            ensureCredentialsSystemContainer(systemContainer);
        }
        
        // Find the container
        return findRemoteSystemContainer(systemContainer, remoteSystem, lazyCreate);
    }
    
    /**
     * Ensure the appropriate aspect is applied to the node which
     *  will hold the Remote Credentials System 
     */
    private void ensureCredentialsSystemContainer(final NodeRef nodeRef)
    {
        AuthenticationUtil.runAsSystem(new RunAsWork<Void>() {
            @Override
            public Void doWork() throws Exception
            {
                if (!nodeService.hasAspect(nodeRef, RemoteCredentialsModel.ASPECT_REMOTE_CREDENTIALS_SYSTEM_CONTAINER))
                {
                    // Add the aspect
                    nodeService.addAspect(nodeRef, RemoteCredentialsModel.ASPECT_REMOTE_CREDENTIALS_SYSTEM_CONTAINER, null);
                    
                    if (logger.isDebugEnabled())
                        logger.debug("Added the Credentials Container aspect to " + nodeRef);
                }
                return null;
            }
        });
    }
    private NodeRef findRemoteSystemContainer(NodeRef nodeRef, String remoteSystem, boolean lazyCreate)
    {
        QName remoteSystemQName = QName.createQName(remoteSystem);
        List<ChildAssociationRef> systems = nodeService.getChildAssocs(
                nodeRef, RemoteCredentialsModel.ASSOC_CREDENTIALS_SYSTEM, remoteSystemQName);
        
        NodeRef system = null;
        if (systems.size() > 0)
        {
            system = systems.get(0).getChildRef();
            
            if (logger.isDebugEnabled())
                logger.debug("Resolved Remote Credentials Container for " + remoteSystem + " of " + system + " in parent " + nodeRef);
        }
        else
        {
            if (lazyCreate)
            {
                // Create, as the current user
                system = nodeService.createNode(
                        nodeRef, RemoteCredentialsModel.ASSOC_CREDENTIALS_SYSTEM,
                        QName.createQName(remoteSystem), RemoteCredentialsModel.TYPE_REMOTE_CREDENTIALS_SYSTEM
                ).getChildRef();
                
                if (logger.isDebugEnabled())
                    logger.debug("Lazy created Remote Credentials Container for " + remoteSystem + 
                                 " in parent " + nodeRef + ", new container is " + system);
            }
            else
            {
                if (logger.isDebugEnabled())
                    logger.debug("No Remote Credentials Container for " + remoteSystem + " found in " +
                                 nodeRef + ", will be lazy created on write");
            }
        }
        
        return system;
    }

    // --------------------------------------------------------

    @Override
    public void deleteCredentials(BaseCredentialsInfo credentialsInfo)
    {
        if (credentialsInfo.getNodeRef() == null)
        {
            throw new IllegalArgumentException("Cannot delete Credentials which haven't been persisted yet!");
        }
        nodeService.deleteNode(credentialsInfo.getNodeRef());
        
        if (logger.isDebugEnabled())
            logger.debug("Deleted credentials " + credentialsInfo + " from " + credentialsInfo.getNodeRef() + 
                         " from Remote System " + credentialsInfo.getRemoteSystemName());
        
        // Leave the Remote System Container, in case special permissions
        //  were previously applied to it that should be retained
    }

    @Override
    public BaseCredentialsInfo createPersonCredentials(String remoteSystem, BaseCredentialsInfo credentials)
    {
        NodeRef personContainer = getPersonContainer(remoteSystem, true);
        return createCredentials(remoteSystem, personContainer, credentials);
    }
    @Override
    public BaseCredentialsInfo createSharedCredentials(String remoteSystem, BaseCredentialsInfo credentials)
    {
        NodeRef shared = getSharedContainer(remoteSystem, true);
        return createCredentials(remoteSystem, shared, credentials);
    }
    private BaseCredentialsInfo createCredentials(String remoteSystem, NodeRef remoteSystemNodeRef, BaseCredentialsInfo credentials)
    {
        if (credentials.getNodeRef() != null)
        {
            throw new IllegalArgumentException("Cannot create Credentials which have already been persisted!");
        }
        
        // Check we know about the type
        RemoteCredentialsInfoFactory factory = credentialsFactories.get(credentials.getCredentialsType());
        if (factory == null)
        {
            throw new TypeConversionException("No Factory registered for type " + credentials.getCredentialsType());
        }
        
        // Build the properties
        Map<QName,Serializable> properties = RemoteCredentialsInfoFactory.FactoryHelper.getCoreCredentials(credentials);
        properties.putAll( factory.serializeCredentials(credentials) );
        
        // Generate a name for it, which will be unique and doesn't need updating
        QName name = QName.createQName(GUID.generate()); 
        
        // Add the node
        NodeRef nodeRef = nodeService.createNode(
                remoteSystemNodeRef, RemoteCredentialsModel.ASSOC_CREDENTIALS,
                name, credentials.getCredentialsType(), properties
        ).getChildRef();
        
        if (logger.isDebugEnabled())
            logger.debug("Created new credentials at " + nodeRef + " for " + remoteSystem + " in " + 
                         remoteSystemNodeRef + " of " + credentials);
        
        // Return the new object
        return factory.createCredentials(
                credentials.getCredentialsType(), nodeRef,
                remoteSystem, remoteSystemNodeRef,
                nodeService.getProperties(nodeRef)
        );
    }


    @Override
    public BaseCredentialsInfo getPersonCredentials(String remoteSystem)
    {
        NodeRef personContainer = getPersonContainer(remoteSystem, false);
        if (personContainer == null) return null;

        // Grab the children
        List<ChildAssociationRef> credentials = 
            nodeService.getChildAssocs(personContainer, RemoteCredentialsModel.ASSOC_CREDENTIALS, RegexQNamePattern.MATCH_ALL);
        if (credentials.size() > 0)
        {
            NodeRef nodeRef = credentials.get(0).getChildRef();
            return loadCredentials(remoteSystem, personContainer, nodeRef);
        }
        return null;
    }
    private BaseCredentialsInfo loadCredentials(String remoteSystem, NodeRef remoteSystemNodeRef, NodeRef credentialsNodeRef)
    {
        QName type = nodeService.getType(credentialsNodeRef);
        RemoteCredentialsInfoFactory factory = credentialsFactories.get(type);
        if (factory == null)
        {
            throw new TypeConversionException("No Factory registered for type " + type);
        }
        
        // Wrap as an object
        return factory.createCredentials(
                type, credentialsNodeRef,
                remoteSystem, remoteSystemNodeRef,
                nodeService.getProperties(credentialsNodeRef)
        );
    }


    @Override
    public BaseCredentialsInfo updateCredentials(BaseCredentialsInfo credentials)
    {
        if (credentials.getNodeRef() == null)
        {
            throw new IllegalArgumentException("Cannot update Credentials which haven't been persisted yet!");
        }
        
        RemoteCredentialsInfoFactory factory = credentialsFactories.get(credentials.getCredentialsType());
        if (factory == null)
        {
            throw new TypeConversionException("No Factory registered for type " + credentials.getCredentialsType());
        }
        
        // Grab the current set of properties
        Map<QName,Serializable> oldProps = nodeService.getProperties(credentials.getNodeRef());
        
        // Overwrite them with the credentials ones
        Map<QName,Serializable> props = new HashMap<QName,Serializable>(oldProps);
        props.putAll( RemoteCredentialsInfoFactory.FactoryHelper.getCoreCredentials(credentials) );
        props.putAll( factory.serializeCredentials(credentials) );
        
        // Store
        nodeService.setProperties(credentials.getNodeRef(), props);
        
        // For now, return as-is
        return credentials;
    }

    @Override
    public BaseCredentialsInfo updateCredentialsAuthenticationSucceeded(boolean succeeded, BaseCredentialsInfo credentials)
    {
        // We can't help with credentials that have never been stored
        if (credentials.getNodeRef() == null)
        {
            throw new IllegalArgumentException("Cannot update Credentials which haven't been persisted yet!");
        }
        
        // Return quickly if the credentials are already in the correct state 
        if (succeeded == credentials.getLastAuthenticationSucceeded())
        {
            return credentials;
        }

        
        // Do the update 
        nodeService.setProperty(credentials.getNodeRef(), RemoteCredentialsModel.PROP_LAST_AUTHENTICATION_SUCCEEDED, succeeded);
        
        // Update the object if we can
        if (credentials instanceof AbstractCredentialsImpl)
        {
            ((AbstractCredentialsImpl)credentials).setLastAuthenticationSucceeded(succeeded);
            return credentials;
        }
        else
        {
            // Need to re-load
            return loadCredentials(credentials.getRemoteSystemName(),
                    credentials.getRemoteSystemContainerNodeRef(), credentials.getNodeRef());
        }
    }
    
    
    @Override
    public PagingResults<String> listAllRemoteSystems(PagingRequest paging)
    {
        return listRemoteSystems(true, true, paging);
    }
    @Override
    public PagingResults<String> listPersonRemoteSystems(PagingRequest paging)
    {
        return listRemoteSystems(true, false, paging);
    }
    @Override
    public PagingResults<String> listSharedRemoteSystems(PagingRequest paging)
    {
        return listRemoteSystems(false, true, paging);
    }
    private PagingResults<String> listRemoteSystems(boolean people, boolean shared, PagingRequest paging)
    {
        List<NodeRef> search = new ArrayList<NodeRef>();

        if (people)
        {
            // Only search if it has the marker aspect
            NodeRef person = repositoryHelper.getPerson();
            if (nodeService.hasAspect(person, RemoteCredentialsModel.ASPECT_REMOTE_CREDENTIALS_SYSTEM_CONTAINER))
            {
                search.add(person);
            }
        }
        if (shared)
        {
            NodeRef system = getSharedContainerNodeRef(false);
            if (system != null)
            {
                search.add(system);
            }
        }
        
        // If no suitable nodes were given, bail out
        if (search.isEmpty())
        {
            return new EmptyPagingResults<String>();
        }
        
        // Look for nodes
        // Because all the information we need is held on the association, we don't
        //  really need to use a Canned Query for this
        Set<String> systems = new HashSet<String>();
        for (NodeRef nodeRef : search)
        {
            List<ChildAssociationRef> refs = 
                nodeService.getChildAssocs(nodeRef, RemoteCredentialsModel.ASSOC_CREDENTIALS_SYSTEM, RegexQNamePattern.MATCH_ALL);
            for (ChildAssociationRef ref : refs)
            {
                // System Name is the association name, no namespace
                systems.add( ref.getQName().getLocalName() );
            }
        }
        
        // Sort, then wrap as paged results
        List<String> sortedSystems = new ArrayList<String>(systems);
        Collections.sort(sortedSystems);
        return new ListBackedPagingResults<String>(sortedSystems, paging);
    }

    @Override
    public PagingResults<? extends BaseCredentialsInfo> listSharedCredentials(String remoteSystem,
            QName credentialsType, PagingRequest paging)
    {
        // Get the container for that system
        NodeRef container = getSharedContainer(remoteSystem, false);
        if (container == null)
        {
            return new EmptyPagingResults<BaseCredentialsInfo>();
        }
        return listCredentials(new NodeRef[] {container}, remoteSystem, credentialsType, paging);
    }
    @Override
    public PagingResults<? extends BaseCredentialsInfo> listPersonCredentials(String remoteSystem,
            QName credentialsType, PagingRequest paging)
    {
        // Get the container for that system
        NodeRef container = getPersonContainer(remoteSystem, false);
        if (container == null)
        {
            return new EmptyPagingResults<BaseCredentialsInfo>();
        }
        return listCredentials(new NodeRef[] {container}, remoteSystem, credentialsType, paging);
    }
    @Override
    public PagingResults<? extends BaseCredentialsInfo> listAllCredentials(String remoteSystem, QName credentialsType,
            PagingRequest paging)
    {
        NodeRef personContainer = getPersonContainer(remoteSystem, false);
        NodeRef systemContainer = getSharedContainer(remoteSystem, false);
        if (personContainer == null && systemContainer == null)
        {
            return new EmptyPagingResults<BaseCredentialsInfo>();
        }
        return listCredentials(new NodeRef[] {personContainer, systemContainer}, remoteSystem, credentialsType, paging);
    }
    /**
     * TODO This would probably be better done as a dedicated Canned Query
     * We want to filter by Assoc Type and Child Node Type, and the node service
     *  currently only allows you to do one or the other 
     */
    private PagingResults<? extends BaseCredentialsInfo> listCredentials(NodeRef[] containers, String remoteSystem, 
            QName credentialsType, PagingRequest paging)
    {
        // NodeService wants an exhaustive list of the types
        // Expand our single Credentials Type to cover all subtypes of it too
        Set<QName> types = null;
        if (credentialsType != null)
        {
            types = new HashSet<QName>( dictionaryService.getSubTypes(credentialsType, true) );
            
            if (logger.isDebugEnabled())
                logger.debug("Searching for credentials of " + credentialsType + " as types " + types);
        }
        
        // Find all the credentials
        List<ChildAssociationRef> credentials = new ArrayList<ChildAssociationRef>();
        for (NodeRef nodeRef : containers)
        {
            if (nodeRef != null)
            {
                // Find the credentials in the node
                List<ChildAssociationRef> allCreds = nodeService.getChildAssocs(
                        nodeRef, RemoteCredentialsModel.ASSOC_CREDENTIALS, RegexQNamePattern.MATCH_ALL);
                
                // Filter them by type, if needed
                if (types == null || types.isEmpty())
                {
                    // No type filtering needed
                    credentials.addAll(allCreds);
                }
                else
                {
                    // Check the type of each one, and add if it matches
                    for (ChildAssociationRef ref : allCreds)
                    {
                        NodeRef credNodeRef = ref.getChildRef();
                        QName credType = nodeService.getType(credNodeRef);
                        if (types.contains(credType))
                        {
                            // Matching type, accept
                            credentials.add(ref);
                        }
                    }
                }
            }
        }
        
        // Did we find any?
        if (credentials.isEmpty())
        {
            return new EmptyPagingResults<BaseCredentialsInfo>();
        }
        
        // Excerpt
        int start = paging.getSkipCount();
        int end = Math.min(credentials.size(), start + paging.getMaxItems());
        if (paging.getMaxItems() == 0)
        {
            end = credentials.size();
        }
        boolean hasMore = (end < credentials.size());
        
        List<ChildAssociationRef> wanted = credentials.subList(start, end);
        
        // Wrap and return
        return new CredentialsPagingResults(wanted, credentials.size(), hasMore, remoteSystem); 
    }
    
    // --------------------------------------------------------
    
    private class CredentialsPagingResults implements PagingResults<BaseCredentialsInfo>
    {
        private List<BaseCredentialsInfo> results;
        private boolean hasMore;
        private int size;
        
        private CredentialsPagingResults(List<ChildAssociationRef> refs, int size, boolean hasMore, String remoteSystem)
        {
            this.size = size;
            this.hasMore = hasMore; 
            
            this.results = new ArrayList<BaseCredentialsInfo>(refs.size());
            for (ChildAssociationRef ref : refs)
            {
                this.results.add( loadCredentials(remoteSystem, ref.getParentRef(), ref.getChildRef()) );
            }
        }

        @Override
        public List<BaseCredentialsInfo> getPage()
        {
            return results; 
        }

        @Override
        public Pair<Integer, Integer> getTotalResultCount()
        {
            return new Pair<Integer,Integer>(size,size);
        }

        @Override
        public boolean hasMoreItems()
        {
            return hasMore; 
        }

        @Override
        public String getQueryExecutionId()
        {
            return null;
        }
    }
    
    // --------------------------------------------------------

    /** Unit testing use only! */
    protected static String getSharedCredentialsSystemContainerName()
    {
        return SHARED_CREDENTIALS_CONTAINER_NAME;
    }
    protected static QName getSharedCredentialsSystemContainerQName()
    {
        return SHARED_CREDENTIALS_CONTAINER_QNAME;
    }
    /** Unit testing use only! Used to avoid tests affecting the real system container */
    protected static void setSharedCredentialsSystemContainerName(String container)
    {
        SHARED_CREDENTIALS_CONTAINER_NAME = container;
        SHARED_CREDENTIALS_CONTAINER_QNAME = 
            QName.createQName(RemoteCredentialsModel.REMOTE_CREDENTIALS_MODEL_URL, SHARED_CREDENTIALS_CONTAINER_NAME);
    }
}