com.sapienter.jbilling.server.process.AgeingBL.java Source code

Java tutorial

Introduction

Here is the source code for com.sapienter.jbilling.server.process.AgeingBL.java

Source

/*
jBilling - The Enterprise Open Source Billing System
Copyright (C) 2003-2009 Enterprise jBilling Software Ltd. and Emiliano Conde
    
This file is part of jbilling.
    
jbilling is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
jbilling 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 Affero General Public License for more details.
    
You should have received a copy of the GNU Affero General Public License
along with jbilling.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
 * Created on Mar 26, 2004
 */
package com.sapienter.jbilling.server.process;

import java.sql.SQLException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;

import javax.naming.NamingException;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.log4j.Logger;

import sun.jdbc.rowset.CachedRowSet;

import com.sapienter.jbilling.common.SessionInternalError;
import com.sapienter.jbilling.server.invoice.InvoiceBL;
import com.sapienter.jbilling.server.invoice.db.InvoiceDTO;
import com.sapienter.jbilling.server.notification.INotificationSessionBean;
import com.sapienter.jbilling.server.notification.MessageDTO;
import com.sapienter.jbilling.server.notification.NotificationBL;
import com.sapienter.jbilling.server.notification.NotificationNotFoundException;
import com.sapienter.jbilling.server.order.OrderBL;
import com.sapienter.jbilling.server.order.db.OrderDAS;
import com.sapienter.jbilling.server.order.db.OrderDTO;
import com.sapienter.jbilling.server.process.db.AgeingEntityStepDAS;
import com.sapienter.jbilling.server.process.db.AgeingEntityStepDTO;
import com.sapienter.jbilling.server.process.event.NewUserStatusEvent;
import com.sapienter.jbilling.server.system.event.EventManager;
import com.sapienter.jbilling.server.user.UserBL;
import com.sapienter.jbilling.server.user.UserDTOEx;
import com.sapienter.jbilling.server.user.db.CompanyDTO;
import com.sapienter.jbilling.server.user.db.UserDAS;
import com.sapienter.jbilling.server.user.db.UserDTO;
import com.sapienter.jbilling.server.user.db.UserStatusDAS;
import com.sapienter.jbilling.server.user.db.UserStatusDTO;
import com.sapienter.jbilling.server.util.Constants;
import com.sapienter.jbilling.server.util.Context;
import com.sapienter.jbilling.server.util.PreferenceBL;
import com.sapienter.jbilling.server.util.audit.EventLogger;
import org.springframework.dao.EmptyResultDataAccessException;

/**
 * @author Emil
 */
public class AgeingBL {

    private AgeingEntityStepDAS ageingDas = null;
    private AgeingEntityStepDTO ageing = null;
    private static final Logger LOG = Logger.getLogger(AgeingBL.class);
    private EventLogger eLogger = null;

    public AgeingBL(Integer ageingId) {
        init();
        set(ageingId);
    }

    public AgeingBL() {
        init();
    }

    private void init() {
        eLogger = EventLogger.getInstance();
        ageingDas = new AgeingEntityStepDAS();

    }

    public AgeingEntityStepDTO getEntity() {
        return ageing;
    }

    public void set(Integer id) {
        ageing = ageingDas.find(id);
    }

