Package org.jresponder.service

Source Code of org.jresponder.service.SubscriberService

/**
* =========================================================================
*     __ ____   ____  __  ____    ___   __  __ ____    ____ ____
*     || || \\ ||    (( \ || \\  // \\  ||\ || || \\  ||    || \\
*     || ||_// ||==   \\  ||_// ((   )) ||\\|| ||  )) ||==  ||_//
*  |__|| || \\ ||___ \_)) ||     \\_//  || \|| ||_//  ||___ || \\
* =========================================================================
*
* Copyright 2012 Brad Peabody
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* =========================================================================
*/
package org.jresponder.service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.jresponder.dao.MainDao;
import org.jresponder.domain.LogEntryType;
import org.jresponder.domain.Subscriber;
import org.jresponder.domain.SubscriberStatus;
import org.jresponder.domain.Subscription;
import org.jresponder.domain.SubscriptionStatus;
import org.jresponder.engine.SendingEngine;
import org.jresponder.message.MessageGroup;
import org.jresponder.message.MessageGroupSource;
import org.jresponder.message.MessageRef;
import org.jresponder.util.PropUtil;
import org.jresponder.util.TokenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
* Perform actions related to subscribing.
* <p>
* TODO: we should look at how return codes are dealt with - should probably
* make it so a certain type of exception is thrown on error, and that
* exception has a standard list of error types - which can correspond
* directly to the error codes sent back via JSON-RPC 2.
*
* @author bradpeabody
*
*/
@Service("jrSubscriberService")
public class SubscriberService {
 
  /* ====================================================================== */
  /* Logger boiler plate                                                    */
  /* ====================================================================== */
  private static Logger l = null;
  private Logger logger() { if (l == null) l = LoggerFactory.getLogger(this.getClass()); return l; }
  /* ====================================================================== */

  @Resource(name="jrTokenUtil")
  private TokenUtil tokenUtil;
 
  @Resource(name="jrPropUtil")
  private PropUtil propUtil;
 
  @Resource(name="jrMainDao")
  private MainDao mainDao;
 
  @Resource(name="jrMessageGroupSource")
  private MessageGroupSource messageGroupSource;
 
  @Resource(name="jrSendingEngine")
  private SendingEngine sendingEngine;
 
 
  /**
   * Default log entry props
   * @return
   */
  private static Map<String,Object> defaultLogEntryProps() {
   
   
    Map<String,Object> myRet = new HashMap<String,Object>();
   
    try {
      // use some Spring magic to get the current request
      HttpServletRequest req =
          ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
          .getRequest();
      myRet.put("ip_address", req.getRemoteAddr());
    }
    catch (IllegalStateException e) {
      LoggerFactory.getLogger(SubscriberService.class).debug("defaultLogEntryProps() got IllegalStateException - this is normal if testing outside of web env");
    }
   
    return myRet;
  }
 
