HTTP response code | Description |
---|---|
200 | “OK” success code, for GET or HEAD request. |
201 | “Created” success code, for POST request. |
204 | “No Content” success code, for DELETE request. |
300 | The value returned when an external ID exists in more than one record. The response body contains the list of matching records. |
304 | The request content has not changed since a specified date and time. The date and time is provided in a If-Modified-Since header. See Get Object Metadata Changes for an example. |
400 | The request couldn’t be understood, usually because the JSON or XML body contains an error. |
401 | The session ID or OAuth token used has expired or is invalid. The response body contains the message anderrorCode. |
403 | The request has been refused. Verify that the logged-in user has appropriate permissions. |
404 | The requested resource couldn’t be found. Check the URI for errors, and verify that there are no sharing issues. |
405 | The method specified in the Request-Line isn’t allowed for the resource specified in the URI. |
415 | The entity in the request is in a format that’s not supported by the specified method. |
500 | An error has occurred within Force.com, so the request couldn’t be completed. Contact Salesforce Customer Support. |
Thursday, August 13, 2015
Error codes for http requests
Friday, July 31, 2015
Status=Length Required, StatusCode=411 error in Apex callout
HttpRequest request = new HttpRequest();request.setHeader('Content-Length', '512');
Generate Json Data
Here is Simple classs to form json data..
public class ParentRecord {
public String recordType, entity, job;
public ChildRecord[] item;
public ParentRecord(String recordType, String entity, String job) {
this.recordType = recordType;
this.entity = entity;
this.job = job;
item = new ChildRecord[0];
}
}
public class ChildRecord {
public String item;
public Decimal quantity, amount;
public ChildRecord(String item, Decimal quantity, Decimal amount) {
this.item = item;
this.quantity = quantity;
this.amount = amount;
}
}
pass paramenters to the above class using below code.
// Query parent and children, you can do both at once.
// Replace object names, relationship names, and field names.
Parent record = [SELECT Id, recordType, entity, job, (SELECT Id, Item, quantity, amount FROM Children) FROM Parent WHERE Id = :someId];
// Create a parent record entry from the utility class
ParentRecord parent = new ParentRecord(record.recordType, record.Entity, record.job);
// Loop through and build the item list
for(Child lineItem: record.Children) {
parent.item.add(new ChildRecord(lineItem.Item, lineItem.quantity, lineItem.Amount));
}
Now convert the data into json using JSON.serialize() method
String jsondatagen= JSON.serialize(parent);
Tuesday, June 2, 2015
Multiple integration ways with salesforce
Real-time mashups – Real-time mashups involve a custom built Visualforce UI in Salesforce. This UI
is similar to a traditional web app built with HTML in that custom branding can be implemented, complex user flows can be built and the web page can perform web service callouts to external systems to retrieve data. Real-time mashups are great for situations when data is not stored in salesforce.com but needs to be accessed immediately. An example of a real-time mashup would be an Agent screen that also needs to pull in related documents from multiple external document management systems for access by the Salesforce user.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Non-Visualforce mashups – In addition to developing Visualforce pages, Salesforce also offers the ability to embed third-party web pages directly within the UI – either directly or using Force.com Canvas. Both of these options integrate already built web applications, developed in other technologies and hosted in other locations, directly into the Salesforce user interface. SSO Web Tabs allow an administrator to create a new tab that passes user credentials to another web page, seamlessly authenticating the user. This is an extremely common use case to integrate a BI application that extends Salesforce’s reporting capabilities.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Force.com Canvas – This allows developers to embed an already built web application, utilizing a salesforce.com-developed JavaScript SDK, directly into existing Salesforce page layouts. For example: If you want to integrate a “Sales Actuals” component from your ERP system directly on your Account screen and you do not want to bother with Visualforce and Apex, you can use Force.com Canvas to display the web app and pass appropriate credentials and record IDs. Force.com Canvas allows for seamless interaction between Salesforce and the embedded web page to allow for dynamic web apps with full UI interactivity.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Real-time data integration – Real-time interfaces are typically required when a Salesforce user performs an action and expects an immediate response, whether it be a calculation result, an “order processed” message or something similar. Real-time integrations have higher risk and require complicated queuing mechanisms and error handling mechanisms to account for system outage and data errors. Often, a near real-time alternative is acceptable in the cases of “fire and forget” patterns (e.g. a user submits a record for processing and will receive an email notification once it has been processed successfully). This type of integration will utilize Salesforce Apex Callouts to call remote web services.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Near real-time data integrations – Near real-time interfaces can involve transactions within seconds or minutes, depending on need. In reality, many interfaces fall into this category. If a user wants to simply mark a record for processing and know that it will be sent within a couple of minutes and that they will be notified upon completion, near real-time is an optimal solution. It is more reliable than real-time because it has built-in queuing and reprocessing out of the box. Options for implementing this are outbound messages, which are a 100% declarative functionality within salesforce.com, and rapid polling (30 sec-5 min batching) by an ETL tool, utilizing the salesforce.com SOAP or REST APIs.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Batch data integrations – Batch data integrations are one of the most common forms of integrations. Batch is an excellent pattern for data that must reside in Salesforce (or be transferred out of, e.g. to a DW) for reporting or workflow purposes, that does not change that often and that is generally in large quantities. Batches can occur with time ranges from seconds to weeks if needed. Batches are the simplest type of interface to implement, with the majority of the development occurring in the ETL tool, not Apex code. Batch is also the ideal solution for handling flat files.
is similar to a traditional web app built with HTML in that custom branding can be implemented, complex user flows can be built and the web page can perform web service callouts to external systems to retrieve data. Real-time mashups are great for situations when data is not stored in salesforce.com but needs to be accessed immediately. An example of a real-time mashup would be an Agent screen that also needs to pull in related documents from multiple external document management systems for access by the Salesforce user.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Non-Visualforce mashups – In addition to developing Visualforce pages, Salesforce also offers the ability to embed third-party web pages directly within the UI – either directly or using Force.com Canvas. Both of these options integrate already built web applications, developed in other technologies and hosted in other locations, directly into the Salesforce user interface. SSO Web Tabs allow an administrator to create a new tab that passes user credentials to another web page, seamlessly authenticating the user. This is an extremely common use case to integrate a BI application that extends Salesforce’s reporting capabilities.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Force.com Canvas – This allows developers to embed an already built web application, utilizing a salesforce.com-developed JavaScript SDK, directly into existing Salesforce page layouts. For example: If you want to integrate a “Sales Actuals” component from your ERP system directly on your Account screen and you do not want to bother with Visualforce and Apex, you can use Force.com Canvas to display the web app and pass appropriate credentials and record IDs. Force.com Canvas allows for seamless interaction between Salesforce and the embedded web page to allow for dynamic web apps with full UI interactivity.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Real-time data integration – Real-time interfaces are typically required when a Salesforce user performs an action and expects an immediate response, whether it be a calculation result, an “order processed” message or something similar. Real-time integrations have higher risk and require complicated queuing mechanisms and error handling mechanisms to account for system outage and data errors. Often, a near real-time alternative is acceptable in the cases of “fire and forget” patterns (e.g. a user submits a record for processing and will receive an email notification once it has been processed successfully). This type of integration will utilize Salesforce Apex Callouts to call remote web services.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Near real-time data integrations – Near real-time interfaces can involve transactions within seconds or minutes, depending on need. In reality, many interfaces fall into this category. If a user wants to simply mark a record for processing and know that it will be sent within a couple of minutes and that they will be notified upon completion, near real-time is an optimal solution. It is more reliable than real-time because it has built-in queuing and reprocessing out of the box. Options for implementing this are outbound messages, which are a 100% declarative functionality within salesforce.com, and rapid polling (30 sec-5 min batching) by an ETL tool, utilizing the salesforce.com SOAP or REST APIs.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Batch data integrations – Batch data integrations are one of the most common forms of integrations. Batch is an excellent pattern for data that must reside in Salesforce (or be transferred out of, e.g. to a DW) for reporting or workflow purposes, that does not change that often and that is generally in large quantities. Batches can occur with time ranges from seconds to weeks if needed. Batches are the simplest type of interface to implement, with the majority of the development occurring in the ETL tool, not Apex code. Batch is also the ideal solution for handling flat files.
Monday, April 27, 2015
Salesforce to Salesforce Integration using rest api
HttpRequest req=new HttpRequest();
req.setMethod('POST');
req.setEndpoint('https://login.salesforce.com/services/oauth2/token');
String str1='grant_type=password&';
String str2='client_id=3MVG9Y6d_Btp4xp43IbeBaeCHQlfOiMTB3JhATKM9alz1Za2J.6ZGtnUdaDstYHy_uzU0deVIznH5NX4h3e7i&';
String str3='client_secret=464548859367584338&';
String str4='username=thimma.castiron@gmail.com&';
String str6='password=XXXXXXXXXXXXvtkdno8cYOq8H1Noa0uN4Jpu';
String str=str1+str2+str3+str4+str6;
req.setBody(str);
System.debug('HttpRequest :' +req);
HttpResponse response = null;
Http http = new Http();
response = http.send(req);
system.debug('@@@@@@@@@@@'+response.getBody());
string srg=response.getBody();
system.debug('&&&&&srg&&&&'+srg);
string srg1=srg.substringAfter( 'access_token ' );
system.debug('&&&&&&&&&'+srg1);
Map<String, Object> m =
(Map<String, Object>)
JSON.deserializeUntyped(srg);
system.debug('%%%%%%%%%%%%%%%%%'+m);
Object acesstoken=m.get('access_token');
system.debug('%%%%%%%%%acesstoken%%%%%%%%'+acesstoken);
string xyz=(string)acesstoken;
system.debug('%%%%%%%%%acesstoken x%%%%%%%%'+xyz);
//--------------------------------------------------------------------------------------------------------------------------------------------------------
HttpRequest req1=new HttpRequest();
req1.setMethod('POST');
req1.setEndpoint('https://ap1.salesforce.com/services/async/29/job');
req1.setHeader('Content-Type','application/xml');
req1.setHeader('X-SFDC-Session', ' ' + UserInfo.getSessionId());
//req1.setHeader('x-SFDC-Session:',xyz);
String str9='<?xml version="1.0" encoding="UTF-8"?><jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload"><operation>query</operation><object>Account</object><contentType>CSV</contentType></jobInfo>';
req1.setBody(str9);
HttpResponse response1 = null;
Http http1 = new Http();
response1 = http1.send(req1);
system.debug('@@@@@responsejflsflsdflsdfks@@@@@@'+response1.getBody());
string srg12=response1.getBody();
JSONParser parser = JSON.createParser(response1.getBody());
string ss=UserInfo.getSessionId();
system.debug('%%%%%%%%%parser %%%%%%%%%%'+parser );
DOM.Document doc = response1.getBodyDocument();
system.debug('%%%%%%%%%parser doc %%%%%%%%%%'+doc );
Dom.XMLNode address = doc.getRootElement();
string id1;
for(DOM.XMLNode xmlnodeobj:doc.getRootElement().getChildElements()){
if(xmlnodeobj.getName()=='id')
{
id1=xmlnodeobj.getText();
}
}
system.debug('%%%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$'+id1);
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
HttpRequest req2=new HttpRequest();
req2.setMethod('POST');
//'https://ap1.salesforce.com/services/async/29/job/75090000002MUlPAAW/batch'
string sss='https://ap1.salesforce.com/services/async/29/job/' +id1+'/batch';
req2.setEndpoint(sss);
req2.setHeader('Content-Type','text/csv');
req2.setHeader('X-SFDC-Session', ' ' + UserInfo.getSessionId());
String str11='select name ,id from account ';
req2.setBody(str11);
HttpResponse response2 = null;
Http http2 = new Http();
response2 = http2.send(req2);
system.debug('@@@@@@@@@@@'+response2.getBody());
DOM.Document doc1 = response2.getBodyDocument();
system.debug('%%%%%%%%%parser doc %%%%%%%%%%'+doc );
address = doc1.getRootElement();
string batchid;
for(DOM.XMLNode xmlnodeobj1:doc1.getRootElement().getChildElements()){
if(xmlnodeobj1.getName()=='id')
{
//id1=xmlnodeobj1.getText();
batchid=xmlnodeobj1.getText();
}
}
system.debug('%%%%%%%%%jobid doc %%%%%%%%%%'+id1 );
system.debug('%%%%%%%%%batchid doc %%%%%%%%%%'+batchid );
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
HttpRequest req3=new HttpRequest();
req3.setMethod('GET');
//sss=sss+'/'+batchid+'/result';
//https://ap1.salesforce.com/services/async/29/job/75090000002MVvuAAG/batch/75190000004CCiZAAW/result
//req3.setEndpoint('https://ap1.salesforce.com/services/async/29/job/75090000002MVvuAAG/batch/75190000004CCiZAAW/result');
system.debug('################################################################'+sss);
system.debug('%%%%%%%%%jobid doc %%%%%%%%%%'+id1 );
system.debug('%%%%%%%%%batchid doc %%%%%%%%%%'+batchid );
string id2=string(id1);
string batchid2=string(batchid);
system.debug('%%%%%%%%%jobid2 doc %%%%%%%%%%'+id2 );
system.debug('%%%%%%%%%batchid2 doc %%%%%%%%%%'+batchid2 );
sss='https://ap1.salesforce.com/services/async/29/job/' + id2+'/batch'+'/'+ batchid2+'/result';
req3.setEndpoint(sss);
req3.setHeader('X-SFDC-Session', ' ' + UserInfo.getSessionId());
HttpResponse response3 = null;
Http http3 = new Http();
response3 = http3.send(req3);
system.debug('@@@@@@5555555555555555555555555555555555555555555555555@@@@@'+response3.getBody());
DOM.Document doc2 = response3.getBodyDocument();
address = doc2.getRootElement();
for(DOM.XMLNode xmlnodeobj2:doc2.getRootElement().getChildElements()){
if(xmlnodeobj2.getName()=='result')
{
id1=xmlnodeobj2.getText();
}
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
HttpRequest req4=new HttpRequest();
req4.setMethod('GET');
sss=sss+'/'+id1;
req4.setEndpoint(sss);
req4.setHeader('X-SFDC-Session', ' ' + UserInfo.getSessionId());
HttpResponse response4 = null;
Http http4 = new Http();
response4 = http4.send(req4);
system.debug('@@@@@@@@@@@'+response4.getBody());
req.setMethod('POST');
req.setEndpoint('https://login.salesforce.com/services/oauth2/token');
String str1='grant_type=password&';
String str2='client_id=3MVG9Y6d_Btp4xp43IbeBaeCHQlfOiMTB3JhATKM9alz1Za2J.6ZGtnUdaDstYHy_uzU0deVIznH5NX4h3e7i&';
String str3='client_secret=464548859367584338&';
String str4='username=thimma.castiron@gmail.com&';
String str6='password=XXXXXXXXXXXXvtkdno8cYOq8H1Noa0uN4Jpu';
String str=str1+str2+str3+str4+str6;
req.setBody(str);
System.debug('HttpRequest :' +req);
HttpResponse response = null;
Http http = new Http();
response = http.send(req);
system.debug('@@@@@@@@@@@'+response.getBody());
string srg=response.getBody();
system.debug('&&&&&srg&&&&'+srg);
string srg1=srg.substringAfter( 'access_token ' );
system.debug('&&&&&&&&&'+srg1);
Map<String, Object> m =
(Map<String, Object>)
JSON.deserializeUntyped(srg);
system.debug('%%%%%%%%%%%%%%%%%'+m);
Object acesstoken=m.get('access_token');
system.debug('%%%%%%%%%acesstoken%%%%%%%%'+acesstoken);
string xyz=(string)acesstoken;
system.debug('%%%%%%%%%acesstoken x%%%%%%%%'+xyz);
//--------------------------------------------------------------------------------------------------------------------------------------------------------
HttpRequest req1=new HttpRequest();
req1.setMethod('POST');
req1.setEndpoint('https://ap1.salesforce.com/services/async/29/job');
req1.setHeader('Content-Type','application/xml');
req1.setHeader('X-SFDC-Session', ' ' + UserInfo.getSessionId());
//req1.setHeader('x-SFDC-Session:',xyz);
String str9='<?xml version="1.0" encoding="UTF-8"?><jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload"><operation>query</operation><object>Account</object><contentType>CSV</contentType></jobInfo>';
req1.setBody(str9);
HttpResponse response1 = null;
Http http1 = new Http();
response1 = http1.send(req1);
system.debug('@@@@@responsejflsflsdflsdfks@@@@@@'+response1.getBody());
string srg12=response1.getBody();
JSONParser parser = JSON.createParser(response1.getBody());
string ss=UserInfo.getSessionId();
system.debug('%%%%%%%%%parser %%%%%%%%%%'+parser );
DOM.Document doc = response1.getBodyDocument();
system.debug('%%%%%%%%%parser doc %%%%%%%%%%'+doc );
Dom.XMLNode address = doc.getRootElement();
string id1;
for(DOM.XMLNode xmlnodeobj:doc.getRootElement().getChildElements()){
if(xmlnodeobj.getName()=='id')
{
id1=xmlnodeobj.getText();
}
}
system.debug('%%%%%%%$$$$$$$$$$$$$$$$$$$$$$$$$$'+id1);
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
HttpRequest req2=new HttpRequest();
req2.setMethod('POST');
//'https://ap1.salesforce.com/services/async/29/job/75090000002MUlPAAW/batch'
string sss='https://ap1.salesforce.com/services/async/29/job/' +id1+'/batch';
req2.setEndpoint(sss);
req2.setHeader('Content-Type','text/csv');
req2.setHeader('X-SFDC-Session', ' ' + UserInfo.getSessionId());
String str11='select name ,id from account ';
req2.setBody(str11);
HttpResponse response2 = null;
Http http2 = new Http();
response2 = http2.send(req2);
system.debug('@@@@@@@@@@@'+response2.getBody());
DOM.Document doc1 = response2.getBodyDocument();
system.debug('%%%%%%%%%parser doc %%%%%%%%%%'+doc );
address = doc1.getRootElement();
string batchid;
for(DOM.XMLNode xmlnodeobj1:doc1.getRootElement().getChildElements()){
if(xmlnodeobj1.getName()=='id')
{
//id1=xmlnodeobj1.getText();
batchid=xmlnodeobj1.getText();
}
}
system.debug('%%%%%%%%%jobid doc %%%%%%%%%%'+id1 );
system.debug('%%%%%%%%%batchid doc %%%%%%%%%%'+batchid );
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
HttpRequest req3=new HttpRequest();
req3.setMethod('GET');
//sss=sss+'/'+batchid+'/result';
//https://ap1.salesforce.com/services/async/29/job/75090000002MVvuAAG/batch/75190000004CCiZAAW/result
//req3.setEndpoint('https://ap1.salesforce.com/services/async/29/job/75090000002MVvuAAG/batch/75190000004CCiZAAW/result');
system.debug('################################################################'+sss);
system.debug('%%%%%%%%%jobid doc %%%%%%%%%%'+id1 );
system.debug('%%%%%%%%%batchid doc %%%%%%%%%%'+batchid );
string id2=string(id1);
string batchid2=string(batchid);
system.debug('%%%%%%%%%jobid2 doc %%%%%%%%%%'+id2 );
system.debug('%%%%%%%%%batchid2 doc %%%%%%%%%%'+batchid2 );
sss='https://ap1.salesforce.com/services/async/29/job/' + id2+'/batch'+'/'+ batchid2+'/result';
req3.setEndpoint(sss);
req3.setHeader('X-SFDC-Session', ' ' + UserInfo.getSessionId());
HttpResponse response3 = null;
Http http3 = new Http();
response3 = http3.send(req3);
system.debug('@@@@@@5555555555555555555555555555555555555555555555555@@@@@'+response3.getBody());
DOM.Document doc2 = response3.getBodyDocument();
address = doc2.getRootElement();
for(DOM.XMLNode xmlnodeobj2:doc2.getRootElement().getChildElements()){
if(xmlnodeobj2.getName()=='result')
{
id1=xmlnodeobj2.getText();
}
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
HttpRequest req4=new HttpRequest();
req4.setMethod('GET');
sss=sss+'/'+id1;
req4.setEndpoint(sss);
req4.setHeader('X-SFDC-Session', ' ' + UserInfo.getSessionId());
HttpResponse response4 = null;
Http http4 = new Http();
response4 = http4.send(req4);
system.debug('@@@@@@@@@@@'+response4.getBody());
Integration
/** Description : Ths class will Integrate Salesforce with Klout.
**/
public with sharing class KloutWithSalesforceTwitterRatingUpdate {
//Wrapper Class for First Response
public class firstResponseParsingWrapper {
//Response variables
public String id;
public String network;
//Constructor
public firstResponseParsingWrapper(String id, String network) {
this.id = id;
this.network = network;
}
}
//Wrapper for second response
public class KloutFinalResponseWrapper {
//Score from the response
public String score;
//Constructor
public KloutFinalResponseWrapper(String score) {
this.score = score;
}
}
//account
public Account account { get; set; }
public String twitterScore { get; set; }
//constructor
public KloutWithSalesforceTwitterRatingUpdate(ApexPages.StandardController stdController){
//Initiallize
twitterScore = '';
//account record
this.account = (Account)stdController.getRecord();
}
//Method for making callout and populating values retrieved from response is going to be diplayed on Visualforce Page
public void kloutTwitterRating() {
try {
//Http
Http http = new Http();
//Request
HttpRequest req = new HttpRequest();
req.setEndpoint('http://api.klout.com/v2/identity.json/twitter?screenName='+ twitterScore +'&key=4j6pe8zamj4dmh2by9tzv5sc');
req.setMethod('GET');
//Send request
HTTPResponse firstResponse = http.send(req);
System.debug('res::::::' + firstResponse.getBody());
//Body
String body = firstResponse.getBody();
//Deserializing response
KloutWithSalesforceTwitterRatingUpdate.firstResponseParsingWrapper parsedResponse = (KloutWithSalesforceTwitterRatingUpdate.firstResponseParsingWrapper)JSON.deserialize(body, firstResponseParsingWrapper.class);
System.debug('parsedResponse:::::::' + parsedResponse);
//String for id in response
String responseId = parsedResponse.id;
System.debug('responseId:::::::' + responseId);
//Second request for score
HttpRequest finalReq = new HttpRequest();
finalReq.setEndpoint('http://api.klout.com/v2/user.json/'+ responseId +'/score?key=4j6pe8zamj4dmh2by9tzv5sc');
finalReq.setMethod('GET');
//Send request
HTTPResponse lastResponse = http.send(finalReq);
System.debug('lastResponse::::::' + lastResponse.getBody());
//Body
String finalBody = lastResponse.getBody();
//Deserializing response
KloutWithSalesforceTwitterRatingUpdate.KloutFinalResponseWrapper parseFinalResponse = (KloutWithSalesforceTwitterRatingUpdate.KloutFinalResponseWrapper)JSON.deserialize(finalBody, KloutFinalResponseWrapper.class);
System.debug('parseFinalResponse::::::' + parseFinalResponse);
//Assigning value
account.Twitter_Rating__c = parseFinalResponse.score;
account.Validate_Score_Successfully__c = true;
account.Validate_Score_Last_Attempt__c = date.today();
}catch (exception e) {
//Error messages
ApexPages.Message errormsg = new ApexPages.Message(ApexPages.severity.ERROR,'UserName Not Found, Sorry Try Again');
ApexPages.addMessage(errormsg);
System.debug('e:::::::' + e);
}
}
}
Subscribe to:
Posts (Atom)