com.example.app.profile.model.user.UserDAO.java Source code

Java tutorial

Introduction

Here is the source code for com.example.app.profile.model.user.UserDAO.java

Source

/*
 * Copyright (c) Interactive Information R & D (I2RD) LLC.
 * All Rights Reserved.
 *
 * This software is confidential and proprietary information of
 * I2RD LLC ("Confidential Information"). You shall not disclose
 * such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered
 * into with I2RD.
 */

package com.example.app.profile.model.user;

import com.example.app.config.ProjectCacheRegions;
import com.example.app.support.service.AppUtil;
import com.example.app.support.service.FileSaver;
import org.apache.commons.fileupload.FileItem;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.Contract;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Repository;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;

import net.proteusframework.cms.FileSystemDirectory;
import net.proteusframework.cms.dao.CmsFrontendDAO;
import net.proteusframework.core.hibernate.dao.DAOHelper;
import net.proteusframework.core.hibernate.dao.EntityRetriever;
import net.proteusframework.core.spring.ApplicationContextUtils;
import net.proteusframework.data.filesystem.DirectoryEntity;
import net.proteusframework.data.filesystem.FileSystemDAO;
import net.proteusframework.ui.search.QLBuilder;
import net.proteusframework.ui.search.QLBuilderImpl;
import net.proteusframework.ui.search.QLResolverOptions;
import net.proteusframework.users.model.AuthenticationDomain;
import net.proteusframework.users.model.Contact;
import net.proteusframework.users.model.Principal;
import net.proteusframework.users.model.PrincipalStatus;
import net.proteusframework.users.model.dao.AuthenticationDomainList;
import net.proteusframework.users.model.dao.PrincipalDAO;

import static com.example.app.support.service.AppUtil.getExtensionWithDot;
import static net.proteusframework.core.StringFactory.isEmptyString;

/**
 * DAO for {@link User}
 *
 * @author Alan Holt (aholt@venturetech.net)
 */
@SuppressWarnings("unused")
@Repository
@Lazy
public class UserDAO extends DAOHelper implements Serializable {

    /** the folder to store User pictures within */
    public static final String USER_PICTURE_FOLDER = "UserPictures";
    /** the user picture file name suffix */
    public static final String USER_PICTURE_FILE_NAME_SUFFIX = "_img";
    /** The alias used by {@link #getUserQLBuilder()} */
    public static final String ALIAS = "userAlias";
    private static final Logger _logger = LogManager.getLogger(UserDAO.class);
    private static final long serialVersionUID = 8994091041309435246L;
    /** entity retriever */
    @Autowired
    protected transient EntityRetriever _er;
    /** cms frontend dao */
    @Autowired
    protected transient CmsFrontendDAO _cmsFrontendDAO;
    /** file system dao */
    @Autowired
    protected transient FileSystemDAO _fileSystemDAO;
    /** app util */
    @Autowired
    protected transient AppUtil _appUtil;
    /** principal dao */
    @Autowired
    protected transient PrincipalDAO _principalDAO;
    private FileSaver<User> _userImageSaver;

    /**
     * Delete the given user from the database
     *
     * @param user the user to delete
     */
    public void deleteUser(User user) {
        doInTransaction(session -> {
            session.delete(user);
        });
    }

    /**
     * Delete the given UserPosition from the database
     *
     * @param userPosition the user position to delete
     */
    public void deleteUserPosition(UserPosition userPosition) {
        if (userPosition.isCurrent()) {
            doInTransaction(session -> {
                userPosition.setCurrent(false);
                session.merge(userPosition);
            });
        }
        doInTransaction(session -> {
            session.delete(userPosition);
        });
    }

    /**
     * Get a User from {@link #getCurrentUser()}
     * Does an assertion on the result to verify that the current user is not null
     *
     * @return a User.
     */
    @Nonnull
    public User getAssertedCurrentUser() {
        return Optional.ofNullable(getCurrentUser())
                .orElseThrow(() -> new IllegalArgumentException("Current User was null."));
    }

    /**
     * Get a User for the current Principal returned from {@link PrincipalDAO#getCurrentPrincipal()}
     *
     * @return a User, or null if no User exists for the current Principal
     */
    @Nullable
    public User getCurrentUser() {
        return getUserForPrincipal(_principalDAO.getCurrentPrincipal());
    }

    /**
     * Get a list of AuthenticationDomains to save on the given User Principal.
     * This does not actually set them on the Principal, but rather returns a list to be set elsewhere.
     *
     * @param user the User to retrieve the existing AuthenticationDomains from
     *
     * @return a list of AuthenticationDomains, including the AuthenticationDomain from
     * {@link CmsFrontendDAO#getOperationalSite()}
     */
    public List<AuthenticationDomain> getAuthenticationDomainsToSaveOnUserPrincipal(@Nullable User user) {
        List<AuthenticationDomain> authDomains = new ArrayList<>();
        if (user != null && !isTransient(user)) {
            user = _er.reattachIfNecessary(user);
            authDomains.addAll(_er.reattachIfNecessary(user.getPrincipal()).getAuthenticationDomains());
        }
        AuthenticationDomain authDomain = _cmsFrontendDAO.getOperationalSite().getDomain();
        if (!authDomains.contains(authDomain))
            authDomains.add(authDomain);

        return authDomains;
    }

