3

Background:

My company sells software. In my org, we sell subscriptions to our software and addons, or subscriptions for software support. We have 3 main types of subscriptions (Support, Software, Communication).

Objects:

Zuora__Subscription__c : Tracks if subscription is active or cancelled.

Zuora__SubscriptionProductCharge__c : Tracks the type of Subscription (from 3 options above)

Zuora__Subscription__c is a child of Account, and Zuora__SubscriptionProductCharge__c (SPC) is a child of Zuora__Subscription__c.

Situation:

I have an apex class that is responsible for sending email alerts to our accounting department if we find more than one ACTIVE subscription of the same TYPE. The types, mentioned above, are stored in a text field on the SubscriptionProductCharge object, and the Active status is on the Subscription object. This code works very well 90% of the time, but recently I have been haunted by null reference exceptions that I can't pinpoint.

Problem:

I've tracked it down to a problem where my accToSPCMap Map sometimes doesn't contain the key, but from my understanding of the code, there shouldn't ever be a situation where something can end up in the map without the key (Account Id). Can someone pleeeease look through my code and tell me why or how records with no associated Account Id are making it into my map? I'm stumped.

Help me Stack Exchange, you're my only hope.

Code:

public with sharing class SendAlertForRelatedProductPlans
{
    // Make the constructor private since this is a utility class that should not be instantiated
    private SendAlertForRelatedProductPlans() 
    {
    }

    public static void checkNewSubscriptionProductCharges(List<Zuora__SubscriptionProductCharge__c> newSubscriptionProductCharges) 
    {
       // The below code will query for the accounts related to the SPCs that are being inserted
        Set<Id> accountIdSet = new Set<Id>();
        List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>();

        for(Zuora__SubscriptionProductCharge__c spc : newSubscriptionProductCharges)
        {
            if(spc.Zuora__Account__c != null && spc.Zuora__Subscription__c != null) // Make sure the SPCs are related to an account and subscription
            {
                accountIdSet.add(spc.Zuora__Account__c);
            }
        }

        if(accountIdSet.size() == 0)
        {
            System.debug('The SPCs being inserted were not associated with accounts, quitting early.');
            return; // If none of the SPCs are related to accounts, quit early 
        }

        List<Account> accounts = [SELECT Id, Name, Customer_Number__c FROM Account WHERE Id IN :accountIdSet];

        // For each account, we'll need to query for *all* the related active subscriptions.
        Map<Id, Zuora__Subscription__c> subscriptions = new Map<Id, Zuora__Subscription__c>([SELECT Id, Name, Zuora__Account__c
                                                                                             FROM Zuora__Subscription__c 
                                                                                             WHERE Zuora__Account__c IN: accountIdSet 
                                                                                             AND Zuora__Status__c = 'Active'
                                                                                             AND Name != null]);

        // If there are no active subscriptions associated with the accounts specified, we should not continue.
        if(subscriptions.size() == 0)
        {
            System.debug('There were no active subscriptions associated with the SPCs being inserted. Quitting early.');
            return;
        }

        Set<Id> subscriptionIdSet = subscriptions.keySet();

        // We also need to query for all SPCs associated with the above active subscriptions.
        // Retrieve any SPCs that are:
        // (a) Associated with the set of subscriptions ids
        // (b) The subscriptions must be active
        // (c) Associated with the set of account ids within this trigger context 
        // (d) The SPC isn't a Discount Record
        // (e) The SPC has a Rate Plan Classification
        Map<Id,Zuora__SubscriptionProductCharge__c> spcsAssociatedWithActiveSubscription = new Map<Id,Zuora__SubscriptionProductCharge__c>(
                                                                                         [SELECT Id, RatePlanClassification__c, Zuora__Account__c, Zuora__Subscription__c
                                                                                          FROM Zuora__SubscriptionProductCharge__c
                                                                                          WHERE Name != 'Discount' 
                                                                                          AND RatePlanClassification__c != null
                                                                                          AND Zuora__Subscription__c IN : subscriptionIdSet
                                                                                          AND Zuora__Subscription__r.Zuora__Status__c = 'Active' 
                                                                                          AND Zuora__Account__c IN :accountIdSet]);

        Map<Id, List<Zuora__SubscriptionProductCharge__c>> accToSPCMap = new Map<Id, List<Zuora__SubscriptionProductCharge__c>> ();
        for(Zuora__SubscriptionProductCharge__c zs: spcsAssociatedWithActiveSubscription.values()){
            List<Zuora__SubscriptionProductCharge__c> sub = accToSPCMap.get(zs.Zuora__Account__c);
            if(sub == null){
                sub = new List<Zuora__SubscriptionProductCharge__c>();
                sub.add(zs);
                accToSPCMap.put(zs.Zuora__Account__c, sub);
            }else{
                sub.add(zs);
            }

        }

        // The below two maps will capture whether a subscription has:
        // (a) at least one 'Support' related SPC 
        // (b) at least one 'Software' related SPC

        Integer count = 0;

        System.debug('KLE accounts : '+accounts.size());

        for(Account account : accounts){
            Set<ID> subscriptionIdHasSupportSPCs = new Set<ID>();
            Set<ID>  subscriptionIdHasSoftwareSPCs = new Set<ID>();
            Set<ID>  subscriptionIdHasCommSPCs = new Set<ID>();

            if(accToSPCMap.size() > 0){
                    System.debug('MTM acctToSPCMap containsKey:' +accToSPCMap.containsKey(account.id));
                if(accToSPCMap.get(account.id).size() > 0){ // THROWS ERROR ON THIS LINE - because of get(account.id) since account.id isn't in the map?
                    System.debug('MTM acctToSPC MapSize>0 containsKey:' +accToSPCMap.containsKey(account.id));
                    System.debug('KLE spc per account : '+accToSPCMap.get(account.id).size() + ' for acc: '+account.id); // THROWS ERROR ON THIS LINE - because of get(account.id) since account.id isn't in the map?
                    for(Zuora__SubscriptionProductCharge__c spc : accToSPCMap.get(account.id)) // THROWS ERROR ON THIS LINE - because of get(account.id) since account.id isn't in the map?
                    { 
                        if(spc.RatePlanClassification__c == 'Support')
                        {
                            subscriptionIdHasSupportSPCs.add(spc.Zuora__Subscription__c);

                        }
                        else if(spc.RatePlanClassification__c == 'Software')
                        {
                            subscriptionIdHasSoftwareSPCs.add(spc.Zuora__Subscription__c);

                        }
                        else if(spc.RatePlanClassification__c == 'Communication')
                        {
                            subscriptionIdHasCommSPCs.add(spc.Zuora__Subscription__c);

                        }
                        count++;

                    } // End of loop through SPCs
                }
           }

Error Being Generated:

Apex script unhandled trigger exception by user/organization: 

SubscriptionProductChargeTrigger: execution of AfterInsert

caused by: System.NullPointerException: Attempt to de-reference a null object

Class.SendAlertForRelatedProductPlans.checkNewSubscriptionProductCharges: line 107, column 1
Trigger.SubscriptionProductChargeTrigger: line 35, column 1
10
  • Hi Morgan. What's the problem with your accToSPCMap? Does this map contains a null key? If so, don't you think it would be possible that some of your Zuora__SubscriptionProductCharge__c objects have their field Zuora__Account__c set to null? Feb 3, 2016 at 15:23
  • Hi Martin, thanks for taking a look at this. My problem is exactly that. The map is returning a null (or false) on containsKey on some parsed records. My problem with that is that we have a for loop that goes through all SubscriptionProductCharges first and only adds the Zuora__Account__c Id to the accountIdSet if the spc.Zuora__Account__c != null && spc,Zuora__Subscription__c != null. So if I'm already making sure that there is an Account Id before I add it to my IdSet, how could something be making its way into the map without an Id? Feb 3, 2016 at 15:31
  • Where are you actually adding values to the acctToSPCMap in the code? I may be a bit blind but I don't see it. Kudos on the superb layout for the problem though. 10/10 for thoroughness!
    – akarnid
    Feb 3, 2016 at 15:36
  • Zuora__Account__c : Is it the account lookup field on SPC? SPC is grandchild of Account as you said above. Feb 3, 2016 at 15:43
  • Can you share the actual error message you get? Just to be sure? By the way, kudos on your code cleanliness. Super easy to read Feb 3, 2016 at 15:46

2 Answers 2

1

Your List<Account> accounts has accounts in it that do not meet the criteria for your query for the Zuora__SubscriptionProductCharge__c records. You are iterating over the list of accounts with the assumption that every one of those accounts will have an entry in the map of SPCs. This isn't the case. Some of the accounts in the list don't have Zuora__SubscriptionProductCharge__c records that meet all of your criteria and the only account.id values in that map are the ones which meet all of that criteria.

One easy correction, instead of this:

if(accToSPCMap.size() > 0){
    System.debug('MTM acctToSPCMap containsKey:' +accToSPCMap.containsKey(account.id));
    if(accToSPCMap.get(account.id).size() > 0){

Write it as:

if(accToSPCMap.size() > 0){
    System.debug('MTM acctToSPCMap containsKey:' +accToSPCMap.containsKey(account.id));

    // see if this account has any SPCs in the map from the SPC query
    List<Zuora__SubscriptionProductCharge__c> accountSPCs = accToSPCMap.get(account.id);

    if (accountSPCs != null && accountSPCs.size() > 0){
        // do the work
3
  • Oh, wow. I can see it so clearly now. I was so focused on the actual map itself not being correct that I didn't think to consider that I was comparing the map to a broader account list in the for loop. I thought the map was missing Ids that it should have been including, when in reality it looks like the map is accurate but I am comparing the map to a broader list, resulting in null when looking for each account.id in the map. I'm going to try to implement this right now to squash this. Just for my own curiousity, couldn't I also just do if(accToSPCMap.containsKey(account.id) == true){ }? Feb 3, 2016 at 20:31
  • 1
    Yep, you sure could do that - you don't even need the == true. I just wanted to provide an example that was a bit more verbose to illustrate the paragraph of text.
    – Mark Pond
    Feb 4, 2016 at 0:07
  • Working great today, got the troublesome records inserted. Thanks again! Feb 4, 2016 at 15:04
1

I can see a potential error in here:

You are getting your list of AccountIds from Zuora__SubscriptionProductCharge__c and assuming that this set will be exactly the same for the Zuora__Subscription__c object. While I'm pretty sure this SHOULD be the case, it's not guaranteed.

If, however you harvest your AccountIds from Zuora__Subscription__c then you will be nearly guaranteed that this error won't occur (other than some edge case where a deletion is occurring mid-transaction - and I think even that can't happen)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .