• Kenji775
  • NEWBIE
  • 100 Points
  • Member since 2008

  • Chatter
    Feed
  • 4
    Best Answers
  • 0
    Likes Received
  • 0
    Likes Given
  • 115
    Questions
  • 248
    Replies

Hey all,

 

So this is likely going to be one of those 'your screwed' questions, but I figure I'd give it a shot. I need to develop a visualforce application that can be hosted on a salesforce site. The application itself is a form, where a user is allowed to create multiple things at once (essentially it's a project submission form, and the user is allowed to submit multiple different configurations for their project at once to receive different bids). I've done this by create a tabbed form, where the user can click to get another tab, with another form in each tab. This form is loaded from another page via an ajax request. So the main page goes and reaches out to the second page, gets the generated content and injects it. This is to keep the actual content of the form somewhat modifiable by another group (since it just contains basic html). 

 

This all works great, except for when it comes to dependent picklists. The javascript used by visualforce to handle the picklists breaks down if there is more than one instance of the same field on the page (which there would be if a user is creating multiple submissions). I've gotten around this before by using the javascript libraries and building my own dynamic picklist system. However, that was when this tool was being hosted in a portal. The javascript libraries are not available through sites. So I can't use visualforce because it breaks down, and I can't use the JS library. 

 

Long story short, can you think of any way, regardless of how crazy or backwards it might seem to get dependent picklist valus on a salesforce.com site? I've though about trying to set up a web service, or something of that nature, but I simply cannot think of any way to get picklist values from a non authenticated source. 

 

Anyway, feel free to post any ideas. Just kind of brainstorming here before I give up and have to hand code in all the picklist options. Thanks!

hey all. I am working on a batch trigger that will update records called resource bookings. These records are imported via a CSV file with a texts fields that specify an opportunity and and a product. The class needs to look at the list of resource booking records that get passed in, and find any with a null opportunity lookup and populate it based on a field called FFA_OPP_REF__c, and populate a lookup to the product using a field called Sim_Id__c. So if a resource booking record comes in with a null opportunity__c (lookup to opportunity) field, and has an FFA_OPP_REF__C populated, then find the opportunity with the same value in it's Proposal_reference__c field (a text field) and populate the resource booking with the ID of the matching opportunity. Same basic deal with products.

 

The tricky part here is that once that relationship is set, I also need to make use of it later in the same batch run, meaning I need to access values on the related opportunity and product. So it is my understanding that the relationship field change needs to be commited to the database (using database.update or update) before I can actually make use of the relationship. So I have a few questions.

 

1) Do I really need to commit the relationship change to the database before the relationship is 'spiderable' (I can navigate the parent child relationship), or since the lookup has been populate in memory is that sufficient?

 

2) If I do need to update those changes (as outlined in question 1), do I need to requery for the records. Since this is a batchable class, my execute method gets passed a list of resource booking records. If I update their relationships to opportunity and products, and I commit the changes, do I need to query for them again for the relationships to be accessable? 

 

3) Right now my method for updating the relationships is to create 2 maps (defined as <string, list<resource_booking__c>>) . One for resource bookings with null opportunities (keyed by FFA_OPP_REF__C) and one for resource bookings with null products/simulators (keyed by Sim_id__c). I run a query to find the opportunities that match, and one for products that maps. So to commit the changes I end up needing to do a loop that looks like this

 

        for(list<Resource_Booking__c> rbl :nullOpportunityBookings.values())
        {
            database.update(rbl,false);
        }     

 

Is this acceptable, or shoudl I loop over each list to create one big list and push it all at once? If I did create one large list, would I hit governor limit problems, or since I am in a batchable class is that restriction lifted (seems like batch is more lax on the limits).

 

Any input is appreciated. The relevant section of my code is below.

global static void processResourceBookings(list<Resource_Booking__c> resourceBookings)
{

//first for this group of resource booking records, we need to populate the opportunity, and the product on the record.
//those are used for logic later on. So loop over all the resource bookings, find any that have a null in the opportunity or product,
//add them to a map (one map for null opps, and one map for null products/simulators) then run a query to update them.

map<string,list<Resource_Booking__c>> nullSimulatorBookings = new map<string,list<Resource_Booking__c>>();
map<string,list<Resource_Booking__c>> nullOpportunityBookings = new map<string,list<Resource_Booking__c>>();

//loop over all the resource bookings to build the list of them that have a blank opportunity or product/simulator so we know which ones
//we need to populate data for.

for(Resource_Booking__c booking :  resourceBookings)
{
    //build map of resource bookings that do not have a simulator relationship
    if(booking.Simulator__c == null)
    {
        //instantiate a list of bookings.
        list<Resource_Booking__c> bookings;
        
        //if there is no list of bookings for this simulator id, then create one.
        if(!nullSimulatorBookings.containsKey(booking.SIM_ID__c))
        {
            bookings =  new list<Resource_Booking__c>();
        }
        //otherwise fetch the existing list so we can append to it.
        else
        {
            bookings = nullSimulatorBookings.get(booking.SIM_ID__c);
        }
        //add this booking to the list of bookings. 
        bookings.add(booking);
        
        //set this list of bookings to the simulator id.
        nullSimulatorBookings.put(booking.SIM_ID__c,bookings);
    }

    //build map of resource bookings that do not have a opportunity relationship
    if(booking.opportunity__c == null)
    {
        //instantiate a list of bookings.
        list<Resource_Booking__c> bookings;
        
        //if there is no list of bookings for this FFA OPP REF code, then create one.
        if(!nullOpportunityBookings.containsKey(booking.FFA_OPP_REF__c))
        {
            bookings =  new list<Resource_Booking__c>();
        }
        //otherwise fetch the existing list so we can append to it.
        else
        {
            bookings = nullOpportunityBookings.get(booking.FFA_OPP_REF__c);
        }
        //add this booking to the list of bookings. 
        bookings.add(booking);
        
        //set this list of bookings to the  FFA OPP REF code.
        nullOpportunityBookings.put(booking.FFA_OPP_REF__c,bookings);
    }
}


//okay so now we have a list of resource booking records where the product/simulator is null, keyed by the simulator id. So lets run a query and update them.            
if(!nullSimulatorBookings.isEmpty())
{
    //find all the products (simulators) where their simulator id field appears in the list of keys of our null simulator map. This loop returns batches of simulators.
    for(Product2[] simulators :  [select id, Simulator_Id__c from Product2 where Simulator_Id__c in :nullSimulatorBookings.keySet() ])
    {
        //loop over each simulator in this batch.
        for(Product2 simulator : simulators)
        {
            //now get the list of resource bookings that belong to this simulator id and update them all.
            for(Resource_Booking__c thisBooking : nullSimulatorBookings.get(simulator.Simulator_Id__c))
            {
                thisBooking.simulator__c = simulator.Id;
            }
        }
    }                        
}

//commit the changes for every resource booking that got touched in the previous update loop. Yes I know an update loop is ugly, but unless I want to create
//code that creates one big list, then updates that in batches, this is probably the easiest approach. Hopefully it works.
for(list<Resource_Booking__c> rbl :nullOpportunityBookings.values())
{
    database.update(rbl,false);
}        

 //okay so now we have a list of resource booking records where the opportunity is null, keyed by the FFA OPP REF code. So lets run a query and update them.            
if(!nullOpportunityBookings.isEmpty())
{
     //find all the opportunities where their FFA OPP REF (proposal ID) field appears in the list of keys of our null Opportunity map. This loop returns batches of opportunities.
    for(Opportunity[] opportunities :  [select id, Proposal_Reference__c from Opportunity where Proposal_Reference__c in :nullOpportunityBookings.keySet() ])
    {
        //loop over each opportunity in the batch
        for(Opportunity opportunity : opportunities)
        {
            //now get the list of resource bookings that belong to this FFA OPP REF code opportunity and update them all.
            for(Resource_Booking__c thisBooking : nullOpportunityBookings.get(opportunity.Proposal_Reference__c))
            {
                thisBooking.opportunity__c =opportunity.Id;
            }
        }
    }
}      

//commit the changes for every resource booking that got touched in the previous update loop. Yes I know an update loop is ugly, but unless I want to create
//code that creates one big list, then updates that in batches, this is probably the easiest approach. Hopefully it works.           
for(list<Resource_Booking__c> rbl :nullSimulatorBookings.values())
{
    database.update(rbl,false);
}      

/* DO MORE STUFF WITH THE RESOURCE BOOKING RECORDS, INCLUDING UTILIZING THE RELATIONSHIPS THAT WERE JUST POPULATED 
...
...
...
*/

 

 

Hey all,

Just a quick simple question.

Trying to update an object through Apex remoting. One of the fields I am attempting to update is a date time. In what format do I need to pass a datetime for it to work? I've tried just passing a date object, and date.toUTCString(). How do I need to format the datetime in javascript for apex to be able to deal with it? Thanks!

 

 

 

 

Hey all.

I'm working on a custom APEX REST service that will take a JSON payload delivered in the request body and deserialize it to a custom class type. This works just fine in my unit test, and when running from the developer console, but actually attempting the same type of request with the apigee app console results in the fairly cryptic error

 

"message": "Can not deserialize SObject out of VALUE_NUMBER_FLOAT token at [line:1, column:1]", "errorCode": "JSON_PARSER_ERROR"

 

Also, I have seen the error

 

"No such column 'reviewers' on sobject of type Challenge__c" show up. 

 

The payload contains a single challenge__c object, an 4 lists of other object types. I am trying to deserialize the into this wrapper class so then I can insert the challenge, and then the lists of all the records related to it.

 

The line causing this error is 

challengeService.challengePayload challengeData = (challengeService.challengePayload)JSON.deserialize(jsonBody, challengeService.challengePayload.class);

 The defenition of the class is

    global class challengePayload
    {
        public Challenge__c challenge;
        public list<Challenge_Comment_Notifier__c> notifiers = new list<Challenge_Comment_Notifier__c>();
        public list<Challenge_Category__c> categories = new list<Challenge_Category__c>();
        public list<Challenge_Prize__c> prizes = new list<Challenge_Prize__c>();
        public list<Challenge_Reviewer__c> reviewers = new list<Challenge_Reviewer__c>();    
    }  

 

 

Running this code works just fine, both in my unit test, and from the console.

        string jsonBody = '{"reviewers":[{"attributes":{"type":"Challenge_Reviewer__c"},"Member_Reference__c":"testguy1"}],"prizes":[{"attributes":{"type":"Challenge_Prize__c"},"Place__c":1,"Points__c":1000,"Prize__c":"$1000.00","Value__c":1000.00},{"attributes":{"type":"Challenge_Prize__c"},"Place__c":2,"Points__c":500,"Prize__c":"$500.00","Value__c":500.00},{"attributes":{"type":"Challenge_Prize__c"},"Place__c":3,"Points__c":200,"Prize__c":"$200.00","Value__c":200.00}],"notifiers":[{"attributes":{"type":"Challenge_Comment_Notifier__c"},"Member_Reference__c":"testguy1"}],"challenge":{"attributes":{"type":"Challenge__c"},"Name":"test Challenge","End_Date__c":"2012-03-27T17:15:15.261+0000","Challenge_Id__c":"1231231232","Winner_Announced__c":"2012-04-03","Start_Date__c":"2012-03-20T17:15:15.262+0000","Description__c":"test Challenge","Requirements__c":"Test","Status__c":"hidden"},"categories":[{"attributes":{"type":"Challenge_Category__c"},"Category__c":"a04E00000017pWrIAI"}]}';      
        challengeService.challengePayload challengeData = (challengeService.challengePayload)JSON.deserialize(jsonBody, challengeService.challengePayload.class);

 

But running this

https://snap.apigee.com/GBvRyx

 

Not really sure what's going on, and why the erros seem to be flip flopping. I am wondering if perhaps apigee is not encoding the request properly or something? Below is my full class. 

 

/************************************
Name: challengeService
Author: Daniel Llewellyn (Kenji776)
Date: 3/20/2012
Description: Creates a cloudspokes challenge and it's associated records when valid
             json data is passed to the post request. Data must include keys for challenge,
             and optionally may includes prizes, reviewers, categories, and people to notify on comment.
*************************************/
@RestResource(urlMapping='/challengeService/v1/*') 
global class challengeService
{
    @HttpPost
    global static string doPost(RestRequest req, RestResponse res) 
    {
        string returnMessage = 'Operation pending';
        try
        {
            //now we need to get at the body of the request, since that is going to contain all juicy data needed for creating the related records.
            string jsonBody = req.requestBody.toString();
            
            //serialize the request body into a challenge data payload, which has the data for the challenge and all it's associated records.
            challengePayload challengeData = (challengePayload)JSON.deserialize(jsonBody, challengePayload.class);
            
            //insert the challenge itself.
            insert challengeData.challenge;
                     
            //populate the notifiers with the challenge Id
            for(Challenge_Comment_Notifier__c ccn : challengeData.notifiers)
            {
                ccn.challenge__c = challengeData.challenge.id;
            }
            
            //insert the notifiers
            database.insert(challengeData.notifiers);
            
            //populate the categories with the challenge Id
            for(Challenge_Category__c cc : challengeData.categories)
            {
                cc.challenge__c = challengeData.challenge.id;
            }
            
            //insert the categories
            database.insert(challengeData.categories);        
    
            //populate the prizes with the challenge Id
            for(Challenge_prize__c p : challengeData.prizes)
            {
                p.challenge__c = challengeData.challenge.id;
            }
            
            //insert the categories
            database.insert(challengeData.prizes); 
    
            //populate the prizes with the challenge Id
            for(challenge_Reviewer__c r : challengeData.reviewers)
            {
                r.challenge__c = challengeData.challenge.id;
            }
            
            //insert the categories
            database.insert(challengeData.reviewers);        
            
            returnMessage = challengeData.challenge.id;
        }
        catch(exception ex)
        {
            returnMessage = ex.getMessage() + ' Cause is ' + ex.getCause() + ' on line ' + ex.getLineNumber();
        }
        return returnMessage;
    }  

    global static list<sObject> populatLookup(list<sObject> objectsList)
    {
        map<string,id> lookupStringToMemberIdMap = new map<string,id>();
        
        for(sObject obj : objectsList)
        {
            if(obj.get('Member_Reference__c') != null)
            {
                lookupStringToMemberIdMap.put((string) obj.get('Member_Reference__c'), null);         
            }
        }    
        list<Member__c> members = [select id, email__c, name from Member__c where name in : lookupStringToMemberIdMap.keySet() or email__c in : lookupStringToMemberIdMap.keySet()];  
        
        for(Member__c member : members)
        {
            lookupStringToMemberIdMap.put(member.email__c,member.id);
            lookupStringToMemberIdMap.put(member.name,member.id);
        }
        
        for(sObject obj : objectsList)
        {
            if(obj.get('Member_Reference__c') != null)
            {    
                obj.put('member__c',lookupStringToMemberIdMap.get((string) obj.get('Member_Reference__c')));
                obj.put('Member_Reference__c',null);
            }
        }    
        
        return objectsList;
    }
    
    //custom class to handle the challenge and all it's associated data.
    global class challengePayload
    {
        public Challenge__c challenge;
        public list<Challenge_Comment_Notifier__c> notifiers = new list<Challenge_Comment_Notifier__c>();
        public list<Challenge_Category__c> categories = new list<Challenge_Category__c>();
        public list<Challenge_Prize__c> prizes = new list<Challenge_Prize__c>();
        public list<Challenge_Reviewer__c> reviewers = new list<Challenge_Reviewer__c>();    
    }  

    @isTest
    public static void testCreateChallenge()
    {
        //create the parent objects so our junction objects have something to reference later.
        Category__c cat1 = new Category__c();
        cat1.name = 'test category 1';
        insert cat1;
        
        Member__c member1 = new Member__c();
        member1.name = 'testguy1';
        member1.email__c = 'testguy1@testguyland.com';
        member1.Profile_Pic__c = 'mypics.jpg';
        
        insert member1;

        //validate json string deserialization into custom class

        string jsonBody = '{"reviewers":[{"attributes":{"type":"Challenge_Reviewer__c"},"Member_Reference__c":"testguy1"}],"prizes":[{"attributes":{"type":"Challenge_Prize__c"},"Place__c":1,"Points__c":1000,"Prize__c":"$1000.00","Value__c":1000.00},{"attributes":{"type":"Challenge_Prize__c"},"Place__c":2,"Points__c":500,"Prize__c":"$500.00","Value__c":500.00},{"attributes":{"type":"Challenge_Prize__c"},"Place__c":3,"Points__c":200,"Prize__c":"$200.00","Value__c":200.00}],"notifiers":[{"attributes":{"type":"Challenge_Comment_Notifier__c"},"Member_Reference__c":"testguy1"}],"challenge":{"attributes":{"type":"Challenge__c"},"Name":"test Challenge","End_Date__c":"2012-03-27T17:15:15.261+0000","Challenge_Id__c":"1231231232","Winner_Announced__c":"2012-04-03","Start_Date__c":"2012-03-20T17:15:15.262+0000","Description__c":"test Challenge","Requirements__c":"Test","Status__c":"hidden"},"categories":[{"attributes":{"type":"Challenge_Category__c"},"Category__c":"a04E00000017pWrIAI"}]}';      
        challengeService.challengePayload challengeData = (challengeService.challengePayload)JSON.deserialize(jsonBody, challengeService.challengePayload.class);
                
        //Create the challenge payload object. We will serialize it later and send it to the post method.
        challengePayload myPayload = new challengePayload();
        myPayload.challenge = new challenge__c();
        myPayload.challenge.Challenge_Id__c = '001231231232';
        myPayload.challenge.Name = 'test Challenge';
        myPayload.challenge.Description__c = 'test Challenge';
        myPayload.challenge.End_Date__c = dateTime.now().addDays(7);
        myPayload.challenge.Requirements__c = 'Test';
        myPayload.challenge.Start_Date__c = dateTime.now();
        myPayload.challenge.Status__c = 'hidden';
        myPayload.challenge.Winner_Announced__c = date.today().addDays(14);
        
        
        Challenge_Comment_Notifier__c notifier1 = new Challenge_Comment_Notifier__c();
        notifier1.Member_Reference__c = member1.name;
        myPayload.notifiers.add(notifier1);
        
        Challenge_Category__c category1 = new Challenge_Category__c();
        category1.category__c = cat1.id;
        myPayload.categories.add(category1);
        
        Challenge_Prize__c prize1 = new Challenge_Prize__c();
        prize1.place__c = 1;
        prize1.points__c = 1000;
        prize1.prize__c = '$1000.00';
        prize1.value__c = 1000.00;

        Challenge_Prize__c prize2 = new Challenge_Prize__c();
        prize2.place__c = 2;
        prize2.points__c = 500;
        prize2.prize__c = '$500.00';
        prize2.value__c = 500.00;

        Challenge_Prize__c prize3 = new Challenge_Prize__c();
        prize3.place__c = 3;
        prize3.points__c = 200;
        prize3.prize__c = '$200.00';
        prize3.value__c = 200.00;     
        
        myPayload.prizes.add(prize1);  
        myPayload.prizes.add(prize2);  
        myPayload.prizes.add(prize3);           
        
        Challenge_Reviewer__c reviewer1 = new Challenge_Reviewer__c();
        reviewer1.Member_Reference__c = member1.name;
        myPayload.reviewers.add(reviewer1);
        
        //Serialize the object
        string jsonPayload = JSON.serialize(myPayload);
        
        System.debug('------------------- Serialized payload JSON format: ' + jsonPayload);

        //Create the rest request and send it with the json data.
        RestRequest req = new RestRequest(); 
        RestResponse res = new RestResponse();
        
        
        req.httpMethod = 'POST';
        req.requestBody = blob.valueOf(jsonPayload);
        
        system.debug('---------------------- Request Bod');
        system.debug(req.requestBody);
        string createChallenge = doPost(req,res);
        system.debug('------------------- Created Challenge. Result is: ' + createChallenge); 
        
        //Get the challenge we just created, and it's related objects.
        list<Challenge__c> assertChallengeCreate = [select id, 
                                                    (select id, member__c from Challenge_Reviewers__r),
                                                    (select id from Challenge_Categories__r),
                                                    (select id from Challenge_Prizes__r),
                                                    (select id, member__c from Challenge_Comment_Notifiers__r)
                                                    from challenge__c where challenge_id__c = '001231231232'];
        
        //Verify the challenge got created
        system.assertEquals(1,assertChallengeCreate.size());
        
        //Verify the categories got attached
        system.assertEquals(myPayload.categories.size(),assertChallengeCreate[0].Challenge_Categories__r.size());
        
        //Verify the prizes got attached
        system.assertEquals(myPayload.prizes.size(),assertChallengeCreate[0].Challenge_Prizes__r.size());
        
        //Verify the notifiers got attached
        system.assertEquals(myPayload.notifiers.size(),assertChallengeCreate[0].Challenge_Comment_Notifiers__r.size());
        
        //Verfiy the reviewers got attached
        system.assertEquals(myPayload.reviewers.size(),assertChallengeCreate[0].Challenge_Reviewers__r.size());
        
        //Verify the reviewer record got properly attached to it's member
         system.assertEquals(member1.id,assertChallengeCreate[0].Challenge_Reviewers__r[0].member__c);

        //Verify the comment notifier record got properly attached to it's member
         system.assertEquals(member1.id,assertChallengeCreate[0].Challenge_Comment_Notifiers__r[0].member__c);              
    }
}

 

Hey all,

 

So I have come to a bit of an impass in a trigger I am writting. It may be because it's early and I can't think yet, but I am just not quite sure how to logically solve this problem.

 

Here is the deal, this trigger is responsible for updating a contact associated with a task. When the task is saved, the status field is checked. If the task is completed, an associated date field on the related contact is set to the completion date on the task (if the date to be used for the update is later than the one currently existing). This is to keep track of things such as 'Last stay in touch call', 'Last meeting', etc. The tasks represent interactions with a contact, and when the task is completed they want to mark the contact so they know the last time that kind of action was performed. I have this working perfectly currently.

 

Where it gets complicated is if a task is deleted. They have decided that they want the information to 'roll back' if a task is deleted. They want the field on the contact to be 'recalculated' to find the task that is now the newest that applies to the field that just got deleted. So if the most recent task that set the 'last stay in touch call' gets deleted, they want the system to find the next most recent and use that. This is where I am a bit stuck. The only approach I can think of (while keeping it bulk friendly) is something like:

 

1) Query for all tasks associated with any contact that appeared in this list of newly deleted tasks.
 

