Sunday, December 18, 2011

Migrating Documents using Force.com Migration Tool/ANT


Had a hell of a time in migrating Document folders from a Sandbox to production using ANT. Didn't want use the force.com IDE for this as it hangs a lot, so I decided to use Force.com Migration Tool/ANT

Writing this post to explain the right process that I explored while migrating the document folders. Its not explained in the salesforce docs properly, so it took me 6 hours to figure it out myself and deploy the folders after already spending 5 hours in retrieving them. What I found surprizing was that I didn't get my answers to my questions from Google :( (sad, but no dis-respect to Google)

I have already described the installation in the previous post, so will not describe here.

Here are the complete set of steps:

1. So, first of all you need to retrieve the names of folders by using the listMetadata command specifying the folder names.

Take the sf.metadataType in build.properties file as "Document". The script in build.xml will look like:

<target name="listMetadata">
      <sf:listMetadata
            sessionid="${sf.sessionid}"
            serverurl="${sf.serverurl}"
            metadataType="${sf.metadataType}"
            folder="<folder name>"
            resultFilePath="list.log"/>
</target>

running above script will list all the files in list.log file in the same folder as build.xml.

2. Open package.xml from Unpackaged folder and list all the file names within <members> tags, it will look like:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members><Folder name1></members>
        <members><Folder name1/File name1></members>
        <members><Folder name1/File name1></members>

        <members><Folder name2></members>
        <members><Folder name2/File name2></members>
        <members><Folder name2/File name2></members>

        <name>Document</name>
    </types>
    <version>23.0</version>
</Package>

3. Run command, retrieveUnpackaged to retrieve the folders and file in them in retrieveUnpackaged/documents directory.

4. Change the username and password for the destination org in build.properties file.

5. Run command, deployUnpackaged to deploy the folders in the destination org.

NOTE: The key point is you need to extract the folder as <members><Folder name1></members> and the files within them as <members><Folder name1/File name1></members>. The folders also get extracted with a metadata and deployed too.

Friday, December 9, 2011

Getting started with Force.com Migration Tool for deployments

Pre-requisites:
  1. Apache ANT in the CLASSPATH
  2. JDK
  3. Force.com Migration tool
 Installation of ANT becomes a little tricky especially for the beginners, so here are the steps along with the screenshots.
  1. Download the latest release of Apache ANT in .zip format from Apache’s web site: http://ant.apache.org/bindownload.cgi
  2. Unzip to an appropriate folder.
  3. Install JRE, if you don’t have it already.
  4. Download force.com migration tool your salesforce org by going to Setup > Develop > Tools
  5. Unzip force.com migration tool and copy ant-salesforce.jar to the lib directory of your ANT folder created in step2.
 Now, to set the classpath for ANT installation, follow the steps below:
  1. Go to My Computer > Properties > Advanced tab > Environment Variables
  2. Create a new variable in user variables with name ANT_HOME and value as <Complete path of your ANT installation>. For eg: if your ANT installation path is D:\Downloads\apache-ant-1.8.1
    • ANT_HOME
    • Note: Don’t leave a space and don’t put a backslash at the end of the path.
    •  
  3. Create another variable JAVA_HOME and put the full path of the JDK installation in the same way as step2.
    JAVA_HOME
                                                                                           
  4. Create a third variable by the name PATH and put the value as %ANT_HOME%\bin;%JAVA_HOME%\bin

Tuesday, December 6, 2011

Ending your search for using command line data loader with MySQL, Oracle, MS-SQL Server

I find Cliq for command line data loader very handy as in you can get ready with the basic configuration in minutes. This post describes how you can use CLI to import, export, insert and update data to and fro between salesforce.com and databases like MySQL, Oracle 10g and MS-SQL Server.

It is useful when you have an existing website that generates leads in your internal database and you want to import those leads to salesforce.com.

