/* 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.notification; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.math.BigDecimal; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Properties; import java.util.ResourceBundle; import java.util.List; import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Part; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JRParameter; import net.sf.jasperreports.engine.JasperExportManager; import net.sf.jasperreports.engine.JasperFillManager; import net.sf.jasperreports.engine.JasperPrint; import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource; import org.apache.log4j.Logger; import org.hibernate.collection.PersistentSet; import org.springframework.jdbc.datasource.DataSourceUtils; import javax.sql.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.invoice.db.InvoiceLineDTO; import com.sapienter.jbilling.server.list.ResultList; import com.sapienter.jbilling.server.notification.db.NotificationMessageDAS; import com.sapienter.jbilling.server.notification.db.NotificationMessageDTO; import com.sapienter.jbilling.server.notification.db.NotificationMessageLineDAS; import com.sapienter.jbilling.server.notification.db.NotificationMessageLineDTO; import com.sapienter.jbilling.server.notification.db.NotificationMessageSectionDAS; import com.sapienter.jbilling.server.notification.db.NotificationMessageSectionDTO; import com.sapienter.jbilling.server.payment.PaymentBL; import com.sapienter.jbilling.server.payment.PaymentDTOEx; import com.sapienter.jbilling.server.pluggableTask.NotificationTask; import com.sapienter.jbilling.server.pluggableTask.PaperInvoiceNotificationTask; import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskBL; import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskManager; import com.sapienter.jbilling.server.user.ContactBL; import com.sapienter.jbilling.server.user.ContactDTOEx; import com.sapienter.jbilling.server.user.EntityBL; import com.sapienter.jbilling.server.user.UserBL; import com.sapienter.jbilling.server.user.contact.db.ContactFieldDTO; import com.sapienter.jbilling.server.user.db.CompanyDAS; import com.sapienter.jbilling.server.user.db.CreditCardDTO; import com.sapienter.jbilling.server.user.partner.PartnerBL; 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.Util; import java.io.StringWriter; import java.util.ArrayList; import java.util.Map; import javax.sql.DataSource; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.springframework.dao.EmptyResultDataAccessException; import org.apache.velocity.tools.generic.DateTool; import org.apache.velocity.tools.generic.MathTool; import org.apache.velocity.tools.generic.NumberTool; import org.apache.velocity.tools.generic.RenderTool; import org.apache.velocity.tools.generic.EscapeTool; import org.apache.velocity.tools.generic.ResourceTool; import org.apache.velocity.tools.generic.AlternatorTool; import org.apache.velocity.tools.generic.ValueParser; import org.apache.velocity.tools.generic.ListTool; import org.apache.velocity.tools.generic.SortTool; import org.apache.velocity.tools.generic.IteratorTool; public class NotificationBL extends ResultList implements NotificationSQL { // private NotificationMessageDAS messageDas = null; private NotificationMessageDTO messageRow = null; private NotificationMessageSectionDAS messageSectionHome = null; private NotificationMessageLineDAS messageLineHome = null; private static final Logger LOG = Logger.getLogger(NotificationBL.class); public NotificationBL(Integer messageId) { init(); LOG.debug("Constructor ..."); messageRow = messageDas.find(messageId); } public NotificationBL() { init(); } private void init() { messageDas = new NotificationMessageDAS(); messageSectionHome = new NotificationMessageSectionDAS(); messageLineHome = new NotificationMessageLineDAS(); } public NotificationMessageDTO getEntity() { return messageRow; } public void set(Integer type, Integer languageId, Integer entityId) { messageRow = messageDas.findIt(type, entityId, languageId); } public MessageDTO getDTO() throws SessionInternalError { MessageDTO retValue = new MessageDTO(); retValue.setLanguageId(messageRow.getLanguage().getId()); retValue.setTypeId(messageRow.getNotificationMessageType().getId()); retValue.setUseFlag(new Boolean(messageRow.getUseFlag() == 1)); setContent(retValue); return retValue; } public Integer createUpdate(Integer entityId, MessageDTO dto) { set(dto.getTypeId(), dto.getLanguageId(), entityId); // it's just so easy to delete cascade and recreate ...:D if (messageRow != null) { messageDas.delete(messageRow); } messageRow = messageDas.create(dto.getTypeId(), entityId, dto .getLanguageId(), dto.getUseFlag()); // add the sections with the lines to the message entity for (int f = 0; f < dto.getContent().length; f++) { MessageSection section = dto.getContent()[f]; // create the section bean NotificationMessageSectionDTO sectionBean = messageSectionHome .create(section.getSection()); int index = 0; while (index < section.getContent().length()) { String line; if (index + MessageDTO.LINE_MAX.intValue() <= section .getContent().length()) { line = section.getContent().substring(index, index + MessageDTO.LINE_MAX.intValue()); } else { line = section.getContent().substring(index); } index += MessageDTO.LINE_MAX.intValue(); NotificationMessageLineDTO nml = messageLineHome.create(line); nml.setNotificationMessageSection(sectionBean); sectionBean.getNotificationMessageLines().add(nml); } sectionBean.setNotificationMessage(messageRow); messageRow.getNotificationMessageSections().add(sectionBean); } PersistentSet msjs = (PersistentSet) messageRow .getNotificationMessageSections(); NotificationMessageSectionDTO nnnn = ((NotificationMessageSectionDTO) msjs .toArray()[0]); PersistentSet nm = (PersistentSet) nnnn.getNotificationMessageLines(); messageDas.save(messageRow); return messageRow.getId(); } /* * Getters. These provide easy generation of messages by their type. So each * getter kows which type will generate, and gets as parameters the * information to generate that particular type of message. */ public MessageDTO[] getInvoiceMessages(Integer entityId, Integer processId, Integer languageId, InvoiceDTO invoice) throws SessionInternalError, NotificationNotFoundException { MessageDTO retValue[] = null; Integer deliveryMethod; // now see what kind of invoice this customers wants if (invoice.getBaseUser().getCustomer() == null) { // this shouldn't be necessary. The only reason is here is // because the test data has invoices for root users. In // reality, all users that will get an invoice have to be // customers deliveryMethod = Constants.D_METHOD_EMAIL; LOG.warn("A user that is not a customer is getting an invoice." + " User id = " + invoice.getBaseUser().getUserId()); } else { deliveryMethod = invoice.getBaseUser().getCustomer() .getInvoiceDeliveryMethod().getId(); } int index = 0; if (deliveryMethod.equals(Constants.D_METHOD_EMAIL_AND_PAPER)) { retValue = new MessageDTO[2]; } else { retValue = new MessageDTO[1]; } if (deliveryMethod.equals(Constants.D_METHOD_EMAIL) || deliveryMethod.equals(Constants.D_METHOD_EMAIL_AND_PAPER)) { retValue[index] = getInvoiceEmailMessage(entityId, languageId, invoice); index++; } if (deliveryMethod.equals(Constants.D_METHOD_PAPER) || deliveryMethod.equals(Constants.D_METHOD_EMAIL_AND_PAPER)) { retValue[index] = getInvoicePaperMessage(entityId, processId, languageId, invoice); index++; } return retValue; } public MessageDTO getInvoicePaperMessage(Integer entityId, Integer processId, Integer languageId, InvoiceDTO invoice) throws SessionInternalError { MessageDTO retValue = new MessageDTO(); retValue.setTypeId(MessageDTO.TYPE_INVOICE_PAPER); retValue.setDeliveryMethodId(Constants.D_METHOD_PAPER); // put the whole invoice as a parameter InvoiceBL invoiceBl = new InvoiceBL(invoice); InvoiceDTO invoiceDto = invoiceBl.getDTOEx(languageId, true); retValue.getParameters().put("invoiceDto", invoiceDto); // the process id is needed to maintain the batch record if (processId != null) { // single pdf invoices for the web-based app can ignore this retValue.getParameters().put("processId", processId); } try { setContent(retValue, MessageDTO.TYPE_INVOICE_PAPER, entityId, languageId); } catch (NotificationNotFoundException e1) { // put blanks MessageSection sectionContent = new MessageSection(new Integer(1), null); retValue.addSection(sectionContent); sectionContent = new MessageSection(new Integer(2), null); retValue.addSection(sectionContent); } return retValue; } public MessageDTO getPaymentMessage(Integer entityId, PaymentDTOEx dto, boolean result) throws SessionInternalError, NotificationNotFoundException { UserBL user = null; Integer languageId = null; MessageDTO message = initializeMessage(entityId, dto.getUserId()); message.setTypeId(result ? MessageDTO.TYPE_PAYMENT : new Integer( MessageDTO.TYPE_PAYMENT.intValue() + 1)); user = new UserBL(dto.getUserId()); languageId = user.getEntity().getLanguageIdField(); setContent(message, message.getTypeId(), entityId, languageId); // find the description for the payment method PaymentBL payment = new PaymentBL(); message.addParameter("method", payment.getMethodDescription(dto .getPaymentMethod(), languageId)); message.addParameter("total", Util.formatMoney(dto.getAmount(), dto .getUserId(), dto.getCurrency().getId(), true)); message.addParameter("payment", payment.getEntity()); // find an invoice in the list of invoices id if (dto.getInvoiceIds() != null && dto.getInvoiceIds().size() > 0) { Integer invoiceId = (Integer) dto.getInvoiceIds().get(0); InvoiceBL invoice = new InvoiceBL(invoiceId); message.addParameter("invoice_number", invoice.getEntity() .getPublicNumber().toString()); message.addParameter("invoice", invoice.getEntity()); } message.addParameter("payment", dto); return message; } public MessageDTO getInvoiceRemainderMessage(Integer entityId, Integer userId, Integer days, Date dueDate, String number, BigDecimal total, Date date, Integer currencyId) throws SessionInternalError, NotificationNotFoundException { UserBL user = null; Integer languageId = null; MessageDTO message = initializeMessage(entityId, userId); message.setTypeId(MessageDTO.TYPE_INVOICE_REMINDER); user = new UserBL(userId); languageId = user.getEntity().getLanguageIdField(); setContent(message, message.getTypeId(), entityId, languageId); message.addParameter("days", days.toString()); message.addParameter("dueDate", Util.formatDate(dueDate, userId)); message.addParameter("number", number); message.addParameter("total", Util.formatMoney(total, userId, currencyId, true)); message.addParameter("date", Util.formatDate(date, userId)); return message; } public MessageDTO getForgetPasswordEmailMessage(Integer entityId, Integer userId, Integer languageId) throws SessionInternalError, NotificationNotFoundException { MessageDTO message = initializeMessage(entityId, userId); message.setTypeId(MessageDTO.TYPE_FORGETPASSWORD_EMAIL); setContent(message, MessageDTO.TYPE_FORGETPASSWORD_EMAIL, entityId, languageId); return message; } public MessageDTO getInvoiceEmailMessage(Integer entityId, Integer languageId, InvoiceDTO invoice) throws SessionInternalError, NotificationNotFoundException { MessageDTO message = initializeMessage(entityId, invoice.getBaseUser() .getUserId()); message.setTypeId(MessageDTO.TYPE_INVOICE_EMAIL); setContent(message, MessageDTO.TYPE_INVOICE_EMAIL, entityId, languageId); message.addParameter("total", Util.formatMoney(invoice.getTotal(), invoice.getBaseUser().getUserId(), invoice.getCurrency().getId(), true)); message.addParameter("id", invoice.getId() + ""); message.addParameter("number", invoice.getPublicNumber()); // format the date depending of the customers locale message.addParameter("due_date", Util.formatDate(invoice.getDueDate(), invoice.getBaseUser().getUserId())); String notes = invoice.getCustomerNotes(); message.addParameter("notes", notes); message.addParameter("invoice", invoice); // if the entity has the preference of pdf attachment, do it try { PreferenceBL pref = new PreferenceBL(); try { pref.set(entityId, Constants.PREFERENCE_PDF_ATTACHMENT); } catch (EmptyResultDataAccessException e1) { // no problem, I'll get the defaults } if (pref.getInt() == 1) { message.setAttachmentFile(generatePaperInvoiceAsFile(invoice)); LOG.debug("Setted attachement " + message.getAttachmentFile()); } } catch (Exception e) { LOG.error(e); } return message; } public MessageDTO getAgeingMessage(Integer entityId, Integer languageId, Integer statusId, Integer userId) throws SessionInternalError, NotificationNotFoundException { MessageDTO message = initializeMessage(entityId, userId); message.setTypeId(new Integer(MessageDTO.TYPE_AGEING.intValue() + statusId.intValue() - 1)); try { setContent(message, message.getTypeId(), entityId, languageId); UserBL user = new UserBL(userId); InvoiceBL invoice = new InvoiceBL(); Integer invoiceId = invoice.getLastByUser(userId); if (invoiceId != null) { invoice.set(invoiceId); message.addParameter("total", Util.decimal2string(invoice.getEntity().getBalance(), user.getLocale())); message.addParameter("invoice", invoice.getEntity()); } else { LOG.warn("user " + userId + " has no invoice but an ageing " + "message is being sent"); } } catch (SQLException e1) { throw new SessionInternalError(e1); } return message; } public MessageDTO getOrderNotification(Integer entityId, Integer step, Integer languageId, Date activeSince, Date activeUntil, Integer userId, BigDecimal total, Integer currencyId) throws SessionInternalError, NotificationNotFoundException { MessageDTO retValue = initializeMessage(entityId, userId); retValue.setTypeId(new Integer(MessageDTO.TYPE_ORDER_NOTIF.intValue() + step.intValue() - 1)); try { setContent(retValue, retValue.getTypeId(), entityId, languageId); Locale locale; try { UserBL user = new UserBL(userId); locale = user.getLocale(); } catch (Exception e) { throw new SessionInternalError(e); } ResourceBundle bundle = ResourceBundle.getBundle( "entityNotifications", locale); SimpleDateFormat formatter = new SimpleDateFormat(bundle .getString("format.date")); retValue .addParameter("period_start", formatter.format(activeSince)); retValue.addParameter("period_end", formatter.format(activeUntil)); retValue.addParameter("total", Util.formatMoney(total, userId, currencyId, true)); } catch (ClassCastException e) { throw new SessionInternalError(e); } return retValue; } public MessageDTO getPayoutMessage(Integer entityId, Integer languageId, BigDecimal total, Date startDate, Date endDate, boolean clerk, Integer partnerId) throws SessionInternalError, NotificationNotFoundException { MessageDTO message = new MessageDTO(); if (!clerk) { message.setTypeId(MessageDTO.TYPE_PAYOUT); } else { message.setTypeId(MessageDTO.TYPE_CLERK_PAYOUT); } try { EntityBL en = new EntityBL(entityId); setContent(message, message.getTypeId(), entityId, languageId); message.addParameter("total", Util.decimal2string(total, en.getLocale())); message.addParameter("company", new CompanyDAS().find(entityId) .getDescription()); PartnerBL partner = new PartnerBL(partnerId); Calendar cal = Calendar.getInstance(); cal.setTime(endDate); message.addParameter("period_end", Util.formatDate(cal.getTime(), partner.getEntity().getUser().getUserId())); cal.setTime(startDate); message.addParameter("period_start", Util.formatDate(cal.getTime(), partner.getEntity().getUser().getUserId())); message.addParameter("partner_id", partnerId.toString()); } catch (ClassCastException e) { throw new SessionInternalError(e); } return message; } public MessageDTO getCreditCardMessage(Integer entityId, Integer languageId, Integer userId, CreditCardDTO creditCard) throws SessionInternalError, NotificationNotFoundException { MessageDTO message = initializeMessage(entityId, userId); message.setTypeId(MessageDTO.TYPE_CREDIT_CARD); setContent(message, message.getTypeId(), entityId, languageId); SimpleDateFormat format = new SimpleDateFormat("MM/yy"); message.addParameter("expiry_date", format.format(creditCard .getCcExpiry())); return message; } private void setContent(MessageDTO newMessage, Integer type, Integer entity, Integer language) throws SessionInternalError, NotificationNotFoundException { set(type, language, entity); if (messageRow != null) { if (messageRow.getUseFlag() == 0) { // if (messageRow.getUseFlag().intValue() == 0) { throw new NotificationNotFoundException("Notification " + "flaged for not use"); } setContent(newMessage); } else { String message = "Looking for notification message type " + type + " for entity " + entity + " language " + language + " but could not find it. This entity has " + "to specify " + "this notification message."; LOG.warn(message); throw new NotificationNotFoundException(message); } } private void setContent(MessageDTO newMessage) throws SessionInternalError { // go through the sections Collection sections = messageRow.getNotificationMessageSections(); for (Iterator it = sections.iterator(); it.hasNext();) { NotificationMessageSectionDTO section = (NotificationMessageSectionDTO) it .next(); // then through the lines of this section StringBuffer completeLine = new StringBuffer(); Collection lines = section.getNotificationMessageLines(); int checkOrder = 0; // there's nothing to assume that the lines // will be retrived in order, but the have to! List vLines = new ArrayList<NotificationMessageSectionDTO>(lines); Collections.sort(vLines, new NotificationLineEntityComparator()); for (Iterator it2 = vLines.iterator(); it2.hasNext();) { NotificationMessageLineDTO line = (NotificationMessageLineDTO) it2 .next(); if (line.getId() <= checkOrder) { // if (line.getId().intValue() <= checkOrder) { LOG.error("Lines have to be retreived in order. " + "See class java.util.TreeSet for solution or " + "Collections.sort()"); throw new SessionInternalError("Lines have to be " + "retreived in order."); } else { checkOrder = line.getId(); // checkOrder = line.getId().intValue(); } completeLine.append(line.getContent()); } // add the content of this section to the message MessageSection sectionContent = new MessageSection(section .getSection(), completeLine.toString()); newMessage.addSection(sectionContent); } } static public String parseParameters(String content, HashMap parameters) { // get the engine from Spring VelocityEngine velocity = (VelocityEngine) Context.getBean(Context.Name.VELOCITY); VelocityContext velocityContext = new VelocityContext(parameters); StringWriter result = new StringWriter(); try { velocity.evaluate(velocityContext, result, "Error template as string?", content); } catch (Exception e) { throw new SessionInternalError("Rendering email", NotificationBL.class, e); } return result.toString(); } /** * A rather expensive call for what it achieves. It looks suitable for caching, but then * it is rarely called (only from the GUI)... and then the orm cache helps too. * @param entityId * @return */ public int getSections(Integer entityId) { int higherSection = 0; try { PluggableTaskManager taskManager = new PluggableTaskManager( entityId, Constants.PLUGGABLE_TASK_NOTIFICATION); NotificationTask task = (NotificationTask) taskManager.getNextClass(); while (task != null) { if (task.getSections() > higherSection) { higherSection = task.getSections(); } task = (NotificationTask) taskManager.getNextClass(); } } catch (Exception e) { throw new SessionInternalError("Finding number of sections for notificaitons", NotificationBL.class, e); } return higherSection; } public CachedRowSet getTypeList(Integer languageId) throws SQLException, Exception { prepareStatement(NotificationSQL.listTypes); cachedResults.setInt(1, languageId.intValue()); execute(); conn.close(); return cachedResults; } public String getEmails(String separator, Integer entityId) throws SQLException { StringBuffer retValue = new StringBuffer(); conn = ((DataSource) Context.getBean(Context.Name.DATA_SOURCE)).getConnection(); PreparedStatement stmt = conn .prepareStatement(NotificationSQL.allEmails); stmt.setInt(1, entityId.intValue()); ResultSet res = stmt.executeQuery(); boolean first = true; while (res.next()) { if (first) { first = false; } else { retValue.append(separator); } retValue.append(res.getString(1)); } res.close(); stmt.close(); conn.close(); return retValue.toString(); } public static byte[] generatePaperInvoiceAsStream(String design, boolean useSqlQuery, InvoiceDTO invoice, ContactDTOEx from, ContactDTOEx to, String message1, String message2, Integer entityId, String username, String password) throws FileNotFoundException, SessionInternalError { JasperPrint report = generatePaperInvoice(design, useSqlQuery, invoice, from, to, message1, message2, entityId, username, password); try { return JasperExportManager.exportReportToPdf(report); } catch (JRException e) { LOG.error("Exception generating paper invoice", e); return null; } } public static String generatePaperInvoiceAsFile(String design, boolean useSqlQuery, InvoiceDTO invoice, ContactDTOEx from, ContactDTOEx to, String message1, String message2, Integer entityId, String username, String password) throws FileNotFoundException, SessionInternalError { JasperPrint report = generatePaperInvoice(design, useSqlQuery, invoice, from, to, message1, message2, entityId, username, password); String fileName = null; try { fileName = com.sapienter.jbilling.common.Util .getSysProp("base_dir") + "invoices/" + entityId + "-" + invoice.getId() + "-invoice.pdf"; JasperExportManager.exportReportToPdfFile(report, fileName); } catch (JRException e) { LOG.error("Exception generating paper invoice", e); } return fileName; } private static JasperPrint generatePaperInvoice(String design, boolean useSqlQuery, InvoiceDTO invoice, ContactDTOEx from, ContactDTOEx to, String message1, String message2, Integer entityId, String username, String password) throws FileNotFoundException, SessionInternalError { try { // This is needed for JasperRerpots to work, for some twisted XWindows issue System.setProperty("java.awt.headless", "true"); String designFile = com.sapienter.jbilling.common.Util.getSysProp("base_dir") + "designs/" + design + ".jasper"; File compiledDesign = new File(designFile); LOG.debug("Generating paper invoice with design file : " + designFile); FileInputStream stream = new FileInputStream(compiledDesign); Locale locale = (new UserBL(invoice.getUserId())).getLocale(); // add all the invoice data HashMap<String, Object> parameters = new HashMap<String, Object>(); parameters.put("invoiceNumber", invoice.getPublicNumber()); parameters.put("invoiceId", invoice.getId()); parameters.put("entityName", printable(from.getOrganizationName())); parameters.put("entityAddress", printable(from.getAddress1())); parameters.put("entityAddress2", printable(from.getAddress2())); parameters.put("entityPostalCode", printable(from.getPostalCode())); parameters.put("entityCity", printable(from.getCity())); parameters.put("entityProvince", printable(from.getStateProvince())); parameters.put("customerOrganization", printable(to.getOrganizationName())); parameters.put("customerName", printable(to.getFirstName(), to.getLastName())); parameters.put("customerAddress", printable(to.getAddress1())); parameters.put("customerAddress2", printable(to.getAddress2())); parameters.put("customerPostalCode", printable(to.getPostalCode())); parameters.put("customerCity", printable(to.getCity())); parameters.put("customerProvince", printable(to.getStateProvince())); parameters.put("customerUsername", username); parameters.put("customerPassword", password); parameters.put("customerId", invoice.getUserId().toString()); parameters.put("invoiceDate", Util.formatDate(invoice.getCreateDatetime(), invoice.getUserId())); parameters.put("invoiceDueDate", Util.formatDate(invoice.getDueDate(), invoice.getUserId())); // customer message LOG.debug("m1 = " + message1 + " m2 = " + message2); parameters.put("customerMessage1", printable(message1)); parameters.put("customerMessage2", printable(message2)); // invoice notes stripped of html line breaks String notes = invoice.getCustomerNotes(); if (notes != null) { notes = notes.replaceAll("<br/>", "\r\n"); } parameters.put("notes", notes); // now some info about payments try { InvoiceBL invoiceBL = new InvoiceBL(invoice.getId()); try { parameters.put("paid", Util.formatMoney(invoiceBL .getTotalPaid(), invoice.getUserId(), invoice .getCurrency().getId(), false)); // find the previous invoice and its payment for extra info invoiceBL.setPrevious(); parameters.put("prevInvoiceTotal", Util.formatMoney( invoiceBL.getEntity().getTotal(), invoice .getUserId(), invoice.getCurrency().getId(), false)); parameters.put("prevInvoicePaid", Util.formatMoney(invoiceBL.getTotalPaid(), invoice .getUserId(), invoice.getCurrency().getId(), false)); } catch (EmptyResultDataAccessException e1) { parameters.put("prevInvoiceTotal", "0"); parameters.put("prevInvoicePaid", "0"); } } catch (Exception e) { LOG.error("Exception generating paper invoice", e); return null; } // add all the custom contact fields // the from for (Iterator it = from.getFieldsTable().values().iterator(); it .hasNext();) { ContactFieldDTO field = (ContactFieldDTO) it.next(); String fieldName = field.getType().getPromptKey(); fieldName = fieldName.substring(fieldName.lastIndexOf('.') + 1); parameters.put("from_custom_" + fieldName, field.getContent()); } for (Iterator it = to.getFieldsTable().values().iterator(); it .hasNext();) { ContactFieldDTO field = (ContactFieldDTO) it.next(); String fieldName = field.getType().getPromptKey(); fieldName = fieldName.substring(fieldName.lastIndexOf('.') + 1); parameters.put("to_custom_" + fieldName, field.getContent()); } // the logo is a file File logo = new File(com.sapienter.jbilling.common.Util .getSysProp("base_dir") + "logos/entity-" + entityId + ".jpg"); parameters.put("entityLogo", logo); // the invoice lines go as the data source for the report // we need to extract the taxes from them, put the taxes as // an independent parameter, and add the taxes rates as more // parameters BigDecimal taxTotal = new BigDecimal(0); int taxItemIndex = 0; // I need a copy, so to not affect the real invoice List<InvoiceLineDTO> lines = new ArrayList<InvoiceLineDTO>(invoice.getInvoiceLines()); // Collections.copy(lines, invoice.getInvoiceLines()); List<InvoiceLineDTO> linesRemoved = new ArrayList<InvoiceLineDTO>(); for (InvoiceLineDTO line: lines) { // log.debug("Processing line " + line); // process the tax, if this line is one if (line.getInvoiceLineType() != null && // for headers/footers line.getInvoiceLineType().getId() == Constants.INVOICE_LINE_TYPE_TAX) { // update the total tax variable taxTotal = taxTotal.add(line.getAmount()); // add the tax amount as an array parameter parameters.put("taxItem_" + taxItemIndex, Util.decimal2string(line.getPrice(), locale)); taxItemIndex++; // taxes are not displayed as invoice lines linesRemoved.add(line); // can't do lines.remove(): ConcurrentModificationException } else if (line.getIsPercentage() != null && line.getIsPercentage().intValue() == 1) { // if the line is a percentage, remove the price line.setPrice(null); } } lines.removeAll(linesRemoved); // removed them once out of the loop. Otherwise it will throw // remove the last line, that is the total footer lines.remove(lines.size() - 1); // now add the tax parameters.put("tax", Util.formatMoney(taxTotal, invoice.getUserId(), invoice .getCurrency().getId(), false)); parameters.put("totalWithTax", Util.formatMoney(invoice.getTotal(), invoice.getUserId(), invoice.getCurrency().getId(), false)); parameters.put("totalWithoutTax", Util.formatMoney(invoice.getTotal().subtract(taxTotal), invoice.getUserId(), invoice.getCurrency().getId(), false)); parameters.put("balance", Util.formatMoney(invoice.getBalance(), invoice.getUserId(), invoice.getCurrency().getId(), false)); parameters.put("carriedBalance", Util.formatMoney(invoice.getCarriedBalance(), invoice.getUserId(), invoice.getCurrency().getId(), false)); LOG.debug("Parameter tax = " + parameters.get("tax") + " totalWithTax = " + parameters.get("totalWithTax") + " totalWithoutTax = " + parameters.get("totalWithoutTax") + " balance = " + parameters.get("balance")); // set report locale parameters.put(JRParameter.REPORT_LOCALE, locale); // set the subreport directory String subreportDir = com.sapienter.jbilling.common.Util .getSysProp("base_dir") + "designs/"; parameters.put("SUBREPORT_DIR", subreportDir); // at last, generate the report JasperPrint report = null; if (useSqlQuery) { DataSource dataSource = (DataSource) Context.getBean(Context.Name.DATA_SOURCE); Connection connection = DataSourceUtils.getConnection(dataSource); report = JasperFillManager.fillReport(stream, parameters, connection); DataSourceUtils.releaseConnection(connection, dataSource); } else { JRBeanCollectionDataSource data = new JRBeanCollectionDataSource(lines); report = JasperFillManager.fillReport(stream, parameters, data); } stream.close(); return report; } catch (Exception e) { LOG.error("Exception generating paper invoice", e); return null; } } private static String printable(String str) { if (str == null) { return ""; } return str; } /** * Safely concatenates 2 strings together with a blank space (" "). Null strings * are handled safely, and no extra concatenated character will be added if one * string is null. * * @param str * @param str2 * @return concatenated, printable string */ private static String printable(String str, String str2) { StringBuilder builder = new StringBuilder(); if (str != null) builder.append(str).append(" "); if (str2 != null) builder.append(str2); return builder.toString(); } public static void sendSapienterEmail(Integer entityId, String messageKey, String attachmentFileName, String[] params) throws MessagingException, IOException { String address = null; ContactBL contactBL = new ContactBL(); contactBL.setEntity(entityId); address = contactBL.getEntity().getEmail(); if (address == null) { // can't send something to the ether LOG.warn("Trying to send email to entity " + entityId + " but no address was found"); return; } sendSapienterEmail(address, entityId, messageKey, attachmentFileName, params); } /** * This method is intended to be used to send an email from the system to * the entity. This is different than from the entity to a customer, which * should use a notification pluggable task. The file * entityNotifications.properties has to have key + "_subject" and key + * "_body" Note: For any truble, the best documentation is the source code * of the MailTag of Jakarta taglibs */ public static void sendSapienterEmail(String address, Integer entityId, String messageKey, String attachmentFileName, String[] params) throws MessagingException, IOException { Properties prop = new Properties(); LOG.debug("seding sapienter email " + messageKey + " to " + address + " of entity " + entityId); // tell the server that is has to authenticate to the maileer // (yikes, this was painfull to find out) prop.setProperty("mail.smtp.auth", "true"); // create the session & message Session session = Session.getInstance(prop, null); Message msg = new MimeMessage(session); msg.setFrom(new InternetAddress(com.sapienter.jbilling.common.Util .getSysProp("email_from"), com.sapienter.jbilling.common.Util .getSysProp("email_from_name"))); // the to address msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse( address, false)); // the subject and body are international EntityBL entity = new EntityBL(entityId); Locale locale = entity.getLocale(); ResourceBundle rBundle = ResourceBundle.getBundle( "entityNotifications", locale); String subject = rBundle.getString(messageKey + "_subject"); String message = rBundle.getString(messageKey + "_body"); // if there are parameters, replace them if (params != null) { for (int f = 0; f < params.length; f++) { message = message.replaceFirst("\\|X\\|", params[f]); } } msg.setSubject(subject); if (attachmentFileName == null) { msg.setText(message); } else { // it is a 'multi part' email MimeMultipart mp = new MimeMultipart(); // the text message is one part MimeBodyPart text = new MimeBodyPart(); text.setDisposition(Part.INLINE); text.setContent(message, "text/plain"); mp.addBodyPart(text); // the attachement is another. MimeBodyPart file_part = new MimeBodyPart(); File file = (File) new File(attachmentFileName); FileDataSource fds = new FileDataSource(file); DataHandler dh = new DataHandler(fds); file_part.setFileName(file.getName()); file_part.setDisposition(Part.ATTACHMENT); file_part.setDescription("Attached file: " + file.getName()); file_part.setDataHandler(dh); mp.addBodyPart(file_part); msg.setContent(mp); } // the date msg.setSentDate(Calendar.getInstance().getTime()); Transport transport = session.getTransport("smtp"); transport.connect(com.sapienter.jbilling.common.Util .getSysProp("smtp_server"), Integer .parseInt(com.sapienter.jbilling.common.Util .getSysProp("smtp_port")), com.sapienter.jbilling.common.Util.getSysProp("smtp_username"), com.sapienter.jbilling.common.Util.getSysProp("smtp_password")); InternetAddress addresses[] = new InternetAddress[1]; addresses[0] = new InternetAddress(address); transport.sendMessage(msg, addresses); } /** * Creates a message object with a set of standard parameters * * @param entityId * @param userId * @return The message object with many useful parameters */ private MessageDTO initializeMessage(Integer entityId, Integer userId) throws SessionInternalError { MessageDTO retValue = new MessageDTO(); try { UserBL user = new UserBL(userId); ContactBL contact = new ContactBL(); // this user's info contact.set(userId); if (contact.getEntity() != null) { retValue.addParameter("contact", contact.getEntity()); retValue.addParameter("first_name", contact.getEntity().getFirstName()); retValue.addParameter("last_name", contact.getEntity().getLastName()); retValue.addParameter("address1", contact.getEntity().getAddress1()); retValue.addParameter("address2", contact.getEntity().getAddress2()); retValue.addParameter("city", contact.getEntity().getCity()); retValue.addParameter("organization_name", contact.getEntity().getOrganizationName()); retValue.addParameter("postal_code", contact.getEntity().getPostalCode()); retValue.addParameter("state_province", contact.getEntity().getStateProvince()); } if (user.getEntity() != null) { retValue.addParameter("user", user.getEntity()); retValue.addParameter("username", user.getEntity().getUserName()); retValue.addParameter("password", user.getEntity().getPassword()); retValue.addParameter("user_id", user.getEntity().getUserId().toString()); } if (user.getCreditCard() != null) { retValue.addParameter("credit_card", user.getCreditCard()); } // the entity info contact.setEntity(entityId); if (contact.getEntity() != null) { retValue.addParameter("company_contact", contact.getEntity()); retValue.addParameter("company_id", entityId.toString()); retValue.addParameter("company_name", contact.getEntity().getOrganizationName()); } //velocity tools retValue.addParameter("tools-date", new DateTool()); retValue.addParameter("tools-math", new MathTool()); retValue.addParameter("tools-number", new NumberTool()); retValue.addParameter("tools-render", new RenderTool()); retValue.addParameter("tools-escape", new EscapeTool()); retValue.addParameter("tools-resource", new ResourceTool()); retValue.addParameter("tools-alternator", new AlternatorTool()); retValue.addParameter("tools-valueParser", new ValueParser()); retValue.addParameter("tools-list", new ListTool()); retValue.addParameter("tools-sort", new SortTool()); retValue.addParameter("tools-iterator", new IteratorTool()); //Adding a CCF Field to Email Template List<ContactDTOEx> listDto= contact.getAll(userId); if (null != listDto && listDto.size() > 0 ) { ContactDTOEx contactDTOEx= listDto.get(0); Map<String, ContactFieldDTO> fieldsMap= contactDTOEx.getFieldsTable(); for (Iterator<?> it= fieldsMap.values().iterator();it.hasNext(); ) { ContactFieldDTO contactFieldDTO= (ContactFieldDTO) it.next(); if ( null != contactFieldDTO && null != contactFieldDTO.getContent() && null != contactFieldDTO.getType()) { retValue.addParameter(contactFieldDTO.getType().getPromptKey(), contactFieldDTO.getContent()); } } } } catch (Exception e) { throw new SessionInternalError(e); } return retValue; } public String generatePaperInvoiceAsFile(InvoiceDTO invoice) throws SessionInternalError { try { Integer entityId = invoice.getBaseUser().getEntity().getId(); // the language doesn't matter when getting a paper invoice MessageDTO paperMsg = getInvoicePaperMessage(entityId, null, invoice.getBaseUser().getLanguageIdField(), invoice); PaperInvoiceNotificationTask task = new PaperInvoiceNotificationTask(); PluggableTaskBL taskBL = new PluggableTaskBL(); taskBL.set(entityId, Constants.PLUGGABLE_TASK_T_PAPER_INVOICE); task.initializeParamters(taskBL.getDTO()); String filename = task.getPDFFile(invoice.getBaseUser(), paperMsg); return filename; } catch (Exception e) { throw new SessionInternalError(e); } } }