2) Iterate over every task. Look to see if the 'Activity_Type__c' matches that of the task that just got deleted for this contact.
 

3) If it is a match on the field, check to see if it most recent than any other entry (could probably eliminate this by sorting the query by the date, and skipping duplicates in my loop). 

 

4) Using the list of tasks, continue with the trigger logic as normal.

 

The issue I have here is, what if one contact is getting multiple tasks deleted at one time? Because when I am iterating through the list of tasks for every contact, I'd have to iterate over every task for them in the trigger context, then find a matching task in the query I just ran, and... argh it gets so complicated and cumbersom. Also, this approach seems excruciatingly inefficient. Does anyone have any better ideas? Below is my code thus far so you can see where I am at. Thanks so much!

 

/**********************************************
Name: ContactActivityRecordTrigger
Author: Daniel Llewellyn
Date 3/14/2012
Description: Will update a contact related to a task when a task is completed. The contact has various date fields that may
                          be populated based on the type of task. Updates performed by the elquoa marketing tool or anyone from 
                          marketing do not fire these updates
***********************************************/                          

trigger ContactActivityRecordTrigger on Task(after insert, after undelete, after update) 
{
    
    try
    {    
        list<Task> tasks;
        if(Trigger.Isundelete)
        {
            tasks = trigger.old;
        }
        else
        {
            tasks = trigger.new;
        }
        //create map of contacts that will contain contacts to update
        map<Id,Contact> contactMap = new map<id,Contact>();
        
        //create a map of users that contain the names of the users related to the tasks.
        map<Id,User> userMap = new map<id,User>();
        
        //we will need to find the DS_Role__c field for all the contacts. So create a map of the contact id, to the contact record
        //so we can run one query, and populate them map, then get the contact details when we need them later without needing
        //to have a query in our loop.
        for (Task thisTask: tasks) 
        {
            //we are only interested in this task if it has a contact, so no contact, just skip adding an entry for this task.
            if(thisTask.WhoId != null)
            {
                contactMap.put(thisTask.WhoId,null);
            }
            if(thisTask.OwnerId != null)
            {
                 userMap.put(thisTask.OwnerId,null);
            }
        }
        
        //populate the map with the id of the contact, then the details about that contact.
        for(Contact con : [select id, DS_Role__c,Last_Meeting_Sales_Call__c,Last_Call__c,Last_Email__c,Last_Demo__c,Last_Mass_Email__c,Last_Sent_Info__c,Last_Marketing_Activity__c from contact where id in :contactMap.keySet()])
        {
            contactMap.put(con.id,con);
        }
    
        //populate the map with the id of the contact, then the details about that contact.
        for(User usr : [select id, Name from User where id in :userMap.keySet()])
        {
            userMap.put(usr.id,usr);
        }
       
       //if this is a delete trigger, the current list of tasks has actually been deleted, so we need to find
       //the task that is now the most recent for each user of the same type that just got deleted
       if(trigger.isDelete)
       {
           //find all the tasks for all the users
           list<task> allTasks = [select id, WhoId, OwnerId,Status,Activity_Type__c, ActivityDate from task where whoId in :contactMap.keySet() order by ActivityDate desc ];
           
           //so now I have to loop over all the tasks I just fetched, and then find all the tasks for the associated contact and see if there is a match and.... arg I have no idea.
           for(Task task : allTasks)
           {
           
           }
       }
       
        //iterate over every task passed in
        for (Task thisTask: tasks)     
        {
            //if this task does not have a contact related to it, then just skip this task and continue.
            if(thisTask.WhoId == null)
            {
                continue;
            }    
            
             //create a reference to the contact associated with this task that we will update
            Contact thisContact =contactMap.get(thisTask.WhoId);
    
            //create a reference to the owner associate with this task
            User thisUser = userMap.get(thisTask.OwnerId);
           
            date activityDate;
            if( thisTask.ActivityDate != null)
            {            
                activityDate = thisTask.ActivityDate;
            }
            else
            {
                activityDate = Date.newInstance(thisTask.LastModifiedDate.year(),thisTask.LastModifiedDate.month(),thisTask.LastModifiedDate.day()); 
            }
            //check if the task status is completed
            if (thisTask.Status.toLowerCase() == 'completed') 
            {                
                //make sure the owner of the task is not eloqua marketing, and make sure the contact's role is not marketing/communications
                if (thisUser.Name.toLowerCase() != 'eloqua marketing' && thisContact.DS_Role__c != 'marketing/communications') 
                {
                    if (thisTask.Activity_Type__c == 'Meeting/Sales Call' && (activityDate > thisContact.Last_Meeting_Sales_Call__c || thisContact.Last_Meeting_Sales_Call__c == null) ) 
                    {
                        thisContact.Last_Meeting_Sales_Call__c = activityDate;
                    }
                    else if (thisTask.Activity_Type__c == 'Call' && (activityDate > thisContact.Last_Call__c ||  thisContact.Last_Call__c == null))
                    {
                        thisContact.Last_Call__c = activityDate;
                    }
                    else if (thisTask.Activity_Type__c == 'Email' && (activityDate > thisContact.Last_Email__c || thisContact.Last_Email__c == null))
                    {
                        thisContact.Last_Email__c = activityDate;
                    }
                    else if (thisTask.Activity_Type__c == 'Demo' && (activityDate > thisContact.Last_Demo__c || thisContact.Last_Demo__c == null)) 
                    {
                        thisContact.Last_Demo__c = activityDate;
                    }
                    else if (thisTask.Activity_Type__c == 'Mass Email' && ( activityDate > thisContact.Last_Mass_Email__c || thisContact.Last_Mass_Email__c == null)) 
                    {
                        thisContact.Last_Mass_Email__c = activityDate;
                    }
                    else if (thisTask.Activity_Type__c == 'Sent Info' && ( activityDate > thisContact.Last_Sent_Info__c || thisContact.Last_Sent_Info__c == null ))
                    {
                        thisContact.Last_Sent_Info__c = activityDate;
                    }
                    else 
                    {
                        if (thisTask.ActivityDate > thisContact.Last_Marketing_Activity__c || thisContact.Last_Marketing_Activity__c == null) 
                        {
                            thisContact.Last_Marketing_Activity__c = activityDate;
                        }          
                    }
                }
            }
            contactMap.put(thisContact.id,thisContact);
        }
   
        //we don't need an all or nothing update, so use database.update with the all or nothing flag set to false
        if(!contactMap.values().isEmpty())
        {
            database.update(contactMap.values(),false);
        }
    }    
    catch(exception e)
    {
        system.debug('Error occured updating related contact' + e);
        
        //Send an email to the admin after an error occures
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        String[] toAddresses = new String[] {'Kenji776@gmail.com'};
        mail.setToAddresses(toAddresses);
        mail.setSubject('Error in ContactActivityRecordTrigger');
        mail.setPlainTextBody('An error occured while running the trigger.' + e.getMessage() + ' On Line Number ' + e.getLineNumber());
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });        
    }    
    
}

 