First of all, use cliq to create a export process and use the sample database-conf.xml provided with the Apex Data Loader.
  1. Copy the database-conf.xml file from the samples directory in data loader setup folder and paste it in the same folder as process-conf.xml
  2. Now modify the following properties in the process-conf.xml file:
    1. Set dataAccess.type property in process-conf as databaseRead or databaseWrite(we are writing to our Access db here, so we will use databaseWrite)
    2. Set dataAccess.Name property as name of the bean called in the database-conf.xml file(we are calling the bean insertAccount)
  3. Create a DSN connection by going to control panel > Administrative Tools > Data Sources (ODBC)
  4. Go to Data Sources(ODBC) and double click on MS Access Database.
  5. Specify a Data Source Name, say dsn1.
  6. Click on Select and choose your MS-Access database file(.mdb) 
  7. Open database-conf.xml and modify as follows:
    1. In dbDataSource bean, change the url property to jdbc:odbc:dsn1, it will look like: <property name="url" value="jdbc:odbc:dsn1"/>
    2. Search for insertAccount bean in database-conf.xml and locate the ref for the property name(in our case it is insertAccountSql) as shown in the image.
    3. In insertAccountSql bean change the sqlString property to the insert SQL command for your database, for eg:
    4. INSERT INTO MyAccounts (Acc_id, ACCOUNT_NAME, BUSINESS_PHONE) VALUES (@ID@, @Name@, @phone)
Note: The values between @’s are the mapping values from .sdl file that would have come as the column headers in csv file, if we are not using dataAccess.type as databaseRead or databaseWrite.Also, since there is no SDL file being used in export process as in here, so the values will be the names of the fields in salesforce.

     8.  Okay!! We are almost done, now save all the files and go to command prompt.
     9.  Move to the folder for cliq process and run the DOS batch file(export_, in our case)
    10. Open your Access database table and check the values.

Here is how process-conf.xml will look like:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="export_" class="com.salesforce.dataloader.process.ProcessRunner" singleton="false">
        <description>Created by Dataloader Cliq.</description>
        <property name="name" value="export_"/>
        <property name="configOverrideMap">
            <map>
                <entry key="dataAccess.name" value="D:\SF Tools\cliq_process\export_\write\export_.csv"/>
                <entry key="dataAccess.readUTF8" value="true"/>
                <entry key="dataAccess.type" value="databaseWrite"/>
                <entry key="dataAccess.name" value="insertAccountSql"/>
                <entry key="dataAccess.writeUTF8" value="true"/>
                <entry key="process.enableExtractStatusOutput" value="true"/>
                <entry key="process.enableLastRunOutput" value="true"/>
                <entry key="process.lastRunOutputDirectory" value="D:\SF Tools\cliq_process\export_\log"/>
                <entry key="process.operation" value="extract_all"/>
                <entry key="process.statusOutputDirectory" value="D:\SF Tools\cliq_process\export_\log"/>
                <entry key="sfdc.endpoint" value="https://www.salesforce.com/services/Soap/u/21.0"/>
                <entry key="sfdc.entity" value="Account"/>
                <entry key="sfdc.extractionRequestSize" value="500"/>
                <entry key="sfdc.extractionSOQL" value="select id, name, phone from account"/>
                <entry key="sfdc.password" value="4eb77947398ff3a5d2ea19c5ae2f606717b24e228a14afbe491654"/>
                <entry key="sfdc.username" value="sankalp.jhingran@paper.com"/>
            </map>
        </property>
    </bean>
</beans>


Here is how the database-conf.xml will look like:

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="dbDataSource"
      class="org.apache.commons.dbcp.BasicDataSource"
      destroy-method="close">
    <property name="driverClassName" value="sun.jdbc.odbc.JdbcOdbcDriver"/>
    <property name="url" value="jdbc:odbc:dsn1"/>
    <property name="username" value="user"/>
    <property name="password" value="password"/>
</bean>

<bean id="insertAccount"
      class="com.salesforce.dataloader.dao.database.DatabaseConfig"
      singleton="true">
    <property name="sqlConfig" ref="insertAccountSql"/>
    <property name="dataSource" ref="dbDataSource"/>
