Java tutorial
/* jBilling - The Enterprise Open Source Billing System Copyright (C) 2003-2011 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/>. */ package com.sapienter.jbilling.server.process; import java.math.BigDecimal; import java.sql.SQLException; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.Logger; import org.hibernate.ScrollableResults; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; import com.sapienter.jbilling.common.SessionInternalError; import com.sapienter.jbilling.common.Util; import com.sapienter.jbilling.server.invoice.InvoiceBL; import com.sapienter.jbilling.server.invoice.PaperInvoiceBatchBL; import com.sapienter.jbilling.server.invoice.db.InvoiceDAS; 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.payment.event.EndProcessPaymentEvent; import com.sapienter.jbilling.server.payment.event.ProcessPaymentEvent; import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskManager; import com.sapienter.jbilling.server.process.db.*; import com.sapienter.jbilling.server.process.event.NoNewInvoiceEvent; import com.sapienter.jbilling.server.process.task.BasicBillingProcessFilterTask; import com.sapienter.jbilling.server.process.task.IBillingProcessFilterTask; import com.sapienter.jbilling.server.system.event.EventManager; import com.sapienter.jbilling.server.user.EntityBL; import com.sapienter.jbilling.server.user.UserBL; import com.sapienter.jbilling.server.user.db.CompanyDAS; import com.sapienter.jbilling.server.user.db.CompanyDTO; import com.sapienter.jbilling.server.util.Constants; import com.sapienter.jbilling.server.util.Context; import com.sapienter.jbilling.server.util.MapPeriodToCalendar; import com.sapienter.jbilling.server.util.PreferenceBL; import com.sapienter.jbilling.server.util.audit.EventLogger; /** * * This is the session facade for the all the billing process and its * related services. */ @Transactional(propagation = Propagation.REQUIRED) public class BillingProcessSessionBean implements IBillingProcessSessionBean { private static final Logger LOG = Logger.getLogger(BillingProcessSessionBean.class); private static final AtomicBoolean running = new AtomicBoolean(false); /** * Gets the invoices for the specified process id. The returned collection * is of extended dtos (InvoiceDTO). * @param processId * @return A collection of InvoiceDTO objects * @throws SessionInternalError */ public Collection getGeneratedInvoices(Integer processId) { // find the billing_process home interface BillingProcessDAS processHome = new BillingProcessDAS(); Collection<InvoiceDTO> invoices = new InvoiceDAS().findByProcess(processHome.find(processId)); for (InvoiceDTO invoice : invoices) { invoice.getOrderProcesses().iterator().next().getId(); // it is a touch } return invoices; } /** * @param entityId * @param languageId * @return * @throws SessionInternalError */ public AgeingDTOEx[] getAgeingSteps(Integer entityId, Integer executorLanguageId, Integer languageId) throws SessionInternalError { try { AgeingBL ageing = new AgeingBL(); return ageing.getSteps(entityId, executorLanguageId, languageId); } catch (Exception e) { throw new SessionInternalError(e); } } /** * @param entityId * @param languageId * @param steps * @throws SessionInternalError */ @Transactional(propagation = Propagation.REQUIRES_NEW) public void setAgeingSteps(Integer entityId, Integer languageId, AgeingDTOEx[] steps) throws SessionInternalError { try { AgeingBL ageing = new AgeingBL(); ageing.setSteps(entityId, languageId, steps); } catch (Exception e) { throw new SessionInternalError(e); } } public void generateReview(Integer entityId, Date billingDate, Integer periodType, Integer periodValue) throws SessionInternalError { LOG.debug("Generating review entity " + entityId); IBillingProcessSessionBean local = (IBillingProcessSessionBean) Context .getBean(Context.Name.BILLING_PROCESS_SESSION); local.processEntity(entityId, billingDate, periodType, periodValue, true); // let know this entity that a new reivew is now pending approval try { String params[] = new String[1]; params[0] = entityId.toString(); NotificationBL.sendSapienterEmail(entityId, "process.new_review", null, params); } catch (Exception e) { LOG.warn("Exception sending email to entity", e); } } /** * Creates the billing process record. This has to be done in its own * transaction (thus, in its own method), so new invoices can link to * an existing process record in the db. */ @Transactional(propagation = Propagation.REQUIRES_NEW) public Integer createProcessRecord(Integer entityId, Date billingDate, Integer periodType, Integer periodValue, boolean isReview, Integer retries) throws SQLException { BillingProcessBL bpBL = new BillingProcessBL(); BillingProcessDTO dto = new BillingProcessDTO(); // process can't leave reviews behind, and a review has to // delete the previous one too bpBL.purgeReview(entityId, isReview); //I need to find the entity CompanyDAS comDas = new CompanyDAS(); CompanyDTO company = comDas.find(entityId); //I need to find the PeriodUnit PeriodUnitDAS periodDas = new PeriodUnitDAS(); PeriodUnitDTO period = periodDas.find(periodType); dto.setEntity(company); dto.setBillingDate(Util.truncateDate(billingDate)); dto.setPeriodUnit(period); dto.setPeriodValue(periodValue); dto.setIsReview(isReview ? new Integer(1) : new Integer(0)); dto.setRetriesToDo(retries); bpBL.findOrCreate(dto); return bpBL.getEntity().getId(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public Integer createRetryRun(Integer processId) { BillingProcessBL process = new BillingProcessBL(processId); // create a new run record BillingProcessRunBL runBL = new BillingProcessRunBL(); runBL.create(process.getEntity(), process.getEntity().getBillingDate()); LOG.debug("created process run " + runBL.getEntity().getId()); return runBL.getEntity().getId(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void processEntity(Integer entityId, Date billingDate, Integer periodType, Integer periodValue, boolean isReview) throws SessionInternalError { if (entityId == null || billingDate == null) { throw new SessionInternalError("entityId and billingDate can't be null"); } try { ConfigurationBL conf = new ConfigurationBL(entityId); IBillingProcessSessionBean local = (IBillingProcessSessionBean) Context .getBean(Context.Name.BILLING_PROCESS_SESSION); Integer billingProcessId = local.createProcessRecord(entityId, billingDate, periodType, periodValue, isReview, conf.getEntity().getRetries()); BillingProcessRunBL billingProcessRunBL = new BillingProcessRunBL(); billingProcessRunBL.setProcess(billingProcessId); // TODO: all the customer's id in memory is not a good idea. 1M customers would be 4MB of memory List<Integer> successfullUsers = billingProcessRunBL.findSuccessfullUsers(); // start processing users of this entity int totalInvoices = 0; boolean onlyRecurring; // find out parameters from the configuration onlyRecurring = conf.getEntity().getOnlyRecurring() == 1; LOG.debug("**** ENTITY " + entityId + " PROCESSING USERS"); //Load the pluggable task for filtering the users PluggableTaskManager taskManager = new PluggableTaskManager(entityId, Constants.PLUGGABLE_TASK_BILL_PROCESS_FILTER); IBillingProcessFilterTask task = (IBillingProcessFilterTask) taskManager.getNextClass(); // If one was not configured just use the basic task by default if (task == null) { task = new BasicBillingProcessFilterTask(); } BillingProcessDAS bpDas = new BillingProcessDAS(); int usersFailed = 0; ScrollableResults userCursor = task.findUsersToProcess(entityId, billingDate); if (userCursor != null) { int count = 0; while (userCursor.next()) { Integer userId = (Integer) userCursor.get(0); if (successfullUsers.contains(userId)) { // TODO: change this by a query to the DB LOG.debug("User #" + userId + " was successfully processed during previous run. Skipping."); continue; } Integer result[] = null; try { result = local.processUser(billingProcessId, userId, isReview, onlyRecurring); } catch (Throwable ex) { LOG.error("Exception was caught when processing User #" + userId + ". Continue process skipping user .", ex); local.addProcessRunUser(billingProcessId, userId, ProcessRunUserDTO.STATUS_FAILED); } if (result != null) { LOG.debug("User " + userId + " done invoice generation."); if (!isReview) { for (int f = 0; f < result.length; f++) { local.emailAndPayment(entityId, result[f], billingProcessId, conf.getEntity().getAutoPayment().intValue() == 1); } LOG.debug("User " + userId + " done email & payment."); } totalInvoices += result.length; local.addProcessRunUser(billingProcessId, userId, ProcessRunUserDTO.STATUS_SUCCEEDED); } else { LOG.debug("User " + userId + " NOT done"); local.addProcessRunUser(billingProcessId, userId, ProcessRunUserDTO.STATUS_FAILED); ++usersFailed; } // make sure the memory doesn't get flooded if (++count % Constants.HIBERNATE_BATCH_SIZE == 0) { bpDas.reset(); } } userCursor.close(); // done with the cursor, needs manual closing } // restore the configuration in the session, the reset removed it conf.set(entityId); if (usersFailed == 0) { // only if all got well processed // if some of the invoices were paper invoices, a new file with all // of them has to be generated try { BillingProcessBL process = new BillingProcessBL(billingProcessId); PaperInvoiceBatchDTO batch = process.getEntity().getPaperInvoiceBatch(); if (totalInvoices > 0 && batch != null) { PaperInvoiceBatchBL batchBl = new PaperInvoiceBatchBL(batch); batchBl.compileInvoiceFilesForProcess(entityId); // send the file as an attachment batchBl.sendEmail(); } } catch (Exception e) { LOG.error("Error generetaing batch file", e); } // now update the billing proces record } if (usersFailed == 0) { Integer processRunId = local.updateProcessRunFinished(billingProcessId, Constants.PROCESS_RUN_STATUS_SUCCESS); if (!isReview) { // the payment processing is happening in parallel // this event marks the end of it EndProcessPaymentEvent event = new EndProcessPaymentEvent(processRunId, entityId); EventManager.process(event); // and finally the next run date in the config GregorianCalendar cal = new GregorianCalendar(); cal.setTime(billingDate); cal.add(MapPeriodToCalendar.map(periodType), periodValue.intValue()); conf.getEntity().setNextRunDate(cal.getTime()); LOG.debug("Updated run date to " + cal.getTime()); } } else { local.updateProcessRunFinished(billingProcessId, Constants.PROCESS_RUN_STATUS_FAILED); billingProcessRunBL.notifyProcessRunFailure(entityId, usersFailed); // TODO: check, if updating totals needed // TODO: in the case of errors during users processing BillingProcessRunBL runBL = new BillingProcessRunBL(); runBL.setProcess(billingProcessId); // update the totals runBL.updateTotals(billingProcessId); } LOG.debug("**** ENTITY " + entityId + " DONE. Failed users = " + usersFailed); // TODO: review that this is not needed: EventManager.process(generatedEvent); } catch (Exception e) { // no need to specify a rollback, an error in any of the // updates would not require the rest to be rolled back. // Actually, it's better to keep as far as it went. LOG.error("Error processing entity " + entityId, e); } } /** * This method process a payment synchronously. It is a wrapper to the payment processing * so it runs in its own transaction */ @Transactional(propagation = Propagation.REQUIRES_NEW) public void processPayment(Integer processId, Integer runId, Integer invoiceId) { try { BillingProcessBL bl = new BillingProcessBL(); bl.generatePayment(processId, runId, invoiceId); } catch (Exception e) { LOG.error("Exception processing a payment ", e); } } /** * This method marks the end of payment processing. It is a wrapper * so it runs in its own transaction */ @Transactional(propagation = Propagation.REQUIRES_NEW) public void endPayments(Integer runId) { BillingProcessRunBL run = new BillingProcessRunBL(runId); run.updatePaymentsFinished(); // update the totals run.updateTotals(run.getEntity().getBillingProcess().getId()); run.updatePaymentsStatistic(run.getEntity().getId()); } @Transactional(propagation = Propagation.REQUIRES_NEW) public boolean verifyIsRetry(Integer processId, int retryDays, Date today) { GregorianCalendar cal = new GregorianCalendar(); // find the last run date BillingProcessBL process = new BillingProcessBL(processId); BillingProcessRunBL runBL = new BillingProcessRunBL(); ProcessRunDTO lastRun = (ProcessRunDTO) Collections.max(process.getEntity().getProcessRuns(), runBL.new DateComparator()); cal.setTime(Util.truncateDate(lastRun.getStarted())); LOG.debug("Retry evaluation lastrun = " + cal.getTime()); cal.add(GregorianCalendar.DAY_OF_MONTH, retryDays); LOG.debug("Added days = " + cal.getTime() + " today = " + today); if (!cal.getTime().after(today)) { return true; } else { return false; } } @Transactional(propagation = Propagation.REQUIRES_NEW) public void doRetry(Integer processId, int retryDays, Date today) throws SessionInternalError { try { IBillingProcessSessionBean process = (IBillingProcessSessionBean) Context .getBean(Context.Name.BILLING_PROCESS_SESSION); if (process.verifyIsRetry(processId, retryDays, today)) { // it's time for a retry LOG.debug("Retring process " + processId); Integer runId = process.createRetryRun(processId); Integer entityId = new BillingProcessDAS().find(processId).getEntity().getId(); // get the invoices yet to be paid from this process InvoiceBL invoiceBL = new InvoiceBL(); for (Iterator it = invoiceBL.getHome().findProccesableByProcess(processId).iterator(); it .hasNext();) { InvoiceDTO invoice = (InvoiceDTO) it.next(); LOG.debug("Retrying invoice " + invoice.getId()); // post the need of a payment process, it'll be done asynchronusly ProcessPaymentEvent event = new ProcessPaymentEvent(invoice.getId(), null, runId, entityId); EventManager.process(event); } // update the end date of this run BillingProcessRunBL runBl = new BillingProcessRunBL(runId); runBl.updateFinished(Constants.PROCESS_RUN_STATUS_SUCCESS); // the payment processing is happening in parallel // this event marks the end of it EndProcessPaymentEvent event = new EndProcessPaymentEvent(runId, entityId); EventManager.process(event); // update the process: one less retry to do BillingProcessBL bl = new BillingProcessBL(processId); int now = bl.getEntity().getRetriesToDo(); now--; bl.getEntity().setRetriesToDo(new Integer(now)); } } catch (Exception e) { throw new SessionInternalError(e); } } @Transactional(propagation = Propagation.REQUIRES_NEW) public void emailAndPayment(Integer entityId, Integer invoiceId, Integer processId, boolean processPayment) { try { InvoiceBL invoice = new InvoiceBL(invoiceId); Integer userId = invoice.getEntity().getBaseUser().getUserId(); LOG.debug("email and payment for user " + userId + " invoice " + invoiceId); // last but not least, let this user know about his/her new // invoice. NotificationBL notif = new NotificationBL(); try { MessageDTO[] invoiceMessage = notif.getInvoiceMessages(entityId, processId, invoice.getEntity().getBaseUser().getLanguageIdField(), invoice.getEntity()); INotificationSessionBean notificationSess = (INotificationSessionBean) Context .getBean(Context.Name.NOTIFICATION_SESSION); for (int msg = 0; msg < invoiceMessage.length; msg++) { notificationSess.notify(userId, invoiceMessage[msg]); } } catch (NotificationNotFoundException e) { LOG.warn("Invoice message not defined for entity " + entityId + " Invoice email not sent"); } if (processPayment) { // when the preference is set, // only process payment if it doesn't have a negative balance // that wasn't caused by a carried balance InvoiceDTO dto = invoice.getDTO(); if (BigDecimal.ZERO.compareTo(dto.getBalance()) > 0 && BigDecimal.ZERO.compareTo(dto.getCarriedBalance()) <= 0) { PreferenceBL preferenceBL = new PreferenceBL(); try { preferenceBL.set(entityId, Constants.PREFERENCE_DELAY_NEGATIVE_PAYMENTS); } catch (EmptyResultDataAccessException fe) { /* use default */ } if (preferenceBL.getInt() == 1) { processPayment = false; LOG.warn("Delaying invoice payment with negative balance and no negative carried balance"); } } if (processPayment && BigDecimal.ZERO.compareTo(dto.getBalance()) != 0) { ProcessPaymentEvent event = new ProcessPaymentEvent(invoiceId, processId, null, entityId); EventManager.process(event); } else { LOG.debug("Not processing a payment, balance of invoice is " + dto.getBalance()); } } } catch (Exception e) { LOG.error("sending email and processing payment", e); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } } /** * Process a user, generating the invoice/s, * @param userId */ @Transactional(propagation = Propagation.REQUIRES_NEW) public Integer[] processUser(Integer processId, Integer userId, boolean isReview, boolean onlyRecurring) { int invoiceGenerated = 0; Integer[] retValue = null; try { UserBL user = new UserBL(userId); if (!user.canInvoice()) { LOG.debug("Skipping non-customer / subaccount user " + userId); return new Integer[0]; } BillingProcessBL processBL = new BillingProcessBL(processId); BillingProcessDTO process = processBL.getEntity(); // payment and notification only needed if this user gets a // new invoice. InvoiceDTO newInvoices[] = processBL.generateInvoice(process, user.getEntity(), isReview, onlyRecurring); if (newInvoices == null) { if (!isReview) { NoNewInvoiceEvent event = new NoNewInvoiceEvent(user.getEntityId(userId), userId, process.getBillingDate(), user.getEntity().getSubscriberStatus().getId()); EventManager.process(event); } return new Integer[0]; } retValue = new Integer[newInvoices.length]; for (int f = 0; f < newInvoices.length; f++) { retValue[f] = newInvoices[f].getId(); invoiceGenerated++; } LOG.info("The user " + userId + " has been processed." + invoiceGenerated); } catch (Throwable e) { LOG.error("Exception caught when processing the user " + userId, e); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // rollback ! return null; // the user was not processed } return retValue; } public BillingProcessDTOEx getDto(Integer processId, Integer languageId) { BillingProcessDTOEx retValue = null; BillingProcessBL process = new BillingProcessBL(processId); retValue = process.getDtoEx(languageId); if (retValue != null) retValue.toString(); // as a form of touch return retValue; } public BillingProcessConfigurationDTO getConfigurationDto(Integer entityId) throws SessionInternalError { BillingProcessConfigurationDTO retValue = null; try { ConfigurationBL config = new ConfigurationBL(entityId); retValue = config.getDTO(); } catch (Exception e) { throw new SessionInternalError(e); } return retValue; } public Integer createUpdateConfiguration(Integer executorId, BillingProcessConfigurationDTO dto) throws SessionInternalError { Integer retValue; try { LOG.debug("Updating configuration " + dto); ConfigurationBL config = new ConfigurationBL(); retValue = config.createUpdate(executorId, dto); } catch (Exception e) { throw new SessionInternalError(e); } return retValue; } public Integer getLast(Integer entityId) throws SessionInternalError { int retValue; try { BillingProcessBL process = new BillingProcessBL(); retValue = process.getLast(entityId); } catch (Exception e) { throw new SessionInternalError(e); } return retValue > 0 ? new Integer(retValue) : null; } public BillingProcessDTOEx getReviewDto(Integer entityId, Integer languageId) { BillingProcessDTOEx dto = null; BillingProcessBL process = new BillingProcessBL(); dto = process.getReviewDTO(entityId, languageId); if (dto != null) dto.toString(); // as a touch return dto; } public BillingProcessConfigurationDTO setReviewApproval(Integer executorId, Integer entityId, Boolean flag) throws SessionInternalError { try { LOG.debug("Setting review approval : " + flag); ConfigurationBL config = new ConfigurationBL(entityId); config.setReviewApproval(executorId, flag.booleanValue()); return getConfigurationDto(entityId); } catch (Exception e) { throw new SessionInternalError(e); } } public boolean trigger(Date pToday) throws SessionInternalError { if (!running.compareAndSet(false, true)) { LOG.warn("Failed to trigger billing process at " + pToday.getTime() + ", another process is already running."); return false; } try { Date today = Util.truncateDate(pToday); EventLogger eLogger = EventLogger.getInstance(); BillingProcessBL processBL = new BillingProcessBL(); GregorianCalendar cal = new GregorianCalendar(); IBillingProcessSessionBean local = (IBillingProcessSessionBean) Context .getBean(Context.Name.BILLING_PROCESS_SESSION); // loop over all the entities EntityBL entityBL = new EntityBL(); Integer entityArray[] = entityBL.getAllIDs(); LOG.debug("Running trigger. Today = " + today + "[" + today.getTime() + "] entities = " + entityArray.length); for (int entityIndex = 0; entityIndex < entityArray.length; entityIndex++) { Integer entityId = entityArray[entityIndex]; LOG.debug("New entity row index " + entityIndex + " of " + entityArray.length); LOG.debug("Processing (1) entity " + entityId + " total = " + entityArray.length); // now process this entity ConfigurationBL configEntity = new ConfigurationBL(entityId); BillingProcessConfigurationDTO config = configEntity.getDTO(); if (!config.getNextRunDate().after(today)) { // there should be a run today boolean doRun = true; LOG.debug("A process has to be done for entity " + entityId); // check that: the configuration requires a review // AND, there is no partial run already there (failed) if (config.getGenerateReport() == 1 && new BillingProcessDAS().isPresent(entityId, 0, config.getNextRunDate()) == null) { // a review had to be done for the run to go ahead boolean reviewPresent = processBL.isReviewPresent(entityId); if (!reviewPresent) { // review wasn't generated LOG.warn("Review is required but not present for " + "entity " + entityId); eLogger.warning(entityId, null, config.getId(), EventLogger.MODULE_BILLING_PROCESS, EventLogger.BILLING_REVIEW_NOT_GENERATED, Constants.TABLE_BILLING_PROCESS_CONFIGURATION); generateReview(entityId, config.getNextRunDate(), config.getPeriodUnit().getId(), config.getPeriodValue()); doRun = false; } else if (new Integer(config.getReviewStatus()) .equals(Constants.REVIEW_STATUS_GENERATED)) { // the review has to be reviewd yet GregorianCalendar now = new GregorianCalendar(); LOG.warn("Review is required but is not approved. Entity " + entityId + " hour is " + now.get(GregorianCalendar.HOUR_OF_DAY)); eLogger.warning(entityId, null, config.getId(), EventLogger.MODULE_BILLING_PROCESS, EventLogger.BILLING_REVIEW_NOT_APPROVED, Constants.TABLE_BILLING_PROCESS_CONFIGURATION); try { // only once per day please if (now.get(GregorianCalendar.HOUR_OF_DAY) < 1) { String params[] = new String[1]; params[0] = entityId.toString(); NotificationBL.sendSapienterEmail(entityId, "process.review_waiting", null, params); } } catch (Exception e) { LOG.warn("Exception sending an entity email", e); } doRun = false; } else if (new Integer(config.getReviewStatus()) .equals(Constants.REVIEW_STATUS_DISAPPROVED)) { // is has been disapproved, let's regenerate LOG.debug("The process should run, but the review has been disapproved"); generateReview(entityId, config.getNextRunDate(), config.getPeriodUnit().getId(), config.getPeriodValue()); doRun = false; } } // do the run if (doRun) { local.processEntity(entityId, config.getNextRunDate(), config.getPeriodUnit().getId(), config.getPeriodValue(), false); } } else { // no run, may be then a review generation LOG.debug("No run scheduled. Next run on " + config.getNextRunDate().getTime()); /* * Review generation */ if (config.getGenerateReport() == 1) { cal.setTime(config.getNextRunDate()); cal.add(GregorianCalendar.DAY_OF_MONTH, -config.getDaysForReport().intValue()); if (!cal.getTime().after(today)) { boolean reviewPresent = processBL.isReviewPresent(entityId); if (reviewPresent && !Constants.REVIEW_STATUS_DISAPPROVED.equals(config.getReviewStatus())) { // there's already a review there, and it's been // either approved or not yet reviewed } else { LOG.debug("Review disapproved. Regeneratting."); generateReview(entityId, config.getNextRunDate(), config.getPeriodUnit().getId(), config.getPeriodValue()); } } } } // else (no run) /* * Retries, only if automatic payment is set */ if (config.getAutoPayment() == 1) { // get the last process Integer[] processToRetry = processBL.getToRetry(entityId); for (Integer aProcessToRetry : processToRetry) { local.doRetry(aProcessToRetry, config.getDaysForRetry(), today); } } } // for all entities } catch (Exception e) { throw new SessionInternalError(e); } finally { running.set(false); } return true; } /** * @return the id of the invoice generated */ public InvoiceDTO generateInvoice(Integer orderId, Integer invoiceId, Integer languageId) throws SessionInternalError { try { BillingProcessBL process = new BillingProcessBL(); InvoiceDTO invoice = process.generateInvoice(orderId, invoiceId); invoice.touch(); return invoice; } catch (Exception e) { throw new SessionInternalError(e); } } public void reviewUsersStatus(Integer entityId, Date today) throws SessionInternalError { try { AgeingBL age = new AgeingBL(); age.reviewAll(entityId, today); } catch (Exception e) { throw new SessionInternalError(e); } } /** * Update status of BillingProcessRun in new transaction * for accessing updated entity from other thread * @param billingProcessId id of billing process for searching ProcessRun * @return id of updated ProcessRunDTO */ @Transactional(propagation = Propagation.REQUIRES_NEW) public Integer updateProcessRunFinished(Integer billingProcessId, Integer processRunStatusId) { BillingProcessRunBL runBL = new BillingProcessRunBL(); runBL.setProcess(billingProcessId); runBL.updateFinished(processRunStatusId); return runBL.getEntity().getId(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public Integer addProcessRunUser(Integer billingProcessId, Integer userId, Integer status) { BillingProcessRunBL runBL = new BillingProcessRunBL(); runBL.setProcess(billingProcessId); return runBL.addProcessRunUser(userId, status).getId(); } /** * Returns true if the Billing Process is running. */ public boolean isBillingRunning() { return running.get(); } }