ApexDoc - A Salesforce Code Documentation Tool

Tuesday, January 25, 2011 by Aslam - The Alexendra
Hi All,
Most of you java people may be familar with "javadoc" utility, and who are not from java they must know about good code documentation :).

A good documentation is always a good thing for your project for long term success. A good documentation contains well defined comments, usage, public methods used, their parameters, return types, author, dates etc. Generally documentations used for a open source project or a library projects, so that if any new person is using your library or project then he will get good help about that easily.

I am working in Salesforce about 3 years with lot of Apex coding. I also created some libraries in Apex also. I wanted something similar documentation for my work using same like javadoc. But i could not found any tool exist for that. So started this utility project in java. My main moto is to make it as simpler as 'javadoc' command. So, here comes my 'apexdoc'.

Guidelines to use apexdoc:-

Go to here http://www.aslambari.com/apexdoc.html and download the "apexdoc" java client. You can also find one screen cast there. Its a zip, so after download, unzip it on your disc, for example D:\apexdoc.

Command Syntax:-

apexdoc <source_directory> [<target_directory?>] [<homefile>] [<authorfile>]

1) source_directory :- The folder location which contains your apex .cls classes. Ideally it is the folder 'classes' under your Eclipse IDE project setup on disc under "src" subfolder. I think best is that you should first copy that classes folder somewhere on disc for example "D:\myproject\classes\", and use this as source folder.

2) target_directory :- This option is optional. This is to specify your target folder where documentation will be generated. If not given then it will generates the output documentation into current directory.

3) homefile :- This option is optional. This is to specify your contents for the home page right panel, Ideally it contains the project information for which you making documentation and it MUST be in html format containing data under tags.

4) authorfile :- This option is optional. This is a simple text file path containing following info.

---------------------
projectname=<project name will be shown on top header>
authorname=<author name will be shown on top header>
email=<email will be shown on top header>

sampleauthor.txt
----------------------
projectname=my project
authorname=Aslam Bari
email=aslam.bari@gmail.com

5) Ideally if you need logo at top of your documentation header, you must put your file under your target directory with name logo.jpg with dimension.

Sample commands:-
1) Sample 1:-

apexdoc D:\myproject\classes D:\home\html D:\myhome.html D:\myauthor.txt

It will generate documentation of all "*.cls" files containing under "D:\myproject\classes" folder and output will be generated under "D:\home\html" folder with name "ApexDocumentation", your home page will contain project info from "D:\myhome.html" file, and your header will get contents from D:\myauthor.txt file.

2) Sample 2:-
apexdoc D:\myproject\classes

It will generate documentation in current folder of given source directory "D:\myproject\classes".

If all goes fine you will get one good documentation of all your classes, public properties and public methods generated. One sample generated output is here:-


Guidelines for comments and code:-

You code must have two types of comments. One for Classes, one for Methods. By default all public properties will be fetched in documentation, no need for comments for them.
Comments must start with /** and ends with */
Following attributes are supported for now:-

@author
@date
@param
@param
---(multiple @param supported)
@description
@return


Some sample code with comments as below:-

/**
* @author Aslam Bari
* @date 25/01/2011
* @description Class calculates some accounting information based on user experience and given methods. Usually user need to make one object of the class and call methods direcly.
*/

public class Accounting{
public string accountNo {get;set;}
public Integer discount {get;set;}
public string bankname {get;set;}

private string balance = 0;


/**
* @author Mark P
* @date 25/01/2011
* @description It accepts one account number and discount from external
user and calculates some info based on user profile and return the total price.
* @param accountNumber Users account number
* @param discount discount applicable for the user
* @return Double Price calculated after calculating based on profile
*/
public double calculatePrice(string accountNumber, integer discount){
-----------------
-----------------
-----------------
return price;
}

}

This tool is in Beta version and may contains bugs. I would like to get your feedbacks on this and will surely enhance this tool based on your reported bugs and feedback. Reach me at aslam.bari@gmail.com

Thanks
Aslam Bari