    public void setUserStatus(Integer executorId, Integer userId, Integer statusId, Date today) {
        // find out if this user is not already in the required status
        UserBL user = new UserBL(userId);
        Integer originalStatusId = user.getEntity().getStatus().getId();
        if (originalStatusId.equals(statusId)) {
            return;
        }

        LOG.debug("Setting user " + userId + " status to " + statusId);
        // see if this guy could login in her present status
        boolean couldLogin = user.getEntity().getStatus().getCanLogin() == 1;

        // log an event
        if (executorId != null) {
            // this came from the gui
            eLogger.audit(executorId, userId, Constants.TABLE_BASE_USER, user.getEntity().getUserId(),
                    EventLogger.MODULE_USER_MAINTENANCE, EventLogger.STATUS_CHANGE,
                    user.getEntity().getStatus().getId(), null, null);
        } else {
            // this is from a process, no executor involved
            eLogger.auditBySystem(user.getEntity().getEntity().getId(), userId, Constants.TABLE_BASE_USER,
                    user.getEntity().getUserId(), EventLogger.MODULE_USER_MAINTENANCE, EventLogger.STATUS_CHANGE,
                    user.getEntity().getStatus().getId(), null, null);
        }

        // make the notification
        NotificationBL notification = new NotificationBL();
        try {
            MessageDTO message = notification.getAgeingMessage(user.getEntity().getEntity().getId(),
                    user.getEntity().getLanguageIdField(), statusId, userId);

            INotificationSessionBean notificationSess = (INotificationSessionBean) Context
                    .getBean(Context.Name.NOTIFICATION_SESSION);
            notificationSess.notify(user.getEntity(), message);
        } catch (NotificationNotFoundException e) {
            LOG.warn("Changeing the satus of a user. An ageing notification " + "should be "
                    + "sent to the user, but the entity doesn't have it. " + "entity "
                    + user.getEntity().getEntity().getId());
        }

        // make the change
        UserStatusDTO status = new UserStatusDAS().find(statusId);
        user.getEntity().setUserStatus(status);
        user.getEntity().setLastStatusChange(today);
        if (status.getId() == UserDTOEx.STATUS_DELETED) {
            // yikes, it's out
            user.delete(executorId);
            return; // her orders were deleted, no need for any change in status
        }

        // see if this new status is suspended
        if (couldLogin && status.getCanLogin() == 0) {
            // all the current orders have to be suspended
            OrderDAS orderDas = new OrderDAS();
            OrderBL order = new OrderBL();
            for (Iterator it = orderDas.findByUser_Status(userId, Constants.ORDER_STATUS_ACTIVE).iterator(); it
                    .hasNext();) {
                OrderDTO orderRow = (OrderDTO) it.next();
                order.set(orderRow);
                order.setStatus(executorId, Constants.ORDER_STATUS_SUSPENDED_AGEING);
            }
        } else if (!couldLogin && status.getCanLogin() == 1) {
            // the oposite, it is getting out of the ageing process
            // all the suspended orders have to be reactivated
            OrderDAS orderDas = new OrderDAS();
            OrderBL order = new OrderBL();
            for (Iterator it = orderDas.findByUser_Status(userId, Constants.ORDER_STATUS_SUSPENDED_AGEING)
                    .iterator(); it.hasNext();) {
                OrderDTO orderRow = (OrderDTO) it.next();
                order.set(orderRow);
                order.setStatus(executorId, Constants.ORDER_STATUS_ACTIVE);
            }
        }

        // make the http call back
        String url = null;
        try {
            PreferenceBL pref = new PreferenceBL();
            pref.set(user.getEntity().getEntity().getId(), Constants.PREFERENCE_URL_CALLBACK);
            url = pref.getString();
        } catch (EmptyResultDataAccessException e2) {
            // no call then
        }

        if (url != null && url.length() > 0) {
            // get the url connection
            try {
                LOG.debug("Making callback to " + url);

                // cook the parameters to be sent
                NameValuePair[] data = new NameValuePair[6];
                data[0] = new NameValuePair("cmd", "ageing_update");
                data[1] = new NameValuePair("user_id", userId.toString());
                data[2] = new NameValuePair("login_name", user.getEntity().getUserName());
                data[3] = new NameValuePair("from_status", originalStatusId.toString());
                data[4] = new NameValuePair("to_status", statusId.toString());
                data[5] = new NameValuePair("can_login", String.valueOf(status.getCanLogin()));

                // make the call
                HttpClient client = new HttpClient();
                client.setConnectionTimeout(30000);
                PostMethod post = new PostMethod(url);
                post.setRequestBody(data);
                client.executeMethod(post);
            } catch (Exception e1) {
                LOG.info("Could not make call back. url = " + url + " Message:" + e1.getMessage());
            }

        }

        // trigger NewUserStatusEvent
        EventManager.process(
                new NewUserStatusEvent(user.getDto().getCompany().getId(), userId, originalStatusId, statusId));
    }

    /**
     * Takes a user out of the ageing system -> back to status active
     * but only if she doesn't have any outstanding invoices.
     * @param user
     */
    public void out(UserDTO user, Integer excludigInvoiceId) throws SQLException {
        // if the user is in the ageing process
        LOG.debug("Taking user " + user.getUserId() + " out of ageing");
        if (user.getStatus().getId() != UserDTOEx.STATUS_ACTIVE) {
            InvoiceBL invoices = new InvoiceBL();
            // only if the user doesn't have any more invoices that are overdue
            if (!invoices.isUserWithOverdueInvoices(user.getUserId(), Calendar.getInstance().getTime(),
                    excludigInvoiceId).booleanValue()) {
                // good, no processable invoices for this guy
                setUserStatus(null, user.getUserId(), UserDTOEx.STATUS_ACTIVE, Calendar.getInstance().getTime());
            } else {
                LOG.debug("User with overdue invoices");
            }
        } else {
            LOG.debug("User already active");
        }

    }

