From 5f98a961d1b15fcfda148fe4902cbe8f97bea428 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Roland=20H=C3=A4der?= Date: Tue, 17 May 2016 15:35:48 +0200 Subject: [PATCH] Continued: - added message-driven bean for mail delivery (generic) - added method init() method to initialize queue/factory - implemented business method resendConfirmationLink() (unfinished) - the business method enqueueEmailAddressForChange() now uses JMS and not directly calling the mailer MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Roland Häder --- nbproject/project.properties | 9 +- .../PizzaEmailChangeSessionBean.java | 358 ++++++++++++++++++ .../PizzaResendLinkSessionBean.java | 108 ++++++ .../delivery/EmailDeliveryMessageBean.java | 95 +++++ 4 files changed, 567 insertions(+), 3 deletions(-) create mode 100644 src/java/org/mxchange/jusercore/model/email_address/PizzaEmailChangeSessionBean.java create mode 100644 src/java/org/mxchange/pizzaapplication/mailer/model/delivery/EmailDeliveryMessageBean.java diff --git a/nbproject/project.properties b/nbproject/project.properties index 9992077..01d5ce7 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -52,12 +52,12 @@ j2ee.platform.wsit.classpath= j2ee.server.type=gfv3ee6 jar.compress=false jar.name=pizzaservice-ejb.jar -jars.in.ejbjar=true +jars.in.ejbjar=false javac.classpath=\ ${file.reference.jcoreee.jar}:\ + ${file.reference.jcore-logger-lib.jar}:\ ${file.reference.jcountry-core.jar}:\ ${file.reference.jcountry-lib.jar}:\ - ${file.reference.jcore-logger-lib.jar}:\ ${file.reference.jcontacts-core.jar}:\ ${file.reference.jcontacts-lib.jar}:\ ${file.reference.juser-core.jar}:\ @@ -73,6 +73,7 @@ javac.classpath=\ ${reference.pizzaservice-lib.jar}:\ ${reference.pizzaservice-mailer.jar}:\ ${file.reference.cdi-api.jar} +javac.compilerargs=-Xlint:unchecked -Xlint:deprecation javac.debug=true javac.deprecation=true javac.processorpath=\ @@ -98,9 +99,11 @@ javadoc.windowtitle=Pizza-Service EJBs meta.inf=${source.root}/conf meta.inf.excludes=sun-cmp-mappings.xml platform.active=default_platform +project.juser-core=../juser-core project.license=agpl30 project.pizzaservice-lib=../pizzaservice-lib project.pizzaservice-mailer=../../NetBeansProjects/pizzaservice-mailer +project.serviceLocator.class=org.mxchange.pizzaapplication.mailer.model.delivery.PizzaMailer reference.pizzaservice-lib.jar=${project.pizzaservice-lib}/dist/pizzaservice-lib.jar reference.pizzaservice-mailer.jar=${project.pizzaservice-mailer}/dist/pizzaservice-mailer.jar resource.dir=setup @@ -114,7 +117,7 @@ source.encoding=UTF-8 source.reference.jcontacts-core.jar=../jcontacts-core/src/ source.reference.jcontacts-lib.jar=../jcontacts-lib/src/ source.reference.jcore-logger-lib.jar=../jcore-logger-lib/src/ -source.reference.jcoreee.jar=../../jcoreee/src/ +source.reference.jcoreee.jar=../jcoreee/src/ source.reference.jcountry-core.jar=../jcountry-core/src/ source.reference.jcountry-lib.jar=../jcountry-lib/src/ source.reference.jcustomer-core.jar=../jcustomer-core/src/ diff --git a/src/java/org/mxchange/jusercore/model/email_address/PizzaEmailChangeSessionBean.java b/src/java/org/mxchange/jusercore/model/email_address/PizzaEmailChangeSessionBean.java new file mode 100644 index 0000000..60d472d --- /dev/null +++ b/src/java/org/mxchange/jusercore/model/email_address/PizzaEmailChangeSessionBean.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2016 Roland Haeder + * + * This program 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. + * + * This program 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 this program. If not, see . + */ +package org.mxchange.jusercore.model.email_address; + +import java.text.MessageFormat; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import javax.annotation.PostConstruct; +import javax.ejb.EJB; +import javax.ejb.EJBException; +import javax.ejb.Stateless; +import javax.faces.FacesException; +import javax.jms.Connection; +import javax.jms.JMSException; +import javax.jms.MessageProducer; +import javax.jms.ObjectMessage; +import javax.jms.Queue; +import javax.jms.QueueConnectionFactory; +import javax.jms.Session; +import javax.mail.Address; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.persistence.NoResultException; +import javax.persistence.Query; +import org.mxchange.jcoreee.database.BaseDatabaseBean; +import org.mxchange.jmailee.model.delivery.wrapper.EmailDeliveryWrapper; +import org.mxchange.jmailee.model.delivery.wrapper.WrapableEmailDelivery; +import org.mxchange.jusercore.model.user.UserSessionBeanRemote; +import org.mxchange.jusercore.model.user.UserUtils; + +/** + * A session bean for changing email addresses + *

+ * @author Roland Haeder + */ +@Stateless (name = "emailchange", description = "A bean handling email changes") +public class PizzaEmailChangeSessionBean extends BaseDatabaseBean implements EmailChangeSessionBeanRemote { + + /** + * Serial number + */ + private static final long serialVersionUID = 182_698_165_971_548L; + + /** + * Connection + */ + private Connection connection; + + /** + * Object message + */ + private ObjectMessage message; + + /** + * Message producer + */ + private MessageProducer messageProducer; + + /** + * Mailer message queue + */ + private Queue queue; + + /** + * Session instance + */ + private Session session; + + /** + * User bean + */ + @EJB + private UserSessionBeanRemote userBean; + + /** + * Default constructor + */ + public PizzaEmailChangeSessionBean () { + } + + @Override + @SuppressWarnings ("unchecked") + public List allQueuedAddressesAsList () { + // Trace message + this.getLoggerBeanLocal().logTrace("allQueuedAddressesAsList: CALLED!"); //NOI18N + + // Get named query + Query query = this.getEntityManager().createNamedQuery("AllEmailAddressChanges", List.class); //NOI18N + + // Get all entries + List emailAddresses = query.getResultList(); + + // Trace message + this.getLoggerBeanLocal().logTrace(MessageFormat.format("allQueuedAddressesAsList: emailAddresses.size()={0} - EXIT!", emailAddresses.size())); //NOI18N + + // Return it + return emailAddresses; + } + + @Override + public void enqueueEmailAddressForChange (final ChangeableEmailAddress emailChange) { + // Trace message + this.getLoggerBeanLocal().logTrace(MessageFormat.format("enqueueEmailAddressForChange: emailChange={0} - CALLED!", emailChange)); //NOI18N + + // Email address change should be valid + if (null == emailChange) { + // Abort here + throw new NullPointerException("emailChange is null"); //NOI18N + } else if (emailChange.getEmailChangeUser() == null) { + // Throw NPE again + throw new NullPointerException("emailChange.emailChangeUser is null"); //NOI18N + } else if (emailChange.getEmailChangeUser().getUserId() == null) { + // Throw NPE again + throw new NullPointerException("emailChange.emailChangeUser.userId is null"); //NOI18N + } else if (emailChange.getEmailChangeUser().getUserId() < 1) { + // Not valid id + throw new IllegalArgumentException(MessageFormat.format("emailChange.emailChangeUser.userId={0} is invalid.", emailChange.getEmailChangeUser().getUserId())); //NOI18N + } else if (!this.userBean.ifUserExists(emailChange.getEmailChangeUser())) { + // User does not exist + throw new EJBException(MessageFormat.format("Email change with id {0} does not exist.", emailChange.getEmailChangeId())); //NOI18N + } else if (emailChange.getEmailAddress().trim().isEmpty()) { + // Email address is empty + throw new IllegalArgumentException("emailChange.emaiLAddress is empty."); //NOI18N + } else if (this.isEmailAddressEnqueued(emailChange.getEmailAddress())) { + // Email address is already enqueued + throw new EJBException(MessageFormat.format("Email address {0} is already enqueued.", emailChange.getEmailAddress())); //NOI18N + } + + // The email change is not (yet) there, add secure hash and "created" timestamp + emailChange.setEmailChangeCreated(new GregorianCalendar()); + this.generateSecureHash(emailChange); + + // Persist it + //this.getEntityManager().persist(emailChange); + + // Prepare mail wrapper + WrapableEmailDelivery emailWrapper = new EmailDeliveryWrapper(); + + try { + // Create email address and set + Address emailAddress = new InternetAddress(emailChange.getEmailAddress()); + emailWrapper.setRecipient(emailAddress); + } catch (final AddressException ex) { + // Throw again + throw new EJBException(ex); + } + + // Set all values + Properties variables = UserUtils.getAllUserFields(emailChange.getEmailChangeUser()); + + // Set all + // @TODO Get locale from user + language from message bundle + emailWrapper.setLocale(Locale.GERMAN); + emailWrapper.setSubjectLine("Email change"); + emailWrapper.setTemplateName("email_change"); //NOI18N + emailWrapper.setTemplateVariables(variables); + + try { + // Send out email change + this.message.setObject(emailWrapper); + + // Send message + this.sendMessage(this.message); + } catch (final JMSException ex) { + // Throw again + throw new EJBException(ex); + } + + // Trace message + this.getLoggerBeanLocal().logTrace("enqueueEmailAddressForChange - EXIT!"); //NOI18N + } + + /** + * Initialization of this bean + */ + @PostConstruct + public void init () { + // Trace message + this.getLoggerBeanLocal().logTrace("init: CALLED!"); //NOI18N + + try { + // Get initial context + Context context = new InitialContext(); + + // Get factory from JMS resource + QueueConnectionFactory connectionFactory = (QueueConnectionFactory) context.lookup("jms/jlandingpage-queue-factory"); //NOI18N + + // Lookup queue + this.queue = (Queue) context.lookup("jms/jlandingpage-email-queue"); //NOI18N + + // Create connection + this.connection = connectionFactory.createConnection(); + + // Init session instance + this.session = this.connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + // And message producer + this.messageProducer = this.session.createProducer(this.queue); + + // Finally the message instance itself + this.message = this.session.createObjectMessage(); + } catch (final NamingException | JMSException e) { + // Continued to throw + throw new FacesException(e); + } + } + + @Override + public boolean isEmailAddressEnqueued (final String emailAddress) { + // Trace message + this.getLoggerBeanLocal().logTrace(MessageFormat.format("isEmailAddressEnqueued: emailAddress={0} - CALLED!", emailAddress)); //NOI18N + + // Create query instance + Query query = this.getEntityManager().createNamedQuery("SearchEmailChangeByEmail"); //NOI18N + + // Add email address as parameter + query.setParameter("email", emailAddress); //NOI18N + + // Initialize variable + boolean isFound = false; + + // Try it + try { + // Try to get single result + ChangeableEmailAddress dummy = (ChangeableEmailAddress) query.getSingleResult(); + + // Found it + isFound = true; + } catch (final NoResultException ex) { + // Log it + this.getLoggerBeanLocal().logException(ex); + } + + // Trace message + this.getLoggerBeanLocal().logTrace(MessageFormat.format("isEmailAddressEnqueued: isFound={0} - EXIT!", isFound)); //NOI18N + + // Return it + return isFound; + } + + @Override + public void updateEmailAddress (final ChangeableEmailAddress emailAddress) { + // Trace message + this.getLoggerBeanLocal().logTrace(MessageFormat.format("updateEmailAddress: emailAddress={0} - CALLED!", emailAddress)); //NOI18N + + // Email address change should be valid + if (null == emailAddress) { + // Abort here + throw new NullPointerException("emailAddress is null"); //NOI18N + } else if (emailAddress.getEmailChangeId() == null) { + // Throw NPE again + throw new NullPointerException("emailAddress.emailChangeId is null"); //NOI18N + } else if (emailAddress.getEmailChangeId() < 1) { + // Not valid + throw new IllegalArgumentException(MessageFormat.format("emailAddress.emailChangeId={0} is not valid.", emailAddress.getEmailChangeId())); //NOI18N + } else if (emailAddress.getEmailAddress().trim().isEmpty()) { + // Email address is empty + throw new IllegalArgumentException("emailAddress.emaiLAddress is empty."); //NOI18N + } else if (!this.userBean.ifUserExists(emailAddress.getEmailChangeUser())) { + // User does not exist + throw new EJBException(MessageFormat.format("Email change with id {0} does not exist.", emailAddress.getEmailChangeId())); //NOI18N + } else if (!this.isEmailAddressEnqueued(emailAddress.getEmailAddress())) { + // Email address is not enqueued + throw new EJBException(MessageFormat.format("Email address {0} is not enqueued.", emailAddress.getEmailAddress())); //NOI18N + } + + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + /** + * Generates a secure, unique hash for given email address change. This + * requires to check if the hash is really not there. + *

+ * @param emailAddress Email address change + */ + private void generateSecureHash (final ChangeableEmailAddress emailAddress) { + // Email address change should be valid + if (null == emailAddress) { + // Abort here + throw new NullPointerException("emailAddress is null"); //NOI18N + } else if (emailAddress.getEmailAddress().trim().isEmpty()) { + // Email address is empty + throw new IllegalArgumentException("emailAddress.emaiLAddress is empty."); //NOI18N + } + + // Initialize loop with null + String hash = null; + + // Default is not used + boolean isUsed = true; + + // Search for free hash + while (isUsed) { + // Generate hash, there is already in UserUtils a nice method that can be used for this purpose. + hash = UserUtils.encryptPassword(String.format("%s:%s", emailAddress.getEmailAddress(), emailAddress.toString())); //NOI18N + + // The hash *may* be unique, better test it + Query query = this.getEntityManager().createNamedQuery("SearchEmailChangeByHash", EmailAddressChange.class); //NOI18N + + // Set hash as parameter + query.setParameter("hash", hash); //NOI18N + + // Try to get single result + try { + // Get single result + ChangeableEmailAddress dummy = (ChangeableEmailAddress) query.getSingleResult(); + } catch (final NoResultException ex) { + // Not found + isUsed = false; + } + } + + // hash should not be null and set + assert (hash != null) : "hash is null"; //NOI18N + assert (!hash.isEmpty()) : "hash is empty"; //NOI18N + + // Set it in email change + emailAddress.setEmailChangeHash(hash); + } + + /** + * Sends given message to configured queue + *

+ * @param message Message to send + *

+ * @throws JMSException if something went wrong + */ + private void sendMessage (final ObjectMessage message) throws JMSException { + // The parameter should be valid + if (null == message) { + // Throw NPE + throw new NullPointerException("message is null"); //NOI18N + } + + // Send it + this.messageProducer.send(message); + } + +} diff --git a/src/java/org/mxchange/pizzaapplication/beans/resendlink/PizzaResendLinkSessionBean.java b/src/java/org/mxchange/pizzaapplication/beans/resendlink/PizzaResendLinkSessionBean.java index cc99884..643877c 100644 --- a/src/java/org/mxchange/pizzaapplication/beans/resendlink/PizzaResendLinkSessionBean.java +++ b/src/java/org/mxchange/pizzaapplication/beans/resendlink/PizzaResendLinkSessionBean.java @@ -16,7 +16,24 @@ */ package org.mxchange.pizzaapplication.beans.resendlink; +import de.chotime.landingpage.beans.resendlink.ResendLinkSessionBeanRemote; +import java.text.MessageFormat; +import java.util.Locale; +import javax.annotation.PostConstruct; import javax.ejb.Stateless; +import javax.faces.FacesException; +import javax.jms.Connection; +import javax.jms.JMSException; +import javax.jms.MessageProducer; +import javax.jms.ObjectMessage; +import javax.jms.Queue; +import javax.jms.QueueConnectionFactory; +import javax.jms.Session; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import org.mxchange.jusercore.model.user.User; +import org.mxchange.jusercore.model.user.status.UserAccountStatus; import org.mxchange.pizzaaplication.database.BasePizzaDatabaseBean; /** @@ -32,4 +49,95 @@ public class PizzaResendLinkSessionBean extends BasePizzaDatabaseBean implements */ private static final long serialVersionUID = 71_546_726_857_195_360L; + /** + * Connection + */ + private Connection connection; + + /** + * Object message + */ + private ObjectMessage message; + + /** + * Message producer + */ + private MessageProducer messageProducer; + + /** + * Mailer message queue + */ + private Queue queue; + + /** + * Session instance + */ + private Session session; + + /** + * Initialization of this bean + */ + @PostConstruct + public void init () { + // Trace message + this.getLoggerBeanLocal().logTrace("init: CALLED!"); //NOI18N + + try { + // Get initial context + Context context = new InitialContext(); + + // Get factory from JMS resource + QueueConnectionFactory connectionFactory = (QueueConnectionFactory) context.lookup("jms/jlandingpage-queue-factory"); //NOI18N + + // Lookup queue + this.queue = (Queue) context.lookup("jms/jlandingpage-email-queue"); //NOI18N + + // Create connection + this.connection = connectionFactory.createConnection(); + + // Init session instance + this.session = this.connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + // And message producer + this.messageProducer = this.session.createProducer(this.queue); + + // Finally the message instance itself + this.message = this.session.createObjectMessage(); + } catch (final NamingException | JMSException e) { + // Continued to throw + throw new FacesException(e); + } + } + + @Override + public String resendConfirmationLink (final User user, final Locale locale) { + // Log trace message + this.getLoggerBeanLocal().logTrace(MessageFormat.format("resendConfirmationLink: user={0} - CALLED!", user)); //NOI18N + + // The user instance should be valid + if (null == user) { + // Throw NPE + throw new NullPointerException("user is null"); //NOI18N + } else if (user.getUserId() == null) { + // Throw NPE again + throw new NullPointerException("user.userId is null"); //NOI18N + } else if (user.getUserId() < 1) { + // Invalid id number + throw new IllegalArgumentException(MessageFormat.format("user.userId={0} is not valid", user.getUserId())); //NOI18N + } else if (user.getUserConfirmKey() == null) { + // Throw NPE again + throw new NullPointerException("this.userConfirmKey is null"); //NOI18N + } else if (user.getUserAccountStatus() != UserAccountStatus.UNCONFIRMED) { + // User account status is not UNCONFIRMED + throw new IllegalStateException(MessageFormat.format("Account status from user.userId={0} is not UNCONFIRMED:{1}", user.getUserId(), user.getUserAccountStatus())); //NOI18N + } else if (null == locale) { + // Locale should be set + throw new NullPointerException("locale is null"); //NOI18N + } + + // @TODO Unfinished! + // All fine + return "resend_done"; //NOI18N + } + } diff --git a/src/java/org/mxchange/pizzaapplication/mailer/model/delivery/EmailDeliveryMessageBean.java b/src/java/org/mxchange/pizzaapplication/mailer/model/delivery/EmailDeliveryMessageBean.java new file mode 100644 index 0000000..c92c026 --- /dev/null +++ b/src/java/org/mxchange/pizzaapplication/mailer/model/delivery/EmailDeliveryMessageBean.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 Cho-Time GmbH + * + * This program 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. + * + * This program 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 this program. If not, see . + */ +package org.mxchange.pizzaapplication.mailer.model.delivery; + +import de.chotime.landingpage.database.BaseLandingDatabaseBean; +import de.chotime.landingpage.mailer.model.delivery.DeliverableLandingEmail; +import de.chotime.landingpage.mailer.model.delivery.LandingMailer; +import java.io.Serializable; +import java.text.MessageFormat; +import javax.ejb.ActivationConfigProperty; +import javax.ejb.MessageDriven; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageListener; +import javax.jms.ObjectMessage; + +/** + * A message-driven bean for sending out emails + *

+ * @author Roland Haeder + */ +@MessageDriven (activationConfig = { + @ActivationConfigProperty (propertyName = "destinationLookup", propertyValue = "jms/jlandingpage-email-queue"), + @ActivationConfigProperty (propertyName = "destinationType", propertyValue = "javax.jms.Queue") +}) +public class EmailDeliveryMessageBean extends BaseLandingDatabaseBean implements MessageListener { + + /** + * Serial number + */ + private static final long serialVersionUID = 75_638_176_619_024L; + + /** + * Mailer instance + */ + private final DeliverableLandingEmail mailer; + + /** + * Default constructor + */ + public EmailDeliveryMessageBean () { + // Init mailer instance + this.mailer = new LandingMailer(); + } + + @Override + public void onMessage (final Message message) { + // Trace message + this.getLoggerBeanLocal().logTrace(MessageFormat.format("onMessage: message={0} - CALLED!", message)); //NOI18N + // The parameter should be valid + if (null == message) { + // Throw NPE + throw new NullPointerException("message is null"); //NOI18N + } else if (!(message instanceof ObjectMessage)) { + // Not implementing right interface + throw new IllegalArgumentException(MessageFormat.format("message={0} does not implemented ObjectMessage", message)); //NOI18N + } + + // Securely cast it + ObjectMessage objectMessage = (ObjectMessage) message; + + // Init variable + Serializable serializable; + + try { + // Get object from message + serializable = objectMessage.getObject(); + } catch (final JMSException ex) { + // Log it and don't continue any further + this.getLoggerBeanLocal().logException(ex); + return; + } + + // Debug message + this.getLoggerBeanLocal().logDebug(MessageFormat.format("onMessage: serializable={0}", serializable)); //NOI18N + + // Trace message + this.getLoggerBeanLocal().logTrace("onMessage - EXIT!"); //NOI18N + } + +} -- 2.39.5