Ruby and Salesforce Integration with SOAP

Saturday, January 15, 2011 by Aslam - The Alexendra
Hi All,
I am not a Ruby guy, but last few days i had to look into this language, moreover in the context of calling apex web service method using Ruby. I love doing this using java and php, even i did in python also, but this time its a challenge for me to achieve this. Its a challenge because lack of documentation and sample codes on net and as a newbie in Ruby world. As every new developer i also searched many times on net but was struggling sometimes setting up Ruby, sometimes rubygems, sometimes things were not working. I left many times work in middle long before also. But searching and searching and i found this blogpost by Andrew very helpful. http://hideoustriumph.wordpress.com/2008/05/05/ws-deathstar-for-the-rest-of-us/.

So most of the things i took from that blog only and here i am describing some sample code how i achieve my task.

1) You first need to install ruby. I had InstantRails 2.0. That fits my need.
2) Now first go to your developer account, Setup->Develop->API, choose "enterprise.wsdl" and save it somewhere on your disk , for example in my case i am saving it as below location.
D:\RubyRails\rails_apps\sfdc\enterprise.xml
3) Open the ruby console
4) Then you need to install "SOAP4R" by this command.
gem install soap4r

When i ran this command, i got errors like "Errno::EADDRNOTAVAIL". Its because my gems were outdated, if this is the same case with you then you need to install first updated gems. so...
5) go to this site http://rubygems.org/pages/download
6) Download the gems and unpack to a directory and go to that directory from the command line
7) Install with:
ruby setup.rb

8) Now issue the command to install soap4r
gem install soap4r

Hopefully this time it will get installed.
9) Now go to that directory where you saved the "enterprise.xml" from command line and issue following command.

ruby wsdl2ruby.rb --wsdl D:\RubyRails\rails_apps\sfdc\enterprise.xml --type client --force

You will see 4 files created into your current directory ('sfdc' in above case)

10) Now here is the code for one extra file which i copied from the above blogpost for easy login
client_auth_header_handler.rb
--------------------------------

require 'soap/header/simplehandler'

class ClientAuthHeaderHandler < SOAP::Header::SimpleHandler
SessionHeader
= XSD::QName.new("rn:enterprise.soap.sforce.com", "SessionHeader")

attr_accessor :sessionid
def initialize
super(SessionHeader)
@sessionid
= nil
end

def on_simple_outbound
if @sessionid
{
"sessionId" => @sessionid}
end
end

def on_simple_inbound(my_header, mustunderstand)
@sessionid
= my_header["sessionid"]
end
end


11) And now here is the final code for doing Login, fetching records from Account object and Printing raw output on screen. Change the username, password and security token as needed.
test.rb
--------------------------------

require 'rubygems'
gem
'soap4r'
require
'soap/soap'
require
'defaultDriver'
require
'client_auth_header_handler'
require
'soap/wsdlDriver'


d
= Soap.new
d.wiredump_dev
= STDOUT

h
= ClientAuthHeaderHandler.new # Create a new handler

l
= d.login(:username => "USERNAME", :password => "PASSWORD" + "SECURITY_TOKEN")

d.endpoint_url
= l.result.serverUrl # Change the endpoint to what login tells us it should be
h.sessionid = l.result.sessionId # Tell the header handler what the session id is
d.headerhandler << h # Add the header handler to the Array of headerhandlers

d.getUserInfo(
"")
d.query(:queryString
=> "select id,name from account")



12) For test the above code, go to ruby console, go to "sfdc" folder and issue following command.
ruby test.rb

13) Now, the main task for which i struggled and not found any easy code on net. Calling apex method from ruby. I tried different things and thought it might be of few lines but may be complex. But finally after so many hit and trial i found that, the code is really very small and easy enough.

14) For example, lets say i want to make one webservice method in Apex to just print one greeting message on screen to passed one Name param. Create one apex class like this.
MyService.cls
--------------------------------
global class MyService{
webservice
static string printMe(string name){
return "Hello " + name;
}
}
15) Save the above class and click on WSDL link/button for this class. You will get one xml file regarding this class. Save it to your disk. For example i am saving this to "service" folder with name "myservice.xml":-
D:\RubyRails\rails_apps\sfdc\service\myservice.xml