  /**
   * Subscribes someone, returns the Subscriber object that corresponds
   *
   * @param aEmail
   * @param aSubscriberPropsMap
   * @param aMessageGroupName
   * @return
   */
  @Transactional(propagation=Propagation.REQUIRED)
  public Subscriber subscribe(String aEmail, Map<String,Object> aSubscriberPropsMap, String aMessageGroupName) {
     
    logger().debug("Starting subscribe()");
   
      try {
        // FIXME: should package these up into util calls so they are not so verbose,
        // do that before too many more of these functions are written
       
        // sanity checks
        if (aEmail == null || aEmail.length() < 1) throw new IllegalArgumentException("email cannot be null or zero length");
        // TODO: configurable?
        aEmail = aEmail.toLowerCase();
       
        // make sure that the message group is not empty and actually exists
        if (aMessageGroupName == null || aMessageGroupName.length() < 1) throw new IllegalArgumentException("messageGroupName cannot be null or zero length");
        if (messageGroupSource.getMessageGroupByName(aMessageGroupName) == null) {
          throw new IllegalArgumentException("message group does not exist");
        }
       
        // get the message group
        MessageGroup myMessageGroup = messageGroupSource.getMessageGroupByName(aMessageGroupName);
      logger().debug("Using MessageGroup: {}", myMessageGroup);
     
        // the opt-in-confirm message, if it exists
        MessageRef myOptInConfirmMessageRef = myMessageGroup.getOptInConfirmMessageRef();
      logger().debug("myOptInConfirmMessageRef: {}", myOptInConfirmMessageRef);
       
        // make sure we have an attribute map, even if it's empty
        if (aSubscriberPropsMap == null) {
          aSubscriberPropsMap = new HashMap<String,Object>();
        }
       
      logger().debug("aSubscriberPropsMap: {}", aSubscriberPropsMap);

       
        // find existing subscriber
        Subscriber mySubscriber = mainDao.getSubscriberByEmail(aEmail);
      logger().debug("Looked up subscriber with email ({}) and got: {}", aEmail, mySubscriber);
       
      Subscription mySubscription = null;
     
        // doesn't exist, create it
        if (mySubscriber == null) {
         
          logger().debug("No subscriber for this email, making a new record");
         
          // create subscriber
          mySubscriber = new Subscriber();
          mySubscriber.setEmail(aEmail);
          mySubscriber.setPropsMap(aSubscriberPropsMap);
          mySubscriber.setSubscriberStatus(SubscriberStatus.OK);
          mainDao.persist(mySubscriber);
         
          logger().debug("Now making a new subscription record");

            // create subscription
            mySubscription = new Subscription(mySubscriber, aMessageGroupName);
            mySubscription.setNextSendDate(new Date());
            mySubscription.setToken(tokenUtil.generateToken());
           
            // send opt in confirm message if applicable
            if (myOptInConfirmMessageRef != null) {
              logger().debug("About to send opt-in confirm message...");
              doOptInConfirmMessage(myOptInConfirmMessageRef, myMessageGroup, mySubscriber, mySubscription);
            }
            // if no opt in, then just mark active
            else {
              logger().debug("No opt-in confirm message, just marking as active");
              mySubscription.setSubscriptionStatus(SubscriptionStatus.ACTIVE);
            }
           
          logger().debug("Saving subscription");
            mainDao.persist(mySubscription);
           
        /* ========================================================== */
        /* Make LogEntry                                              */
        /* ========================================================== */
            mainDao.logEntry
                      (
                          LogEntryType.SUBSCRIBED,
                          mySubscriber,
                          aMessageGroupName,
                          defaultLogEntryProps()
                      );
           
        }
       
        // already there, merge
        else {
         
          logger().debug("Already have a Subscriber object for email address ({}): {}", aEmail, mySubscriber);
         
          // update attributes
          mySubscriber.setPropsMap(
              (Map<String,Object>)PropUtil.getInstance().propMerge(mySubscriber.getPropsMap(), aSubscriberPropsMap)
              );
         
          if (logger().isDebugEnabled()) {
            logger().debug("Saving updated properties: {}", mySubscriber.getPropsMap());
          }
         
            mainDao.persist(mySubscriber);
         
            // see if the subscription is there
            mySubscription =
                mainDao.getSubscriptionBySubscriberAndMessageGroupName
                  (
                      mySubscriber,
                      myMessageGroup.getName()
                  );
           
        logger().debug("Looking for corresponding Subscription record for subscriber and message group ({}) found: {}", myMessageGroup.getName(), mySubscription);
           
            // no subscription, create it
            if (mySubscription == null) {
             
              mySubscription = new Subscription(mySubscriber, aMessageGroupName);
                mySubscription.setNextSendDate(new Date());
                mySubscription.setToken(tokenUtil.generateToken());

                // send opt in confirm message if applicable
                if (myOptInConfirmMessageRef != null) {
                  logger().debug("About to send opt-in confirm message...");
                  doOptInConfirmMessage(myOptInConfirmMessageRef, myMessageGroup, mySubscriber, mySubscription);
                }
                // if no opt in, then just mark active
                else {
                  logger().debug("No opt-in confirm message, just marking as active");
                  mySubscription.setSubscriptionStatus(SubscriptionStatus.ACTIVE);
                }

              logger().debug("Saving subscription");
                mainDao.persist(mySubscription);
               
          /* ========================================================== */
          /* Make LogEntry                                              */
          /* ========================================================== */
              mainDao.logEntry
                        (
                            LogEntryType.SUBSCRIBED,
                            mySubscriber,
                            aMessageGroupName,
                            defaultLogEntryProps()
                        );
            }
           
            // we do already have a subscription
            else {
             
              // see if it's active
              if (mySubscription.getSubscriptionStatus() != SubscriptionStatus.ACTIVE) {
               
                    // send opt in confirm message if applicable
                    if (myOptInConfirmMessageRef != null) {
                      doOptInConfirmMessage(myOptInConfirmMessageRef, myMessageGroup, mySubscriber, mySubscription);
                    }
                    // if no opt in, then just mark active
                    else {
                      mySubscription.setSubscriptionStatus(SubscriptionStatus.ACTIVE);
                    }
                   
              }
             
              // save subscription
              mainDao.persist(mySubscription);
             

          /* ========================================================== */
          /* Make LogEntry                                              */
          /* ========================================================== */
              mainDao.logEntry
                        (
                            LogEntryType.RESUBSCRIBED,
                            mySubscriber,
                            aMessageGroupName,
                            defaultLogEntryProps()
                        );
            }
           
        }
       
        // now that we've done all the work -
        // re-read subscriber, so we get the latest (probably not
        // necessary, but it makes it me feel better ;)
        mySubscriber = mainDao.getSubscriberById(mySubscriber.getId());
       
        // log an info, just for politeness
      logger().info("User '{}' subscribed to message group '{}' ({} opt-in confirm)",
              new Object[] { mySubscriber.getEmail(), mySubscription.getMessageGroupName(),
                  (myOptInConfirmMessageRef != null ? "with" : "without")});

        return mySubscriber;
       
      }
      catch (Throwable t) {
        throw new RuntimeException(t);
      }
   
  }
 