Hey all.

I am writting a simple trigger that will update records related to leads when the lead is converted. When the lead is converted it needs to populate all associated lead_detail__c objects project__c field with the Id of the created opportunity. Issue is it doesn't seem like when the lead is converted that it's ConvertedOpportunityId is getting populated, so my lead_detail__c objects are not getting updated. I'm not totally sure why (pretty new to the lead conversion process) so any help would be appreciated. Here is my class.

public class leadClasses 
{
	public static void transferLeadDetails(lead[] leadList)
	{		
		//map that holds list of lead id's to the opportunities that they created after conversion
		map<id,id> LeadToOpportunityMap = new map<id,id>();
		
		//iterate over every lead passed in
		for(Lead lead : leadList)
		{
			// if a new opportunity was created due to a conversion
      		if (lead.ConvertedOpportunityId != null)
      		{
      			//add the lead id and it's associated opportunity to a map
      			LeadToOpportunityMap.put(lead.id,lead.ConvertedOpportunityId);
      		}
		}
		
		//if any of those leads passed in had opportunities created, then continue
		if(!LeadToOpportunityMap.isEmpty())
		{
			//find any lead details that belong to the leads in the list
			Lead_Detail__c[] leadDetails = [select id from Lead_Detail__c where lead__c in :LeadToOpportunityMap.keySet()];
			
			//iterate over all those lead details
			for (Lead_Detail__c leadDetail : leadDetails) 
			{
				//set the project__c relationship field on the lead to the created opportunity which is stored in the map
				leadDetail.Project__c = LeadToOpportunityMap.get(leadDetail.lead__c);
			}	
			
			//update the lead detail objects
			update leadDetails;        	        
		}
	}
	
	@isTest
	public static void testLeadClasses()
	{
		//create a lead
        Lead lead = testDataGenerator.createTestLead();
        
        //create a lead detail associated to the lead
        Lead_Detail__c thisTestLeadDetail = testDataGenerator.createTestLeadDetail(lead.id);
		
		//convert the lead, which will create an opportunity
		Database.LeadConvert lc = new database.LeadConvert();
		lc.setLeadId(lead.Id);
		LeadStatus convertstatus = [select Id, MasterLabel from LeadStatus where IsConverted=true limit 1];
		lc.setConvertedStatus(convertStatus.MasterLabel);
		Database.LeadConvert[] lcArray = new Database.LeadConvert[] {lc};
		Database.LeadConvertResult[] results = Database.convertLead(lcArray);
		
		//check to make sure the conversion was a success
		System.assert(results[0].IsSuccess());
		
		
		System.debug('----------- CREATED OPPORTUNITY ID! --------------');
		System.debug(results[0].getOpportunityId());
		
		//query the lead detail so we can see if the project__c got updated with the created opportunity id
		thisTestLeadDetail = [select lead__c, project__c from Lead_Detail__c where id = : thisTestLeadDetail.id];
		
		//check to make sure the lead detail's project__c field got updated with the created opportunity id.
		System.assertEquals(results[0].getOpportunityId(), thisTestLeadDetail.project__c); //This assert fails. Says opportnity id is null.
		
	}

}

 

 

 

The class gets called by my trigger properly, just non of them pass the first if statment that checks to see if the ConvertedOpportunityId is not null, meaning it things they are all null. Shrug, I'll keep playing around with it, but I figured I'd see if anyone knew off the top of their head whats up. Thanks!

Hey all,

I am working on an application that was working, trying to add a bit of new functionality. It just takes a list of objects, looks to see if any of the fields specified at run time have a value that is predefined on a custom object. If so, it performs some actions, ranging from sending emails to blocking the save entirly. This is mostly for chatter posts, to prevent users from posting sensative data. My class that does all the heavy lifting works perfectly for commenting on objects, but fails a users 'wall' post. The error it returns is Error: Error: Feed item doesn't exist.

 

What's weird is everything in my class works, just the chatter post doesn't actually get saved. The emails get sent, the logging records get created, every last line of code in the class seems to get run, so I'm not sure what the issue is.

 

The trigger for the objects (which works) looks like this.

 

trigger filterChatterFeedItem on FeedItem (before insert, before update) 
{
     //when a user posts on an object, the field that contains the text is the 'body' field, so pass that for filtering
String[] filterFields = new String[]{'Body'}; redacted.filterObject(trigger.new,filterFields,'parentId'); }

 

 

And the failing one for users looks like this

 

trigger filterChatterUserStatus on User (before insert, before update) 
{
    //when a user updates, we want to filter their current status field.
    String[] filterFields = new String[] {'CurrentStatus'};
    redacted.filterObject(trigger.new,filterFields);   
}

 

And here is the class. It's kind of complicated, but pretty well commented.

global class redacted
{
    //takes a list of objects, and a list of fields on that object to filter.
    public static void filterObject(list<sObject> recordsToFilter, list<string> fieldsToFilter)
    {
       //what type of object are we filtering (returns a string representing the object type, such as 'contact' or 'user'
        string objectType = string.valueOf(recordsToFilter[0].getSObjectType());
        
        //who is posting this content?
        string postingUserId = UserInfo.getUserId();
        
        //which field on the passed in set of records is going to contain the relationship that points to the object this chatter post is related to?
        string objectIdField = 'parentId';
        
        //if this a user object, the id field is what we want, because that points to the user themselves, which is the object which the post is on.
        if(objectType.toLowerCase() == 'user')
        {
            objectIdField = 'id';
        }
                                   
        list<Redacted_Filter_Match__c> matches = new list<Redacted_Filter_Match__c>();
        
        //Find the current (posting users) users email. So we know who to email if they match a rule.
        User posterInfo = [select id, email from user where id = :UserInfo.getUserId()];


        
        list<Redacted_Filter__c> filters = [   select
                                              Name,
                                              Applies_To__c,
                                              Error_on_Match__c,   
                                              Notification_Email_Address__c,
                                              Pattern_String__c,
                                              Replacement_Pattern__c,
                                              Replace_Pattern__c,
                                              Send_Email_on_Match__c,
                                              Error_Message__c,
                                              Track_Matches__c,
                                              Email_Poster_On_Match__c,
                                              Warning_Message__c
                                              from Redacted_Filter__c
                                              Where Active__c = true ];
        for(sObject post : recordsToFilter)
        {
            for(Redacted_Filter__c filter : filters)
            {
                //Don't bother running this filter if it doesn't apply to this object type
                
                //Find and filters that apply to this type of object that has been passed in.
                string relatedObjectType = getObjectTypeFromId(string.ValueOf(post.get(objectIdField)));
                if(!filter.Applies_To__c.toLowerCase().contains(relatedObjectType.toLowerCase()))
                {
                    continue;
                }            

                for(String postBodyField : fieldsToFilter)
                {
                    String postBody = string.valueOf(post.get(postBodyField));
                    if(postBody == null)
                    {
                        continue;
                    }  
                    //okay, we have to get a little tricky here. Since the native apex string.contains() method
                    //does not support regular expressions, we have to use of the methods that does, and test to see if that
                    //did anything. If it did, then we know the string contains the regular expression and we can continue accordingly.
                    //so lets use the find and replace method to replace any match with this big long string that should never appear normally
                    //then check to see if the string contains it. If so, it matched the post and we should continue accordingly.
                    string matchString = 'thisisamatchingstringthatshouldnoteverappearinaregularchatterpost';
                   
                    string bodyMatchesPattern = postBody.replaceAll(filter.Pattern_String__c, matchString);
                                                       
                    //If a match was found, lets do some filtering and crap
                    if(bodyMatchesPattern.contains(matchString))
                    {
                        //If the user has configured this rule to be tracked, create a new tracking object
                        //and populate it with data.
                        if(filter.Track_Matches__c)
                        {
                            Redacted_Filter_Match__c thisMatch = new Redacted_Filter_Match__c();
                            thisMatch.Filter__c = filter.id;
                            thisMatch.Chatter_Post__c = postBody;
                            thisMatch.Poster__c = postingUserId;
                            matches.add(thisMatch);
                        }
                        //If this rule is set to send an email lets do that
                       
                        list<string> emailRecips = new list<string>();
                        //send the email to the notification email address specified on the filter if there is one, and that option is set
                        if(filter.Notification_Email_Address__c != null && filter.Send_Email_on_Match__c)
                        {
                            emailRecips = new list<string>();
                            emailRecips.add(filter.Notification_Email_Address__c);                        
                            sendPatternMatchEmail(emailRecips, filter, postingUserId, postBody, null, string.ValueOf(post.get(objectIdField)));
                        }
                        //send the email to the user tho posted the content in the first place if that option is set. Send them the warning message.
                        if(filter.Email_Poster_On_Match__c)
                        {
                            emailRecips = new list<string>();
                            emailRecips.add(posterInfo.email);
                            sendPatternMatchEmail(emailRecips, filter, postingUserId, postBody, filter.Warning_Message__c, string.ValueOf(post.get(objectIdField)));
                        }
                       
                        
                        //if this rule errors on match, attach an error to this post
                        
                        if(filter.Error_on_Match__c)
                        {
                            post.addError(filter.Error_Message__c);
                        }
                        //otherwise if it doesn't have erroring, but it does have replacements, lets do that
                        else if(filter.Replace_Pattern__c)
                        {
                            try
                            {                        
                                //Run a replacment using the provided regular expression and the replacment string specified
                                postBody = postBody.replaceAll(filter.Pattern_String__c,filter.Replacement_Pattern__c);
                                //overwrite the post body with the updated body
 
                                post.put(postBodyField,postBody);
                            }
                            catch(Exception e)
                            {
                                post.addError('This post has triggered a filtering rule. However the '+postBodyField+' on the '+objectType+' object is not editable so the filter rule may not be completed. This indicates an error with filter setup. Please notify your administrator' + e.getMessage());
                            }
                        }
                        
                    }
                }
            }
        }
        //if there are any feed matches to insert, do that now.
        if(!matches.isEmpty())
        {       
                database.insert(matches,false);
        }
    }

    public static void sendPatternMatchEmail(list<String> recipients, Redacted_Filter__c filter, Id userId, string postBody, string emailBody, id chatterPostId)
    {
        if(userId != null)
        {
            //lets try and find the user based on the id passed in. Failing that, then set the offending user as the current user (this will always be the case anyway, except in the case of imports)
            User offendingUser = [select firstname, lastname, id, email, username from user where id = :userId];
          
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            //Set email address
            String[] toAddresses = recipients;
            mail.setToAddresses(toAddresses);
            mail.setReplyTo('noreply@salesforce.com'); //the reply address doesn't matter
            mail.setSubject('Chatter Compliance Rule '+filter.name+' was Invoked');
            mail.setBccSender(false);  //we don't want to Bcc ourselves on this
            if(emailBody == null)
            {
                  // mail.setPlainTextBody('User ' +user.username+ ' created a chatter post which matched filter rule ' +filter.name+'. The content of the post was "'+postBody+'".');
                   mail.setPlainTextBody('User ' +offendingUser.username+ ' created a chatter post which matched filter rule ' +
                   filter.name+'. The content of the post was "'+postBody+'. View the post here ' + 
                   URL.getSalesforceBaseUrl().toExternalForm() +'/'+chatterPostId);
            }
            else
            {
                mail.setPlainTextBody(emailbody);
            }
           
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail }); //send the email
        }   
    }

    global static string getObjectTypeFromId(string objectId)
    {
        Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe(); 
        Map<String,String> keyPrefixMap = new Map<String,String>{};
        Set<String> keyPrefixSet = gd.keySet();
        for(String sObj : keyPrefixSet)
        {
           Schema.DescribeSObjectResult r =  gd.get(sObj).getDescribe();
           String tempName = r.getName();
           String tempPrefix = r.getKeyPrefix();
           keyPrefixMap.put(tempPrefix,tempName);
        }
    
        return keyPrefixMap.get(objectId.subString(0,3));  
    }
       
   

 

