org.everit.jira.hr.admin.SchemeUsersComponent.java Source code

Java tutorial

Introduction

Here is the source code for org.everit.jira.hr.admin.SchemeUsersComponent.java

Source

/*
 * Copyright (C) 2011 Everit Kft. (http://www.everit.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.everit.jira.hr.admin;

import java.io.StringWriter;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.everit.jira.hr.admin.schema.qdsl.QDateRange;
import org.everit.jira.hr.admin.schema.qdsl.util.DateRangeUtil;
import org.everit.jira.hr.admin.util.AvatarUtil;
import org.everit.jira.hr.admin.util.DateUtil;
import org.everit.jira.hr.admin.util.LocalizedTemplate;
import org.everit.jira.hr.admin.util.QueryResultWithCount;
import org.everit.jira.hr.admin.util.QueryUtil;
import org.everit.jira.querydsl.schema.QAvatar;
import org.everit.jira.querydsl.schema.QCwdUser;
import org.everit.jira.querydsl.support.QuerydslSupport;
import org.everit.jira.querydsl.support.ri.QuerydslSupportImpl;
import org.everit.web.partialresponse.PartialResponseBuilder;

import com.atlassian.sal.api.transaction.TransactionTemplate;
import com.querydsl.core.types.ConstantImpl;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.Coalesce;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.sql.RelationalPath;
import com.querydsl.sql.SQLQuery;
import com.querydsl.sql.dml.SQLDeleteClause;
import com.querydsl.sql.dml.SQLInsertClause;
import com.querydsl.sql.dml.SQLUpdateClause;

public class SchemeUsersComponent {

    public static class QUserSchemeEntityParameter {
        public NumberPath<Long> dateRangeId;

        public RelationalPath<?> schemeEntityPath;

        public StringExpression schemeName;

        public NumberPath<Long> schemeSchemeId;

        public NumberPath<Long> userId;

        public RelationalPath<?> userSchemeEntityPath;

        public NumberPath<Long> userSchemeId;

        public NumberPath<Long> userSchemeSchemeId;
    }

    public static class SchemeUserDTO {

        public Long avatarId;

        public String avatarOwner;

        public long dateRangeId;

        public Date endDateExcluded;

        public Date startDate;

        public String userDisplayName;

        public String userName;

        public long userSchemeId;

        public Date getEndDate() {
            return DateUtil.addDays(endDateExcluded, -1);
        }

    }

    private static final int PAGE_SIZE = 500000;

    private static final Set<String> SUPPORTED_ACTIONS;

    private static final LocalizedTemplate TEMPLATE = new LocalizedTemplate("/META-INF/component/scheme_users",
            ManageSchemeComponent.class.getClassLoader());

    static {
        Set<String> supportedActions = new HashSet<>();
        supportedActions.add("scheme-user-savenew");
        supportedActions.add("scheme-user-delete");
        supportedActions.add("scheme-user-update");
        supportedActions.add("scheme-user-filter");
        SUPPORTED_ACTIONS = Collections.unmodifiableSet(supportedActions);
    }

    private final QuerydslSupport querydslSupport;
    private final QUserSchemeEntityParameter qUserSchemeEntityParameter;

    private final TransactionTemplate transactionTemplate;

    public SchemeUsersComponent(final QUserSchemeEntityParameter qUserSchemeEntityParameter,
            final TransactionTemplate transactionTemplate) {
        this.qUserSchemeEntityParameter = qUserSchemeEntityParameter;
        this.transactionTemplate = transactionTemplate;
        try {
            this.querydslSupport = new QuerydslSupportImpl();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void delete(final Long userSchemeId) {
        transactionTemplate.execute(() -> querydslSupport.execute((connection, configuration) -> {
            QDateRange qDateRange = QDateRange.dateRange;
            Long dateRangeId = new SQLQuery<Long>(connection, configuration).select(qDateRange.dateRangeId)
                    .from(qDateRange).innerJoin(qUserSchemeEntityParameter.userSchemeEntityPath)
                    .on(qUserSchemeEntityParameter.dateRangeId.eq(qDateRange.dateRangeId))
                    .where(qUserSchemeEntityParameter.userSchemeId.eq(userSchemeId)).fetchOne();

            new SQLDeleteClause(connection, configuration, qUserSchemeEntityParameter.userSchemeEntityPath)
                    .where(qUserSchemeEntityParameter.userSchemeId.eq(userSchemeId)).execute();

            new DateRangeUtil(connection, configuration).removeDateRange(dateRangeId);
            return null;
        }));
    }

    private Set<String> getSchemeNamesWithOverlappingTimeRange(final Long userId, final Date startDate,
            final Date endDateExcluded, final Long userSchemeIdToExclude) {

        List<String> schemeNames = querydslSupport.execute((connection, configuration) -> {
            QDateRange qDateRange = QDateRange.dateRange;
            SQLQuery<String> query = new SQLQuery<>(connection, configuration)
                    .select(qUserSchemeEntityParameter.schemeName).from(qDateRange)
                    .innerJoin(qUserSchemeEntityParameter.userSchemeEntityPath)
                    .on(qDateRange.dateRangeId.eq(qUserSchemeEntityParameter.dateRangeId))
                    .innerJoin(qUserSchemeEntityParameter.schemeEntityPath)
                    .on(qUserSchemeEntityParameter.userSchemeSchemeId
                            .eq(qUserSchemeEntityParameter.schemeSchemeId));

            List<Predicate> predicates = new ArrayList<>();
            predicates.add(qUserSchemeEntityParameter.userId.eq(userId));

            if (userSchemeIdToExclude != null) {
                predicates.add(qUserSchemeEntityParameter.userSchemeId.ne(userSchemeIdToExclude));
            }

            predicates.add(QueryUtil.dateRangeOverlaps(qDateRange, ConstantImpl.create(startDate),
                    ConstantImpl.create(endDateExcluded)));

            query.where(predicates.toArray(new Predicate[predicates.size()]));
            return query.fetch();
        });
        return new TreeSet<>(schemeNames);
    }

    public Set<String> getSupportedActions() {
        return SUPPORTED_ACTIONS;
    }

    private Long getUserId(final String user) {
        if (user == null) {
            return null;
        }
        return querydslSupport.execute((connection, configuration) -> {
            QCwdUser qUser = QCwdUser.cwdUser;
            return new SQLQuery<Long>(connection, configuration).select(qUser.id).from(qUser)
                    .where(qUser.lowerUserName.eq(user.toLowerCase())).fetchOne();
        });
    }

    public void processAction(final HttpServletRequest req, final HttpServletResponse resp) {
        String action = req.getParameter("action");

        switch (action) {
        case "scheme-user-filter":
            processFilter(req, resp);
            break;
        case "scheme-user-savenew":
            processSave(req, resp);
            break;
        case "scheme-user-update":
            processEdit(req, resp);
            break;
        case "scheme-user-delete":
            processDelete(req, resp);
            break;
        default:
            break;
        }
    }

    private void processDelete(final HttpServletRequest req, final HttpServletResponse resp) {
        Long userSchemeId = Long.parseLong(req.getParameter("scheme-user-userscheme-id"));
        delete(userSchemeId);
        Long userCount = schemeUserCount(req.getParameter("schemeId"));

        try (PartialResponseBuilder prb = new PartialResponseBuilder(resp)) {
            prb.replace("#scheme-user-table", render(req, resp.getLocale(), "scheme-user-table"));
            prb.replace("#delete-schema-validation-dialog", (writer) -> {
                DeleteSchemaValidationComponent.INSTANCE.render(writer, resp.getLocale(), userCount);
            });
        }
    }

    private void processEdit(final HttpServletRequest req, final HttpServletResponse resp) {
        long recordId = Long.parseLong(req.getParameter("record-id"));
        long schemeId = Long.parseLong(req.getParameter("schemeId"));
        String userName = req.getParameter("user");
        Date startDate = Date.valueOf(req.getParameter("start-date"));
        Date endDate = Date.valueOf(req.getParameter("end-date"));
        Date endDateExcluded = DateUtil.addDays(endDate, 1);

        Long userId = getUserId(userName);
        if (userId == null) {
            renderAlert("User does not exist", "error", resp);
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        if (startDate.compareTo(endDate) > 0) {
            renderAlert("Start date must not be after end date", "error", resp);
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        Set<String> schemeNamesWithOverlappingTimeRange = getSchemeNamesWithOverlappingTimeRange(userId, startDate,
                endDateExcluded, recordId);

        if (!schemeNamesWithOverlappingTimeRange.isEmpty()) {
            renderAlert(
                    "The user is assigned overlapping with the specified date range to the"
                            + " following scheme(s): " + schemeNamesWithOverlappingTimeRange.toString(),
                    "error", resp);
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        update(recordId, schemeId, userId, startDate, endDateExcluded);

        try (PartialResponseBuilder prb = new PartialResponseBuilder(resp)) {
            renderAlertOnPrb("Updating user successful", "info", prb, resp.getLocale());
            prb.replace("#scheme-user-table", render(req, resp.getLocale(), "scheme-user-table"));
        }
    }

    private void processFilter(final HttpServletRequest req, final HttpServletResponse resp) {
        try (PartialResponseBuilder prb = new PartialResponseBuilder(resp)) {
            prb.replace("#scheme-user-table", render(req, resp.getLocale(), "scheme-user-table"));
        }
    }

    private void processSave(final HttpServletRequest req, final HttpServletResponse resp) {
        long schemeId = Long.parseLong(req.getParameter("schemeId"));
        String userName = req.getParameter("user");
        Date startDate = Date.valueOf(req.getParameter("start-date"));
        Date endDate = Date.valueOf(req.getParameter("end-date"));
        Date endDateExcluded = DateUtil.addDays(endDate, 1);

        Long userId = getUserId(userName);
        if (userId == null) {
            renderAlert("User does not exist", "error", resp);
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        if (startDate.compareTo(endDate) > 0) {
            renderAlert("Start date must not be after end date", "error", resp);
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        Set<String> schemeNamesWithOverlappingTimeRange = getSchemeNamesWithOverlappingTimeRange(userId, startDate,
                endDateExcluded, null);
        if (!schemeNamesWithOverlappingTimeRange.isEmpty()) {
            renderAlert(
                    "The user is assigned overlapping with the specified date range to the"
                            + " following scheme(s): " + schemeNamesWithOverlappingTimeRange.toString(),
                    "error", resp);
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        save(schemeId, userName, startDate, endDateExcluded);
        Long userCount = schemeUserCount(String.valueOf(schemeId));
        try (PartialResponseBuilder prb = new PartialResponseBuilder(resp)) {
            renderAlertOnPrb("Assiging user successful", "info", prb, resp.getLocale());
            prb.replace("#scheme-user-table", render(req, resp.getLocale(), "scheme-user-table"));
            prb.replace("#delete-schema-validation-dialog", (writer) -> {
                DeleteSchemaValidationComponent.INSTANCE.render(writer, resp.getLocale(), userCount);
            });
        }
    }

    private QueryResultWithCount<SchemeUserDTO> querySchemeUsers(final int pageIndex, final long schemeId,
            final String userName, final boolean currentTimeRanges) {
        return querydslSupport.execute((connection, configuration) -> {
            QDateRange qDateRange = QDateRange.dateRange;
            QCwdUser qCwdUser = QCwdUser.cwdUser;

            SQLQuery<SchemeUserDTO> query = new SQLQuery<>(connection, configuration);
            query.from(qUserSchemeEntityParameter.userSchemeEntityPath).innerJoin(qDateRange)
                    .on(qDateRange.dateRangeId.eq(qUserSchemeEntityParameter.dateRangeId)).innerJoin(qCwdUser)
                    .on(qCwdUser.id.eq(qUserSchemeEntityParameter.userId));

            QAvatar qAvatar = AvatarUtil.joinAvatarToCwdUser(query, qCwdUser, "avatar");

            List<Predicate> predicates = new ArrayList<>();
            predicates.add(qUserSchemeEntityParameter.userSchemeSchemeId.eq(schemeId));

            if (currentTimeRanges) {
                Date currentDate = new Date(new java.util.Date().getTime());
                predicates
                        .add(qDateRange.startDate.loe(currentDate).and(qDateRange.endDateExcluded.gt(currentDate)));
            }

            if (userName != null) {
                predicates.add(qCwdUser.userName.eq(userName));
            }

            Expression<Long> defaultAvatarId = ConstantImpl.create(AvatarUtil.DEFAULT_AVATAR_ID);

            StringExpression userDisplayNameExpression = qCwdUser.displayName.as("userDisplayName");
            query.select(Projections.fields(SchemeUserDTO.class,
                    qUserSchemeEntityParameter.userSchemeId.as("userSchemeId"), qDateRange.dateRangeId,
                    qCwdUser.userName, userDisplayNameExpression, qDateRange.startDate, qDateRange.endDateExcluded,
                    new Coalesce<>(Long.class, qAvatar.id, defaultAvatarId).as("avatarId"),
                    qAvatar.owner.as("avatarOwner")));

            query.where(predicates.toArray(new Predicate[0]));

            long count = query.fetchCount();

            query.orderBy(userDisplayNameExpression.asc(), qDateRange.startDate.desc());
            long offset = PAGE_SIZE * (pageIndex - 1);
            if (offset >= count) {
                offset = PAGE_SIZE * (count / PAGE_SIZE - 1);
                if (offset < 0) {
                    offset = 0;
                }
            }

            List<SchemeUserDTO> resultSet;
            if (offset >= count) {
                resultSet = Collections.emptyList();
            } else {
                resultSet = query.limit(PAGE_SIZE).offset(offset).fetch();
            }

            QueryResultWithCount<SchemeUserDTO> result = new QueryResultWithCount<>(resultSet, count);
            return result;
        });
    }

    public String render(final HttpServletRequest request, final Locale locale) {
        return render(request, locale, "body");
    }

    public String render(final HttpServletRequest request, final Locale locale, final String fragment) {
        String userFilter = request.getParameter("schemeUsersUserFilter");
        boolean currentOnly = Boolean.parseBoolean(request.getParameter("schemeUsersCurrentFilter"));
        String schemeIdParam = request.getParameter("schemeId");
        Long schemeId = (schemeIdParam != null) ? Long.parseLong(schemeIdParam) : null;
        int pageIndex = Integer.parseInt(Objects.toString(request.getParameter("pageIndex"), "1"));

        QueryResultWithCount<SchemeUserDTO> schemeUsers;
        if (schemeId != null) {
            schemeUsers = querySchemeUsers(pageIndex, schemeId, userFilter, currentOnly);
        } else {
            schemeUsers = new QueryResultWithCount<>(Collections.emptyList(), 0);
        }

        Map<String, Object> vars = new HashMap<String, Object>();
        vars.put("request", request);
        vars.put("schemeUsers", schemeUsers);
        vars.put("userFilter", userFilter);
        vars.put("currentTimeRangesFilter", currentOnly);
        vars.put("pageIndex", pageIndex);

        StringWriter sw = new StringWriter();
        TEMPLATE.render(sw, vars, locale, fragment);
        return sw.toString();
    }

    private void renderAlert(final String message, final String alertType, final HttpServletResponse resp) {

        try (PartialResponseBuilder prb = new PartialResponseBuilder(resp)) {
            renderAlertOnPrb(message, alertType, prb, resp.getLocale());
        }
    }

    private void renderAlertOnPrb(final String message, final String alertType, final PartialResponseBuilder prb,
            final Locale locale) {

        prb.append("#aui-message-bar",
                (writer) -> AlertComponent.INSTANCE.render(writer, message, alertType, locale));
    }

    public String renderInitialFragments(final HttpServletRequest req, final HttpServletResponse resp) {
        StringWriter writer = new StringWriter();
        Map<String, Object> vars = new HashMap<>();
        vars.put("request", req);
        vars.put("response", resp);
        TEMPLATE.render(writer, vars, resp.getLocale(), "dialogs");
        return writer.toString();
    }

    private void save(final long schemeId, final String userName, final Date startDate,
            final Date endDateExcluded) {
        transactionTemplate.execute(() -> querydslSupport.execute((connection, configuration) -> {
            QCwdUser qCwdUser = QCwdUser.cwdUser;
            Long userId = new SQLQuery<Long>(connection, configuration).select(qCwdUser.id).from(qCwdUser)
                    .where(qCwdUser.userName.eq(userName)).fetchOne();

            Long dateRangeId = new DateRangeUtil(connection, configuration).createDateRange(startDate,
                    endDateExcluded);

            new SQLInsertClause(connection, configuration, qUserSchemeEntityParameter.userSchemeEntityPath)
                    .set(qUserSchemeEntityParameter.dateRangeId, dateRangeId)
                    .set(qUserSchemeEntityParameter.userId, userId)
                    .set(qUserSchemeEntityParameter.userSchemeSchemeId, schemeId).execute();

            return null;
        }));
    }

    /**
     *  Count users belonging to a scheme (work or holiday scheme).
     *
     * @param schemeIdString
     *          Scheme id in string, usually from request parameter.
     * @return If parameter null or empty, return 0. Otherwise, returns the number of users belonging
     *         to the specified schema.
     */
    public Long schemeUserCount(final String schemeIdString) {
        if (StringUtils.isEmpty(schemeIdString)) {
            return Long.valueOf(0);
        } else {
            Long schemeId = Long.valueOf(schemeIdString);
            return querydslSupport.execute((connection, configuration) -> {
                // select count(user_id) from everit_jira_user_work_scheme where work_scheme_id = schemeId;
                return new SQLQuery<Long>(connection, configuration)
                        .from(qUserSchemeEntityParameter.userSchemeEntityPath)
                        .where(qUserSchemeEntityParameter.userSchemeSchemeId.eq(schemeId)).fetchCount();
            });
        }
    }

    private void update(final long recordId, final long schemeId, final long userId, final Date startDate,
            final Date endDateExcluded) {
        transactionTemplate.execute(() -> querydslSupport.execute((connection, configuration) -> {

            Long dateRangeId = new SQLQuery<Long>(connection, configuration)
                    .select(qUserSchemeEntityParameter.dateRangeId)
                    .from(qUserSchemeEntityParameter.userSchemeEntityPath)
                    .where(qUserSchemeEntityParameter.userSchemeId.eq(recordId)).fetchOne();

            new DateRangeUtil(connection, configuration).modifyDateRange(dateRangeId, startDate, endDateExcluded);

            new SQLUpdateClause(connection, configuration, qUserSchemeEntityParameter.userSchemeEntityPath)
                    .set(qUserSchemeEntityParameter.userId, userId)
                    .where(qUserSchemeEntityParameter.userSchemeId.eq(recordId));
            return null;
        }));

    }
}