    /**
     * Gets users by email address.
     *
     * @param emailAddress the email address
     * @param domainList authentication domain list.
     * @return the users by email address.
     */
    @SuppressWarnings("unchecked")
    public List<User> getUsersByEmailAddress(String emailAddress, AuthenticationDomainList domainList) {
        return (List<User>) doInTransaction(session -> {
            @Language("HQL")
            String hql = "SELECT DISTINCT u FROM User u INNER JOIN u.principal p\n"
                    + "INNER JOIN p.credentials cred\n" + "INNER JOIN p.authenticationDomains ad\n"
                    + "INNER JOIN p.contact c INNER JOIN c.emailAddresses ea\n"
                    + "WHERE (LOWER(ea.email) = LOWER(:email) OR LOWER(cred.username) = LOWER(:email))\n";
            if (!domainList.isEmpty()) {
                hql += " AND ad IN (:authDomains)";
            }

            Query query = session.createQuery(hql).setParameter("email", emailAddress);
            if (!domainList.isEmpty()) {
                query.setParameterList("authDomains", domainList.getAuthenticationDomainList());
            }

            return query.list();
        });
    }

    /**
     * Get a user by login.
     *
     * @param emailAddressOrUsername the login.
     * @param status optional status to limit.
     *
     * @return the user.
     */
    public Optional<User> getUserByLogin(String emailAddressOrUsername, @Nullable PrincipalStatus... status) {
        if (isEmptyString(emailAddressOrUsername))
            return Optional.empty();
        final Principal login = _principalDAO.getPrincipalByLogin(emailAddressOrUsername, null);
        return Optional.ofNullable(getUserForPrincipal(login, status));
    }

    /**
     * Get a User for the given Principal.
     *
     * @param principal the Principal to search for
     * @param status optional status to limit.
     *
     * @return a User, or null if none exists for the given Principal
     */
    @Nullable
    public User getUserForPrincipal(@Nullable Principal principal, @Nullable PrincipalStatus... status) {
        if (principal == null)
            return null;
        String queryString = "SELECT u FROM User u INNER JOIN u.principal p WHERE p = :principal";
        final boolean hasStatus = status != null && status.length > 0;
        if (hasStatus)
            queryString += " AND p.status IN (:status)";
        final Query query = getSession().createQuery(queryString);
        query.setCacheable(true);
        query.setCacheRegion(ProjectCacheRegions.MEMBER_QUERY);
        query.setParameter("principal", principal);
        if (hasStatus)
            query.setParameterList("status", status);
        return (User) query.uniqueResult();
    }

    /**
     * Get a QLBuilder instance for User
     *
     * @return a QLBuilder
     */
    public QLBuilder getUserQLBuilder() {
        return getUserQLBuilder(null);
    }

    /**
     * Get a QLBuilder instance for User
     *
     * @param options the QLResolverOptions for this QLBuilder
     *
     * @return a QLBuilder
     */
    public QLBuilder getUserQLBuilder(@Nullable QLResolverOptions options) {
        return new QLBuilderImpl(User.class, "userAlias").setQLResolverOptions(options);
    }

    /**
     * Get users matching the specified parameters.
     * All parameters are optional. If none are specified, an empty list is returned.
     *
     * @param firstName first name.
     * @param lastName last name.
     * @param email email address.
     * @param exclude optional exclusion collection.
     *
     * @return the user list.
     */
    public List<User> getUsers(@Nullable String firstName, @Nullable String lastName, @Nullable String email,
            @Nullable Collection<User> exclude) {
        boolean hasFirst = !isEmptyString(firstName);
        boolean hasLast = !isEmptyString(lastName);
        boolean hasEmail = !isEmptyString(email);
        boolean hasExclude = exclude != null && !exclude.isEmpty();
        if (!hasFirst && !hasLast && !hasEmail)
            return new ArrayList<>();
        StringBuilder hql = new StringBuilder();
        hql.append("SELECT DISTINCT user FROM User user \n" + " INNER JOIN user.principal as p\n"
                + " LEFT JOIN p.contact as c \n");
        if (hasFirst || hasLast) {
            hql.append(" LEFT JOIN c.name as n\n");
        }
        if (hasEmail) {
            hql.append(" LEFT JOIN c.emailAddresses as ea\n");
        }
        hql.append(" WHERE (");
        if (hasFirst) {
            hql.append(" LOWER(n.first) LIKE :firstName");
        }
        if (hasLast) {
            if (hasFirst)
                hql.append(" OR");
            hql.append(" LOWER(n.last) LIKE :lastName");
        }
        if (hasEmail) {
            if (hasFirst || hasLast)
                hql.append(" OR");
            hql.append(" LOWER(ea.email) LIKE :email");
        }
        hql.append(')');

        if (hasExclude) {
            hql.append(" AND user NOT IN (:exclude)");
        }

        final Session session = getSession();
        final Query query = session.createQuery(hql.toString());
        query.setCacheable(true);
        query.setCacheRegion(ProjectCacheRegions.MEMBER_QUERY);

        if (hasFirst)
            query.setParameter("firstName", '%' + firstName.trim().toLowerCase() + '%');
        if (hasLast)
            query.setParameter("lastName", '%' + lastName.trim().toLowerCase() + '%');
        if (hasEmail)
            query.setParameter("email", '%' + email.trim().toLowerCase() + '%');
        if (hasExclude)
            query.setParameterList("exclude", exclude);

        @SuppressWarnings("unchecked")
        final List<User> list = query.list();
        return list;
    }