Any ideas are appreciated. Thank you.

Hey all,

 

This is one of those questions when I feel like I'm probably going about a simple process the wrong way and over complicating it, but I'm not sure the alternative. So I'll ask my question and if you have a better approach, please feel free to share.

 

The deal is that I have an Apex REST service that allows a user to pass in an object type, and any field values in the URL.

EX : http://na2.salesforce.com/apex/reservice/contact?firstname=frank&lastname=jones&age__c=21

 

My code works fine for any string values, but chokes on numerics. Because I instantiate a new instance of the type of object they pass in, then I loop over all the fields in the query string and dynamically add them to the sObject. Of course as far as apex is concerned all those arguments in the query string are in fact strings, so when adding non string things to the sObject it explodes. 

 

Ideally I could use the get describe info about the field I'm inserting to find it's type and cast the value to be inserted to the correct type, but as far as I know there isn't a way to do dynamic casting at runtime. A series of if statments is about the only alternative I see, but that feels really dirty. 

 

This is what I have currently (you can see I'm kind of using the if statment path, trying to make it as simple as possible).

 

Just to be clear, this code works, it's just not as efficient/dynamic as I'd like.

 

    public static sObject saveSObject(string objectType, string recordid, RestRequest req)
    {
        //create a generic sObject to contain the update values.
        sObject updateObj;      
                
        //get the describe object for the type of object passed in (its just passed in as a string from the URL)
        Schema.sObjectType objectDef = Schema.getGlobalDescribe().get(objectType).getDescribe().getSObjectType();
        
        //find all the fields for this object type
        Map<String, Schema.SobjectField> ObjectFieldsMap = objectDef.getDescribe().fields.getMap();
        
 
        //this method can handle updates or inserts. If a record ID was passed in, 
        //cast the object as the type represented by the ID. If not, just create a
        //new object of the type found in the object describe.
        if(recordId != null)
        {
            updateObj = objectDef.newSobject(recordid);
        }
        else
        {
            updateObj = objectDef.newSobject();
        }    
        // populate the object's fields by looping over all the params in the rest request.
        for (String key : req.params.keySet())
        {
            // only add params if they are valid field on the object
            if (ObjectFieldsMap.containsKey(key))
            {
                //figure out the type of this field so we can cast it to the correct type
                string fieldType = ObjectFieldsMap.get(key).getDescribe().getType().name().ToLowerCase();
                
                //since I don't know how to do, or if it's even possible to do dynamic casting we need a 
                //series of if statments to handle the casting to numeric types. I think all the others should
                //be fine if left as a string. Dates might explode, not sure.
                
                
                if(fieldType == 'currency' || fieldType == 'double' || fieldType == 'percent' || fieldType == 'decimal' )
                {
                    updateObj.put(key, decimal.valueOf(req.params.get(key).trim())); 
                }
                else if(fieldType == 'boolean')
                {
                    updateObj.put(key, Boolean.valueOf(req.params.get(key))); 
                }                   
                else if(fieldType == 'date')
                {
                    updateObj.put(key, date.valueOf(req.params.get(key))); 
                }                
                else
                {
                    updateObj.put(key, req.params.get(key));
                }
            }
            else
            {
                system.debug('Invalid field: '+ key + ' for object type ' + objectType);
            }
        }
        //update/insert the object
        upsert updateObj;
        
        //return the saved object.
        return updateObj;
        
    }

 

Hey Guys,

I've just been having terrible luck recently. Everything I do seems to casue an internal salesforce error. What I am working on at this point is upserting a list of person accounts based on an external id field (SSN__C). I've never done an upsert before (I know, I'm lame) so I am unsure if the error I am getting is due to me getting the process wrong, or if I found yet another bug. The list of accounts comes from parsing a CSV file, and if I change the upsert to just an insert, it works fine so I know the data it's trying to pass in for creating is legit. Here is the relevant code.

//get a list of people to upsert by parsing the CSV file into a list of account objects.
 list<Account> accounts = csvTosObject(parseResult, 'Account');
 

 system.debug('========================= List of Accounts');
 system.debug(accounts); //shows all accounts parsed fine
                         
//upsert them (need to get a reference to the external field to use for id)
Schema.SObjectField f = Account.Fields.SSN__c;                        
List<Database.upsertResult> accountSaveResult = Database.upsert(accounts,f); // <---- Explodes

//if instead of an upsert I have an insert, it works fine. So i know the data is good.
//List<Database.saveResult> accountSaveResult = Database.Insert(accounts,false);

//iterate the person save results to create the import result log
for(database.UpsertResult r : accountSaveResult)
{
    if(r.isSuccess())
    {
        if(r.isCreated())
        {
            personCreates++;
        }
        else
        {
            personUpdates++;
        }
    }
    else
    {
       personErrors++;
        for(Database.Error e : r.getErrors())
        {
            personErrorText.add(e.getMessage());
        }
    }
}

 
Otherwise I can break the operation into two separate insert and update statments probably (seems hackish but it would work) but I'd prefer to just upsert if I can. Any help is appreciated. Thanks!

 

Hey all,

I posted a bit about this on twitter but I figured I'd make a thread to get some more details. What's happening is that I have a method (it's an Apex Rest method if that matters) whose job is to insert a list of sObjects. For some reason, attempting to do so causes an uncatchable Internal Salesforce Error. Weird part is, if I remove the offending code, and run it by itself, it works just fine. The object in question here is a custom object called survey_answer__c. These are created by deserializing some JSON from a remote webservice. For example

[{"Answer__c":2.0,"Id__c":"51345X1607X53053","Question__c":"what is blah 1?"},{"Answer__c":3.0,"Id__c":"51345X1607X53100","Question__c":"what is blah 2?"}]

 

 Contains data for two answer objects to get inserted. So I deserailize it, which goes just fine. Now I have a list of sObjects. I attempt to insert it using the plain old insert command and I get my error. Now the weird part is if I mimic this setup by itself in an execute anonymous block using the following code, it will run fine. You'll notice all the data for the objects is exactly the same, except for the second set has an Id for the survey_entry__c relationship field (which is appended to each sObject in the first example later on by iterating the list of sObjects and setting the field manually).

list<Survey_Answer__c> answers = new list<Survey_Answer__c>();

Survey_Answer__c ans1 = new Survey_Answer__c();
ans1.Question__c= 'what is blah 1?';
ans1.Id__c='51345X1607X53053';
ans1.Survey_Entry__c='a0zS0000001HSavIAG';
ans1.Answer__c='2.0';

Survey_Answer__c ans2 = new Survey_Answer__c();
ans2.Question__c= 'what is blah 2?';
ans2.Id__c='51345X1607X53100';
ans2.Survey_Entry__c='a0zS0000001HSavIAG';
ans2.Answer__c='3.0';

answers.add(ans1);
answers.add(ans2);

insert answers;

 

 So there you have it. Random error for no good reason. I'll post the entire class below for you to review. If you want the object definiton files to play with it yourself, let me know and I can host them somewhere.

 

@RestResource(urlMapping='/importSurvey/*') 
global class importSurveyData 
{
	public static boolean isApexTest = false;
	
	//this method takes a post request with a survey id, a user token and other paramters about the survey. It reaches out to another 
	//webservice which gets the survey answer data and returns it. The data is used to generate survey_answer__c objects and insert them.
	//It will also create a survey__c and survey_entry__c object if required (if they don't exist). Each campaign should have only one survey__c object
	//which can contain multiple survey entries (one per person who took the survey). Each survey entry can contain multiple answers. Each contact will
	//have only one survey entry for a survey
	
	/* Schema
	
	Campaign
	|
	+-> Survey__c 
	    |
	    +-> Survey_Entry__c <- Contact 
	        |
	        +->Survey_Answer__c
	*/
	@HttpPost
	global static list<Survey_Answer__c> doPost(RestRequest req, RestResponse res) 
	{
		list<Survey_Answer__c> answers;
		String jsonString;	
		Survey_Entry__c surveyEntry = new Survey_Entry__c();	
		
		//Get the ID of the survey we are working with from the URL
		string survey = req.params.get('survey');
		
		//Loop over all the parameters passed in and try to assign them to the survey object. Just discard any unusable data
		for(string param : req.params.keySet())
		{
			try
			{
				surveyEntry.put(param,req.params.get(param));
			}
			catch(Exception e)
			{
				
			}
		}
		
		//We have to call out to a web service to get the lime survey answer data. It will get returned as a JSON encoded array
		//of answer objects. These can be directly de-serialized to survey_answer__c objects. They contain all the actual answers
		//to the questions as well as data about the questions itself
        try 
        {           
            
            HttpRequest getDataReq = new HttpRequest();
            getDataReq.setMethod('GET'); 
            getDataReq.setEndpoint( 'http://xxxxxxxxxxxxxxxxxxxxxxxx.com/?method=getLimeSurveyAnswers&surveyId='+survey+'&Token='+surveyEntry.token__c);
              
            Http http = new Http();

			if(!isApexTest)
			{
            	//Execute web service call here     
            	HTTPResponse getDataRes = http.send(getDataReq);   
            
	            if(getDataRes.getStatusCode() == 200)
	            {
					jsonString = getDataRes.getBody();  
	            }
	            else
	            {
	                system.debug('Could not contact web service');
	            }
			}
			else
			{
				system.debug('Apex Test Mode. Webservice not Invoked');
				jsonString = '[{"Answer__c":2.0,"Id__c":"51345X1607X53053","Question__c":"what is blah 1?"},{"Answer__c":3.0,"Id__c":"51345X1607X53100","Question__c":"what is blah 2?"}]';
			}
			
			//This is just put here for testing. It's faster and more reliable for testing to just have the same JSON content every time
			//in production this line would be removed
			jsonString = '[{"Answer__c":2.0,"Id__c":"51345X1607X53053","Question__c":"what is blah 1?"},{"Answer__c":3.0,"Id__c":"51345X1607X53100","Question__c":"what is blah 2?"}]';		
			
			//deserialize the list of answers retreived from the webservice call
			answers = (List<Survey_Answer__c>) JSON.deserialize(jsonString, List<Survey_Answer__c>.class);	
			
			//Now we need to find the ID of the survey to attach these answers to. This method will either find the existing
			//survey_entry__c id (based on token, contact, and lime survey id) or create a new one and return it. Either way
			//it's going to return a survey_entry__c object (it will also create a survey object to attach itself to if there isn't one)					
			Id surveyEntryId = importSurveyData.getSurveyEntry(surveyEntry, survey).id;									
			
			//We don't want duplicate data, and this may get run more than once, so delete any existing answer data for this survey entry	
			List<Survey_Answer__c> toDelete = [select id from Survey_Answer__c where Survey_Entry__c = :surveyEntryId];
			delete toDelete;
			
			//We now also need to update these survey answer objects with the survey_entry__c id so the relationship gets set
			for(Survey_Answer__c ans : answers)
			{
				ans.Survey_Entry__c = surveyEntryId;
			}	
				
			insert answers;	//This line errors for no discernable reason.		        
        } 
        catch(Exception e) 
        {
            system.debug('======================== ERROR IMPORTING ANSWERS: ' +e.getMessage() + ' ' +e.getCause() + ' ' +e.getTypeName());
        } 
        		
		return answers;
	}
	
	//this will either find an existing survey_entry__c or create a new one if one does not exist.
	//it will also request creation of a survey__c object to attach itself to if one does not exist.
	global static Survey_Entry__c getSurveyEntry(Survey_Entry__c surveyEntry, string surveyId)
	{	
		//try to find existing survey entry for this person 	
		list<Survey_Entry__c> entries = [select id, 
												Survey__c, 
												token__c, 
												contact__c 
										 from Survey_Entry__c 
										 where survey__r.study__r.lime_survey_id__c = :surveyId 
										 and contact__c = :surveyEntry.contact__c 
										 limit 1];
										 	
		//if a survey entry is not found, create one otherwise return existing
		if(entries.isEmpty())
		{				
			
			surveyEntry.Survey__c = getSurvey(surveyId).id;
			insert surveyEntry;		
			return surveyEntry;
		}	
		else
		{
			return entries[0];	
		}
		
	}
	
	//create a survey for the given campaign if does not exist, otherwise return the existing one.
	global static Survey__c getSurvey(string surveyId)
	{
		//find the existing survey if there is one for this id
		list<Survey__c> surveyObj = [select id 
									 from Survey__c 
									 where Study__r.Lime_Survey_ID__c = :surveyId 
									 limit 1];		
		if(surveyObj.isEmpty())
		{
			Survey__c survey = new Survey__c();
			RecordType ParentRecordType = [select id from RecordType where name = 'FPI Parent Campaign' ];
			list<Campaign> parentCampaign = [select id from Campaign where Lime_Survey_ID__c = :surveyId and recordTypeId = :ParentRecordType.id order by createdDate asc limit 1];
			
			survey.Study__c = parentCampaign[0].id;		
			insert survey;
			return survey;
		}	
		else
		{
			return surveyObj[0];
		}						 
	}
    @isTest 
    public static void importSurveyDataTest()
    {
    	isApexTest = true;
    	string surveyID = '51345';
    	
        //Insert the check record, with testContact as the contact
        Account thisTestAccount = testDataGenerator.createTestAccount();
        Contact thisTestContact = testDataGenerator.createTestContact(thisTestAccount.id);
        Campaign thisTestUmbrellaCampaign = testDataGenerator.createTestUmbrellaCampaign(thisTestContact.id);
        Campaign thisTestParentCampaign = testDataGenerator.createTestParentCampaign(thisTestUmbrellaCampaign.id, thisTestContact.id); 
        
        thisTestParentCampaign.Lime_Survey_ID__c = surveyID;
        
        update thisTestParentCampaign;

        RestRequest req = new RestRequest(); 
        RestResponse res = new RestResponse();
 
	
 		//Do a sample request that should invoke most of the methods 
        req.requestURI = 'https://cs1.salesforce.com/services/apexrest/importSurvey';
        req.httpMethod = 'POST';	 
		req.addParameter('survey',surveyID);
		req.addParameter('token__c','mkwcgvixvxskchn'); 
		req.addParameter('contact__c',thisTestContact.id);
		req.addParameter('type__c','survey');
		req.addParameter('label__c','test survey');
		req.addParameter('result__c','qualified');
		
        list<Survey_Answer__c> importedAnswers = doPost(req,res); 
        
        system.debug(importedAnswers);
        //The first thing it should have done is create the survey object itself, attached to the master campaign.
        list<Survey__c> testSurvey = [select id from Survey__c where study__r.Lime_Survey_ID__c = :surveyID];
        system.assertEquals(1,testSurvey.size());
        
        //now it also should have created a survey entry for this person, with the attributes passed in the request
        list<Survey_Entry__c> testEntry = [select id,type__c,label__c,result__c,token__c from Survey_Entry__c where contact__c = :thisTestContact.id and survey__c = :testSurvey[0].id];
        system.assertEquals('survey',testEntry[0].type__c);
        system.assertEquals('test survey',testEntry[0].label__c);
        system.assertEquals('qualified',testEntry[0].result__c);
        system.assertEquals('mkwcgvixvxskchn',testEntry[0].token__c);
        
        //Also all the survey answers should have been imported. There should be two of them.        
   		list<Survey_Answer__c> answers = [select id, Answer__c, Question__c from Survey_Answer__c where Survey_Entry__c = :testEntry[0].id];
   		system.assertEquals(2,answers.size());
    }
    
}

 

Hey all,

I'm a bit new to writting anything more than very simple Apex REST services. All the ones I have written have had one method per HTTP verb. There was one GET, one POST etc. So now I am writting something a bit more complex. This one needs to support 5 different GET methods, based on the request URL. Good REST design says have basic operations separated by slashes, and hide complexity behind the question mark. So I want to be able to do something like

 

//Methods for retreiving data from the system. All query methods accept
// fields parameter which specifies data returned in the query. All methods also accept
// a filter param which can accepts an SOQL where string to refine results.
//Methods include:

// /customer - get all customers
// /customer/Id - get customer based on dmp code, SSN, or Salesforce Record ID
// /customer/Id/cars - get all customer cars based on dmp code, SSN, or Salesforce Record ID
// /car - get all cars
// /car/vin - get car by VIN

//Ex: /customer?fields=name,firstname,email&filter=firstname='frank'
//get the name, firstname, and email for any contact with a firstname frank

//Ex: /car?filter=type__c='truck'
//Find all cars that are of type truck




So there are 5 different GET actions. Of course you can only declair one method as  @HttpGet annotated. So within that method you need to inspect the request URL and do different things. The only real way I know of to inspect the URL is through just basic string manipulation/inspection, which seems messy. I am just wondering if I am even on the right track, or if people have another method? My first thought is to take the URL, split it based on the / and evalutate the strings in the various positions to figure out what I need to do, but it seems like doing that is going to be very.. static also just begging for null pointer exceptions. Any thoughts on this would be great.

    @HttpGet
    global static list<sObject> doGet(RestRequest req, RestResponse res) 
    {
        list<sObject> returnValues = new list<sObject>();
        String queryObject = 'first element after first /';
        String queryObjectId = 'second element after first /, may be null';  
        string queryFields = 'id,name';
        
        if (req.params.containsKey('fields'))
        {
            queryFields = req.params.get('fields');
        }
        
        if(queryObject = 'customer')
        {
             //call the query customer method
        }
        else if(queryObject = 'car')
        {
         //call the query car method
        }
        
        
        
        
        return returnValues;
    }

 

 

Hey all,
 

Working on an import project, and I am trying to create a record (Ownership__c) that has lookups to two other kinds of objects (Account, and Auto__c). Account has a field called SSN__C which is unique and set as an external ID. Auto__c has a field called VIN__c which is also unique and set as an external ID. (Keep in mind the account and auto__c will already exist in the system, I'm just creating them in my code here for clarity sake).
 

/* This chunk is just for example, in my production system the accounts and autos will already exist. Hence the reason I need to use the foreign keys */
Account newAccount = new Account(); newAccount.firstname = 'test'; newAccount.lastname = 'jones'; newAccount.SSN__c = '12345'; insert newAccount; Auto__c newAuto = new Auto__c(); newAuto.name = 'testcar'; newAuto.VIN = 'VIN9999'; insert newAuto; /* End clarity code */
Ownership__c ownership = new Ownership__c(); ownership.put('Account__c','12345'); //Lookup to Account object ownership.put('Auto__c','VIN9999'); //lookup to Auto__c objects. insert ownership;

 


The inserting the ownerhsip records fails, stating that the field is not a proper ID. I thought maybe I need to create a reference to the related objects and pass that, but that didn't work either. That attemp looked something like

 

Account thisAccount = new Account(SSN__c='121212');
Auto__c thisAuto = new Auto__c(VIN__c='VIN1234');

Ownership__c ownership = new Ownership__c();
ownership.put('SSN__c',thisAccount.id);
ownership.put('VIN__c',thisAuto.id); 
insert ownership;  

 but that doesn't work either, because of course the two created objects don't have ID's yet. I tried passing the objects to the __r version of the relationship fields, but that doesn't work either. 


I thought I could set the value of the lookup to an external ID and it would work. Am I mistaken? What am I doing wrong here? 

Hey all.

I am building a small system that can import CSV files emailed to it. There are 3 objects involved, Accounts (a person account), and two custom objects, Auto, and Ownership. The import is responsible for creating ownership records (simply a junction object between an account and an auto).

 

So the Ownership has a master detail relationship with both Account and Auto. The account has a field called SSN, and the auto has a field called VIN. These are both marked as external IDs.

Account                   Ownership                              Auto
SSN  <----------  Account   |   Auto------------------> VIN

 

The question becomes how can I create an owernship record from my import file that contains a SSN and a VIN. Can you populate a lookup field with an external ID, or will I need to run a query first and populate the lookup relationships with the Id's from the query? I'd like to avoid having to query to save on performance and potential governor limits, but I know that may not be doable. In that case I'd like just build a set of SSN and VIN and do one query for each object type using the IN() SOQL operator correct?

Thanks for any insight! 

Hey everyone,

I am working on a small project where I need to call an Apex method from a home page component. I don't care how it gets done, I just have to figure out some way to do it.

 

First I tried rigging a visualforce page to the class/method I want, and calling it via an http requst on click, but of course since the home page components and visualforce pages are on different domains that failed due to cross domain security policies.

 

Second I tried using the connection libraries like you can use for button for calling apex in custom buttons, but I'm getting an session Id error.
 

Uncaught {faultcode:'sf:INVALID_SESSION_ID', faultstring:'INVALID_SESSION_ID: Invalid Session ID found in SessionHeader: Illegal Session', }

So it doesn't seem as though that is going to work. Any other ideas? The more elegant the better. 

Hey all,

I am working on a trigger that will update about 12 fields on around 200 opportunities based on information from an external system. The trigger pulls in information from a JSON based webservice, parses it and updates the opportunities accordingly. Problem is, I am getting a strange message, and I'm not sure if it is from Salesforce native, or another managed package. The error is this.

 

Too many inserts or updates. The automatic numberic of sales orders only supports a maximum batch size of 10

 Does anyone know what this is refering to? I couldn't find much information searching around, and I am suspecting it may be a poorly written managed package causing me issues.

Really the code for this is quite simple. It's just this method

	webservice static void importCosts(string jobName)
	{
	    HttpRequest req = new HttpRequest();
	    req.setMethod('GET'); 

	    req.setEndpoint( 'http://xxxxxxxxxxxx/webservice.cfc?method=getProjectCostData);

	    Http http = new Http();
	    string costDataJson;
	    
	    if(!Test.isRunningTest())
	    {
	        //Execute web service call here     
	        HTTPResponse res = http.send(req); 
	        costDataJson = res.getBody();
	        system.debug(costDataJson);
		    List<Opportunity> costData = (List<Opportunity>) System.JSON.deserialize(costDataJson, List<Opportunity>.class);		
			update costData;
		}
	}

 

 Which returns a list of JSON encoded opportunity objects (with their Id's and fields to update). Any thoughts?

Hey all.

I am working with the new JSON Parsing functionality, and am hitting a bit of snag trying to parse some JSON into a list of custom objects. The exact error is

 

Internal System Error: 1094975380-22831 (-1000130141)

 

Which looks like there is probably an issue with the JSON parser. I have been able to parse single instaces of an object, but have not been able to do with with a list of them yet. 


Here is what the code looks like.

 

	public class surveyAnswer
	{
		Id contact;
		Integer surveyId;
		String questionText;
		String questionId;
		String questionAnswer;
		
		public surveyAnswer(Id c, Integer s, String qt, String qi, String qa)
		{
			contact = c;
			surveyId = s;
			questionText = qt;
			questionId = qi;
			questionAnswer = qa;			
		}
	}

string jsonString = '[{"questionText":"BLah 1","questionId":"51345X1607X53053","surveyId":51345,"questionAnswer":2.0,"contact":"0034000000UefNI"},{"questionText":"Blah 2","questionId":"51345X1607X53100","surveyId":51345,"questionAnswer":3.0,"contact":"0034000000UefNI"},{"questionText":"blah 3","questionId":"51345X1607X53101","surveyId":51345,"questionAnswer":1.0,"contact":"0034000000UefNI"},{"questionText":"blah 4","questionId":"51345X1607X53103","surveyId":51345,"questionAnswer":1.0,"contact":"0034000000UefNI"}]';
list<surveyAnswer> answers = (List<surveyAnswer>)JSON.deserialize(JSONString, List<surveyAnswer>.class);

 

I thought it might be a problem with the JSON structure, but it validates just fine in JSLint, so it should be fine. This approach should be doable according to the docs at

http://www.salesforce.com/us/developer/docs/apexcode/index_Left.htm#StartTopic=Content/apex_methods_system_json.htm#apex_methods_system_json 

 

Any help would be much appreciated. Thank you!

I know chatter messages are brand new in the 23.0 release, so not a ton of people know about them yet.

However, I was wondering if anyone knew if there is a way to create messages programatically. It looks like you'd have to create a conversation, then add members, and messages to that conversation, but the API docs says those are all read only from the API.

 

http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_objects_chattermessage.htm
http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_objects_chatterconversation.htm
http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_objects_chatterconversationmember.htm

Does that mean I am out of luck if I want to create them programatically?

 

Working on a bit of code for a salesforce hosted site that will create a campaign member. Getting this error. I have seen something like this before, when you pass a bad ID, but it usually tells you which ID is bad, here it just says null. This code works perfectly in sandbox, but fails in production, so it seems like it must just be some security setting is off, but I have no idea what setting it could be.

 

We do have the private data model, with sharing rules set up for contacts (but of course sharing rules don't apply to the sites guest user). 

 

The full error is 

Error creating campaign member. Insert failed. First exception on row 0; first error: INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY, insufficient access rights on cross-reference id: [] null System.DmlException Error during campaign member creation. Contact: 0034000000kJdQQ Campaign: 70140000000N01iAAC null scheduler.customException 

 

So you can see the campaign and contact are being passed in. Both of those are valid IDs in the org. So I don't think that is the issue (I know I've seen that before when passing in a bad hard coded ID).

 

What security settings could I look at to try and fix this? I feel like I've looked at all the applicable public access settings for the user (contact, campaign member, and campaign settings are maxed out).

 

Any help is appreciated. Thanks!

Hey all,

Here is the deal I've got going. I have a trigger, that when a user posts a new status, another type of record is created. That created record needs to store the ID of the feedpost that was created by the user posting a new status. I don't totally understand the chatter data model (it's kind of a complicated beast) but it seems as though it goes:

 

1) User posts status update (stored on user record)

2) feedpost is created (which is non querable)

3) userfeed item is created as result of feedpost, userfeed gets linked to feedpost.

 

The issue is I don't know of any reliable way to get the feedpost id from the user record (unless I query for the actual text of the post which could get very inefficient in large batches).

 

What are your guys thoughts? 

hey all,

So since I don't really have any programming training and just kinda figured things out by hacking along, I don't know some pretty basic things. In this case, I have a variable that will be shared by many methods in my class. Of course I have made it public, so it is accessable by all.

 

Now, the contents of this variable take some time to compute, but currently every method calculates it and sets it, because they have no way of knowing if it has been set before (I could have it evaluate for null, but that seems messy). As I add more methods to this class, any comination of which may run (based on after/before, insert/update/delete/undelete) is there a keyword I assign the variable that will make it so my methods set it if it hasn't been in that instance, or skip it if it has? I think there is a keyword called final, is that what I am looking for? 

 

I know, it's kind of a dumb question, but I would really like to know, so I'm swallowing my pride here :P Feel free to mock if neccesary. 

Hey all,

 

So this is likely going to be one of those 'your screwed' questions, but I figure I'd give it a shot. I need to develop a visualforce application that can be hosted on a salesforce site. The application itself is a form, where a user is allowed to create multiple things at once (essentially it's a project submission form, and the user is allowed to submit multiple different configurations for their project at once to receive different bids). I've done this by create a tabbed form, where the user can click to get another tab, with another form in each tab. This form is loaded from another page via an ajax request. So the main page goes and reaches out to the second page, gets the generated content and injects it. This is to keep the actual content of the form somewhat modifiable by another group (since it just contains basic html). 

 

This all works great, except for when it comes to dependent picklists. The javascript used by visualforce to handle the picklists breaks down if there is more than one instance of the same field on the page (which there would be if a user is creating multiple submissions). I've gotten around this before by using the javascript libraries and building my own dynamic picklist system. However, that was when this tool was being hosted in a portal. The javascript libraries are not available through sites. So I can't use visualforce because it breaks down, and I can't use the JS library. 

 

Long story short, can you think of any way, regardless of how crazy or backwards it might seem to get dependent picklist valus on a salesforce.com site? I've though about trying to set up a web service, or something of that nature, but I simply cannot think of any way to get picklist values from a non authenticated source. 

 

Anyway, feel free to post any ideas. Just kind of brainstorming here before I give up and have to hand code in all the picklist options. Thanks!

Hey all,

Just a quick simple question.

Trying to update an object through Apex remoting. One of the fields I am attempting to update is a date time. In what format do I need to pass a datetime for it to work? I've tried just passing a date object, and date.toUTCString(). How do I need to format the datetime in javascript for apex to be able to deal with it? Thanks!

 

 

 

 

Perhaps I'm missing something here but I can't find any docs or way to write unit tests for Apex REST classes for v24. Here's my class:

 

@RestResource(urlMapping='/v.9/member/*/results/*') 
global with sharing class MemberRestSvc {
 
  @HttpGet
  global static String doGet() {
  
    String[] uriKeys = RestContext.request.requestURI.split('/');
    // do awesome programming stuff here

  }
    
}

 

With a v23, I would typically write a test like:

 

RestRequest req = new RestRequest(); 
RestResponse res = new RestResponse();
 
req.requestURI = 'https://cs9.salesforce.com/services/apexrest/v.9/member/me/results/today';  
req.httpMethod = 'GET';

String results = MemberRestSvc.doGet(req, res);
System.assertEquals('awesomeness-happened',results);

 

However, I can't figure out how to specify the requestURI. It looks like the the System.RestContext class is not writeable? I keep getting NPEs. Any help would be greatly appreciated.

 

Thanks

Jeff Douglas

Appirio / CloudSpokes

http://blog.jeffdouglas.com

Hey all,

 

So I have come to a bit of an impass in a trigger I am writting. It may be because it's early and I can't think yet, but I am just not quite sure how to logically solve this problem.

 

Here is the deal, this trigger is responsible for updating a contact associated with a task. When the task is saved, the status field is checked. If the task is completed, an associated date field on the related contact is set to the completion date on the task (if the date to be used for the update is later than the one currently existing). This is to keep track of things such as 'Last stay in touch call', 'Last meeting', etc. The tasks represent interactions with a contact, and when the task is completed they want to mark the contact so they know the last time that kind of action was performed. I have this working perfectly currently.

 

Where it gets complicated is if a task is deleted. They have decided that they want the information to 'roll back' if a task is deleted. They want the field on the contact to be 'recalculated' to find the task that is now the newest that applies to the field that just got deleted. So if the most recent task that set the 'last stay in touch call' gets deleted, they want the system to find the next most recent and use that. This is where I am a bit stuck. The only approach I can think of (while keeping it bulk friendly) is something like:

 

1) Query for all tasks associated with any contact that appeared in this list of newly deleted tasks.
 

2) Iterate over every task. Look to see if the 'Activity_Type__c' matches that of the task that just got deleted for this contact.
 

3) If it is a match on the field, check to see if it most recent than any other entry (could probably eliminate this by sorting the query by the date, and skipping duplicates in my loop). 

 