</bean>
<bean id="insertAccountSql"
      class="com.salesforce.dataloader.dao.database.SqlConfig" singleton="true">
    <property name="sqlString">
        <value>
            INSERT INTO MyAccounts (Acc_id, ACCOUNT_NAME, BUSINESS_PHONE)
            VALUES (@ID@, @Name@, @phone)
        </value>
    </property>
    <property name="sqlParams">
        <map>
            <entry key="id"    value="java.lang.String"/>
            <entry key="Name"  value="java.lang.String"/>
            <entry key="Phone" value="java.lang.String"/>
        </map>
    </property>
</bean>
</beans>

For connecting to MySQL:
  1. Download the MySQL JDBC connector http://www.mysql.com/downloads/connector/j/. Extract the jar file to the Data Loader home directory (the same dir as DataLoader.jar).
  2. Change the URL property of dbDataSource as:
    • <property name="url" value="jdbc:mysql://{MYSQL HOSTNAME}/{DATABASE NAME}"/>
    • <property name="url" value="jdbc:mysql://localhost:3306/MyDB"/>
     3.  Edit export_.bat and add the mysql connector to the path as follows:

replace
call %DLPATH%\_jvm\bin\java.exe -cp %DLPATH%\DataLoader.jar
-Dsalesforce.config.dir=%DLCONF% com.salesforce.dataloader.process.ProcessRunner process.name=my_process

with
call %DLPATH%\_jvm\bin\java.exe -cp %DLPATH%\DataLoader.jar:%DLPATH%\mysql-connector-java-5.1.18-bin.jar
-Dsalesforce.config.dir=%DLCONF% com.salesforce.dataloader.process.ProcessRunner process.name=export_

For Oracle, use:
“jdbc:oracle:thin:@{serverName}:{portNumber}:sid”

NOTE: Instead of downloading JDBC drivers for the databases, every DB provides its ODBC connector for windows, so, you can use create a DSN for every DB like MySQL, Oracle, MS-SQL Server etc. and specify name of the DSN in the URL property.

I tried to make it as simple as possible, however scope of improvement is also there and hence feedback is highly appreciated.

Thursday, November 24, 2011

Start working with Command Line Data Loader in 5 minutes, Cheers!!

About Cliq(CLI Quickstart):
CLIq provides a simple wizard to create directory structures and configuration files for the Salesforce Data Loader Command Line Interface. You can spend hours configuring the CLI manually, or use CLIq and you will have a working configuration in less than 5 minutes. After creating a CLIq configuration, you can reference the Data Loader documentation and make adjustments as needed.


Installation Instructions: 
  1. Install GUI version of Apex data loader in a directory other than program files(Actually, windows7 does not let you create any files in the data loader directory in program files).
  2. Make sure you have JRE installed.
  3. Extract the zip file from the above link and paste the folder in the data loader installation directory.
  4. Execute Cliq.bat to run Cliq!!

How to use: 
Login with your salesforce.com username, password + security token.
If you face any issues while logging in, try changing the following in cliq.properties file in Cliq directory:
(remember you have to replace the version of your data loader you are using)
sfdc.endpoint=https://www.salesforce.com/services/Soap/u/21.0
sfdc.endpoint=https://login.salesforce.com/services/Soap/u/21.0  (for production)
sfdc.endpoint=https://test.salesforce.com/services/Soap/u/21.0    (for sandbox)


  
Select the operation and enter the name of the process. Cliq will create various directories(Config, read, write and log) in this folder named as "cliq_process" under GUI data loaders parent dir.





Select the object(for example, if you have selected Insert operation)



Click on "Create Data Loader CLI Files". If you have given the process name as "InsertProcess_" in step2 then you will find a dir named as "InsertProcess_" and a batch file(.bat) of the same name within that directory. Double click on it and you are done!






You can change the various values and parameters in process-conf.xml created in the "config" dir in "InsertProcess_" dir.


Try it and let me know how you feel after using it.

 

Monday, November 21, 2011

Where to download all versions of ApexDataLoader?


Here is an archive of all versions of Apex Data Loader. So for anyone having issues with any of the versions of data loader, they can download a different version and investigate.

All thanks to Cloud Success :)





Saturday, November 19, 2011

Custom Pagination in Visualforce made eady

