Tuesday 17 September 2013

bulkiying apex,soql,js in vf,etc

Copied from :http://www.opfocus.com/2013/07/create-task-assigned-to-lead-owner-of-new-web-lead/


Bottom of Form
Apex
July 16, 2013 by Boom Bertetti • Apex
I recently got a request from a client to write a trigger that creates a task when a lead is created from Web-to-Lead (Lead Source = “Web”). The Task should have Due Date = Today, WhoId is the Lead.Id, and Status = New.
I thought that was a simple trigger.  I originally designed the trigger to be an after insert trigger on the Lead object, which checked for LeadSource = ‘Web’ and created new Task like this:
http://www.opfocus.com/wp-content/uploads/Task1-184x300.png
   Task t = new Task();
   t.WhoId = l.Id;
   t.Subject = 'Web Lead';
   t.OwnerId = l.OwnerId
   t.ActivityDate = date.today();

Then I added the new Task (t) to lstNewTasks and insert.
Simple, right? No, no, no!  Not until the Lead Assignment rule was implemented. Here is the scenario:
·         In Lead Settings, the default lead owner is “Jack Bauer”
·         Lead Assignment rule has State = MA, assign to “Sydney Bristow”
A new lead came in from the website with State = MA.  Who would be the Task Owner?  Sydney, of course.  But the actual Task Owner was Jack Bauer.  Why?
According to Salesforce’s Triggers and Order of Execution, on the server, Salesforce executes all after triggers before assignment rules.  Wow, I didn’t expect that.
What should I do to assign the Task Owner to the same Owner as the Lead?  I thought it would be nice to have a Task Assignment Rule for this particular case.  Then I began my search for some good ideas from fellow developers and I found this blog article Opportunity Assignment Rules in Salesforce.com via Apex & Imagination.  MJ Kahn introduced to me the way to convert Lead Assignment Rules to Opportunity Assignment Rules, so I thought “Why not?  I can use this same tactic with my Task Assignment Rules, too.”  My approach is a little bit easier than MJ’s Opportunity Assignment Rules and here are the steps:
1.     Add a checkbox field called “From WebLead” in Activity Custom Field
2.     Tweak the Lead Trigger to not set the Task Owner Id and set From WebLead = true.
3.     Write an Apex trigger that fires when a Task is created.  The trigger will:
o     Query for the default Lead Assignment rule.
o     Check if the Task is from web lead (From WebLead = true) and WhoId starts with ’00Q’.
o      Set Database Savepoint.
o      Insert a new Lead that has all the important fields equal to the same values in the Lead that the Task was created from, except FirstName is the Lead’s Id.  (Oh.  You might not want to copy the Lead’s email from the web lead, since some people like to put an invalid email address and that would cause the Web to Lead to fail to insert the original lead to the database.)  Apply the default Lead Assignment Rule to the new Lead.
o     Determine which user the default Lead Assignment Rule assigned to the new Lead and the Task.From Weblead is true, then change the Task to be owned by that user.
o    Roll back the database to the state it was in when you set the Savepoint, so we don’t save the new Lead.
o     Allow the creation of the Task to continue. The Task will be created and we now have Task Owner equals to the Lead Owner based on the default Lead Assignment Rule!
Let’s see the Task trigger below.
trigger Task on Task (before insert) {
          if (Trigger.isBefore && Trigger.isInsert) {
                      AssignmentRule rule = [select id from AssignmentRule where SObjectType = 'Lead' and Active = true limit 1];
                      Database.DmlOptions dmo = new Database.DmlOptions();
                      dmo.assignmentRuleHeader.assignmentRuleId = rule.id;
                      Set<Id> setLeadIds            = new Set<Id>();
                      Map<Id, Lead> mapWebLeads     = new Map<Id, Lead>();
                      Map<Id, Lead> mapLeadsbyWhoId = new Map<Id, Lead>();
                      for (Task t : Trigger.new) {
                                 String whoId;
                                 whoId = t.WhoId;
                                 if (t.WhoId == null) continue;
                                 if (whoId.contains('00Q')) setLeadIds.add(t.WhoId);
                      }
                      // Get the current information from the Lead that we create a task for
                      mapWebLeads = new Map<Id, Lead>([select LastName, OwnerId, Email, Company, LeadSource, Country, State,
                                            Status, AZ_Auctioneer_ID__c
                                            from   Lead
                                            where  Id IN:setLeadIds ]);
                      if (!mapWebLeads.isEmpty()) {
                                 for (Task t : Trigger.new) {
                                            String whoId;
                                            whoId = t.WhoId;
                                 if (whoId.contains('00Q') && t.From_WebLead__c==true) {
                                         Lead newLead = new Lead();
                                         newLead.OwnerId    = mapWebLeads.get(t.WhoId).OwnerId;
                                         newLead.LastName   = mapWebLeads.get(t.WhoId).LastName;
                                         newLead.FirstName  = t.WhoId;
                                         newLead.Company    = mapWebLeads.get(t.WhoId).Company;
                                         newLead.LeadSource = mapWebLeads.get(t.WhoId).LeadSource;
                                         // flag the Lead to be assigned by our Assignment Rule
                                         newLead.setOptions(dmo);
                                         mapLeadsbyWhoId.put(t.WhoId, newLead);
                                            }
                                 }
                      }
          Savepoint sp = Database.setSavepoint();
          // Insert the newLeads (and trigger the reassignment)
          insert mapLeadsbyWhoId.values();
          // Create a map of Leads, indexed by their Ids in the FirstName
          Map<Id, Lead> mapLeadsByWhoIdNew = new Map<Id, Lead>();
          for (Lead ld : [select id, FirstName, Email, OwnerId from Lead where id in :mapLeadsbyWhoId.values()]) {
                      mapLeadsByWhoIdNew.put((Id)ld.FirstName, ld);
          }
                      // Rollback database so the temp newLeads are not saved to real database
                      Database.rollback(sp);
                      // mapLeadsByWhoIdNew should now contain all of the Leads in Trigger.new
                      for (Task t : Trigger.new) {
                                 String whoId;
                                 whoId = t.WhoId;
                                 if (t.WhoId == null) continue;
                                 if (whoId.contains('00Q') && t.From_WebLead__c==true) {
                                            // Reassign the Task
                                            t.OwnerId = mapLeadsByWhoIdNew.get(t.WhoId).OwnerId;
                                 }
                      }
          }
}