4) Using the list of tasks, continue with the trigger logic as normal.

 

The issue I have here is, what if one contact is getting multiple tasks deleted at one time? Because when I am iterating through the list of tasks for every contact, I'd have to iterate over every task for them in the trigger context, then find a matching task in the query I just ran, and... argh it gets so complicated and cumbersom. Also, this approach seems excruciatingly inefficient. Does anyone have any better ideas? Below is my code thus far so you can see where I am at. Thanks so much!

 

/**********************************************
Name: ContactActivityRecordTrigger
Author: Daniel Llewellyn
Date 3/14/2012
Description: Will update a contact related to a task when a task is completed. The contact has various date fields that may
                          be populated based on the type of task. Updates performed by the elquoa marketing tool or anyone from 
                          marketing do not fire these updates
***********************************************/                          

trigger ContactActivityRecordTrigger on Task(after insert, after undelete, after update) 
{
    
    try
    {    
        list<Task> tasks;
        if(Trigger.Isundelete)
        {
            tasks = trigger.old;
        }
        else
        {
            tasks = trigger.new;
        }
        //create map of contacts that will contain contacts to update
        map<Id,Contact> contactMap = new map<id,Contact>();
        
        //create a map of users that contain the names of the users related to the tasks.
        map<Id,User> userMap = new map<id,User>();
        
        //we will need to find the DS_Role__c field for all the contacts. So create a map of the contact id, to the contact record
        //so we can run one query, and populate them map, then get the contact details when we need them later without needing
        //to have a query in our loop.
        for (Task thisTask: tasks) 
        {
            //we are only interested in this task if it has a contact, so no contact, just skip adding an entry for this task.
            if(thisTask.WhoId != null)
            {
                contactMap.put(thisTask.WhoId,null);
            }
            if(thisTask.OwnerId != null)
            {
                 userMap.put(thisTask.OwnerId,null);
            }
        }
        
        //populate the map with the id of the contact, then the details about that contact.
        for(Contact con : [select id, DS_Role__c,Last_Meeting_Sales_Call__c,Last_Call__c,Last_Email__c,Last_Demo__c,Last_Mass_Email__c,Last_Sent_Info__c,Last_Marketing_Activity__c from contact where id in :contactMap.keySet()])
        {
            contactMap.put(con.id,con);
        }
    
        //populate the map with the id of the contact, then the details about that contact.
        for(User usr : [select id, Name from User where id in :userMap.keySet()])
        {
            userMap.put(usr.id,usr);
        }
       
       //if this is a delete trigger, the current list of tasks has actually been deleted, so we need to find
       //the task that is now the most recent for each user of the same type that just got deleted
       if(trigger.isDelete)
       {
           //find all the tasks for all the users
           list<task> allTasks = [select id, WhoId, OwnerId,Status,Activity_Type__c, ActivityDate from task where whoId in :contactMap.keySet() order by ActivityDate desc ];
           
           //so now I have to loop over all the tasks I just fetched, and then find all the tasks for the associated contact and see if there is a match and.... arg I have no idea.
           for(Task task : allTasks)
           {
           
           }
       }
       
        //iterate over every task passed in
        for (Task thisTask: tasks)     
        {
            //if this task does not have a contact related to it, then just skip this task and continue.
            if(thisTask.WhoId == null)
            {
                continue;
            }    
            
             //create a reference to the contact associated with this task that we will update
            Contact thisContact =contactMap.get(thisTask.WhoId);
    
            //create a reference to the owner associate with this task
            User thisUser = userMap.get(thisTask.OwnerId);
           
            date activityDate;
            if( thisTask.ActivityDate != null)
            {            
                activityDate = thisTask.ActivityDate;
            }
            else
            {
                activityDate = Date.newInstance(thisTask.LastModifiedDate.year(),thisTask.LastModifiedDate.month(),thisTask.LastModifiedDate.day()); 
            }
            //check if the task status is completed
            if (thisTask.Status.toLowerCase() == 'completed') 
            {                
                //make sure the owner of the task is not eloqua marketing, and make sure the contact's role is not marketing/communications
                if (thisUser.Name.toLowerCase() != 'eloqua marketing' && thisContact.DS_Role__c != 'marketing/communications') 
                {
                    if (thisTask.Activity_Type__c == 'Meeting/Sales Call' && (activityDate > thisContact.Last_Meeting_Sales_Call__c || thisContact.Last_Meeting_Sales_Call__c == null) ) 
                    {
                        thisContact.Last_Meeting_Sales_Call__c = activityDate;
                    }
                    else if (thisTask.Activity_Type__c == 'Call' && (activityDate > thisContact.Last_Call__c ||  thisContact.Last_Call__c == null))
                    {
                        thisContact.Last_Call__c = activityDate;
                    }
                    else if (thisTask.Activity_Type__c == 'Email' && (activityDate > thisContact.Last_Email__c || thisContact.Last_Email__c == null))
                    {
                        thisContact.Last_Email__c = activityDate;
                    }
                    else if (thisTask.Activity_Type__c == 'Demo' && (activityDate > thisContact.Last_Demo__c || thisContact.Last_Demo__c == null)) 
                    {
                        thisContact.Last_Demo__c = activityDate;
                    }
                    else if (thisTask.Activity_Type__c == 'Mass Email' && ( activityDate > thisContact.Last_Mass_Email__c || thisContact.Last_Mass_Email__c == null)) 
                    {
                        thisContact.Last_Mass_Email__c = activityDate;
                    }
                    else if (thisTask.Activity_Type__c == 'Sent Info' && ( activityDate > thisContact.Last_Sent_Info__c || thisContact.Last_Sent_Info__c == null ))
                    {
                        thisContact.Last_Sent_Info__c = activityDate;
                    }
                    else 
                    {
                        if (thisTask.ActivityDate > thisContact.Last_Marketing_Activity__c || thisContact.Last_Marketing_Activity__c == null) 
                        {
                            thisContact.Last_Marketing_Activity__c = activityDate;
                        }          
                    }
                }
            }
            contactMap.put(thisContact.id,thisContact);
        }
   
        //we don't need an all or nothing update, so use database.update with the all or nothing flag set to false
        if(!contactMap.values().isEmpty())
        {
            database.update(contactMap.values(),false);
        }
    }    
    catch(exception e)
    {
        system.debug('Error occured updating related contact' + e);
        
        //Send an email to the admin after an error occures
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        String[] toAddresses = new String[] {'Kenji776@gmail.com'};
        mail.setToAddresses(toAddresses);
        mail.setSubject('Error in ContactActivityRecordTrigger');
        mail.setPlainTextBody('An error occured while running the trigger.' + e.getMessage() + ' On Line Number ' + e.getLineNumber());
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });        
    }    
    
}

 

