protected boolean sendAgeingNotification(
      UserDTO user, UserStatusDTO oldStatus, UserStatusDTO newStatus) {
    try {
      MessageDTO message =
          new NotificationBL()
              .getAgeingMessage(
                  user.getEntity().getId(),
                  user.getLanguage().getId(),
                  newStatus.getId(),
                  user.getId());

      INotificationSessionBean notification =
          (INotificationSessionBean) Context.getBean(Context.Name.NOTIFICATION_SESSION);
      notification.notify(user, message);

    } catch (NotificationNotFoundException e) {
      LOG.warn(
          "Failed to send ageing notification. Entity "
              + user.getEntity().getId()
              + " does not have an ageing message configured for status '"
              + getStatusDescription(newStatus)
              + "'.");
      return false;
    }
    return true;
  }
  /**
   * Moves a user one step forward in the ageing process (move from active -> suspended etc.). The
   * user will only be moved if they have spent long enough in their present status.
   *
   * @param steps ageing steps
   * @param user user to age
   * @param today today's date
   * @return the resulting ageing step for the user after ageing
   */
  public AgeingEntityStepDTO ageUser(
      Set<AgeingEntityStepDTO> steps, UserDTO user, Date today, Integer executorId) {
    LOG.debug("Ageing user " + user.getId());

    Integer currentStatusId = user.getStatus().getId();
    UserStatusDTO nextStatus = null;
    AgeingEntityStepDTO ageingStep = null;

    if (currentStatusId.equals(UserDTOEx.STATUS_ACTIVE)) {
      // send welcome message (initial step after active).
      nextStatus = getNextAgeingStep(steps, UserDTOEx.STATUS_ACTIVE);

    } else {
      // user already in the ageing process
      ageingStep = new AgeingEntityStepDAS().findStep(user.getEntity().getId(), currentStatusId);

      if (ageingStep != null) {
        // determine the next ageing step
        if (isAgeingRequired(user, ageingStep, today)) {
          nextStatus = getNextAgeingStep(steps, currentStatusId);
          LOG.debug(
              "User "
                  + user.getId()
                  + " needs to be aged to '"
                  + getStatusDescription(nextStatus)
                  + "'");
        }

      } else {
        // User is in a non-existent ageing status... Either the status was removed or
        // the data is bad. As a workaround, just move to the next status.
        nextStatus = getNextAgeingStep(steps, currentStatusId);
        LOG.warn(
            "User "
                + user.getId()
                + " is in an invalid ageing step. Moving to '"
                + getStatusDescription(nextStatus)
                + "'");
      }
    }

    // set status
    if (nextStatus != null) {
      setUserStatus(user, nextStatus, today, null);

    } else {
      LOG.debug("Next status is null, no further ageing steps are available.");
      eLogger.warning(
          user.getEntity().getId(),
          user.getUserId(),
          user.getUserId(),
          EventLogger.MODULE_USER_MAINTENANCE,
          EventLogger.NO_FURTHER_STEP,
          Constants.TABLE_BASE_USER);
    }

    return ageingStep;
  }
  /**
   * This method sends an email to the given user with the link to reset his password
   *
   * @param user the user
   */
  @Override
  public void resetPassword(UserDTO user) {
    ResetPasswordCodeDAS resetCodeDAS = new ResetPasswordCodeDAS();
    // find previous passwordCode

    ResetPasswordCodeDTO resetCode = resetCodeDAS.findByUser(user);
    if (resetCode == null) {
      resetCode = new ResetPasswordCodeDTO();
      resetCode.setUser(user);
      resetCode.setDateCreated(new Date());
      resetCode.setToken(RandomStringUtils.random(32, true, true));
      resetCodeDAS.save(resetCode);
      resetCodeDAS.flush();
    } else {
      DateTime dateResetCode = new DateTime(resetCode.getDateCreated());
      DateTime today = DateTime.now();
      Duration duration = new Duration(dateResetCode, today);
      Long minutesDifference = duration.getStandardMinutes();
      Long expirationMinutes =
          PreferenceBL.getPreferenceValueAsIntegerOrZero(
                      user.getEntity().getId(),
                      CommonConstants.PREFERENCE_FORGOT_PASSWORD_EXPIRATION)
                  .longValue()
              * 60;
      if (minutesDifference > expirationMinutes) {
        resetCodeDAS.delete(resetCode);
        resetCodeDAS.flush();
        resetCode = new ResetPasswordCodeDTO();
        resetCode.setUser(user);
        resetCode.setDateCreated(new Date());
        resetCode.setToken(RandomStringUtils.random(32, true, true));
        resetCodeDAS.save(resetCode);
      }
    }

    try {
      new UserBL()
          .sendLostPassword(
              user.getCompany().getId(), user.getId(), 1, generateLink(resetCode.getToken()));

    } catch (SessionInternalError e) {
      LOG.error("Exception while sending notification : " + e.getMessage());
      throw new SessionInternalError("forgotPassword.notification.not.found");
    } catch (NotificationNotFoundException e) {
      e.printStackTrace();
    }
  }
  protected boolean performAgeingCallback(
      UserDTO user, UserStatusDTO oldStatus, UserStatusDTO newStatus) {
    String url = null;
    try {
      PreferenceBL pref = new PreferenceBL();
      pref.set(user.getEntity().getId(), Constants.PREFERENCE_URL_CALLBACK);
      url = pref.getString();

    } catch (EmptyResultDataAccessException e) {
      /* ignore, no callback preference configured */
    }

    if (url != null && url.length() > 0) {
      try {
        LOG.debug("Performing ageing HTTP callback for URL: " + 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", String.valueOf(user.getId()));
        data[2] = new NameValuePair("login_name", user.getUserName());
        data[3] = new NameValuePair("from_status", String.valueOf(oldStatus.getId()));
        data[4] = new NameValuePair("to_status", String.valueOf(newStatus.getId()));
        data[5] = new NameValuePair("can_login", String.valueOf(newStatus.getCanLogin()));

        // make the call
        HttpClient client = new HttpClient();
        client.setConnectionTimeout(30000);
        PostMethod post = new PostMethod(url);
        post.setRequestBody(data);
        client.executeMethod(post);

      } catch (Exception e) {
        LOG.error("Exception occurred posting ageing HTTP callback for URL: " + url, e);
        return false;
      }
    }
    return true;
  }
  public InvoiceDTO[] generateInvoice(
      BillingProcessDTO process, UserDTO user, boolean isReview, boolean onlyRecurring)
      throws SessionInternalError {

    Integer userId = user.getUserId();
    Integer entityId = user.getEntity().getId();

    // get the configuration
    boolean useProcessDateForInvoice = true;
    int maximumPeriods = 1;
    boolean paymentApplication = false;
    try {
      ConfigurationBL config = new ConfigurationBL(process.getEntity().getId());
      useProcessDateForInvoice = config.getEntity().getInvoiceDateProcess() == 1;
      maximumPeriods = config.getEntity().getMaximumPeriods();
      paymentApplication = config.getEntity().getAutoPaymentApplication() == 1;
    } catch (Exception e) {
      // swallow exception
    }

    // this contains the generated invoices, one per due date
    // found in the applicable purchase orders.
    // The key is the object TimePeriod
    Hashtable<TimePeriod, NewInvoiceDTO> newInvoices = new Hashtable<TimePeriod, NewInvoiceDTO>();
    InvoiceDTO[] retValue = null;

    LOG.debug(
        "In generateInvoice for user " + userId + " process date:" + process.getBillingDate());

    /*
     * Go through the orders first
     * This method will recursively call itself to find sub-accounts in any
     * level
     */
    boolean includedOrders =
        processOrdersForUser(
            user,
            entityId,
            process,
            isReview,
            onlyRecurring,
            useProcessDateForInvoice,
            maximumPeriods,
            newInvoices);

    if (!includedOrders || newInvoices.size() == 0) {
      // check if invoices without orders are allowed
      PreferenceBL preferenceBL = new PreferenceBL();
      try {
        preferenceBL.set(entityId, Constants.PREFERENCE_ALLOW_INVOICES_WITHOUT_ORDERS);
      } catch (EmptyResultDataAccessException fe) {
        // use default
      }

      if (preferenceBL.getInt() == 0) {
        LOG.debug("No applicable orders. No invoice generated (skipping invoices).");
        return null;
      }
    }

    if (!isReview) {
      for (Map.Entry<TimePeriod, NewInvoiceDTO> newInvoiceEntry : newInvoices.entrySet()) {
        // process events before orders added to invoice
        processOrderToInvoiceEvents(newInvoiceEntry.getValue(), entityId);
      }
    }

    /*
     * Include those invoices that should've been paid
     * (or have negative balance, as credits)
     */
    LOG.debug("Considering overdue invoices");
    // find the invoice home interface
    InvoiceDAS invoiceDas = new InvoiceDAS();
    // any of the new invoices being created could hold the overdue invoices
    NewInvoiceDTO holder =
        newInvoices.isEmpty() ? null : (NewInvoiceDTO) newInvoices.elements().nextElement();

    Collection dueInvoices = invoiceDas.findWithBalanceByUser(user);
    LOG.debug("Processing invoices for user " + user.getUserId());
    // go through each of them, and update the DTO if it applies

    for (Iterator it = dueInvoices.iterator(); it.hasNext(); ) {
      InvoiceDTO invoice = (InvoiceDTO) it.next();
      if (invoice.getToProcess() == InvoiceDTO.DO_NOT_PROCESS) {
        LOG.debug("skipping invoice " + invoice.getId() + " because DO_NOT_PROCESS is set");
        continue;
      }
      LOG.debug("Processing invoice " + invoice.getId());
      // apply any invoice processing filter pluggable task
      try {
        PluggableTaskManager taskManager =
            new PluggableTaskManager(entityId, Constants.PLUGGABLE_TASK_INVOICE_FILTER);
        InvoiceFilterTask task = (InvoiceFilterTask) taskManager.getNextClass();
        boolean isProcessable = true;
        while (task != null) {
          isProcessable = task.isApplicable(invoice, process);
          if (!isProcessable) {
            break; // no need to keep doing more tests
          }
          task = (InvoiceFilterTask) taskManager.getNextClass();
        }

        // include this invoice only if it complies with all the rules
        if (isProcessable) {
          // check for an invoice
          if (holder == null) {
            // Since there are no new invoices (therefore no orders),
            // don't let invoices with positive balances generate
            // an invoice.
            if (BigDecimal.ZERO.compareTo(invoice.getBalance()) < 0) {
              continue;
            }

            // no invoice/s yet (no applicable orders), so create one
            holder = new NewInvoiceDTO();
            holder.setDate(process.getBillingDate());
            holder.setIsReview(isReview ? new Integer(1) : new Integer(0));
            holder.setCarriedBalance(BigDecimal.ZERO);
            holder.setInvoiceStatus(new InvoiceStatusDAS().find(Constants.INVOICE_STATUS_UNPAID));

            // need to set a due date, so use the order default
            OrderBL orderBl = new OrderBL();
            OrderDTO order = new OrderDTO();
            order.setBaseUserByUserId(user);
            orderBl.set(order);
            TimePeriod dueDatePeriod = orderBl.getDueDate();

            holder.setDueDatePeriod(dueDatePeriod);
            newInvoices.put(dueDatePeriod, holder);
          }

          InvoiceBL ibl = new InvoiceBL(invoice);
          holder.addInvoice(ibl.getDTO());
          // for those invoices wiht only overdue invoices, the
          // currency has to be initialized

          if (holder.getCurrency() == null) {
            holder.setCurrency(invoice.getCurrency());
          } else if (holder.getCurrency().getId() != invoice.getCurrency().getId()) {
            throw new SessionInternalError(
                "An invoice with different "
                    + "currency is not supported. "
                    + "Currency = "
                    + holder.getCurrency().getId()
                    + "invoice = "
                    + invoice.getId());
          }
          // update the amount of the new invoice that is due to
          // previously unpaid overdue invoices

          // carry the remaining balance, plus the previously carried balance to the new invoice
          BigDecimal balance =
              (invoice.getBalance() == null) ? BigDecimal.ZERO : invoice.getBalance();
          BigDecimal carried = balance.add(holder.getCarriedBalance());
          holder.setCarriedBalance(carried);
        }

        LOG.debug("invoice " + invoice.getId() + " result " + isProcessable);

      } catch (PluggableTaskException e) {
        LOG.fatal("Problems handling task invoice filter.", e);
        throw new SessionInternalError("Problems handling task invoice filter.");
      } catch (TaskException e) {
        LOG.fatal("Problems excecuting task invoice filter.", e);
        throw new SessionInternalError("Problems executing task invoice filter.");
      }
    }

    if (newInvoices.size() == 0) {
      // no orders or invoices for this invoice
      LOG.debug("No applicable orders or invoices. No invoice generated (skipping invoices).");
      return null;
    }

    try {
      retValue = new InvoiceDTO[newInvoices.size()];
      int index = 0;
      for (NewInvoiceDTO invoice : newInvoices.values()) {
        LOG.debug(
            "Processing invoice "
                + invoice.getId()
                + " "
                + invoice.getBalance()
                + " "
                + invoice.getTotal());
        /*
         * Apply invoice composition tasks to the new invoices object
         */
        composeInvoice(entityId, user.getUserId(), invoice);

        LOG.debug(
            "  after composeInvoice "
                + invoice.getId()
                + " "
                + invoice.getBalance()
                + " "
                + invoice.getTotal());
        if (!isReview) {
          // process events after orders added to invoice
          processOrderAddedOnInvoiceEvents(invoice, entityId);
          LOG.debug("processing old invoices");
          for (InvoiceDTO oldInvoice : invoice.getInvoices()) {
            // since this invoice is being delegated, mark it as being carried forward
            // so that it is not re-processed later. do not clear the old balance!
            LOG.debug(
                "  setting status unpaid and carried on "
                    + oldInvoice.getId()
                    + " "
                    + oldInvoice.getBalance()
                    + " "
                    + oldInvoice.getTotal());
            oldInvoice.setInvoiceStatus(
                new InvoiceStatusDAS().find(Constants.INVOICE_STATUS_UNPAID_AND_CARRIED));
          }
        }

        /*
         * apply this object to the DB, generating the actual rows
         */
        // only if the invoice generated actually has some lines in it
        if (invoice.areLinesGeneratedEmpty()) {
          LOG.warn(
              "User "
                  + user.getUserId()
                  + " had orders or invoices but"
                  + " the invoice composition task didn't generate any lines.");
          continue;
        }

        // If this is a web services API call, the billing
        // process id is 0. Don't link to the billing process
        // object for API calls.
        retValue[index] =
            generateDBInvoice(
                user.getUserId(),
                invoice,
                (process.getId() != 0 ? process : null),
                Constants.ORDER_PROCESS_ORIGIN_PROCESS);

        // try to get this new invioce paid by previously unlinked
        // payments
        if (paymentApplication && !isReview) {
          PaymentBL pBL = new PaymentBL();
          pBL.automaticPaymentApplication(retValue[index]);
        }

        index++;
      }
    } catch (PluggableTaskException e1) {
      LOG.error("Error handling pluggable tasks when composing an invoice");
      throw new SessionInternalError(e1);
    } catch (TaskException e1) {
      LOG.error("Task exception when composing an invoice");
      throw new SessionInternalError(e1);
    } catch (Exception e1) {
      LOG.error("Error, probably linking payments", e1);
      throw new SessionInternalError(e1);
    }

    InvoicesGeneratedEvent generatedEvent = new InvoicesGeneratedEvent(entityId, process.getId());
    generatedEvent.addInvoices(retValue);
    EventManager.process(generatedEvent);

    return retValue;
  }