  /**
   * Unsubscribe from token.  Sets this individual Subscription as inactive.
   *
   * @param aToken
   * @return true if found and marked, false if not found
   */
  @Transactional(propagation=Propagation.REQUIRED)
  public boolean unsubscribeFromToken(String aToken) {
   
    Subscription mySubscription = mainDao.getSubscriptionByToken(aToken);
    if (mySubscription == null) { return false; }
   
    mySubscription.setSubscriptionStatus(SubscriptionStatus.INACTIVE);
    mySubscription.setNextSendDate(null);
   
    mainDao.persist(mySubscription);
   
    Subscriber mySubscriber = mySubscription.getSubscriber();
    if (mySubscriber == null) {
      throw new IllegalStateException("Subscription record found but no corresponding subscriber record - this is an internal database error");
    }
   
    logger().info("Subscription corresponding to (email={} messageGroupName={}) was marked as INACTIVE (this particular email/message group combination is now not active)", mySubscriber.getEmail(), mySubscription.getMessageGroupName());

    return true;
  }
 
 
  /**
   * Sets the subscriber status to REMOVE, meaning he won't
   * get any more mail at all from this system.
   *
   * @param aToken
   * @return true if found and marked, false if not found
   */
  @Transactional(propagation=Propagation.REQUIRED)
  public boolean removeFromToken(String aToken) {
   
    Subscription mySubscription = mainDao.getSubscriptionByToken(aToken);
    if (mySubscription == null) { return false; }
   
    Subscriber mySubscriber = mySubscription.getSubscriber();
    if (mySubscriber == null) {
      throw new IllegalStateException("Subscription record found but no corresponding subscriber record - this is an internal database error");
    }
   
    mySubscriber.setSubscriberStatus(SubscriberStatus.REMOVED);
    mainDao.persist(mySubscriber);
   
    logger().info("Subscriber with email ({}) was REMOVED (no more emails from an message groups)", mySubscriber.getEmail());
   
    return true;

  }
 