I have a visualforce page that use javascript remoting that is put inline into a standard page layout.

 

When I use the page outside of the standard page layout it works fine but when I try to use it inside of the standard layout I receive this error: 

 

Unsafe JavaScript attempt to access frame with URL https://cs7.salesforce.com/a1hM00000008eaY from frame with URL https://c.cs7.visual.force.com/servlet/servlet.Integration?lid=066M00000008kBb&ic=1. Domains, protocols and ports must match.

 

Anyone experience this before - what does this error mean?

Hey all.

I am writting a simple trigger that will update records related to leads when the lead is converted. When the lead is converted it needs to populate all associated lead_detail__c objects project__c field with the Id of the created opportunity. Issue is it doesn't seem like when the lead is converted that it's ConvertedOpportunityId is getting populated, so my lead_detail__c objects are not getting updated. I'm not totally sure why (pretty new to the lead conversion process) so any help would be appreciated. Here is my class.

public class leadClasses 
{
	public static void transferLeadDetails(lead[] leadList)
	{		
		//map that holds list of lead id's to the opportunities that they created after conversion
		map<id,id> LeadToOpportunityMap = new map<id,id>();
		
		//iterate over every lead passed in
		for(Lead lead : leadList)
		{
			// if a new opportunity was created due to a conversion
      		if (lead.ConvertedOpportunityId != null)
      		{
      			//add the lead id and it's associated opportunity to a map
      			LeadToOpportunityMap.put(lead.id,lead.ConvertedOpportunityId);
      		}
		}
		
		//if any of those leads passed in had opportunities created, then continue
		if(!LeadToOpportunityMap.isEmpty())
		{
			//find any lead details that belong to the leads in the list
			Lead_Detail__c[] leadDetails = [select id from Lead_Detail__c where lead__c in :LeadToOpportunityMap.keySet()];
			
			//iterate over all those lead details
			for (Lead_Detail__c leadDetail : leadDetails) 
			{
				//set the project__c relationship field on the lead to the created opportunity which is stored in the map
				leadDetail.Project__c = LeadToOpportunityMap.get(leadDetail.lead__c);
			}	
			
			//update the lead detail objects
			update leadDetails;        	        
		}
	}
	