This was an assignment given by one of my friend on Nov 12th, 2011. I thought it was an easy task, just making a visual force page with pagination it, but to my surprise, it was not -:(

So, here is the objective, putting it in exact words of my dear friend:

“You have accounts and a number of opportunities at different stages are associated to them. You have to design an inline Visualforce page on account detail page that shows a dropdown containing various opportunity stages. When user selects any of the listed stage, the PageBlockTable should show the all the Opportunities with the selected stage.”

This was the basic requirement, now comes the tough part:
      1. It should also use the pagination with first, last, previous, next and page size options.
      2. It should also use sorting and the entire functionality should be ajaxified.

I started designing the page and finished the basic functionality in 2 hours. Then came the pagination part for which I knew I cannot use standard list controllers, so I decided to use a custom class, SObjectPaginator from the package Apex-lang(http://code.google.com/p/apex-lang/). All thanks to Richard Vanhook for developing Apex-lang and making our lives easier.

It’s very easy to use to SObjectPaginator, you just need to implement the interface and the method handlePageChange(List newPage) and call the SObjectPaginator.setRecords(List) to set the records in pagination.

Solution: Here is the screenshot of what came out after 8 hours of hard work.

Inline visualforce page on account detail page




The Visualforce Page:
<apex:page standardController="Account" extensions="OpportunityOnAccountDetailExtension">
  <apex:form >
    <apex:pageBlock mode="edit" >
      <apex:pageBlockSection columns="1">
         <apex:outputText >
           <B>Select an Opportunity stage to view opportunities related to this account:</B>
         </apex:outputText>
         
   <apex:actionRegion >
            <apex:selectList size="1" multiselect="false" value="{!selectedStage}"  >
                <apex:selectOptions value="{!Items}"  />
                   <apex:actionSupport event="onchange"
                            action="{!handleAccountSelected}"
                            rerender="pbs1"
                            status="status"/ >
            </apex:selectList>
         </apex:actionRegion>
      </apex:PageBlockSection>
      
 <apex:pageBlockSection columns="1" id="pbs1">
      <apex:actionstatus id="status" startText="testing...">
        <apex:facet name="stop">
           Opportunity Stage Selected: <apex:outputText value="{!selectedStage}" />
    
  <apex:pageBlockTable value="{!accounts}" var="opp" id="pb1">
            <apex:column value="{!opp.obj.name}" />
            <apex:column value="{!opp.obj.StageName}" />
            <apex:column value="{!opp.obj.Amount}" />
            <apex:column value="{!opp.obj.CloseDate}" />                        
                    
            <apex:facet name="footer">
              <apex:outputPanel layout="block">
 
    <!-- PAGE X OF X IN X RESULTS -->
                Page {!IF(paginator.pageCount=0, 0, paginator.pageNumberDisplayFriendly)} of {!paginator.pageCount} in {!paginator.recordCount} results
                &nbsp;&nbsp;       
                
    <!-- FIRST --> 
                <apex:outputText value="First"
                                 rendered="{!NOT(paginator.hasPrevious)}" />
                    <apex:commandLink value="First"
                                      rendered="{!paginator.hasPrevious}"
                                      action="{!paginator.first}" />
                &nbsp;&nbsp;
                                    
     <!-- PREVIOUS -->    
                <apex:outputText value="Previous"
                                 rendered="{!NOT(paginator.hasPrevious)}" />
                    <apex:commandLink value="Previous"
                                      rendered="{!paginator.hasPrevious}"
                                      action="{!paginator.previous}" />
                &nbsp;&nbsp;
                                            
     <!-- PAGE SHORTCUTS -->
    <apex:repeat value="{!paginator.previousSkipPageNumbers}" var="skipPageNumber">
                    <apex:outputPanel >
                        <apex:commandLink value="{!skipPageNumber+1}" action="{!skipToPage}">
                           <apex:param name="pageNumber" id="pageNumber" value="{!skipPageNumber}" assignto="{!pageNumber}" />
                        </apex:commandLink>
              
           </apex:outputPanel>
                &nbsp;&nbsp;
                </apex:repeat>
                
    <apex:outputText style="text-decoration:none; font-weight:bold; background-color: #FFFF00"
                                 value="{!paginator.pageNumber+1}"/>
                &nbsp;&nbsp;
                <apex:repeat value="{!paginator.nextSkipPageNumbers}" var="skipPageNumber">
                    <apex:outputPanel >
                    <apex:commandLink value="{!skipPageNumber+1}" action="{!skipToPage}">
                       <apex:param name="pageNumber" id="pageNumber" value="{!skipPageNumber}" assignto="{!pageNumber}" />
                    </apex:commandLink>
                    </apex:outputPanel>
                &nbsp;&nbsp;
                </apex:repeat>
                                               
    <!-- NEXT -->
                <apex:outputText value="Next"
                                 rendered="{!NOT(paginator.hasNext)}" />
                    <apex:commandLink value="Next"
                                      rendered="{!paginator.hasNext}"
                                      action="{!paginator.next}" />
                &nbsp;&nbsp;
                                                      
    <!-- LAST -->
                <apex:outputText value="Last"
                                 rendered="{!NOT(paginator.hasNext)}" />
                    <apex:commandLink value="Last"
                                      rendered="{!paginator.hasNext}"
                                      action="{!paginator.last}" />
                &nbsp;&nbsp;
                                                                 
     <!-- Page Size Options -->
                Page Size:
                  <apex:actionRegion >
                    <apex:selectList value="{!paginator.pageSize}"
                                     size="1">
                      <apex:selectOptions value="{!paginator.pageSizeOptions}" />
                          <apex:actionSupport event="onchange"
                                              rerender="pbs1"
                                              status="status" />
                    </apex:selectList>
                  </apex:actionRegion>
              </apex:outputPanel>
            </apex:facet>
        </apex:pageBlockTable>
     </apex:facet>
      </apex:actionstatus>
 </apex:PageBlockSection>
 </apex:pageBlock>
 </apex:form>
</apex:page>


The Controller Code:



global class OpportunityOnAccountDetailExtension implements SObjectPaginatorListener {

String selectedStage;
Account acc;
    
    global List accounts   {get;set;}
    global SObjectPaginator paginator  {get;set;} 
    global Integer          pageNumber {get;set;}
    private String sortDirection = 'ASC';
    private String sortExp = 'name';
    
List oppList = new List();

    public OpportunityOnAccountDetailExtension(ApexPages.StandardController controller) {
        this.acc = (Account)controller.getRecord();
        oppList = null;
        this.accounts = new List();
        this.paginator = new SObjectPaginator(
            2,                          //pageSize
            new List{2,5,10, 25, 50, 100, 200},  //pageSizeIntegerOptions
            this                        //listener
        ); 
        //this.paginator.setRecords(getoppList());
        
    }
    
    public String getselectedStage() {
        return selectedStage;
    }
 
    public void setselectedStage(String selectedStage) {
        system.debug('%%%%%%%%%%%% ' + this.selectedStage);
        this.selectedStage = selectedStage;
        system.debug('%%%%%%%%%%%% ' + this.selectedStage);
    }
    
    public List getItems() {
            List options = new List();
            options.add(new SelectOption('','--SELECT Opportunity Stage--'));
            
            Schema.DescribeFieldResult field = Opportunity.StageName.getDescribe();
 
        for (Schema.PicklistEntry f : field.getPicklistValues())
          options.add(new SelectOption(f.getLabel(),f.getLabel()));
          
            return options;
        }
    
    /*    
    public List getoppList(){
        system.debug('$$$$$$$$$$$$$$$$$$ ' + selectedStage);
            if(selectedStage==null){
                oppList  = null;
            }else{
            oppList  = [select name, stageName, Amount, CloseDate from Opportunity where AccountId =: acc.id and StageName =: String.escapeSingleQuotes(selectedStage)];
            }
            
            return oppList;
                
    }
    */
    
   public PageReference handleAccountSelected(){
        system.debug('$$$$$$$$$$ in handleAccountSelected()');
        string sortFullExp = sortExpression  + ' ' + sortDirection;
        string query = 'select name, stageName, Amount, CloseDate from Opportunity where AccountId =';
        query = query + ' ' + acc.id + ' ' + ' ';
        query = query + 'and StageName =' + ' '  + String.escapeSingleQuotes(selectedStage) + ' '+ ' order by ' + sortFullExp;
        system.debug('################## ' + query);
        this.paginator.setRecords([select name, stageName, Amount, CloseDate from Opportunity where AccountId =: acc.id and StageName =: String.escapeSingleQuotes(selectedStage)]);
        
        return null;
    }
    
    
    public PageReference test(){
        return null;
    } 


    global void handlePageChange(List newPage){
        this.accounts.clear();
        if(newPage != null && newPage.size() > 0){
            for(Integer i = 0; i < newPage.size(); i++){
                this.accounts.add(
                    new OppWrapper(
                        (Opportunity)newPage.get(i)
                        , i + this.paginator.pageStartPosition + 1
                    )
                );
            }
        }
    }
    
    
    global PageReference skipToPage(){
        this.paginator.skipToPage(pageNumber);
        return null;
    }
    
       public String sortExpression
   {
     get
     {
        return sortExp;
     }
     set
     {
       //if the column is clicked on then switch between Ascending and Descending modes
       if (value == sortExp)
         sortDirection = (sortDirection == 'ASC')? 'DESC' : 'ASC';
       else
         sortDirection = 'ASC';
       sortExp = value;
     }
   }

 public String getSortDirection()
 {
    //if not column is selected 
    if (sortExpression == null || sortExpression == '')
      return 'ASC';
    else
     return sortDirection;
 }

 public void setSortDirection(String value)
 {  
   sortDirection = value;
 }
 
    
       
    public class OppWrapper{
        
        public Opportunity obj{get;set;}
        public Integer serialNumber{get;set;}
        public Boolean selected{get;set;}
        
        public OppWrapper(Opportunity obj, Integer serialNumber){
            this.obj = obj;
            this.serialNumber = serialNumber;
            system.debug('@@@@@@@@@@@@@@@@ ' + obj.Name);
        }

        
    }
    
    
}

Wednesday, October 12, 2011

Experimenting with SOSL with JS Remoting for the first time


I tried SOSL for the first time in recently on 15th October, 2011 when I got an email from someone asking for help in developing a search functionality using visualforce.
His is requirements was simple, he wanted to search on contacts but wanted to use only single text box for searching on contacts. I had earlier developed a simple search functionality using SOQL and Java Script Remoting, so I just modified that page with SOSL working in the controller.

Here is the code for Visualforce page:

<apex:page controller="remoteTest">

 <script>
    var contacts;
    
    function contactSearch(name) {
         myp1.remoteTest.findContacts(name,handleContacts);
    }
     
    function handleContacts(result, event) {
       if(event.type == 'exception') {
             alert(event.message);
       } else {
             contacts = result;
             showContacts();
              }
    } 

     function showContacts() {
        var newList = "";
          for(var i = 0; i < contacts.length; i++) {
               newList += "<a href onclick='showContact("+i+")'>"+ contacts[i].Name+"</a><BR/>";

          }
               document.getElementById('contactList').innerHTML = newList;

          } 

      function showContact(index) {
           document.getElementById('phone').innerHTML = 'Phone: '+contacts[index].Phone;
           document.getElementById('email').innerHTML = 'Email: '+contacts[index].Email;

       }

 </script> 

    <input id="nameField" type="text" onKeyDown="contactSearch(document.getElementById('nameField').value)" />

      <button onChange="contactSearch(document.getElementById('nameField').value)">Search Contacts</button>
 

 <div style="width: 50%;">
    <div id="contactList"></div>
   

    <div style="float: right;">
        <div id="phone"></div>
        <div id="email"></div>
    </div>

  </div>

  </apex:page>
Demo Page: http://testdomain5-developer-edition.na3.force.com/MySite


Here is the controller code:

global class remoteTest {

      @RemoteAction
      global static Contact[] findContacts(string Name) {
          Name = '%'+Name+'%';
          Contact[] c = [SELECT ID, Name, Phone, Email from Contact where NAME LIKE :Name ];
          return c;
      }
  }