    /**
     * Will move the user one step forward in the ageing proces ONLY IF
     * the user has been long enough in the present status. (for a user
     * in active status, it always moves it to the first ageing step).
     * @param userId
     * @throws NamingException
     * @throws SessionInternalError
     */
    public void age(UserDTO user, Date today) throws NamingException, SessionInternalError {
        LOG.debug("Ageing user:" + user.getUserId());
        Integer status = user.getStatus().getId();
        Integer nextStatus = null;
        if (status.equals(UserDTOEx.STATUS_ACTIVE)) {
            // welcome to the ageing process
            nextStatus = getNextStep(user.getEntity(), UserDTOEx.STATUS_ACTIVE);
        } else {
            LOG.debug("she's already in the ageing");
            // this guy is already in the ageing
            AgeingEntityStepDTO step = new AgeingEntityStepDAS().findStep(user.getEntity().getId(), status);
            if (step != null) {
                ageing = ageingDas.find(step.getId());

                // verify if it is time for another notch
                GregorianCalendar cal = new GregorianCalendar();
                Date lastChange = user.getLastStatusChange();
                if (lastChange == null) {
                    lastChange = user.getCreateDatetime();
                }
                cal.setTime(lastChange);
                cal.add(Calendar.DATE, ageing.getDays());
                LOG.debug("last time + days=" + cal.getTime() + " today " + today + "compare="
                        + cal.getTime().compareTo(today));
                if (cal.getTime().compareTo(today) <= 0) {
                    nextStatus = getNextStep(user.getEntity(), user.getStatus().getId());
                } else {
                    return;
                }
            } else {
                // this user is an ageing status that has been removed.
                // may be this is a bug, and a currently-in-use status
                // should not be removable.
                // Now it will simple jump to the next status.
                nextStatus = getNextStep(user.getEntity(), user.getStatus().getId());
            }
        }
        if (nextStatus != null) {
            setUserStatus(null, user.getUserId(), nextStatus, today);
        } else {
            eLogger.warning(user.getEntity().getId(), user.getUserId(), user.getUserId(),
                    EventLogger.MODULE_USER_MAINTENANCE, EventLogger.NO_FURTHER_STEP, Constants.TABLE_BASE_USER);
        }
    }

    /**
     * Give a current step, finds the next one. If there's none next, then
     * it returns null. The current step can be missing (for the case that
     * it has been deleted by the admin).
     * @param entity
     * @param statusId
     * @return
     * @throws SessionInternalError
     */
    private Integer getNextStep(CompanyDTO entity, Integer statusId) throws SessionInternalError {
        // this will return the next step, even if statusId doesn
        // exists in the current set of steps.
        // The steps are returned order by status id.
        for (AgeingEntityStepDTO step : entity.getAgeingEntitySteps()) {
            Integer stepId = step.getUserStatus().getId();
            if (stepId.compareTo(statusId) > 0) {
                return stepId;
            }
        }

        return null;
    }

    public static boolean isAgeingInUse(CompanyDTO entity) {
        return entity.getAgeingEntitySteps().size() > 1;
    }