	@isTest
	public static void testLeadClasses()
	{
		//create a lead
        Lead lead = testDataGenerator.createTestLead();
        
        //create a lead detail associated to the lead
        Lead_Detail__c thisTestLeadDetail = testDataGenerator.createTestLeadDetail(lead.id);
		
		//convert the lead, which will create an opportunity
		Database.LeadConvert lc = new database.LeadConvert();
		lc.setLeadId(lead.Id);
		LeadStatus convertstatus = [select Id, MasterLabel from LeadStatus where IsConverted=true limit 1];
		lc.setConvertedStatus(convertStatus.MasterLabel);
		Database.LeadConvert[] lcArray = new Database.LeadConvert[] {lc};
		Database.LeadConvertResult[] results = Database.convertLead(lcArray);
		
		//check to make sure the conversion was a success
		System.assert(results[0].IsSuccess());
		
		
		System.debug('----------- CREATED OPPORTUNITY ID! --------------');
		System.debug(results[0].getOpportunityId());
		
		//query the lead detail so we can see if the project__c got updated with the created opportunity id
		thisTestLeadDetail = [select lead__c, project__c from Lead_Detail__c where id = : thisTestLeadDetail.id];
		
		//check to make sure the lead detail's project__c field got updated with the created opportunity id.
		System.assertEquals(results[0].getOpportunityId(), thisTestLeadDetail.project__c); //This assert fails. Says opportnity id is null.
		
	}

}

 

 

 

The class gets called by my trigger properly, just non of them pass the first if statment that checks to see if the ConvertedOpportunityId is not null, meaning it things they are all null. Shrug, I'll keep playing around with it, but I figured I'd see if anyone knew off the top of their head whats up. Thanks!

Hey all,

I am working on an application that was working, trying to add a bit of new functionality. It just takes a list of objects, looks to see if any of the fields specified at run time have a value that is predefined on a custom object. If so, it performs some actions, ranging from sending emails to blocking the save entirly. This is mostly for chatter posts, to prevent users from posting sensative data. My class that does all the heavy lifting works perfectly for commenting on objects, but fails a users 'wall' post. The error it returns is Error: Error: Feed item doesn't exist.

 

What's weird is everything in my class works, just the chatter post doesn't actually get saved. The emails get sent, the logging records get created, every last line of code in the class seems to get run, so I'm not sure what the issue is.

 

The trigger for the objects (which works) looks like this.

 

trigger filterChatterFeedItem on FeedItem (before insert, before update) 
{
     //when a user posts on an object, the field that contains the text is the 'body' field, so pass that for filtering
String[] filterFields = new String[]{'Body'}; redacted.filterObject(trigger.new,filterFields,'parentId'); }

 

 

And the failing one for users looks like this

 

trigger filterChatterUserStatus on User (before insert, before update) 
{
    //when a user updates, we want to filter their current status field.
    String[] filterFields = new String[] {'CurrentStatus'};
    redacted.filterObject(trigger.new,filterFields);   
}

 

And here is the class. It's kind of complicated, but pretty well commented.

global class redacted
{
    //takes a list of objects, and a list of fields on that object to filter.
    public static void filterObject(list<sObject> recordsToFilter, list<string> fieldsToFilter)
    {
       //what type of object are we filtering (returns a string representing the object type, such as 'contact' or 'user'
        string objectType = string.valueOf(recordsToFilter[0].getSObjectType());
        
        //who is posting this content?
        string postingUserId = UserInfo.getUserId();
        
        //which field on the passed in set of records is going to contain the relationship that points to the object this chatter post is related to?
        string objectIdField = 'parentId';
        
        //if this a user object, the id field is what we want, because that points to the user themselves, which is the object which the post is on.
        if(objectType.toLowerCase() == 'user')
        {
            objectIdField = 'id';
        }
                                   
        list<Redacted_Filter_Match__c> matches = new list<Redacted_Filter_Match__c>();
        
        //Find the current (posting users) users email. So we know who to email if they match a rule.
        User posterInfo = [select id, email from user where id = :UserInfo.getUserId()];


        
        list<Redacted_Filter__c> filters = [   select
                                              Name,
                                              Applies_To__c,
                                              Error_on_Match__c,   
                                              Notification_Email_Address__c,
                                              Pattern_String__c,
                                              Replacement_Pattern__c,
                                              Replace_Pattern__c,
                                              Send_Email_on_Match__c,
                                              Error_Message__c,
                                              Track_Matches__c,
                                              Email_Poster_On_Match__c,
                                              Warning_Message__c
                                              from Redacted_Filter__c
                                              Where Active__c = true ];
        for(sObject post : recordsToFilter)
        {
            for(Redacted_Filter__c filter : filters)
            {
                //Don't bother running this filter if it doesn't apply to this object type
                
                //Find and filters that apply to this type of object that has been passed in.
                string relatedObjectType = getObjectTypeFromId(string.ValueOf(post.get(objectIdField)));
                if(!filter.Applies_To__c.toLowerCase().contains(relatedObjectType.toLowerCase()))
                {
                    continue;
                }            

                for(String postBodyField : fieldsToFilter)
                {
                    String postBody = string.valueOf(post.get(postBodyField));
                    if(postBody == null)
                    {
                        continue;
                    }  
                    //okay, we have to get a little tricky here. Since the native apex string.contains() method
                    //does not support regular expressions, we have to use of the methods that does, and test to see if that
                    //did anything. If it did, then we know the string contains the regular expression and we can continue accordingly.
                    //so lets use the find and replace method to replace any match with this big long string that should never appear normally
                    //then check to see if the string contains it. If so, it matched the post and we should continue accordingly.
                    string matchString = 'thisisamatchingstringthatshouldnoteverappearinaregularchatterpost';
                   
                    string bodyMatchesPattern = postBody.replaceAll(filter.Pattern_String__c, matchString);
                                                       
                    //If a match was found, lets do some filtering and crap
                    if(bodyMatchesPattern.contains(matchString))
                    {
                        //If the user has configured this rule to be tracked, create a new tracking object
                        //and populate it with data.
                        if(filter.Track_Matches__c)
                        {
                            Redacted_Filter_Match__c thisMatch = new Redacted_Filter_Match__c();
                            thisMatch.Filter__c = filter.id;
                            thisMatch.Chatter_Post__c = postBody;
                            thisMatch.Poster__c = postingUserId;
                            matches.add(thisMatch);
                        }
                        //If this rule is set to send an email lets do that
                       
                        list<string> emailRecips = new list<string>();
                        //send the email to the notification email address specified on the filter if there is one, and that option is set
                        if(filter.Notification_Email_Address__c != null && filter.Send_Email_on_Match__c)
                        {
                            emailRecips = new list<string>();
                            emailRecips.add(filter.Notification_Email_Address__c);                        
                            sendPatternMatchEmail(emailRecips, filter, postingUserId, postBody, null, string.ValueOf(post.get(objectIdField)));
                        }
                        //send the email to the user tho posted the content in the first place if that option is set. Send them the warning message.
                        if(filter.Email_Poster_On_Match__c)
                        {
                            emailRecips = new list<string>();
                            emailRecips.add(posterInfo.email);
                            sendPatternMatchEmail(emailRecips, filter, postingUserId, postBody, filter.Warning_Message__c, string.ValueOf(post.get(objectIdField)));
                        }
                       
                        
                        //if this rule errors on match, attach an error to this post
                        
                        if(filter.Error_on_Match__c)
                        {
                            post.addError(filter.Error_Message__c);
                        }
                        //otherwise if it doesn't have erroring, but it does have replacements, lets do that
                        else if(filter.Replace_Pattern__c)
                        {
                            try
                            {                        
                                //Run a replacment using the provided regular expression and the replacment string specified
                                postBody = postBody.replaceAll(filter.Pattern_String__c,filter.Replacement_Pattern__c);
                                //overwrite the post body with the updated body
 
                                post.put(postBodyField,postBody);
                            }
                            catch(Exception e)
                            {
                                post.addError('This post has triggered a filtering rule. However the '+postBodyField+' on the '+objectType+' object is not editable so the filter rule may not be completed. This indicates an error with filter setup. Please notify your administrator' + e.getMessage());
                            }
                        }
                        
                    }
                }
            }
        }
        //if there are any feed matches to insert, do that now.
        if(!matches.isEmpty())
        {       
                database.insert(matches,false);
        }
    }

    public static void sendPatternMatchEmail(list<String> recipients, Redacted_Filter__c filter, Id userId, string postBody, string emailBody, id chatterPostId)
    {
        if(userId != null)
        {
            //lets try and find the user based on the id passed in. Failing that, then set the offending user as the current user (this will always be the case anyway, except in the case of imports)
            User offendingUser = [select firstname, lastname, id, email, username from user where id = :userId];
          
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            //Set email address
            String[] toAddresses = recipients;
            mail.setToAddresses(toAddresses);
            mail.setReplyTo('noreply@salesforce.com'); //the reply address doesn't matter
            mail.setSubject('Chatter Compliance Rule '+filter.name+' was Invoked');
            mail.setBccSender(false);  //we don't want to Bcc ourselves on this
            if(emailBody == null)
            {
                  // mail.setPlainTextBody('User ' +user.username+ ' created a chatter post which matched filter rule ' +filter.name+'. The content of the post was "'+postBody+'".');
                   mail.setPlainTextBody('User ' +offendingUser.username+ ' created a chatter post which matched filter rule ' +
                   filter.name+'. The content of the post was "'+postBody+'. View the post here ' + 
                   URL.getSalesforceBaseUrl().toExternalForm() +'/'+chatterPostId);
            }
            else
            {
                mail.setPlainTextBody(emailbody);
            }
           
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail }); //send the email
        }   
    }

    global static string getObjectTypeFromId(string objectId)
    {
        Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe(); 
        Map<String,String> keyPrefixMap = new Map<String,String>{};
        Set<String> keyPrefixSet = gd.keySet();
        for(String sObj : keyPrefixSet)
        {
           Schema.DescribeSObjectResult r =  gd.get(sObj).getDescribe();
           String tempName = r.getName();
           String tempPrefix = r.getKeyPrefix();
           keyPrefixMap.put(tempPrefix,tempName);
        }
    
        return keyPrefixMap.get(objectId.subString(0,3));  
    }
       
   

 

Any ideas are appreciated. Thank you.

Hey all,

 

This is one of those questions when I feel like I'm probably going about a simple process the wrong way and over complicating it, but I'm not sure the alternative. So I'll ask my question and if you have a better approach, please feel free to share.

 

The deal is that I have an Apex REST service that allows a user to pass in an object type, and any field values in the URL.

EX : http://na2.salesforce.com/apex/reservice/contact?firstname=frank&lastname=jones&age__c=21

 

My code works fine for any string values, but chokes on numerics. Because I instantiate a new instance of the type of object they pass in, then I loop over all the fields in the query string and dynamically add them to the sObject. Of course as far as apex is concerned all those arguments in the query string are in fact strings, so when adding non string things to the sObject it explodes. 

 

Ideally I could use the get describe info about the field I'm inserting to find it's type and cast the value to be inserted to the correct type, but as far as I know there isn't a way to do dynamic casting at runtime. A series of if statments is about the only alternative I see, but that feels really dirty. 

 