Have you reassigned a Task based on Lead Assignment rule before?  What approach did you use?
March 29, 2013 by Rob Kemper • Apex
Business HoursSalesforce provides a simple mechanism for setting business hours in your organization.  Just navigate to:
Setup -> Administration Setup -> Company Profile -> Business Hours
Which brings you to this screen:

Business Hours Configuration

You set the hours for each day of the week that the company is available.  I’m using a single Business Hours record named Default.  You can have multiple business hours in multiple time zones if you have a complex support organization.  With business hours you can work in specific time zones and locations with:
·         Cases
·         Case escalation rules
·         Case milestones in entitlement processes
This is accessible through Apex, but you will likely need to customize the logic to your needs.  Salesforce thoughtfully provides a BusinessHours class, but it is very basic, just 3 methods.  Since I needed to check if today was a business day and a few other things I had to write it myself.
Here is the Apex code:

 1 public class BusinessDays {
 2
 3 private List<Boolean> businessDay = new Boolean[7];
 4 private List<Time> startHours = new Time [7];
 5 private List<Time> endHours = new Time [7];
 6 private Date knownSunday = date.newInstance(2013, 1, 6);
 7
 8   // Constructor creates businessDay array
 9   public BusinessDays() {
10
11     BusinessHours bh =
12       [Select
13         SundayStartTime, MondayStartTime, TuesdayStartTime,
14         WednesdayStartTime, ThursdayStartTime, FridayStartTime,
15         SaturdayStartTime, SundayEndTime, MondayEndTime,TuesdayEndTime,
16         WednesdayEndTime, ThursdayEndTime, FridayEndTime,SaturdayEndTime
17       From BusinessHours 
18       Where IsDefault=true];
19
20     businessDay[0] = (bh.SundayStartTime != null);
21     businessDay[1] = (bh.MondayStartTime != null);
22     businessDay[2] = (bh.TuesdayStartTime != null);
23     businessDay[3] = (bh.WednesdayStartTime != null);
24     businessDay[4] = (bh.ThursdayStartTime != null);
25     businessDay[5] = (bh.FridayStartTime != null);
26     businessDay[6] = (bh.SaturdayStartTime != null);
28 
29     startHours[0] = bh.SundayStartTime;
30     startHours[1] = bh.MondayStartTime;
31     startHours[2] = bh.TuesdayStartTime;
32     startHours[3] = bh.WednesdayStartTime;
33     startHours[4] = bh.ThursdayStartTime;
34     startHours[5] = bh.FridayStartTime;
35     startHours[6] = bh.SaturdayStartTime;
36
37     endHours[0] = bh.SundayEndTime;
38     endHours[1] = bh.MondayEndTime;
39     endHours[2] = bh.TuesdayEndTime;
40     endHours[3] = bh.WednesdayEndTime;
41     endHours[4] = bh.ThursdayEndTime;
42     endHours[5] = bh.FridayEndTime;
43     endHours[6] = bh.SaturdayEndTime;
44
45   }
46
47   // Check if today is a business day
48   public Boolean isBusinessDay(Date inputDate) {
49     // index i is index into the businessDay array based on inputDate
50     Integer i = Math.mod(Math.abs(this.knownSunday.daysBetween(inputDate)),7);
51     return (businessDay[i]);
52    }
53 
54   // Get the start time
53   public Time getStartTime(Date inputDate) {
54     Integer i = Math.mod(Math.abs(this.knownSunday.daysBetween(inputDate.date)),7);
55     return (startHours[i]);
56   }
57
58   // Gets next business day, skipping non business days
59   public Date nextBusinessDay(Datetime inputDatetime) {
60     Integer i =
61       Math.mod(Math.abs(this.knownSunday.daysBetween(inputDatetime.date())),7);
62     Datetime returnDate = inputDatetime;
63     while (!businessDay[Math.mod(i, 7)]) {
64       i++;
65       returnDate = returnDate.addDays(1);
66     }
67     return returnDate.date;
68   }
69
70 }

