package com.atlassian.stash.internal.crowd;

import com.atlassian.crowd.dao.membership.InternalMembershipDao;
import com.atlassian.crowd.dao.membership.MembershipDAOHibernate;
import com.atlassian.crowd.embedded.api.Directory;
import com.atlassian.crowd.embedded.spi.MembershipDao;
import com.atlassian.crowd.exception.ObjectNotFoundException;
import com.atlassian.crowd.model.directory.DirectoryImpl;
import com.atlassian.crowd.model.membership.InternalMembership;
import com.atlassian.crowd.search.hibernate.HQLQueryTranslater;
import com.atlassian.crowd.search.hibernate.HibernateSearchResultsTransformer;
import com.atlassian.crowd.search.query.membership.MembershipQuery;
import com.atlassian.stash.internal.ApplicationConstants;
import org.hibernate.Query;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Collection;
import java.util.List;

/**
 * Extension of Crowd's {@link MembershipDAOHibernate} that walks all lazy loaded references on {@link InternalMembership}
 * instances before returning them. Since Stash does not use the open-session-in-view pattern, without this code
 * {@link org.hibernate.LazyInitializationException} would occur when trying access one of the lazy loaded fields
 * outside of a transaction.
 *
 * @since 1.3
 */
public class HibernateMembershipDao extends MembershipDAOHibernate implements MembershipDao, InternalMembershipDao {

    @Override
    public <T> List<T> search(long directoryId, MembershipQuery<T> query) {
        Query hibernateQuery = createHibernateSearchQuery(directoryId, query);

        hibernateQuery.setCacheable(true);
        hibernateQuery.setCacheRegion(ApplicationConstants.QUERY_CACHE_GROUP_MEMBERSHIPS);

        @SuppressWarnings("unchecked")
        List<T> results = HibernateSearchResultsTransformer.transformResults(hibernateQuery.list());
        
        for (T item : results) {
            if (item instanceof InternalMembership) {
                initialize(InternalMembership.class.cast(item));
            }
        }
        
        return results;
    }

    @Override
    public List<InternalMembership> findAll(Collection<Directory> directories) {
        return initialize(super.findAll(directories));
    }

    @Override
    public List<InternalMembership> findAllLocal(Collection<Directory> directories) {
        return initialize(super.findAllLocal(directories));
    }

    @Override
    public Object load(long ID) throws ObjectNotFoundException {
        return initialize(InternalMembership.class.cast(super.load(ID)));
    }

    @Override
    public Object loadReference(long id) {
        return initialize(InternalMembership.class.cast(super.loadReference(id)));
    }

    @Autowired
    @Override
    public void setHqlQueryTranslater(HQLQueryTranslater hqlQueryTranslater) {
        super.setHqlQueryTranslater(hqlQueryTranslater);
    }

    /**
     * Initializes all lazy loaded fields on a membership.
     * @param membership the membership to initialize
     * @return the provided membership, returned for convenience.
     */
    static InternalMembership initialize(InternalMembership membership) {
        if (membership != null) {
            HibernateDirectoryDao.initialize(DirectoryImpl.class.cast(membership.getDirectory()));
        }
        return membership;
    }

    /**
     * Initializes all lazy loaded fields for a list of memberships.
     * @param memberships the memberships to initialize.
     * @return the provided list of memberships, returned for convenience.
     */
    static List<InternalMembership> initialize(List<InternalMembership> memberships) {
        for (InternalMembership membership : memberships) {
            initialize(membership);
        }
        return memberships;
    }
}
