Thursday, July 6, 2017

Fetching file from external/public URL and storing it into Salesforce

If you have file URL for file stored outside the salesforce, then you can fetch file information from external URL by performing a call out to external system and store the file in salesforce.

For Demo purpose, I have uploaded a pdf file in Google drive and shared it with link with people. Now I will fetch this file and will store it as attachment in salesforce under account record.

File URL-  https://drive.google.com/file/d/0ByXILxflqQ2jWGpNVmI1WW9uYTQ/view?usp=sharing


Below is apex class which will help us to perform this activity:
Public class FileDownLoadUtility{
public static blob fetchFileFromExternalUrl(String extFileUrl){
//extFileUrl='https://drive.google.com/file/d/0ByXILxflqQ2jWGpNVmI1WW9uYTQ/view?usp=sharing';
Http h = new Http();
HttpRequest req = new HttpRequest();
//Replace any spaces in extFileUrl with %20
extFileUrl = extFileUrl.replace(' ', '%20');
//Set the end point URL
req.setEndpoint(extFileUrl);
req.setMethod('GET');
req.setHeader('Content-Type', 'application/pdf');
req.setCompressed(true);
req.setTimeout(60000);
//Now Send HTTP Request
HttpResponse res = h.send(req);
system.debug('Response from Server: ' + res.getBody());
//getBodyAsBlob method was will convert the response into Blob
blob retFile = res.getBodyAsBlob();
return retFile;
}
public static Id createAttachment(blob fileContent, String recordId, String fileType){
//for pdf files content type should be pdf
//for jpeg file content type should be image/jpeg
Attachment attach = new Attachment();
attach.ParentId = recordId;
attach.Name = 'FileFromExtenalSource.pdf';
attach.Body = fileContent;
attach.contentType = fileType;
insert attach;
return attach.id;
}
}


Now you can run below code in developer console to test this:

//you can specify any record Id where you want to store file as attachment
String RecordId='0019000000ld4kN'; 
String fileContentType='pdf';
String extFileURL='https://drive.google.com/file/d/0ByXILxflqQ2jWGpNVmI1WW9uYTQ/view?usp=sharing';
blob fileBlob=FileDownLoadUtility.fetchFileFromExternalUrl(extFileURL);
Id attachmentId = FileDownLoadUtility.createAttachment(fileBlob, RecordId , fileContentType);
system.debug('*****attachmentId:'+attachmentId);

Note: 
  • If you are trying to fetch file from external source which is authenticated, then pass authorization parameters in HTTP Request headers
  • You should specify the file content type before creating attachment in order to properly view file.
  • You can add additional parameter as fileName in "createAttachment" method, if you you want specify the attachment name while creating attachment.

While trying above code you may get below error: 

For this you need to add external URL in Remote Site Settings. For this example, I have added "https://drive.google.com/" in remote site settings.

Web page : Ways to resolve "Collection size xxxx exceeds maximum size of 1000" on VF page

Visualforce pages are not designed to display more than 1000 records in UI. So if you have controller method which returns more than 1000 records and you are displaying the records on UI, then you will receive error "Collection size xxxx exceeds maximum size of 1000"  where xxxx is number of records returned by your controller method.


So now if you have to display more than 1000 records on UI, then you can use below options:
  • Use @ReadOnly annotation on method or on page attribute.

As per Salesforce Documetation:

Normally, queries for a single Visualforce page request may not retrieve more than 50,000 rows. In read-only mode, this limit is relaxed to allow querying up to 1 million rows.

In addition to querying many more rows, the readOnly attribute also increases the maximum number of items in a collection that can be iterated over using components such as <apex:dataTable>, <apex:dataList>, and <apex:repeat>. This limit increased from 1,000 items to 10,000.

  • You can restrict the number of records returned by method and provide pagination option using controller method.

For this you have to write the logic in controller and every time you will send request to server in order to render the page.


You can also jquery datatables and pass the JSON data to it to create table for you and you will get pagination, sorting or filtering of records on client side itself which will be very quick.

So we will pass more than 1000 records as a JSON to VF page and by using jQuery, we can build table.

Below is sample apex class and VF page code:
public class CollectionLimitController {
public List<Account> collectionSizeVariable{get;set;}
public String AccountInformationString {get;set;}
public CollectionLimitController(){
collectionSizeVariable = new List<Account>();
for(integer i=0;i<50007;i++){
collectionSizeVariable.add(new Account(name='sunil'+i, type='Prospect', Industry='IT'));
}
datasetForTable dataSet =new datasetForTable();
list<list<String>> dataValues=new list<list<String>>();
list<list<String>> columnsLabels=new list<list<String>>();
columnsLabels.add(new List<String>{'Account name'});
columnsLabels.add(new List<String>{'Type'});
columnsLabels.add(new List<String>{'Industry'});
for(Account sb: collectionSizeVariable){
list<String> fieldvalues = new List<String>();
if(sb.name != null && sb.name != ''){
fieldvalues.add(sb.name );
}else{
fieldvalues.add('');
}
if(sb.Type != null && sb.Type != ''){
fieldvalues.add(sb.Type);
}else{
fieldvalues.add('');
}
if(sb.Industry != null && sb.Industry != ''){
fieldvalues.add(sb.Industry);
}else{
fieldvalues.add('');
}
dataValues.add(fieldvalues);
}
dataSet.data = dataValues;
dataSet.columns = columnsLabels ;
AccountInformationString = JSON.serialize(dataSet);
system.debug('************AccountInformationString:'+AccountInformationString);
}
public class datasetForTable{
public List<List<String>> columns;
public List<List<String>> data;
}
}
<apex:page controller="CollectionLimitController" sidebar="false">
<head>
<apex:includescript value="https://code.jquery.com/jquery-1.11.1.min.js" / >
<apex:includescript value="https://cdn.datatables.net/1.10.4/js/jquery.dataTables.min.js" />
<apex:stylesheet value="https://cdn.datatables.net/1.10.4/css/jquery.dataTables.css" />
<apex:includescript value="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js" / >
<apex:includescript value="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" />
<script>
var sk= $.noConflict();
var tableData = '{!AccountInformationString}';
//alert('tableData :'+tableData );
sk(document).ready(function() {
tableData =tableData .replace(/(&quot\;)/g,"\"")
tableData = tableData .replace(/(&lt\;)/g,'<').replace(/(&gt\;)/g,'>').replace(/(&#39\;)/g,'\'').replace(/(&amp\;)/g,'&');
var jsonData = JSON.parse(tableData)
//alert('*****jsonData :'+jsonData );
console.log(jsonData);
generateTable(JSON.parse(tableData));
});
function generateTable(jsonData){
var tableHeaders="";
$.each(jsonData.columns, function(i, val){
tableHeaders += "<th>" + val + "</th>";
});
$("#tableDiv").empty();
$("#tableDiv").append('<table id="displayTable" class="display" cellspacing="0" width="100%"><thead><tr>' + tableHeaders + '</tr></thead></table>');
$("#displayTable").dataTable(jsonData);
}
</script>
</head>
<body>
<div id="tableDiv" style="width:100%;overflow: auto;"></div>
</body>
</apex:page>

VF page Output (Displaying more than 50000 records)



If you have to dispaly hyperlink on account name, then modify the account name value before adding it to fieldvalues list at line number 19 in apex class as shown below:
sb.name= '<a href=\'/' + sb.id + '\'  target=\'_blank\'>' + sb.name+ '</a>';



This will display hyper link to account record. In my example code, I am adding new records to list so id are not present. If you are using query to add records in account list, then you can use above code snippet to show hyperlink.