16) Now here is the complete code to invoke the "printMe" method, for calling this you also need login first, so the complete example is as below.
test.rb
------------------------------------------------
require 'rubygems'
gem
'soap4r'
require
'soap/soap'
require
'defaultDriver'
require
'client_auth_header_handler'
require
'soap/wsdlDriver'


d
= Soap.new
d.wiredump_dev
= STDOUT

h
= ClientAuthHeaderHandler.new # Create a new handler

l
= d.login(:username => "USERNAME", :password => "PASSWORD" + "SECURITY_TOKEN")

d.endpoint_url
= l.result.serverUrl # Change the endpoint to what login tells us it should be
h.sessionid = l.result.sessionId # Tell the header handler what the session id is
d.headerhandler << h # Add the header handler to the Array of headerhandlers

client
= SOAP::WSDLDriverFactory.new( 'service/myservice.xml' ).create_rpc_driver
client.wiredump_dev
= STDOUT
client.headerhandler
<< h
result
= client.printMe(:name => "Aslam Bari");


17) If all goes well, you will see raw xml output on console printed.
18) Now its time to explore more in your Ruby skills with SFDC :)


Thanks
Aslam Bari

Simple Paypal Integration with Salesforce (Step by Step)

Tuesday, January 11, 2011 by Aslam - The Alexendra
Hi All,
Recently I got a need to implement Paypal integration with Salesforce for a basic payment for Merchant Type account, the payments would be from Customers using Credit Cards like Visa etc. I started looking into Paypal methods to find out which is best to implement using Apex, then i found one old blog post very helpful (Unfortunately that blog post is removed recently so not able to paste url here). The method which i preferred is DoDirectPayment. I did some modification in that code and provided in this blog.
The main problem i faced, was not the code, the testing. Like me, many new developers face this problem, thats why i came with this blog to describe step by step procedure to do a simple test with your paypal payment process using salesforce code. This might be useful for the developers who dealing with Paypal first time as well as want to integrate it with Salesforce.




1) Setup one sandbox account.

Go to http://developer.paypal.com and create one account there. After creating, login there only. In the left menu you will find "Test Accounts" link. Click on that.
From the right side screen, you can create multiple test accounts.
For testing purpose generally you need two type of accounts. One is "Seller" and one is "Buyer". Its always better to create preconfigured accounts.
Click on "PreConfigured" link to create one 'Buyer' account, set some amount there.
Click on "PreConfigured" link to create one 'Seller' account, set some amount there.
Click on "PreConfigured" link to create one 'Website Payments Pro' account, used for Merchant (Seller), set some amount there. Dont choose seller account this time because we going to use "DoDirectPayment" API method which need this type of account.

We dont need "Seller" account but i found after some struggle that if you not create any Seller account first then if you create any 'Website Payments Pro', then it will always be "unverified". I am not sure why it is like this. But this is "My Best Practice" that you should first create one Seller account, then create this ''Website Payments Pro' account. And verify, that one is marked as "Verified" as shown in the below screenshot.




2) Test Merchant Account API Setup

Now, select your 'website payments pro' account using radio button there and click 'Enter Sandbox Test Site'.
Login with your password.
Click on Profile link after you login. It will be on top right side.
Click on 'Request API Credentials' link in bottom left side.
Click on 'Set up PayPal API credentials and permissions' link.
Click on 'View API Signature' link and note down your API Username, API Password, API Signature
Click on 'Grant API Access' link on previous screen. Here give your API username and Check all checkboxes and Save
Now you ok with configuration.

2) Salesforce Code setup
Now copy paste following Apex Class code in your org.
Add "https://api-3t.sandbox.paypal.com/2.0/" url into "Remote Site Setting".