This is what I have currently (you can see I'm kind of using the if statment path, trying to make it as simple as possible).

 

Just to be clear, this code works, it's just not as efficient/dynamic as I'd like.

 

    public static sObject saveSObject(string objectType, string recordid, RestRequest req)
    {
        //create a generic sObject to contain the update values.
        sObject updateObj;      
                
        //get the describe object for the type of object passed in (its just passed in as a string from the URL)
        Schema.sObjectType objectDef = Schema.getGlobalDescribe().get(objectType).getDescribe().getSObjectType();
        
        //find all the fields for this object type
        Map<String, Schema.SobjectField> ObjectFieldsMap = objectDef.getDescribe().fields.getMap();
        
 
        //this method can handle updates or inserts. If a record ID was passed in, 
        //cast the object as the type represented by the ID. If not, just create a
        //new object of the type found in the object describe.
        if(recordId != null)
        {
            updateObj = objectDef.newSobject(recordid);
        }
        else
        {
            updateObj = objectDef.newSobject();
        }    
        // populate the object's fields by looping over all the params in the rest request.
        for (String key : req.params.keySet())
        {
            // only add params if they are valid field on the object
            if (ObjectFieldsMap.containsKey(key))
            {
                //figure out the type of this field so we can cast it to the correct type
                string fieldType = ObjectFieldsMap.get(key).getDescribe().getType().name().ToLowerCase();
                
                //since I don't know how to do, or if it's even possible to do dynamic casting we need a 
                //series of if statments to handle the casting to numeric types. I think all the others should
                //be fine if left as a string. Dates might explode, not sure.
                
                
                if(fieldType == 'currency' || fieldType == 'double' || fieldType == 'percent' || fieldType == 'decimal' )
                {
                    updateObj.put(key, decimal.valueOf(req.params.get(key).trim())); 
                }
                else if(fieldType == 'boolean')
                {
                    updateObj.put(key, Boolean.valueOf(req.params.get(key))); 
                }                   
                else if(fieldType == 'date')
                {
                    updateObj.put(key, date.valueOf(req.params.get(key))); 
                }                
                else
                {
                    updateObj.put(key, req.params.get(key));
                }
            }
            else
            {
                system.debug('Invalid field: '+ key + ' for object type ' + objectType);
            }
        }
        //update/insert the object
        upsert updateObj;
        
        //return the saved object.
        return updateObj;
        
    }

 

Hey Guys,

I've just been having terrible luck recently. Everything I do seems to casue an internal salesforce error. What I am working on at this point is upserting a list of person accounts based on an external id field (SSN__C). I've never done an upsert before (I know, I'm lame) so I am unsure if the error I am getting is due to me getting the process wrong, or if I found yet another bug. The list of accounts comes from parsing a CSV file, and if I change the upsert to just an insert, it works fine so I know the data it's trying to pass in for creating is legit. Here is the relevant code.

//get a list of people to upsert by parsing the CSV file into a list of account objects.
 list<Account> accounts = csvTosObject(parseResult, 'Account');
 

 system.debug('========================= List of Accounts');
 system.debug(accounts); //shows all accounts parsed fine
                         
//upsert them (need to get a reference to the external field to use for id)
Schema.SObjectField f = Account.Fields.SSN__c;                        
List<Database.upsertResult> accountSaveResult = Database.upsert(accounts,f); // <---- Explodes

//if instead of an upsert I have an insert, it works fine. So i know the data is good.
//List<Database.saveResult> accountSaveResult = Database.Insert(accounts,false);

//iterate the person save results to create the import result log
for(database.UpsertResult r : accountSaveResult)
{
    if(r.isSuccess())
    {
        if(r.isCreated())
        {
            personCreates++;
        }
        else
        {
            personUpdates++;
        }
    }
    else
    {
       personErrors++;
        for(Database.Error e : r.getErrors())
        {
            personErrorText.add(e.getMessage());
        }
    }
}

 
Otherwise I can break the operation into two separate insert and update statments probably (seems hackish but it would work) but I'd prefer to just upsert if I can. Any help is appreciated. Thanks!

 

Hey all,

I posted a bit about this on twitter but I figured I'd make a thread to get some more details. What's happening is that I have a method (it's an Apex Rest method if that matters) whose job is to insert a list of sObjects. For some reason, attempting to do so causes an uncatchable Internal Salesforce Error. Weird part is, if I remove the offending code, and run it by itself, it works just fine. The object in question here is a custom object called survey_answer__c. These are created by deserializing some JSON from a remote webservice. For example

[{"Answer__c":2.0,"Id__c":"51345X1607X53053","Question__c":"what is blah 1?"},{"Answer__c":3.0,"Id__c":"51345X1607X53100","Question__c":"what is blah 2?"}]

 

 Contains data for two answer objects to get inserted. So I deserailize it, which goes just fine. Now I have a list of sObjects. I attempt to insert it using the plain old insert command and I get my error. Now the weird part is if I mimic this setup by itself in an execute anonymous block using the following code, it will run fine. You'll notice all the data for the objects is exactly the same, except for the second set has an Id for the survey_entry__c relationship field (which is appended to each sObject in the first example later on by iterating the list of sObjects and setting the field manually).

list<Survey_Answer__c> answers = new list<Survey_Answer__c>();

Survey_Answer__c ans1 = new Survey_Answer__c();
ans1.Question__c= 'what is blah 1?';
ans1.Id__c='51345X1607X53053';
ans1.Survey_Entry__c='a0zS0000001HSavIAG';
ans1.Answer__c='2.0';

Survey_Answer__c ans2 = new Survey_Answer__c();
ans2.Question__c= 'what is blah 2?';
ans2.Id__c='51345X1607X53100';
ans2.Survey_Entry__c='a0zS0000001HSavIAG';
ans2.Answer__c='3.0';

answers.add(ans1);
answers.add(ans2);

insert answers;

 

 So there you have it. Random error for no good reason. I'll post the entire class below for you to review. If you want the object definiton files to play with it yourself, let me know and I can host them somewhere.

 

@RestResource(urlMapping='/importSurvey/*') 
global class importSurveyData 
{
	public static boolean isApexTest = false;
	
	//this method takes a post request with a survey id, a user token and other paramters about the survey. It reaches out to another 
	//webservice which gets the survey answer data and returns it. The data is used to generate survey_answer__c objects and insert them.
	//It will also create a survey__c and survey_entry__c object if required (if they don't exist). Each campaign should have only one survey__c object
	//which can contain multiple survey entries (one per person who took the survey). Each survey entry can contain multiple answers. Each contact will
	//have only one survey entry for a survey
	
	/* Schema
	
	Campaign
	|
	+-> Survey__c 
	    |
	    +-> Survey_Entry__c <- Contact 
	        |
	        +->Survey_Answer__c
	*/
	@HttpPost
	global static list<Survey_Answer__c> doPost(RestRequest req, RestResponse res) 
	{
		list<Survey_Answer__c> answers;
		String jsonString;	
		Survey_Entry__c surveyEntry = new Survey_Entry__c();	
		
		//Get the ID of the survey we are working with from the URL
		string survey = req.params.get('survey');
		
		//Loop over all the parameters passed in and try to assign them to the survey object. Just discard any unusable data
		for(string param : req.params.keySet())
		{
			try
			{
				surveyEntry.put(param,req.params.get(param));
			}
			catch(Exception e)
			{
				
			}
		}
		
		//We have to call out to a web service to get the lime survey answer data. It will get returned as a JSON encoded array
		//of answer objects. These can be directly de-serialized to survey_answer__c objects. They contain all the actual answers
		//to the questions as well as data about the questions itself
        try 
        {           
            
            HttpRequest getDataReq = new HttpRequest();
            getDataReq.setMethod('GET'); 
            getDataReq.setEndpoint( 'http://xxxxxxxxxxxxxxxxxxxxxxxx.com/?method=getLimeSurveyAnswers&surveyId='+survey+'&Token='+surveyEntry.token__c);
              
            Http http = new Http();

			if(!isApexTest)
			{
            	//Execute web service call here     
            	HTTPResponse getDataRes = http.send(getDataReq);   
            
	            if(getDataRes.getStatusCode() == 200)
	            {
					jsonString = getDataRes.getBody();  
	            }
	            else
	            {
	                system.debug('Could not contact web service');
	            }
			}
			else
			{
				system.debug('Apex Test Mode. Webservice not Invoked');
				jsonString = '[{"Answer__c":2.0,"Id__c":"51345X1607X53053","Question__c":"what is blah 1?"},{"Answer__c":3.0,"Id__c":"51345X1607X53100","Question__c":"what is blah 2?"}]';
			}
			
			//This is just put here for testing. It's faster and more reliable for testing to just have the same JSON content every time
			//in production this line would be removed
			jsonString = '[{"Answer__c":2.0,"Id__c":"51345X1607X53053","Question__c":"what is blah 1?"},{"Answer__c":3.0,"Id__c":"51345X1607X53100","Question__c":"what is blah 2?"}]';		
			
			//deserialize the list of answers retreived from the webservice call
			answers = (List<Survey_Answer__c>) JSON.deserialize(jsonString, List<Survey_Answer__c>.class);	
			
			//Now we need to find the ID of the survey to attach these answers to. This method will either find the existing
			//survey_entry__c id (based on token, contact, and lime survey id) or create a new one and return it. Either way
			//it's going to return a survey_entry__c object (it will also create a survey object to attach itself to if there isn't one)					
			Id surveyEntryId = importSurveyData.getSurveyEntry(surveyEntry, survey).id;									
			
			//We don't want duplicate data, and this may get run more than once, so delete any existing answer data for this survey entry	
			List<Survey_Answer__c> toDelete = [select id from Survey_Answer__c where Survey_Entry__c = :surveyEntryId];
			delete toDelete;
			
			//We now also need to update these survey answer objects with the survey_entry__c id so the relationship gets set
			for(Survey_Answer__c ans : answers)
			{
				ans.Survey_Entry__c = surveyEntryId;
			}	
				
			insert answers;	//This line errors for no discernable reason.		        
        } 
        catch(Exception e) 
        {
            system.debug('======================== ERROR IMPORTING ANSWERS: ' +e.getMessage() + ' ' +e.getCause() + ' ' +e.getTypeName());
        } 
        		
		return answers;
	}
	
	//this will either find an existing survey_entry__c or create a new one if one does not exist.
	//it will also request creation of a survey__c object to attach itself to if one does not exist.
	global static Survey_Entry__c getSurveyEntry(Survey_Entry__c surveyEntry, string surveyId)
	{	
		//try to find existing survey entry for this person 	
		list<Survey_Entry__c> entries = [select id, 
												Survey__c, 
												token__c, 
												contact__c 
										 from Survey_Entry__c 
										 where survey__r.study__r.lime_survey_id__c = :surveyId 
										 and contact__c = :surveyEntry.contact__c 
										 limit 1];
										 	
		//if a survey entry is not found, create one otherwise return existing
		if(entries.isEmpty())
		{				
			
			surveyEntry.Survey__c = getSurvey(surveyId).id;
			insert surveyEntry;		
			return surveyEntry;
		}	
		else
		{
			return entries[0];	
		}
		
	}
	
	//create a survey for the given campaign if does not exist, otherwise return the existing one.
	global static Survey__c getSurvey(string surveyId)
	{
		//find the existing survey if there is one for this id
		list<Survey__c> surveyObj = [select id 
									 from Survey__c 
									 where Study__r.Lime_Survey_ID__c = :surveyId 
									 limit 1];		
		if(surveyObj.isEmpty())
		{
			Survey__c survey = new Survey__c();
			RecordType ParentRecordType = [select id from RecordType where name = 'FPI Parent Campaign' ];
			list<Campaign> parentCampaign = [select id from Campaign where Lime_Survey_ID__c = :surveyId and recordTypeId = :ParentRecordType.id order by createdDate asc limit 1];
			
			survey.Study__c = parentCampaign[0].id;		
			insert survey;
			return survey;
		}	
		else
		{
			return surveyObj[0];
		}						 
	}
    @isTest 
    public static void importSurveyDataTest()
    {
    	isApexTest = true;
    	string surveyID = '51345';
    	
        //Insert the check record, with testContact as the contact
        Account thisTestAccount = testDataGenerator.createTestAccount();
        Contact thisTestContact = testDataGenerator.createTestContact(thisTestAccount.id);
        Campaign thisTestUmbrellaCampaign = testDataGenerator.createTestUmbrellaCampaign(thisTestContact.id);
        Campaign thisTestParentCampaign = testDataGenerator.createTestParentCampaign(thisTestUmbrellaCampaign.id, thisTestContact.id); 
        
        thisTestParentCampaign.Lime_Survey_ID__c = surveyID;
        
        update thisTestParentCampaign;

        RestRequest req = new RestRequest(); 
        RestResponse res = new RestResponse();
 
	
 		//Do a sample request that should invoke most of the methods 
        req.requestURI = 'https://cs1.salesforce.com/services/apexrest/importSurvey';
        req.httpMethod = 'POST';	 
		req.addParameter('survey',surveyID);
		req.addParameter('token__c','mkwcgvixvxskchn'); 
		req.addParameter('contact__c',thisTestContact.id);
		req.addParameter('type__c','survey');
		req.addParameter('label__c','test survey');
		req.addParameter('result__c','qualified');
		
        list<Survey_Answer__c> importedAnswers = doPost(req,res); 
        
        system.debug(importedAnswers);
        //The first thing it should have done is create the survey object itself, attached to the master campaign.
        list<Survey__c> testSurvey = [select id from Survey__c where study__r.Lime_Survey_ID__c = :surveyID];
        system.assertEquals(1,testSurvey.size());
        
        //now it also should have created a survey entry for this person, with the attributes passed in the request
        list<Survey_Entry__c> testEntry = [select id,type__c,label__c,result__c,token__c from Survey_Entry__c where contact__c = :thisTestContact.id and survey__c = :testSurvey[0].id];
        system.assertEquals('survey',testEntry[0].type__c);
        system.assertEquals('test survey',testEntry[0].label__c);
        system.assertEquals('qualified',testEntry[0].result__c);
        system.assertEquals('mkwcgvixvxskchn',testEntry[0].token__c);
        
        //Also all the survey answers should have been imported. There should be two of them.        
   		list<Survey_Answer__c> answers = [select id, Answer__c, Question__c from Survey_Answer__c where Survey_Entry__c = :testEntry[0].id];
   		system.assertEquals(2,answers.size());
    }
    
}

 

When JSON support was added in APEX, I was one of those guys who jumped up and down. Started using heavily for one of my integration project and everything was fine and dandy for couple week. Since yesterday, I have been noticing some weired behavior. The first time I noticed it, I thought it was one of those APEX issues I love to call "APEX weirdness" and hoped that it will fix itself (READ: getting fixed without us knowing). That hasn't happened. :(

 

Here is the issue. 

 

My JSON parsing code looks like this:

class SomeApexWrapper {

public static SomeApexWrapper getInstance(String jsonString)

JSONParser parser = JSON.createParser(jsonString);       

SomeApexWrapper w = (SomeApexWrapper) parser.readValueAs(SomeApexWrapper.class);

}

}

 

This code was working fine until two days ago. It stops working If I change any class that uses this class to parse json string. The error I get is "[Source: java.io.StringReader@21363a13; line: 1, column: 1] Don't know the type of the Apex object to deserialize"

 

Just saving the SomeApexWrapper  class again fixes the issue. 

 

Has anyone had/having this issue? Is there a permanent solution for this?

  • November 07, 2011
  • Like
  • 0