    /**
     * Goes over all the users that are not active, and calls age on them.
     * This doesn't discriminate over entities.
     */
    public void reviewAll(Date today) throws NamingException, SessionInternalError, SQLException {
        // go over all the users already in the ageing system
        for (UserDTO userRow : new UserDAS().findAgeing()) {
            age(userRow, today);
        }

        // now go over the active users with payable invoices
        UserBL user = new UserBL();
        CachedRowSet usersSql;
        try {
            usersSql = user.findActiveWithOpenInvoices();
        } catch (Exception e) {
            // avoid further problems
            LOG.error("Exception finding users to age", e);
            return;
        }

        InvoiceBL invoiceBL = new InvoiceBL();

        while (usersSql.next()) {
            Integer userId = new Integer(usersSql.getInt(1));
            user.set(userId);
            UserDTO userRow = user.getEntity();
            // get the grace period for the entity of this user
            PreferenceBL prefs = new PreferenceBL();
            prefs.set(userRow.getEntity().getId(), Constants.PREFERENCE_GRACE_PERIOD);
            int gracePeriod = prefs.getInt();
            LOG.debug("Reviewing invoices of user:" + userRow.getUserId() + " grace: " + gracePeriod);
            // now go over this user's pending invoices
            for (Iterator it2 = invoiceBL.getHome().findProccesableByUser(userRow).iterator(); it2.hasNext();) {
                InvoiceDTO invoice = (InvoiceDTO) it2.next();
                GregorianCalendar cal = new GregorianCalendar();
                cal.setTime(invoice.getDueDate());
                if (gracePeriod > 0) {
                    cal.add(Calendar.DATE, gracePeriod);
                }

                if (userRow.getUserId().intValue() == 17) {
                    LOG.debug("invoice " + invoice.getId() + " due+grace=" + cal.getTime() + " today=" + today
                            + " compare=" + (cal.getTime().compareTo(today)));
                }

                if (cal.getTime().compareTo(today) < 0) {
                    // ok, this user has an overdue invoice
                    age(userRow, today);
                    break;
                }
            }
        }
    }

    public String getWelcome(Integer entityId, Integer languageId, Integer statusId) throws NamingException {
        AgeingEntityStepDTO step = new AgeingEntityStepDAS().findStep(entityId, statusId);
        ageing = ageingDas.find(step.getId());
        return ageing.getWelcomeMessage(languageId);
    }

    public AgeingDTOEx[] getSteps(Integer entityId, Integer executorLanguageId, Integer languageId)
            throws NamingException {
        AgeingDTOEx[] result = new AgeingDTOEx[UserDTOEx.STATUS_DELETED.intValue()];

        // go over all the steps
        for (int step = UserDTOEx.STATUS_ACTIVE.intValue(); step <= UserDTOEx.STATUS_DELETED.intValue(); step++) {
            AgeingDTOEx newStep = new AgeingDTOEx();
            newStep.setStatusId(new Integer(step));
            UserStatusDTO statusRow = new UserStatusDAS().find(step);
            newStep.setStatusStr(statusRow.getDescription(executorLanguageId));
            newStep.setCanLogin(statusRow.getCanLogin());
            AgeingEntityStepDTO myStep = new AgeingEntityStepDAS().findStep(entityId, new Integer(step));
            if (myStep != null) { // it doesn't have to be there
                ageing = ageingDas.find(myStep.getId());

                newStep.setDays(ageing.getDays());
                newStep.setFailedLoginMessage(ageing.getFailedLoginMessage(languageId));
                newStep.setInUse(new Boolean(true));
                newStep.setWelcomeMessage(ageing.getWelcomeMessage(languageId));
            } else {
                newStep.setInUse(new Boolean(false));
            }
            result[step - 1] = newStep;
        }

        return result;
    }

    public void setSteps(Integer entityId, Integer languageId, AgeingDTOEx[] steps) throws NamingException {
        LOG.debug("Setting a total of " + steps.length + " steps");
        for (int f = 0; f < steps.length; f++) {
            // get the existing data for this step
            LOG.debug("Processing step[" + f + "]:" + steps[f].getStatusId());
            AgeingEntityStepDTO myStep = new AgeingEntityStepDAS().findStep(entityId, steps[f].getStatusId());
            if (myStep != null) {
                ageing = ageingDas.find(myStep.getId());

                LOG.debug("step present");
            } else {
                LOG.debug("step not present");
                ageing = null;
            }
            if (!steps[f].getInUse().booleanValue()) {
                // delete if now is not wanted
                if (ageing != null) {
                    LOG.debug("Removig step.");
                    ageingDas.delete(ageing);
                }
            } else {
                // it is wanted
                LOG.debug("welcome = " + steps[f].getWelcomeMessage());
                if (ageing == null) {
                    // create
                    LOG.debug("Creating step.");
                    ageingDas.create(entityId, steps[f].getStatusId(), steps[f].getWelcomeMessage(),
                            steps[f].getFailedLoginMessage(), languageId, steps[f].getDays());
                } else {
                    // update
                    LOG.debug("Updating step.");
                    ageing.setDays(steps[f].getDays());
                    ageing.setFailedLoginMessage(languageId, steps[f].getFailedLoginMessage());
                    ageing.setWelcomeMessage(languageId, steps[f].getWelcomeMessage());
                }
            }
        }
    }

}