In the below code change your API Username, API Password and Signature.
public class PaypalProccessor{
public string amount;
public String result {set;get;}
public string FirstName{get; set; }
public string LastName{get; set; }
public string Addy1{get; set; }
public string Addy2{get; set; }
public string Country{get; set; }
public string CardType{get; set; }
public string CardNo{get; set; }
public string expMo{get; set; }
public string expYe{get; set; }
public string CVV{get; set; }
public string city{get; set; }
public string state{get; set; }
public string zip{get; set; }
public string payer{ get; set; }
public string transid {get; set;}
public string message {get; set; }
public string err {get; set; }
public string rawResponse {get; set; }

public PaypalProccessor(){
city
= '';
state
= '';
zip
= '';
CVV
= '';
expYe
= '';
expMo
= '';
CardNo
= '';
CardType
= 'Visa';
FirstName
= '';
LastName
= '';
Country
= 'US';
Addy1
= '';
Addy2
= '';
payer
= '';
err
= '';
message
= '';
}


public String doDirectPayment()
{

Http h
= new Http();
HttpRequest req
= new HttpRequest();
String url
= 'https://api-3t.sandbox.paypal.com/2.0/';
string un
= 'sel2_1294222298_biz_api1.gmail.com';
string pw
= 'xxxxxxxxxx';
string sig
= 'AwG6Yp1inPr4tudjFvxxxxxxxtAVuo9M5cH1nAqpxK2Biv1RrZa4gX';


String doDirectRequest;
doDirectRequest
= '<soap:Envelope xmlns:soap=' + '\'' + 'http://schemas.xmlsoap.org/soap/envelope/' + '\'' + ' xmlns:xsi=' + '\''+ 'http://www.w3.org/2001/XMLSchema-instance' + '\'' + ' xmlns:xsd=' + '\''+ 'http://www.w3.org/2001/XMLSchema' + '\'' + '>';
doDirectRequest
+= '<soap:Header><RequesterCredentials xmlns="urn:ebay:api:PayPalAPI"><Credentials xmlns="urn:ebay:apis:eBLBaseComponents">';
doDirectRequest
+= '<Username>' + un + '</Username><ebl:Password xmlns:ebl="urn:ebay:apis:eBLBaseComponents">' + pw;
doDirectRequest
+= '</ebl:Password><Signature>' + sig + '</Signature>';
doDirectRequest
+= '</Credentials></RequesterCredentials></soap:Header><soap:Body><DoDirectPaymentReq xmlns="urn:ebay:api:PayPalAPI">';
doDirectRequest
+= '<DoDirectPaymentRequest><Version xmlns="urn:ebay:apis:eBLBaseComponents">1.00</Version>';
doDirectRequest
+= '<DoDirectPaymentRequestDetails xmlns="urn:ebay:apis:eBLBaseComponents">';
doDirectRequest
+= '<PaymentAction>Sale</PaymentAction><PaymentDetails><OrderTotal currencyID="USD">' + amount + '</OrderTotal>';
doDirectRequest
+= '<ShipToAddress><Name>' + FirstName + ' ' + LastName + '</Name><Street1>' + Addy1 + '</Street1><Street2>' +Addy2 + '</Street2>';
doDirectRequest
+= '<CityName>' + city + '</CityName><StateOrProvince>' + state + '</StateOrProvince><PostalCode>' + zip + '</PostalCode>';
doDirectRequest
+= '<Country>' + country + '</Country></ShipToAddress>';
doDirectRequest
+= '</PaymentDetails><CreditCard><CreditCardType>' + CardType + '</CreditCardType><CreditCardNumber>' + CardNo + '</CreditCardNumber>';
doDirectRequest
+= '<ExpMonth>' + expMo + '</ExpMonth><ExpYear>' + expYe + '</ExpYear><CardOwner><PayerStatus>verified</PayerStatus>';
doDirectRequest
+= '<PayerName><FirstName>' + FirstName+ '</FirstName><LastName>' + LastName + '</LastName></PayerName><PayerCountry>' + country + '</PayerCountry>';
doDirectRequest
+= '<Address><Street1>' + Addy1 + '</Street1><Street2>' + Addy2 + '</Street2><CityName>' + city + '</CityName>';
doDirectRequest
+= '<StateOrProvince>' + state + '</StateOrProvince><Country>' + country + '</Country><PostalCode>' + zip + '</PostalCode></Address>';
doDirectRequest
+= '</CardOwner><CVV2>' + CVV + '</CVV2></CreditCard></DoDirectPaymentRequestDetails>';
doDirectRequest
+= '</DoDirectPaymentRequest></DoDirectPaymentReq></soap:Body></soap:Envelope>';

req.setBody(doDirectRequest);

req.setEndpoint(url);
req.setMethod(
'POST');
req.setHeader(
'Content-length', '1753' );
req.setHeader(
'Content-Type', 'text/xml;charset=UTF-8');
req.setHeader(
'SOAPAction','');
req.setHeader(
'Host','api-aa.sandbox.paypal.com');
HttpResponse res
= h.send(req);
String xml
= res.getBody();
rawResponse
= xml;
system.debug(
'::' + rawResponse);
XmlStreamReader reader
= res.getXmlStreamReader();
result
= readXMLResponse(reader,'Ack');
reader
= res.getXmlStreamReader();
err
= readXMLResponse(reader, 'LongMessage');

if (result == 'Success')
{
reader
= res.getXmlStreamReader();
transid
= readXMLResponse(reader, 'TransactionID');
system.debug(
'::' + transid );
}
else
{
result
= err;
}
return result;
}

public String readXMLResponse(XmlStreamReader reader, String sxmltag)
{
string retValue;
// Read through the XML
while(reader.hasNext())
{
if (reader.getEventType() == XmlTag.START_ELEMENT)
{
if (reader.getLocalName() == sxmltag) {
reader.next();
if (reader.getEventType() == XmlTag.characters)
{
retValue
= reader.getText();
}
}
}
reader.next();
}
return retValue;
}

public String pay(){

err
= '';
if (FirstName == '')
err
= err + 'You must enter a First Name.\n';
if (LastName == '')
err
= err + 'You must enter a Last Name.\n';
if (Addy1 == '')
err
= err + 'You must enter an Address.\n';
if (city == '')
err
= err + 'You must enter a City.\n';
if (state == '')
err
= err + 'You must enter a State.\n';
if (zip == '')
err
= err + 'You must enter a Zip.\n';
if (CardNo == '')
err
= err + 'You must enter a Credit Card Number.\n';
if (expMo.length() != 2)
err
= err + 'Expiration month must be in the format MM.\n';
if (expYe.length() != 4)
err
= err + 'Expiration year must be in the format YYYY.\n';

if (amount == '0')
{
err
+= 'Amount 0 can not process.\n';
message
= err;
}
message
= err;
if (err == '')
{
message
= doDirectPayment();
}

if (message == 'Success')
{

}
else
{
//pr = null;
}
return message;
}

}


3) Run and Test
Now you can use below code to do a sample payment. Run this code either on your VF screen or from System Log screen.
PaypalProccessor p = new PaypalProccessor();
p.city
= 'ajm';
p.state
= 'CA';
p.zip
= '4534';
p.CVV
= '';
p.expYe
= '2016';
p.expMo
= '01';
p.CardNo
= '4028398122647025';
p.CardType
= 'Visa';
p.FName
= 'aslam';
p.LName
= 'bari';
p.Country
= 'US';
p.Addy1
= '44';
p.Addy2
= '433';
p.payer
= 'abuy_1294681533_per@gmail.com';
p.amount
= '100';
string message
= p.pay();
string transactionid
= p.transid;
system.debug(
'#### Message::' + message);
system.debug(
'#### Transaction Id::' + transactionid);

You will see that you got one Transaction Id and 'Success' message. If you dont find success message, then print "err" property to check whats wrong.
Log into your test pro account and check your balance, it might be increased. If so, its all fine :)


Thanks
Aslam Bari