  /**
   * Performs an opt-in confirmation from a token.
   *
   * @param aToken
   * @return true if found and confirmed, false if not found or could
   *         not confirm due to subscriber or subscription being in
   *         funky state.
   */
  @Transactional(propagation=Propagation.REQUIRED)
  public void confirmFromToken(String aToken) throws ServiceException {
   
    Subscription mySubscription = mainDao.getSubscriptionByToken(aToken);
    if (mySubscription == null) {
      throw new ServiceException(ServiceExceptionType.NO_SUCH_SUBSCRIPTION,
          propUtil.mkprops("token", aToken));
    }
   
    Subscriber mySubscriber = mySubscription.getSubscriber();
    if (mySubscriber == null) {
      throw new ServiceException(ServiceExceptionType.NO_SUCH_SUBSCRIBER,
          propUtil.mkprops("token", aToken, "jr_subscription_id", mySubscription.getId()));
    }
   
    logger().debug("Attempting to confirm subscription (token={}) for subscriber (email={})",
        new Object[] { mySubscription.getToken(), mySubscriber.getEmail() });
   
    // make sure subscriber is marked as OK
    if (mySubscriber.getSubscriberStatus() != SubscriberStatus.OK) {
      logger().debug("Could not confirm, subscriber status was not OK, instead it was: {}", mySubscriber.getSubscriberStatus() );
      throw new ServiceException(ServiceExceptionType.SUBSCRIBER_NOT_OK,
          propUtil.mkprops("token", aToken));
    }
   
    // make sure subscription is marked as confirm wait or is already active
    if (!
        (
            mySubscription.getSubscriptionStatus() == SubscriptionStatus.CONFIRM_WAIT ||
            mySubscription.getSubscriptionStatus() == SubscriptionStatus.ACTIVE
        )
      ) {
      logger().debug("Could not confirm, subscription status was not CONFIRM_WAIT or ACTIVE, instead it was: {}", mySubscription.getSubscriptionStatus() );
      throw new ServiceException(ServiceExceptionType.UNEXPECTED_SUBSCRIPTION_STATUS,
          propUtil.mkprops("token", aToken));
    }
   
    mySubscription.setSubscriptionStatus(SubscriptionStatus.ACTIVE);
    // if next send date is null, then set it to now - so it goes to the
    // engine to be scheduled properly
    if (mySubscription.getNextSendDate() == null) {
      mySubscription.setNextSendDate(new Date());
    }
    mainDao.persist(mySubscription);
   
    logger().info("Subscription (email={},message_group_name={},token={}) was confirmed!",
        new Object[] { mySubscriber.getEmail(), mySubscription.getMessageGroupName(), mySubscription.getToken() });

  }

  /**
   * Send an opt-in confirmation message
   *
   * @param aOptInConfirmMessageRef
   * @param aSubscriber
   * @param aSubscription
   */
  @Transactional(propagation=Propagation.REQUIRED)
  protected void doOptInConfirmMessage
            (
                MessageRef aOptInConfirmMessageRef,
                MessageGroup aMessageGroup,
                Subscriber aSubscriber,
                Subscription aSubscription
            ) {

    // send message
    if (sendingEngine.sendMessage(aOptInConfirmMessageRef, aMessageGroup, aSubscriber, aSubscription, LogEntryType.MESSAGE_SENT)) {
      logger().debug("Sent opt-in confirm message");
      aSubscription.setSubscriptionStatus(SubscriptionStatus.CONFIRM_WAIT);
    }
   
    // if opt-in message skipped then we mark as active right away
    else {
      logger().debug("Opt-in confirm message was skipped (due to no body)");
      aSubscription.setSubscriptionStatus(SubscriptionStatus.ACTIVE);
    }
   
  }

  /**
   * Gets a subscriber and all of his Subscriptions
   *
   * @param aEmail
   * @return
   */
  @Transactional(propagation=Propagation.REQUIRED, readOnly=true)
  public Subscriber lookupSubscriber(String aEmail) {
     
      try {
       
        if (aEmail == null) { return null; }
       
        aEmail = aEmail.toLowerCase();

        // find existing subscriber
        Subscriber mySubscriber = mainDao.getSubscriberByEmail(aEmail);
       
        // force Hibernate to populate the Subscriptions list
        if (mySubscriber != null) {
          mySubscriber.getSubscriptions();
        }
       
        return mySubscriber;
       
      }
      catch (Throwable t) {
        throw new RuntimeException(t);
      }
   
  }

 
}
TOP

Related Classes of org.jresponder.service.SubscriberService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.