/* * * Copyright 2013 Entando S.r.l. (http://www.entando.com) All rights reserved. * * This file is part of Entando software. * Entando is a free software; * You can redistribute it and/or modify it * under the terms of the GNU General Public License (GPL) as published by the Free Software Foundation; version 2. * * See the file License for the specific language governing permissions * and limitations under the License * * * * Copyright 2013 Entando S.r.l. (http://www.entando.com) All rights reserved. * */ package com.agiletec.plugins.jpnewsletter.aps.system.services.newsletter; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Random; import java.util.Set; import java.util.TimerTask; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.StringEscapeUtils; import com.agiletec.aps.system.ApsSystemUtils; import com.agiletec.aps.system.SystemConstants; import com.agiletec.aps.system.common.AbstractService; import com.agiletec.aps.system.common.entity.IEntityManager; import com.agiletec.aps.system.common.entity.model.EntitySearchFilter; import com.agiletec.aps.system.exception.ApsSystemException; import com.agiletec.aps.system.services.authorization.IApsAuthority; import com.agiletec.aps.system.services.authorization.authorizator.IApsAuthorityManager; import com.agiletec.aps.system.services.baseconfig.ConfigInterface; import com.agiletec.aps.system.services.category.Category; import com.agiletec.aps.system.services.group.Group; import com.agiletec.aps.system.services.keygenerator.IKeyGeneratorManager; import com.agiletec.aps.system.services.lang.ILangManager; import com.agiletec.aps.system.services.user.IUserManager; import com.agiletec.aps.system.services.user.UserDetails; import com.agiletec.aps.util.DateConverter; import com.agiletec.plugins.jacms.aps.system.services.content.IContentManager; import com.agiletec.plugins.jacms.aps.system.services.content.model.Content; import com.agiletec.plugins.jacms.aps.system.services.linkresolver.ILinkResolverManager; import com.agiletec.plugins.jacms.aps.system.services.renderer.IContentRenderer; import com.agiletec.plugins.jpmail.aps.services.mail.IMailManager; import com.agiletec.plugins.jpmail.aps.services.mail.MailSendersUtilizer; import com.agiletec.plugins.jpnewsletter.aps.system.JpnewsletterSystemConstants; import com.agiletec.plugins.jpnewsletter.aps.system.services.newsletter.model.ContentReport; import com.agiletec.plugins.jpnewsletter.aps.system.services.newsletter.model.NewsletterConfig; import com.agiletec.plugins.jpnewsletter.aps.system.services.newsletter.model.NewsletterContentReportVO; import com.agiletec.plugins.jpnewsletter.aps.system.services.newsletter.model.NewsletterContentType; import com.agiletec.plugins.jpnewsletter.aps.system.services.newsletter.model.NewsletterReport; import com.agiletec.plugins.jpnewsletter.aps.system.services.newsletter.model.NewsletterSearchBean; import com.agiletec.plugins.jpnewsletter.aps.system.services.newsletter.model.Subscriber; import com.agiletec.plugins.jpnewsletter.aps.system.services.newsletter.parse.NewsletterConfigDOM; import com.agiletec.plugins.jpnewsletter.aps.system.services.newsletter.util.ShaEncoder; import java.security.NoSuchAlgorithmException; import org.entando.entando.aps.system.services.userprofile.IUserProfileManager; import org.entando.entando.aps.system.services.userprofile.model.IUserProfile; /** * Servizio gestore delle newsletter. * @author E.Santoboni, A.Turrini, E.Mezzano */ public class NewsletterManager extends AbstractService implements INewsletterManager, MailSendersUtilizer, INewsletterSchedulerManager { @Override public void init() throws Exception { this.loadConfigs(); this.startScheduler(); ApsSystemUtils.getLogger().debug(this.getClass().getName() + ": initialized"); } @Override public void refresh() throws Throwable { synchronized (this) { super.refresh(); } } @Override protected void release() { this._scheduler.cancel(); this._scheduler = null; } @Override public void destroy() { this._scheduler.cancel(); this._scheduler = null; } /** * Carica e salva la configurazione di sistema del servizio. * @throws ApsSystemException */ protected void loadConfigs() throws ApsSystemException { try { ConfigInterface configManager = this.getConfigManager(); String xml = configManager.getConfigItem(JpnewsletterSystemConstants.NEWSLETTER_CONFIG_ITEM); if (xml == null) { throw new ApsSystemException("Configuration item not present: " + JpnewsletterSystemConstants.NEWSLETTER_CONFIG_ITEM); } NewsletterConfigDOM configDOM = new NewsletterConfigDOM(); this.setConfig(configDOM.extractConfig(xml)); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "loadConfigs"); throw new ApsSystemException("Errore in fase di inizializzazione", t); } } @Override public void startScheduler() throws ApsSystemException { NewsletterConfig config = this.getConfig(); Date start = config.getNextTaskTime(); this._scheduler = new Scheduler(this, start, config.getHoursDelay()); } /** * Restart the scheduler at the intended time and NOT at the next iteration. This may * result in an immediate execution of the delivery process * @param config */ private void restartScheduler(NewsletterConfig config) { Date start = config.getStartScheduler(); this._scheduler = new Scheduler(this, start, config.getHoursDelay()); } @Override public void stopScheduler() throws ApsSystemException { this.release(); } @Override public NewsletterConfig getNewsletterConfig() { return this._config.clone(); } @Override public void updateNewsletterConfig(NewsletterConfig config) throws ApsSystemException { Date originalStartDate = null; try { originalStartDate = this.getConfig().getStartScheduler(); String xml = new NewsletterConfigDOM().createConfigXml(config); this.getConfigManager().updateConfigItem(JpnewsletterSystemConstants.NEWSLETTER_CONFIG_ITEM, xml); this.setConfig(config); // restart the scheduler if necessary if (originalStartDate.getTime() != config.getStartScheduler().getTime()) { ApsSystemUtils.getLogger().info("Newsletter: scheduler restart issued"); stopScheduler(); restartScheduler(config); ApsSystemUtils.getLogger().info("Newsletter: scheduler restart completed"); } } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "updateNewsletterConfig"); throw new ApsSystemException("Errore in fase di aggiornamento configurazione newsletter", t); } } @Override public void addContentToQueue(String contentId) throws ApsSystemException { try { this.getNewsletterDAO().addContentToQueue(contentId); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "addContentToQueue"); // Do not throws exceptions } } @Override public void removeContentFromQueue(String contentId) throws ApsSystemException { try { this.getNewsletterDAO().deleteContentFromQueue(contentId); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "removeContentFromQueue"); throw new ApsSystemException("Errore in aggiunta contenuto in coda newsLetter", t); } } @Override public List<String> getContentQueue() throws ApsSystemException { try { return this.getNewsletterDAO().loadContentQueue(); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "getContentQueue"); throw new ApsSystemException("Errore in caricamento coda contenuti newsLetter", t); } } @Override public boolean existsContentReport(String contentId) throws ApsSystemException { try { return this.getNewsletterDAO().existsContentReport(contentId); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "getContentQueue"); throw new ApsSystemException("Error verifying content report existence", t); } } @Override public List<String> getSentContentIds() throws ApsSystemException { try { return this.getNewsletterDAO().loadSentContentIds(); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "getContentQueue"); throw new ApsSystemException("Error loading sent contents ids", t); } } @Override public NewsletterContentReportVO getContentReport(String contentId) throws ApsSystemException { try { return this.getNewsletterDAO().loadContentReport(contentId); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "getContentQueue"); throw new ApsSystemException("Error loading content report", t); } } /** * Istanzia un thread che esegue l'invio della newsletter. * Verifica che in ogni istante sia in esecuzione al più un processo di invio della newsletter. * @throws ApsSystemException In caso di errore. */ @Override public void sendNewsletter() throws ApsSystemException { if (!this.isSendingNewsletter() && this.getContentQueue().size()>0) { NewsletterSenderThread sender = new NewsletterSenderThread(this); sender.setName(JpnewsletterSystemConstants.NEWSLETTER_SENDER_THREAD_NAME); sender.start(); } } protected void sendNewsletterFromThread() throws ApsSystemException { synchronized (this) { if (this.isSendingNewsletter()) { return; } else { this.setSendingNewsletter(true); } } try { ApsSystemUtils.getLogger().info("Newletter: delivery process initiated"); if (!this.getConfig().isActive()) return; List<String> contentIds = this.getContentQueue(); if (contentIds.size() > 0) { List<Content> contents = new ArrayList<Content>(contentIds.size()); for (int i = 0; i < contentIds.size(); i++) { String id = contentIds.get(i); Content content = this.getContentManager().loadContent(id, true); if (content != null) { contents.add(content); } } if (contents.size() > 0) { this.sendNewsletterToUsers(contents); } else { ApsSystemUtils.getLogger().info("Newletter: had some contents ids selected, corresponding to no actual content: this is quite strange!"); } this.getNewsletterDAO().cleanContentQueue(contentIds); } else { ApsSystemUtils.getLogger().info("Newletter: no contents found for delivery"); } ApsSystemUtils.getLogger().info("Newletter: delivery process completed"); } catch (Throwable t) { ApsSystemUtils.getLogger().info("Newletter: delivery process ended abnormally"); ApsSystemUtils.logThrowable(t, this, "sendNewsletterFromThread"); } finally { this.setSendingNewsletter(false); } } @Override public String buildMailBody(Content content, boolean html) throws ApsSystemException { String mailBody = null; try { NewsletterConfig config = this.getConfig(); NewsletterContentType contentType = config.getContentType(content.getTypeCode()); int modelId = html ? contentType.getHtmlModel() : contentType.getSimpleTextModel(); mailBody = this.buildMailBody(content, modelId, html); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "buildMailBody"); throw new ApsSystemException("Errore in generazione body contenuto " + content.getId(), t); } return mailBody; } /** * Invia i contenuti dati agli utenti registrati alla newsletter discriminando, per ogni utente, * i contenuti a lui visibili e per i quali ha fatto implicita richiesta nel proprio profilo. * @param contents La lista dei contenuti da inviare tramite newsletter. * @throws ApsSystemException In caso di errore. */ protected void sendNewsletterToUsers(List<Content> contents) throws ApsSystemException { try { Map<String, List<String>> profileAttributes = this.prepareProfileAttributesForContents(contents); NewsletterReport newsletterReport = this.prepareNewsletterReport(contents); Set<String> usernames = this.extractUsernames(); if (null != usernames && usernames.size() > 0) { Iterator<String> userIter = usernames.iterator(); while (userIter.hasNext()) { String username = (String) userIter.next(); UserDetails user = this.getUserManager().getUser(username); if (null != user && !user.isDisabled()) { this.sendNewsletterToUser(username, contents, profileAttributes, newsletterReport); } } } else { ApsSystemUtils.getLogger().error("Newsletter: no receivers to send newsletter to!"); } this.sendNewsletterToSubscribers(contents, newsletterReport); this.addNewsletterReport(newsletterReport); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "sendNewsletterToUsers"); throw new ApsSystemException("Error sending Newsletter To Users ", t); } } /** * Salva il report della newsletter appena inviata. * @param newsletterReport Il report della newsletter. * @throws ApsSystemException In caso di errore. */ protected void addNewsletterReport(NewsletterReport newsletterReport) throws ApsSystemException { if (null == newsletterReport) return; try { IKeyGeneratorManager keyGeneratorManager = this.getKeyGeneratorManager(); newsletterReport.setId(keyGeneratorManager.getUniqueKeyCurrentValue()); for (ContentReport contentReport : newsletterReport.getContentReports().values()) { contentReport.setId(keyGeneratorManager.getUniqueKeyCurrentValue()); } this.getNewsletterDAO().addNewsletterReport(newsletterReport); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "buildMailBody", "Error adding newsletter report : id " + newsletterReport.getId()); } } private Map<String, List<String>> prepareProfileAttributesForContents(List<Content> contents) { Map<String, List<String>> profileAttributes = new HashMap<String, List<String>>(); Properties subscriptions = this.getConfig().getSubscriptions(); for (int i=0; i<contents.size(); i++) { Content content = contents.get(i); List<String> contentProfileAttributes = this.extractProfileAttributesForContent(content, subscriptions); if (contentProfileAttributes != null && contentProfileAttributes.size() > 0) { profileAttributes.put(content.getId(), contentProfileAttributes); } } return profileAttributes; } protected NewsletterReport prepareNewsletterReport(List<Content> contents) { NewsletterConfig config = this.getConfig(); NewsletterReport newsletterReport = new NewsletterReport(); newsletterReport.setSubject(config.getSubject()); newsletterReport.setSendDate(new Date()); String defaultLang = this.getLangManager().getDefaultLang().getCode(); boolean alsoHtml = config.isAlsoHtml(); for (Content content : contents) { boolean isConfiguredWithModels = false; ContentReport contentReport = new ContentReport(); contentReport.setContentId(content.getId()); String textBodyPart = this.prepareMailBodyContentPart(content, defaultLang, false); if (null != textBodyPart) { isConfiguredWithModels = true; contentReport.setTextBody(textBodyPart); } if (alsoHtml) { String htmlBodyPart = this.prepareMailBodyContentPart(content, defaultLang, true); contentReport.setHtmlBody(htmlBodyPart); } if (isConfiguredWithModels) { newsletterReport.addContentReport(contentReport); } else { ApsSystemUtils.getLogger().info(" Newsletter content " + content.getId() + " not added, because has not model in config."); } } return newsletterReport; } private void sendNewsletterToUser(String username, List<Content> contents, Map<String, List<String>> profileAttributes, NewsletterReport newsletterReport) { NewsletterConfig config = this.getConfig(); try { UserDetails user = this.getUserManager().getUser(username); IUserProfile profile = (IUserProfile) user.getProfile(); if (profile != null) { String eMail = (String) profile.getValue(config.getMailAttrName()); if (eMail != null && eMail.length() > 0) { List<Content> userContents = this.extractContentsForUser(user, eMail, contents, profileAttributes, newsletterReport); if (userContents.size() > 0) { String[] emailAddresses = { eMail }; String simpleText = this.prepareMailBody(userContents, newsletterReport, false); if (config.isAlsoHtml()) { String htmlText = this.prepareMailBody(userContents, newsletterReport, true); this.getMailManager().sendMixedMail(simpleText, htmlText, config.getSubject(), null, null, null, emailAddresses, config.getSenderCode()); } else { this.getMailManager().sendMail(simpleText, config.getSubject(), null, null, emailAddresses, config.getSenderCode()); } } } } } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "sendNewsletterToUser"); } } protected String prepareMailBody(List<Content> userContents, NewsletterReport newsletterReport, boolean isHtml) { StringBuffer body = this.prepareMailCommonBody(userContents, newsletterReport, isHtml); NewsletterConfig config = this.getConfig(); body.append(isHtml ? config.getHtmlFooter() : config.getTextFooter()); return body.toString(); } protected StringBuffer prepareMailCommonBody(List<Content> userContents, NewsletterReport newsletterReport, boolean isHtml) { NewsletterConfig config = this.getConfig(); StringBuffer body = new StringBuffer(isHtml ? config.getHtmlHeader() : config.getTextHeader()); String separator = isHtml ? config.getHtmlSeparator() : config.getTextSeparator(); boolean first = true; for (Content content : userContents) { ContentReport contentReport = newsletterReport.getContentReport(content.getId()); if (contentReport != null) { if (first) { first = false; } else { body.append(separator); } String text = isHtml ? contentReport.getHtmlBody() : contentReport.getTextBody(); body.append(text); } } return body; } /** * Prepara il body della mail in base al contenuto e al modello. * @param content Il Contenuto per cui costruire il body della mail. * @param modelId L'id del modello utilizzato. * @param html Indica se il modello è di tipo html o testo semplice. * @return Il body della mail completo di blocchi iniziale e finale. * @throws ApsSystemException */ private String buildMailBody(Content content, long modelId, boolean html) throws ApsSystemException { NewsletterConfig config = this.getConfig(); String header = html ? config.getHtmlHeader() : config.getTextHeader(); String defaultLang = this.getLangManager().getDefaultLang().getCode(); String mailContentBody = this.getContentRenderer().render(content, modelId, defaultLang, null); mailContentBody = this.getLinkResolver().resolveLinks(mailContentBody, null); String footer = html ? config.getHtmlFooter() : config.getTextFooter(); String mailBody = header.concat(mailContentBody).concat(footer); if (!html) { return StringEscapeUtils.unescapeHtml(mailBody); } return mailBody; } /** * Estrae gli username dei destinatari della newsletter. * @return Il set contenente gli username dei destinatari della newsletter. * @throws ApsSystemException */ private Set<String> extractUsernames() throws ApsSystemException { NewsletterConfig config = this.getConfig(); Set<String> usernames = new HashSet<String>(); String allContentsAttribute = config.getAllContentsAttributeName(); if (null != allContentsAttribute && allContentsAttribute.trim().length() > 0) { EntitySearchFilter filter = new EntitySearchFilter(allContentsAttribute, true, new Boolean(true), false); EntitySearchFilter[] filters = {filter}; try { List<String> usernamesForAllContents = ((IEntityManager) this.getProfileManager()).searchId(filters); usernames.addAll(usernamesForAllContents); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "extractUsernames"); throw new ApsSystemException("Error searching for usernames on profile's attribute " + allContentsAttribute, t); } } Iterator<Object> subscriptionsIter = config.getSubscriptions().values().iterator(); while (subscriptionsIter.hasNext()) { String boolAttributeName = (String) subscriptionsIter.next(); if (null != boolAttributeName && boolAttributeName.trim().length() > 0) { EntitySearchFilter filter = new EntitySearchFilter(boolAttributeName, true, new Boolean(true), false); EntitySearchFilter[] filters = {filter}; try { List<String> usernamesForCategory = ((IEntityManager) this.getProfileManager()).searchId(filters); usernames.addAll(usernamesForCategory); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "extractUsernames"); throw new ApsSystemException("Error searching for usernames on profile's attribute " + boolAttributeName, t); } } } return usernames; } private List<Content> extractContentsForUser(UserDetails user, String eMail, List<Content> contents, Map<String, List<String>> profileAttributes, NewsletterReport newsletterReport) throws ApsSystemException { NewsletterConfig config = this.getConfig(); List<Content> userContents = new ArrayList<Content>(); String username = user.getUsername(); IUserProfile profile = (IUserProfile) user.getProfile(); if (profile != null) { String allContentsAttribute = config.getAllContentsAttributeName(); boolean allContents = false; if (null != allContentsAttribute) { Boolean value = (Boolean) profile.getValue(allContentsAttribute); allContents = value != null && value.booleanValue(); } List<String> groupNames = this.extractUserGroupNames(user); boolean isGroupAdmin = groupNames.contains(Group.ADMINS_GROUP_NAME); for (int i=0; i<contents.size(); i++) { Content content = contents.get(i); String contentId = content.getId(); List<String> contentProfileAttributes = profileAttributes.get(contentId); ContentReport contentReport = newsletterReport.getContentReport(contentId); if (contentReport != null && (isGroupAdmin || this.checkUserAllowedOnContent(groupNames, content))) { if (allContents) { userContents.add(content); contentReport.addRecipient(username, eMail); } else if (contentProfileAttributes!=null && contentProfileAttributes.size() > 0) { for (String profileAttrName : contentProfileAttributes) { Boolean value = (Boolean) profile.getValue(profileAttrName); if (value != null && value.booleanValue()) { userContents.add(content); contentReport.addRecipient(username, eMail); break; } } } } } } return userContents; } private List<String> extractProfileAttributesForContent(Content content, Properties subscriptions) { Collection<String> categories = this.extractCategoryCodes(content); List<String> profileAttributes = new ArrayList<String>(); for (String categoryCode : categories) { String attributeName = subscriptions.getProperty(categoryCode); if (attributeName != null) { profileAttributes.add(attributeName); } } return profileAttributes; } private String prepareMailBodyContentPart(Content content, String defaultLang, boolean isHtml) { NewsletterContentType contentType = this.getConfig().getContentTypes().get(content.getTypeCode()); int modelId = isHtml ? contentType.getHtmlModel() : contentType.getSimpleTextModel(); String mailContentBody = this.getContentRenderer().render(content, modelId, defaultLang, null); mailContentBody = this.getLinkResolver().resolveLinks(mailContentBody, null); if (!isHtml) { return StringEscapeUtils.unescapeHtml(mailContentBody); } return mailContentBody; } private List<String> extractUserGroupNames(UserDetails user) throws ApsSystemException { List<IApsAuthority> authorities = this.getGroupManager().getAuthorizationsByUser(user); List<String> groupNames = new ArrayList<String>(authorities.size()); for (IApsAuthority authority : authorities) { groupNames.add(authority.getAuthority()); } return groupNames; } private boolean checkUserAllowedOnContent(List<String> userGroups, Content content) { String mainGroup = content.getMainGroup(); boolean allowed = false; if (Group.FREE_GROUP_NAME.equals(mainGroup) || userGroups.contains(mainGroup)) { allowed = true; } else if (!this.getConfig().isOnlyOwner()) { for (String current : content.getGroups()) { if (Group.FREE_GROUP_NAME.equals(current) || userGroups.contains(current)) { allowed = true; break; } } } return allowed; } /** * Estrae i codici delle categorie della newsletter * @param content Il contenuto a cui si riferisce la newsletter. * @return Il set contenente i codici delle categorie della newsletter come {@link String}. */ private Set<String> extractCategoryCodes(Content content) { Set<String> categoryCodes = new HashSet<String>(); Iterator<Category> categoryIter = content.getCategories().iterator(); while (categoryIter.hasNext()) { Category category = categoryIter.next(); this.addCategoryCode(category, categoryCodes); } return categoryCodes; } /** * Aggiunge al {@link Set} dei codici il codice della categoria data. * @param category La categoria da aggiungere. * @param codes Il {@link Set} dei codici delle categorie. */ private void addCategoryCode(Category category, Set<String> codes) { codes.add(category.getCode()); Category parentCategory = (Category) category.getParent(); if (null != parentCategory && !parentCategory.getCode().equals(parentCategory.getParentCode())) { this.addCategoryCode(parentCategory, codes); } } @Override public List<String> loadNewsletterContentIds(EntitySearchFilter[] filters, Collection<String> userGroupCodes, NewsletterSearchBean searchBean) throws ApsSystemException { List<String> contentsId = null; try { NewsletterConfig config = this.getConfig(); String[] contentTypes = config.getContentTypesArray(); if (contentTypes==null || contentTypes.length==0) { contentsId = new ArrayList<String>(); } else { String[] categories = (null != config.getAllContentsAttributeName()) ? null : config.getCategoriesArray(); contentsId = this.getNewsletterSearcherDAO().loadNewsletterContentsId(contentTypes, categories, filters, userGroupCodes); Boolean inQueue = searchBean.getInQueue(); if (inQueue!=null) { List<String> contentQueue = this.getNewsletterDAO().loadContentQueue(); if (inQueue.booleanValue()) { contentsId = this.intersectContentIds(contentsId, contentQueue); } else { contentsId = this.exceptContentIds(contentsId, contentQueue); } } Boolean sent = searchBean.getSent(); if (sent!=null) { List<String> sentContentIds = this.getNewsletterDAO().loadSentContentIds(); if (sent.booleanValue()) { contentsId = this.intersectContentIds(contentsId, sentContentIds); } else { contentsId = this.exceptContentIds(contentsId, sentContentIds); } } } } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "loadNewsletterContentsId", "Errore spedizione lista contenuti newsletter "); } return contentsId; } private List<String> exceptContentIds(List<String> contentIds, List<String> exceptedContentIds) { if (exceptedContentIds.size()>0 && contentIds.size()>0) { for (String contentId : exceptedContentIds) { contentIds.remove(contentId); if (contentIds.size()==0) { break; } } } return contentIds; } protected List<String> intersectContentIds(List<String> contentIds, List<String> intersectedContentIds) { List<String> intersection = new ArrayList<String>(); if (intersectedContentIds.size()>0 && contentIds.size()>0) { for (String contentId : intersectedContentIds) { if (contentIds.remove(contentId)) { intersection.add(contentId); } } } return intersection; } @Override public List<Subscriber> loadSubscribers() throws ApsSystemException { List<Subscriber> subscribers = new ArrayList<Subscriber>(); try { subscribers = this.getNewsletterDAO().loadSubscribers(); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "loadSubscribers"); throw new ApsSystemException( "Errore caricamento di tutte le sottoscrizioni", t); } return subscribers; } @Override public List<Subscriber> searchSubscribers(String mailAddress, Boolean active) throws ApsSystemException { List<Subscriber> subscribers = new ArrayList<Subscriber>(); try { subscribers = this.getNewsletterDAO().searchSubscribers(mailAddress, active); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "searchSubscribers"); throw new ApsSystemException("Errore in ricerca sottoscrizioni", t); } return subscribers; } @Override public Subscriber loadSubscriber(String mailAddress) throws ApsSystemException { try { this.cleanOldSubscribers(); Subscriber subscriber = this.getNewsletterDAO().loadSubscriber(mailAddress); return subscriber; } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "loadSubscriber"); throw new ApsSystemException("Errore caricamento di un sottoscrizioni " + mailAddress, t); } } @Override public void addSubscriber(String mailAddress) throws ApsSystemException { try { Subscriber subscriber = new Subscriber(); subscriber.setMailAddress(mailAddress); subscriber.setSubscriptionDate(new Date()); subscriber.setActive(false); String token = this.createToken(mailAddress); this.getNewsletterDAO().addSubscriber(subscriber, token); this.sendSubscriptionMail(mailAddress, token); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "addSubscriber"); throw new ApsSystemException("Errore in aggiunta sottoscrizione", t); } } @Override public void resetSubscriber(String mailAddress) throws ApsSystemException { try { Subscriber subscriber = new Subscriber(); subscriber.setMailAddress(mailAddress); subscriber.setSubscriptionDate(new Date()); subscriber.setActive(false); String token = this.createToken(mailAddress); this.getNewsletterDAO().updateSubscriber(subscriber, token); this.sendSubscriptionMail(mailAddress, token); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "resetSubscriber"); throw new ApsSystemException("Errore in reset sottoscrizione", t); } } @Override public void deleteSubscriber(String mailAddress) throws ApsSystemException { try { this.getNewsletterDAO().deleteSubscriber(mailAddress); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "deleteSubscriber"); throw new ApsSystemException("Errore cancellazione sottoscrizione", t); } } @Override public void activateSubscriber(String mailAddress, String token) throws ApsSystemException { try { this.cleanOldSubscribers(); this.getNewsletterDAO().activateSubscriber(mailAddress); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "activateSubscriber"); throw new ApsSystemException("Errore attivazione sottoscrizione", t); } } @Override public String getAddressFromToken(String token) throws ApsSystemException { try { String mailAddress = this.getNewsletterDAO().getAddressFromToken(token); return mailAddress; } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "getAddressFromToken"); throw new ApsSystemException("Error loading address from token", t); } } @Override public void cleanOldSubscribers() throws ApsSystemException { int days = this.getConfig().getSubscriptionTokenValidityDays(); long time = new Date().getTime() - (86400000l * days); Date expiration = new Date(time); this.getNewsletterDAO().cleanOldSubscribers(expiration); } @Override public Boolean isAlreadyAnUser(String mailAddress) throws ApsSystemException { List<String> usernames = null; try { String emailAttributeName = this.getConfig().getMailAttrName(); EntitySearchFilter filterByEmail = new EntitySearchFilter(emailAttributeName, true, mailAddress, true); EntitySearchFilter[] filters = {filterByEmail}; usernames = ((IEntityManager) this.getProfileManager()).searchId(filters); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "isAlreadyAnUser"); throw new ApsSystemException("Errore ricerca indirizzo e-mail tra gli utenti registrati", t); } return (null != usernames && usernames.size() > 0); } /** * Generated random token */ protected String createToken(String word) throws NoSuchAlgorithmException { Random random = new Random(); StringBuilder salt = new StringBuilder(); long rndLong = random.nextLong(); salt.append(rndLong); String date = DateConverter.getFormattedDate(new Date(), "SSSmmyyyy-SSS-MM:ssddmmHHmmEEE"); salt.append(date); rndLong = random.nextLong(); salt.append(rndLong); // genero il token in base a username e salt String token = ShaEncoder.encodeWord(word, salt.toString()); return token; } protected void sendSubscriptionMail(String mailAddress, String token) throws ApsSystemException { try { EmailSenderThread thread = new EmailSenderThread(mailAddress, token, this); thread.setName(JpnewsletterSystemConstants.EMAIL_SENDER_NAME_THREAD_PREFIX + System.currentTimeMillis()); thread.start(); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "sendMail"); throw new ApsSystemException("errore in invio email", t); } } /** * Create link for email confirmation * */ protected String createLink(String mailAddress, String token) { NewsletterConfig config = this.getConfig(); String langcode = this.getLangManager().getDefaultLang().getCode(); String pageCode = config.getSubscriptionPageCode(); String applBaseUrl = this.getConfigManager().getParam(SystemConstants.PAR_APPL_BASE_URL); StringBuffer link = new StringBuffer(applBaseUrl); link.append(langcode); link.append("/"); link.append(pageCode); link.append(".wp"); link.append("?mailAddress="); link.append(mailAddress); link.append("&token="); link.append(token); return link.toString(); } protected String parseText(String defaultText, Map<String, String> params) { String body = defaultText; for (Entry<String, String> pairs : params.entrySet()) { String regExp = "\\{" + pairs.getKey() + "\\}"; Pattern pattern = Pattern.compile(regExp); Matcher codeMatcher = pattern.matcher(""); codeMatcher.reset(body); if (codeMatcher.find()) { body = codeMatcher.replaceAll((String) pairs.getValue()); } } return body; } protected void sendSubscriptionFormThread(String mailAddress, String token) throws ApsSystemException { try { NewsletterConfig config = this.getConfig(); String senderCode = config.getSenderCode(); String subject = config.getSubscriptionSubject(); if (subject!=null) { Map<String, String> bodyParams = new HashMap<String, String>(); String link = this.createLink(mailAddress, token); bodyParams.put("subscribeLink", link); String textBody = this.parseText(config.getSubscriptionTextBody(), bodyParams); String[] recipientsTo = new String[] { mailAddress }; if (config.isAlsoHtml()) { String htmlBody = this.parseText(config.getSubscriptionHtmlBody(), bodyParams); this.getMailManager().sendMixedMail(textBody, htmlBody, subject, null, recipientsTo, null, null, senderCode); } else { this.getMailManager().sendMail(textBody, subject, recipientsTo, null, null, senderCode, IMailManager.CONTENTTYPE_TEXT_PLAIN); } } else { ApsSystemUtils.getLogger().warn("Incomplete configuration for newsletter subscribers! CHECK " + JpnewsletterSystemConstants.NEWSLETTER_CONFIG_ITEM + " item!!"); } } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "sendMail"); throw new ApsSystemException("Error sending email for subscription confirmation request to address " + mailAddress, t); } } private void sendNewsletterToSubscribers(List<Content> contents, NewsletterReport newsletterReport) throws ApsSystemException { List<Content> contentsToSubscribers = new ArrayList<Content>(); for (int i = 0; i < contents.size(); i++) { Content content = contents.get(i); if (this.isContentToSend(content)) { contentsToSubscribers.add(content); } } this.sendContentsToSubscribers(contentsToSubscribers, newsletterReport); } private boolean isContentToSend(Content content) { if (null == content) return false; String mainGroup = content.getMainGroup(); if (null != mainGroup && Group.FREE_GROUP_NAME.equals(mainGroup)) return true; Set<String> groups = content.getGroups(); if (null != groups && groups.contains(Group.FREE_GROUP_NAME)) { return true; } return false; } private void sendContentsToSubscribers(List<Content> contents, NewsletterReport newsletterReport) throws ApsSystemException { List<Subscriber> subscribers = this.searchSubscribers(null, new Boolean(true)); NewsletterConfig config = this.getConfig(); for (Subscriber subscriber : subscribers) { String mailAddress = subscriber.getMailAddress(); String[] emailAddresses = { mailAddress }; String simpleText = this.prepareSubscribersMailBody(contents, newsletterReport, false, mailAddress); if (config.isAlsoHtml()) { String htmlText = this.prepareSubscribersMailBody(contents, newsletterReport, true, mailAddress); this.getMailManager().sendMixedMail(simpleText, htmlText, config.getSubject(), null, null, null, emailAddresses, config.getSenderCode()); } else { this.getMailManager().sendMail(simpleText, config.getSubject(), null, null, emailAddresses, config.getSenderCode()); } } } private String prepareSubscribersMailBody(List<Content> userContents, NewsletterReport newsletterReport, boolean isHtml, String mailAddress) { NewsletterConfig config = this.getConfig(); String unsubscriptionLink = isHtml ? config.getSubscribersHtmlFooter() : config.getSubscribersTextFooter(); if (unsubscriptionLink!=null) { StringBuffer body = this.prepareMailCommonBody(userContents, newsletterReport, isHtml); String link = this.createUnsubscriptionLink(mailAddress); Map<String, String> footerParams = new HashMap<String, String>(); footerParams.put("unsubscribeLink", link); String unlink = this.parseText(unsubscriptionLink, footerParams); body.append(unlink); return body.toString(); } else { ApsSystemUtils.getLogger().warn("Incomplete configuration for newsletter subscribers! CHECK " + JpnewsletterSystemConstants.NEWSLETTER_CONFIG_ITEM + " item!!"); return this.prepareMailBody(userContents, newsletterReport, isHtml); } } /** * Create link for newsletter unsubscription * */ protected String createUnsubscriptionLink(String mailAddress) { NewsletterConfig config = this.getConfig(); String langcode = this.getLangManager().getDefaultLang().getCode(); String pageCode = config.getUnsubscriptionPageCode(); String applBaseUrl = this.getConfigManager().getParam(SystemConstants.PAR_APPL_BASE_URL); StringBuffer link = new StringBuffer(applBaseUrl); link.append(langcode); link.append("/"); link.append(pageCode); link.append(".wp"); link.append("?mailAddress="); link.append(mailAddress); return link.toString(); } protected boolean isSendingNewsletter() { return _sendingNewsletter; } protected void setSendingNewsletter(boolean sendingNewsletter) { this._sendingNewsletter = sendingNewsletter; } protected NewsletterConfig getConfig() { return this._config; } protected void setConfig(NewsletterConfig config) { this._config = config; } @Override public String[] getSenderCodes() { return new String[] { this.getConfig().getSenderCode() }; } protected IMailManager getMailManager() { return _mailManager; } public void setMailManager(IMailManager mailManager) { this._mailManager = mailManager; } protected IUserManager getUserManager() { return _userManager; } public void setUserManager(IUserManager userManager) { this._userManager = userManager; } protected IContentManager getContentManager() { return _contentManager; } public void setContentManager(IContentManager contentManager) { this._contentManager = contentManager; } public IApsAuthorityManager getGroupManager() { return _groupManager; } public void setGroupManager(IApsAuthorityManager groupManager) { this._groupManager = groupManager; } protected IUserProfileManager getProfileManager() { return _profileManager; } public void setProfileManager(IUserProfileManager profileManager) { this._profileManager = profileManager; } protected ILinkResolverManager getLinkResolver() { return _linkResolver; } public void setLinkResolver(ILinkResolverManager linkResolver) { this._linkResolver = linkResolver; } protected ILangManager getLangManager() { return _langManager; } public void setLangManager(ILangManager langManager) { this._langManager = langManager; } protected IContentRenderer getContentRenderer() { return _contentRenderer; } public void setContentRenderer(IContentRenderer renderer) { this._contentRenderer = renderer; } /** * Returns the configuration manager. * @return The Configuration manager. */ protected ConfigInterface getConfigManager() { return _configManager; } /** * Set method for Spring bean injection.<br /> Set the Configuration manager. * @param configManager The Configuration manager. */ public void setConfigManager(ConfigInterface configManager) { this._configManager = configManager; } protected IKeyGeneratorManager getKeyGeneratorManager() { return _keyGeneratorManager; } public void setKeyGeneratorManager(IKeyGeneratorManager keyGeneratorManager) { this._keyGeneratorManager = keyGeneratorManager; } protected INewsletterDAO getNewsletterDAO() { return _newsletterDAO; } public void setNewsletterDAO(INewsletterDAO newsletterDAO) { this._newsletterDAO = newsletterDAO; } protected INewsletterSearcherDAO getNewsletterSearcherDAO() { return _newsletterSearcherDAO; } public void setNewsletterSearcherDAO(INewsletterSearcherDAO newsletterSearcherDAO) { this._newsletterSearcherDAO = newsletterSearcherDAO; } private NewsletterConfig _config; private boolean _sendingNewsletter = false; private IMailManager _mailManager; private IUserProfileManager _profileManager; private IUserManager _userManager; private IContentManager _contentManager; private IApsAuthorityManager _groupManager; private ILinkResolverManager _linkResolver; private ILangManager _langManager; private IContentRenderer _contentRenderer; private ConfigInterface _configManager; private INewsletterDAO _newsletterDAO; private INewsletterSearcherDAO _newsletterSearcherDAO; private IKeyGeneratorManager _keyGeneratorManager; private TimerTask _scheduler; }