    /**
     * Persist the given User in the database via a merge operation
     *
     * @param user the user to merge
     *
     * @return the persisted User
     */
    public User mergeUser(User user) {
        return doInTransaction(session -> {
            if (user.getPrincipal().getContact() != null)
                user.getPrincipal().setContact((Contact) session.merge(user.getPrincipal().getContact()));
            return (User) session.merge(user);
        });
    }

    /**
     * Persist the given UserPosition in the database.  Ensures that if the UserPosition to save is set to current, there are
     * no other current UserPositions for the Position's User
     *
     * @param userPosition the user position to save
     *
     * @return the persisted UserPosition
     */
    public UserPosition mergeUserPosition(UserPosition userPosition) {
        Optional<UserPosition> currentPosition = getCurrentUserPosition(userPosition.getUser());
        if (userPosition.isCurrent() && currentPosition.isPresent()
                && !Objects.equals(userPosition, currentPosition.get())) {
            doInTransaction(session -> {
                currentPosition.get().setCurrent(false);
                session.merge(currentPosition.get());
            });
        }
        return doInTransaction(session -> (UserPosition) session.merge(userPosition));
    }

    /**
     * Get the given User's current UserPosition based on the absence of an endDate
     *
     * @param user the User to use for getting the UserPosition
     *
     * @return an optional that could contains the User's current UserPosition, if there is one.
     */
    @Nonnull
    public Optional<UserPosition> getCurrentUserPosition(@Nonnull User user) {
        user = _er.reattachIfNecessary(user);
        return Optional.ofNullable((UserPosition) getSession()
                .createQuery("select position\n" + "from UserPosition position\n" + "where position._user=:user\n"
                        + "and position._current=true\n")
                .setParameter("user", user).setCacheable(true).setCacheRegion(ProjectCacheRegions.MEMBER_QUERY)
                .uniqueResult());
    }

    /**
     * Set the {@link User} image and save the user before returning the updated User instance.
     * Ensures that the user is persisted (has an id greater than 0) within the database before setting the image
     * on the User, as the User id is used to determine file location of the image.
     *
     * @param user the user to set the image on and save
     * @param image the fileItem that is the image to save
     *
     * @return the updated and recently saved User
     */
    @Nonnull
    public User saveUserImage(@Nonnull User user, @Nullable FileItem image) {
        return getUserImageSaver().save(user, image);
    }

    /**
     * Get the User Image Saver
     *
     * @return the User Image Saver
     */
    @Nonnull
    protected FileSaver<User> getUserImageSaver() {
        if (_userImageSaver == null) {
            _userImageSaver = new FileSaver<>((user, image) -> {
                user.setImage(image);
                return user;
            }, User::getImage, (user, image) -> {
                // FUTURE : fix this so it doesn't save so many items into a single directory.
                final DirectoryEntity directory = FileSystemDirectory.Pictures
                        .getDirectory2(_cmsFrontendDAO.getOperationalSite());
                return _fileSystemDAO.mkdirs(directory, null, USER_PICTURE_FOLDER);
            }, (user, image) -> {
                String fileNameSuffix = USER_PICTURE_FILE_NAME_SUFFIX + getExtensionWithDot(image);
                return (user.getId() > 0L ? user.getId() : UUID.randomUUID()) + fileNameSuffix;
            }, this::mergeUser);
        }
        return _userImageSaver;
    }

    private Object readResolve() throws ObjectStreamException {
        ApplicationContext context = ApplicationContextUtils.getInstance().getContext();
        assert context != null;
        return context.getBean(UserDAO.class);
    }

    @Contract(pure = true)
    private Object writeReplace() throws ObjectStreamException {
        return this;
    }
}