I build 3 arrays.  One to hold the days of the week the company is open, one to hold the start time for each day, and one to hold the end time of each day.  I’m not using the end time array at the moment, it is available just in case.
Notice the variable knowSunday on line 6.  You could also use the method Date.toStartOfWeek(), but you would have to match the indexes to the locale of your company.  According to the Apex reference:  ”…the start of a week is Sunday in the United States locale, and Monday in European locales.”  I usually work for US companies, but I’ve gotten in trouble before by making assumptions about locale dependent information when working for customers in Europe.
If you want to know if we are open for business today:

  if (busDays.isBusinessDay(Date.today())) { ...

If you want to know when we open the doors for customers:

  if (busDays.getStartTime(Date.today())) { ...

If you want to know what the next day we are open:

  Datetime nextBusDay = busDays.nextBusinessDay(Datetime.now());

These are just a few of the possibilities.  The customers I’ve worked for always seem to have their own unique rules for determining when to send the invoice, how to calculate the date in that custom field, and so on.  The bad news is that almost nothing is pre-built in the API.  The good news is that we can use Apex to implement whatever rules our customers dream up.
March 26, 2013 by Rob Kemper • ApexVisualforce
When I started programming using web frameworks like Visualforce, it wasn’t entirely clear what was happening on the server vs the client web browser. I routinely see questions about this in the Force.com developer forums, so I created a simple example to illustrate the round trip from the server, to the browser, to the server and back.
Here is a VF page. It displays a table of information about the Users. It also has a text input field, an HTML button, and an output field. When you enter a value in the input field and click Validate, a popup dialog displays the text, then refreshes part of the page putting the entered text in the Text Field page block. The entered text is read and processed by the JavaScript function checkField first. If it passes validation, in this case a simple check for null, then it is sent to the server via an actionFunction. Even though the same field is bound in both places, why doesn’t it update in the outputText (2) after I type it in the inputText (1)? The answer lies in the page lifecycle as I will explain below.  Here is the page:
VF Page


We entered some text, clicked Validate:

JavaScript popup
JavaScript popup

Voila!  The outputText shows the text:

outputField Updated
outputField Updated

Now lets look at the code. First the VF. The page uses a standard controller for the User object and a controller extension. There is an apex:pageBlockTable on line 19 for listing the details of the Users. The field testField is bound in 2 places, lines 29 and 38. When the page is initialized, the apex:inputText and apex:outputText show the value from the server, null. When you type some text in the inputText, it is just sitting there in the browser.  Its bound to a variable in the controller, but until the form is submitted, the server won’t be updated.  I could have submitted it to the server with an apex:commandButton, but I used an HTML button to show that in the browser VF tags are really just your standard HTML/CSS/JavaScript.  When we click the button on line 30, we call the JavaScript function on line 4.  After a check for null, we finally send the entered text to the server using an apex:actionFunction on line 31.  The server side variable is updated with the entered text, returned to the server, and the apex:outputPanel on line 35 gets re-rendered.  Finally the apex:outputField displays the text we entered.

 1 <apex:page standardController="User" extensions="HelloWorldUsers_ConExt" recordSetVar="users">
 2 <script> 
 3   // JavaScript function to check the value entered in the inputField
 4   function checkField() {
 5     var val = document.getElementById('{!$Component.theForm.thePageBlock.theFieldInput}').value;
 6     if (val != null && val != '') {
 7       alert('val is '+val);
 8       // call the actionFunction, sends the value to the server
 9       fieldUpdateFunction(val);
10     }
11     else alert('Please enter some text');
12   }
13 </script>
14
15 <apex:form id="theForm" >
16
17 <apex:outputPanel id="phoneListPanel" >
18 <apex:pageBlock title="Phone List">
19 <apex:pageBlockTable value="{!users}" var="user">
20 <apex:column value="{!user.Name}" />
21 <apex:column value="{!user.Phone}" />
22 <apex:column value="{!user.MobilePhone}" />
23 <apex:column value="{!user.Fax}" />
24 </apex:pageBlockTable>
25 </apex:pageBlock>
26 </apex:outputPanel>
27
28 <apex:pageBlock id="thePageBlock" title="Test Validation">
29 <apex:inputText id="theFieldInput" value="{!testField}"  />
30 <input type="button" value="Validate" onclick="checkField();" />
31 <apex:actionFunction name="fieldUpdateFunction"
32                      action="{!fieldUpdate}" rerender="phoneListPanel,fieldPanel" />
33 </apex:pageBlock>
34
35 <apex:outputPanel id="fieldPanel">
36 <apex:pageBlock id="anotherPageBlock" title="Text Field">
37 <apex:pageBlockSection >
38 <apex:outputText label="Field value from server" value="{!testField}"  />
39 </apex:pageBlockSection>
40 </apex:pageBlock>
41 </apex:outputPanel>
42
43 </apex:form>
44
45 </apex:page>

There is not much to it, but lets take a look at the server code, the controller extension:
 1 // Controller Extension for HelloWorldUsers
 2 public with sharing class HelloWorldUsers_ConExt {
 3
 4   // Field to bind to the inputText component
 5   public String testField {public get; public set;}
 6
 7   // Simple contructor
 8   public HelloWorldUsers_ConExt(ApexPages.StandardSetController stdController) {
 9   }
10
11   // Method called by the actionFunction on the HelloWorldUsers page
12   public PageReference fieldUpdate() {
13     System.debug('=============>>> testField: '+testField);
14
15     // return null to keep us on the page
16     return null;
17   }
18
19 }
We have a String textField on line 5 that is bound to the inputText and outputText. The method fieldUpdate is called by the actionFunction. On line 16 it returns null so that when it returns to the browser it stays in the same page.
Knowing the difference between the server and browser begs the question: do you want to do the processing in the browser or on the server?  Typically you want to do what you can in the browser.  Its ideal to do simple validation, like name/address/phone format, especially if you don’t need to connect with another service.  You can do more on the server, but it takes a least one round trip so its usually slower.  A complex integration may involve AJAX to the server, connecting with RESTful web services, and lots of other fun machinations that are beyond the scope of this post.
I hope this article helps illustrate the difference between the server and the browser.  The minutia of programming is hard enough, so its critical to understand the bigger picture like the lifecycle of a Visualforce page.
February 19, 2013 by Rob Kemper • ApexHow ToSalesforce.comTipsVisualforce
Clicks, clicks, and more clicks!  Everyone wants the computer to do the work, clicking the mouse is just too hard!  So let’s make the UI in our Salesforce org shoulder some of the work for our customer.  Here I’ll describe how to use a Visualforce actionSupport tag to automatically populate the Opportunity probability percentage value when the customer selects a StageName from a picklist.
First lets look at the objects we are working with:  Opportunity and OpportunityStage.  The sales and marketing folks tell us that Opportunities have distinct Stages.  As they progress through each Stage, chatting up a prospective client, the chance of landing the Opportunity increases, such that if they make it to negotiating the contract they have a 90% probability of successfully turning the Opportunity into a Contract.  So they say.  There are 10 default Stage values for a Salesforce org are shown below:

https://lh3.googleusercontent.com/4m28BMSsoYDHOxssr6puSMb_7OnHpxFcXbrzq2NbqrNZlrsMQHM72m4UAybN2V_VHNn-loiAzKuVTGjYmG-BQl2FnQDhTIaTHquyVfmMx3jAhcNpOD_y

We can represent the Probability with a Visualforce inputField and have our customer type it in, but here we will do the work for them.
First the Visualforce.  Here is a snippet of a page with an apex:inputField for the Stage Name and another apex:inputField for the Probability:
 1 <table>
 2  <tr>
 3    <th>Opportunity Name</th>
 4      <td><apex:inputField value="{!thisOpp.Name}" /></td>
 5  </tr>
 6  <tr>
 7    <th>Opportunity Stage</th>
 8    <td>
 9      <apex:actionRegion >
10        <apex:inputField value="{!thisOpp.StageName}" >
11          <apex:actionSupport event="onchange" action="{!changeStageName}"
12                              rerender="probability,messages" />
13        </apex:inputField>
14      </apex:actionRegion>
15    </td>
16  </tr>
17  <tr>
18    <th>Probability (%)</th>
19    <td>
20      <apex:inputField value="{!thisOpp.Probability}" id="probability" />
21    </td>
22  </tr>
23 </table>

The Stage Name will be rendered in the browser as a picklist.  The Probability is rendered as a text input:

https://lh6.googleusercontent.com/OR3MEy0QDsleUFM47l4JrLd5ZzLo0332-DzptpZLePXoz4qSzaFdAyoKkV3-FP1saTI8lC9ex8j6PtFV7uKwWCivighdSRMYE-7gFE2vFKXcMDwuNPgQ

We use an apex:actionSupport tag to invoke the changeStageName method in our controller when the picklist value changes.  Notice the rerender on line 12.  We don’t want the whole Visualforce page to reload.  If there are other dynamic elements then managing their interactions can get very complex.  We wrap an apex:actionRegion around the picklist so that only the picklist value is submitted to the server.  Once again, if this is part of a large complex page with other dynamic elements we probably do not want them all updating with each change.  For example, this could cause a problem if the form needs to be validated only when the entire form is saved.
Now let’s look at the Apex code in the controller.  The actionSupport element action changeStageName is bound to an identically name method in the controller:

 1 // The user changed the Opportunity StageName. Set the Probability to the
 2 // correct value, based on the defaults set up in the OpportunityStage object.
 3 public transient Map<String, Decimal> probabilityStageNameMap;
 4
 5 public PageReference changeStageName() {
 6
 7   if (probabilityStageNameMap == null) {
 8     probabilityStageNameMap = new Map<String, Decimal>();
 9     for (OpportunityStage oppStage : [Select MasterLabel, DefaultProbability
10                                       From OpportunityStage]) {
11       probabilityStageNameMap.put(oppStage.MasterLabel, oppStage.DefaultProbability);
12     }
13   }
14 
15   if (probabilityStageNameMap.containsKey(thisOpp.StageName)) {
16     thisOpp.Probability = probabilityStageNameMap.get(thisOpp.StageName);
17   }
18
19   return null;
20 }

We only create a Map of StageName to probability once, the first time this method is invoked as you can see on line 3.  Also, notice we return null – we are not navigating to a new page, we are just quietly updating one field:
https://lh6.googleusercontent.com/erYdc7zLS5UfIMQd_tI1WoRoD9CMQ3VSDe07tZ31pZWmWGAnvGWbxS_wIhevbxHsFDRbTeZl3r89iotaEdQ0Red5Ky71vePILc03jltauUpFfr6ok-3a

The end result is that your customer selects a StageName from a picklist and automagically the percentage field is populated.  Your customer can manually input a different Probability percentage value if they choose, but more than likely they want the default value and they don’t want the extra clicking and typing.  The use of dynamic elements like this will make your interface faster to use and more engaging to your customers.
January 23, 2013 by Rob Kemper • ApexHow ToSalesforce.com
http://www.opfocus.com/wp-content/uploads/4407032688_e77137f088.jpegOne of the cardinal rules for programming with Apex is never make a SOQL query in a loop.  Even when you’re 110% sure that the loop will make no more than 2 passes, you migrate your code from the sandbox to production and… BOOM!  SOQL query limit exceeded!  This is particularly bad in code that is not customer facing because it may be some time before the failure is apparent.
The answer is to always bulkify your queries.  As an example, you want to get all the Accounts assigned to the current User, then you want all the Contacts for each Account.  First, the non-bulkified example:
// We want a Map of Account IDs to all associated Contact IDs
Map <ID, List <ID>> accountContactMap = new Map <ID, List <ID>>();

// Select all Accounts owned by the current User
List <Account> accountList =
    [Select ID, Name From Account Where OwnerId =:UserInfo.getUserId()];

// For each Account get the associated Contacts
for (Account nextAccount : accountList) {
    // Bad!  Very bad!  No SOQL in a loop!  What was I thinking!?!??
    // Get the Contacts associated with this Account
    List <Contact> nextContacts =
        [Select ID, Name From Contact Where AccountId =:nextAccount.ID];
    System.debug('Account '+nextAccount.Name+' has '+nextContacts.size()+' Contacts');
   // Create a List of all the associated Contacts
   List <ID> contactIDs = new List <ID> ();
   for (Contact nextContact : nextContacts) {
        contactIDs.add(nextContact.ID);
    }
    // Add them to the Map
    accountContactMap.put(nextAccount.ID, contactIDs);
}

Now the bulkified version:
 1  // We want a Map of Account IDs to all associated Contact IDs
 2  Map <ID, List <ID>> accountContactMap = new Map <ID, List <ID>>();
 3 
 4  // Select all Accounts owned by the current User
 5  List <Account> accountList =
 6      [Select ID, Name From Account Where OwnerId =:UserInfo.getUserId()];       
 7 
 8  // Make a List of Account IDs
 9  List <ID> accountIDList = new List <ID> ();
10  for (Account nextAccount : accountList) {
11      accountIDList.add(nextAccount.ID);
12  }       
13 
14  // Now get the Contacts associated with the Accounts
15  List <Contact> allContactList =
16      [Select ID, Name       
17          From Contact        
18          Where AccountId in :accountIDList       
19          order by AccountId];       
20  if (allContactList.size() == 0) {       
20      return;       
21  }
22 
23  List <Contact> contactList = new List <Contact> ();
24  ID previousAcctID = contactList[0].AccountID;
25 
26  for (Contact nextContact : contactList) {
27      if (nextContact.AccountID != previousAcctID) {       
28          accountContactMap.put(previousAcctID, contactList);
29          contactList = new List <Contact> ();
30          previousAcctID = nextContact.AccountID;       
31      }
32      contactList.add(nextContact.ID);       
33  }
34  accountContactMap.put(previousAcctID, contactList);

The new code is longer and not as readable, but we’ve gotten rid of the dangerous SOQL in the for loop.
Notice the order by AccountId on line 19.  This is critical to insure that we make a clean transition from Contacts of one Account to another.  If you are using this pattern for more dimensions than just ID, you may have to order by multiple fields.  For example, let’s say you have a custom field Last_Ping_Date__c on Contact.  You need the Map of Account IDs to Contact IDs and you will be charting them by date.  This is simple to do in the query as shown here:
    // Now get the Contacts associated with the Accounts
    // Order by date so the chart is properly ordered
    List <Contact> allContactList =
        [Select ID, Name       
         From Contact       
         Where AccountId in :accountIDList       
         order by AccountId, Last_Ping_Date__c];
     
Don’t forget to test for nulls and empty collections.  The return statement on line 20 is required because we are referencing field 0 in the List on line 24.
Finally, notice the second accountContactMap.put() on line 34.  We can’t forget to add the final Account ID and its Contacts to the Map.
One consequence of this pattern is a generous use of script statements.  Of course, that may run into a different governor limit of a maximum 200,000 script statements.  Apex programming with large data sets can be a juggling act – staying under some limits may bump into others.  However, with only 100 SOQL statements to work with, bulkifying your queries should always be your first step.

Photo Credit: mccoryjames on flikr


No comments:

Post a Comment