At Dreamforce a couple of weeks ago, Heroku announced the public beta for the Play! Framework on their cedar stack. So if you love Java but hate the pain associated with developing web apps, then the Play Framework is for you. It’s essentially a Ruby on Rails framework for Java. It makes Java development fun again!
Once again, this is a demo Java app running on Heroku using the Play! Framework. It uses the Force.com Web Service Connector (WSC) and the Partner jar to connect to a DE salesforce.com org. It uses the web services API to query for records, retrieve records to display, create new records and update existing ones. It should be good fodder for anyone wanting to start out with Play! and Force.com.
All of the code for this app is at github so fork awey. I’ve pulled out some of the more important parts of the app for a quick peek. There’s also an overview of one of the important classes to give you something to look at.
app/controllers/Account.java
The account controller contains all of the business logic for integration with Force.com and then packages up the returns for the views.
Abstract: As more and more applications move to the Cloud, Force.com integration is no longer just about connecting with on-premise systems and databases. You now also need to effectively connect with other cloud platforms. This session will show you how to integrate your Force.com application with other cloud platforms such as Amazon’s S3 storage, Google App Engine, and Microsoft Azure. The session will feature demos and code walkthroughs of the various cloud integrations.
Twilio just released a new beta version of their Apex toolkit. You can fork it here on GitHub. It includes some new stuff, mainly the Capabilities class for Twilio Client. The majority of it was written by Kyle Roche so it’s pretty top notch stuff.
If you are interested in getting your hands dirty with the new toolkit, we have a Twilio Client for Salesforce Customer Portal development challenge over at CloudSpokes with $1000 up for grabs.
Dreamforce 11 is over and I can finally catch my breath. Despite the un-godly start time of 8:30am Friday morning, my session went fairly well. I had a good turnout and some interesting questions.
I’ve posted the PDF of my deck to the session’s Chatter group, but it may be easier to access them from my blog. Here is the PDF and PPT version of my presentation along with the source code.
It’s official! The Heroku blog proudly announced a couple of hours ago that “Java is the fourth official language available on the Cedar stack”. There’s even a nifty little “heroku for java in 5 minutes tutorial” that you can walk through to get up and running.
Sometimes you just want to send a crapload of email from Salesforce.com. However, like every PaaS platform there are limits baked into the multi-tenant environment so you don’t stomp on other tenants’ resources. Salesforce.com limits you to 2000 emails per day for each Salesforce license. So if you don’t have a lot of Salesforce licenses or a different kind of license, you may be out of luck if you want to send out large volumes. There are a few AppExchange products but they seem more targeted towards marketing purposes.
Google App Engine may be a good solution in this case. With Google App Engine quotas you get 7,000 Mail API calls per day free and can bump that up as high as 1.7M with a paid account.
Here’s how to roll your own basic bulk emailer using Google App Engine. Take a look at the video below, but it essentially queries Force.com for records, uses the Google Mail Java API to send out individual emails and then send an administrator a notification via Jabber (Google Talk). You can schedule your application (essentially a Servlet) to run on a timed basis using the cron service.
package com.jeffdouglas.emailer;
[removed imports]
/**
* MailServlet.java - a simple, schedulable servlet for sending mail
* with salesforce.com
* @author Jeff Douglas
* @version 1.0
* @see http://code.google.com/appengine/docs/java/mail/overview.html
* for more details on using Mail with App Engine
*/
@SuppressWarnings("serial")
public class MailServlet extends HttpServlet {
private static final Logger logger = Logger.getLogger(ConnectionManager.class.getName());
private String jabberRecipient = "jeffdonthemic@gmail.com";
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("text/html");
String mailerMsg = "No contact found to email!!";
QueryResult result = null;
// get a reference to the salesforce connection
PartnerConnection connection = ConnectionManager.getConnectionManager().getConnection();
try {
// query for contacts based upon some criteria -- emailNotSent boolean
result = connection.query("Select Id, FirstName, LastName, Email " +
"FROM Contact Where Email = 'jeff@jeffdouglas.com' Limit 1");
} catch (ConnectionException e) {
e.printStackTrace();
logger.severe(e.getCause().toString());
} catch (NullPointerException npe) {
npe.printStackTrace();
logger.severe(npe.getCause().toString());
}
// if records were returned then send out email
if (result != null) {
for (SObject contact : result.getRecords()) {
// construct the 'name' for the email recipient
String contactName = contact.getField("FirstName").toString() + " " +
contact.getField("LastName").toString();
logger.info("Sending emil to " + contactName + " at " +
contact.getField("Email").toString());
/// send the email
mailerMsg = sendMail(contact.getField("Email").toString(), contactName);
// send a jabber notification of the status
sendJabberNotification(jabberRecipient, mailerMsg);
}
// TODO - make a call back into salesforce and update these records
// as having their emails sent. Implementation is up to you.
} else {
logger.warning("No results returned from salesforce");
}
resp.getOutputStream().println(mailerMsg);
}
/**
* Sends an email
* @param toAddress the email address of the recipient
* @param toName the name that appears for the recipeint in their email client
* @return A String representing the status of the email sent
*/
private String sendMail(String toAddress, String toName) {
String msg = "Email sent successfully to " + toAddress;
Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);
String messageBody = "This is the body of my email";
try {
Message emailMessage = new MimeMessage(session);
// must be the email address of an administrator for the application. see docs
emailMessage.setFrom(new InternetAddress("jeffdonthemic@gmail.com","Jeff Douglas"));
emailMessage.addRecipient(Message.RecipientType.TO,
new InternetAddress(toAddress, toName));
emailMessage.setSubject("My Email Subject");
emailMessage.setText(messageBody);
Transport.send(emailMessage);
} catch (AddressException e) {
msg = e.toString();
} catch (MessagingException e) {
msg = e.toString();
} catch (UnsupportedEncodingException e) {
msg = e.toString();
}
return msg;
}
/**
* Sends a message to any XMPP-compatible chat messaging service (google talk).
* See http://code.google.com/appengine/docs/java/xmpp/overview.html
* for more detils
* @param recipient the jid of the jabber recipient of the notification
* @param msgBody the body of the message to be sent
*/
private void sendJabberNotification(String recipient, String msgBody) {
JID jid = new JID(recipient);
com.google.appengine.api.xmpp.Message msg = new MessageBuilder()
.withRecipientJids(jid)
.withBody(msgBody)
.build();
boolean messageSent = false;
XMPPService xmpp = XMPPServiceFactory.getXMPPService();
if (xmpp.getPresence(jid).isAvailable()) {
SendResponse status = xmpp.sendMessage(msg);
messageSent = (status.getStatusMap().get(jid) == SendResponse.Status.SUCCESS);
}
logger.info("Jabber notifiation sent: " + messageSent);
}
}
Master-Details relationships in Force.com are very handy but don’t fit every scenario. For instance, it’s not possible to implement a rollup summary on formula field or text fields. Here’s a small trigger that you can use for a starter for these types of situations. The code for each class is available at GitHub for your forking pleasure.
So here’s the (not very useful) use case. Sales Order is the Master object which can have multiple Sales Order Items (detail object). The Sales Order Item has a “primary” Boolean field and a “purchased country” field. Each time Sales Order Items are inserted or updated, if the Sales Order Item is marked as “primary” then the value of “purchased country” is written into the “primary country” field on the Sales Order. I’m assuming that there can only be one Sales Order Item per Sales Order that is marked as primary. Essentially this is just a quick reference on the Sales Order to see which country is primary on any of the multiple Sales Order Items. Not very useful but illustrative.
The code is broken down into a Trigger and an Apex “handler” class that implements the actual functionality. It’s best practice to only have one trigger for each object and to avoid complex logic in triggers. To simplify testing and resuse, triggers should delegate to Apex classes which contain the actual execution logic. See Mike Leach’s excellent trigger template for more info.
SalesOrderItemTrigger (source on GitHub) - Implements trigger functionality for Sales Order Items. Delegates responsibility to SalesOrderItemTriggerHandler.
SalesOrderItemTriggerHandler (source on GitHub) - Implements the functionality for the sales order item trigger after insert and after update. Looks at each sales order item and if it is marked as primary_item__c then moves the primary_country__c value from the sales order item to the associated sales order’s primary_country__c field.
public with sharing class SalesOrderItemTriggerHandler {
// update the primary country when new records are inserted from trigger
public void OnAfterInsert(List<Sales_Order_Item__c> newRecords){
updatePrimaryCountry(newRecords);
}
// update the primary country when records are updated from trigger
public void OnAfterUpdate(List<Sales_Order_Item__c> oldRecords,
List<Sales_Order_Item__c> updatedRecords, Map<ID, Sales_Order_Item__c> oldMap,
Map<ID, Sales_Order_Item__c> newMap){
updatePrimaryCountry(updatedRecords);
}
// updates the sales order with the primary purchased country for the item
private void updatePrimaryCountry(List<Sales_Order_Item__c> newRecords) {
// create a new map to hold the sales order id / country values
Map<ID,String> salesOrderCountryMap = new Map<ID,String>();
// if an item is marked as primary, add the purchased country
// to the map where the sales order id is the key
for (Sales_Order_Item__c soi : newRecords) {
if (soi.Primary_Item__c)
salesOrderCountryMap.put(soi.Sales_Order__c,soi.Purchased_Country__c);
}
// query for the sale orders in the context to update
List<Sales_Order__c> orders = [select id, Primary_Country__c from Sales_Order__c
where id IN :salesOrderCountryMap.keyset()];
// add the primary country to the sales order. find it in the map
// using the sales order's id as the key
for (Sales_Order__c so : orders)
so.Primary_Country__c = salesOrderCountryMap.get(so.id);
// commit the records
update orders;
}
}
Test_SalesOrderItemTriggerHandler (source on GitHub) - Test class for SalesOrderItemTrigger and SalesOrderItemTriggerHandler. Achieves 100% code coverage.
Hop on over to http://popart.appirio.com for a chance to win $1000. Here’s how it works. Enter a caption in the pop art image of your choice and submit the entry. It will create an image and send it over to our Pop Art Picassa Gallery. Get people to vote for your images and you can win tickets to our invite only Dreamforce SFMOMA party or even $1000!!!
Check out some of the submitted artwork. Pretty funny stuff.
Popart was created from the Art Meets Text Mashup CloudSpokes contest! Created by the Force.com community for the Force.com community.
Let’s talk about the standard salesforce.com “lookup” popup window for a few minutes. You know what I’m talking about.. this button right here
It’s a handy little button that pops up whenever you need to search for related records. It does a pretty good job but it has some serious drawbacks:
It’s virtually impossible to modify your search criteria. What if you want your users to do some crazy search based upon custom logic or search a field that is not typically available? Sorry… you are out of luck. Not possible.
It’s terrible for creating new records. Let’s say that a user searches for a specific related record and it (absolutely) doesn’t exist. To create the new record they need to close the lookup window, navigate to the tab to create the new related record, create the new record, then go back to the original record they were either editing or creating, pop up the lookup window again and find their newly created record. Wow! That’s a lot of work.
“Quick Create” is evil! You can enable the “Quick Create” option for an entire org but don’t do it! It displays, by default, on the tab home pages for leads, accounts, contacts, forecasts, and opportunities! The major problems are that you can only create new records for these 5 objects (what about the other ones!?), you can’t customize the fields on the page and validation rules don’t fire (can you say, “bad data”).
Here’s the Solution!
I have some good news and some bad news. For standard page layouts I can’t help you. Go vote for this idea and this idea. However, for Visualforce page I have a solution to all of these problems with code!
Here’s how it looks. It may be easier to watch it full screen at YouTube.
Here’s the code you need to accomplish this. You need two Visualforce pages (the record you are editing and the popup window) and two Apex controllers (a simple one for the record you are editing and the controller for the search and new record popup).
Here’s the Apex controller for the record you are either creating or editing. This is an extremely simple controller that just creates a new contact so you can use the lookup for the related account field.
1
2
3
4
5
6
7
8
9
public with sharing class MyCustomLookupController {
public Contact contact {get;set;}
public MyCustomLookupController() {
contact = new Contact();
}
}
This is the “magical” Visualforce page that uses jQuery to intercept the popup and instead of showing the standard salesforce.com pop, shows our custom popup instead. The user experience is seamless.
<apex:page controller="MyCustomLookupController" id="Page" tabstyle="Contact">
<script type="text/javascript">
function openLookup(baseURL, width, modified, searchParam){
var originalbaseURL = baseURL;
var originalwidth = width;
var originalmodified = modified;
var originalsearchParam = searchParam;
var lookupType = baseURL.substr(baseURL.length-3, 3);
if (modified == '1') baseURL = baseURL + searchParam;
var isCustomLookup = false;
// Following "001" is the lookup type for Account object so change this as per your standard or custom object
if(lookupType == "001"){
var urlArr = baseURL.split("&");
var txtId = '';
if(urlArr.length > 2) {
urlArr = urlArr[1].split('=');
txtId = urlArr[1];
}
// Following is the url of Custom Lookup page. You need to change that accordingly
baseURL = "/apex/CustomAccountLookup?txt=" + txtId;
// Following is the id of apex:form control "myForm". You need to change that accordingly
baseURL = baseURL + "&frm=" + escapeUTF("{!$Component.myForm}");
if (modified == '1') {
baseURL = baseURL + "&lksearch=" + searchParam;
}
// Following is the ID of inputField that is the lookup to be customized as custom lookup
if(txtId.indexOf('Account') > -1 ){
isCustomLookup = true;
}
}
if(isCustomLookup == true){
openPopup(baseURL, "lookup", 350, 480, "width="+width+",height=480,toolbar=no,status=no,directories=no,menubar=no,resizable=yes,scrollable=no", true);
}
else {
if (modified == '1') originalbaseURL = originalbaseURL + originalsearchParam;
openPopup(originalbaseURL, "lookup", 350, 480, "width="+originalwidth+",height=480,toolbar=no,status=no,directories=no,menubar=no,resizable=yes,scrollable=no", true);
}
}
</script>
<apex:sectionHeader title="Demo" subtitle="Custom Lookup" />
<apex:form id="myForm">
<apex:PageBlock id="PageBlock">
<apex:pageBlockSection columns="1" title="Custom Lookup">
<apex:inputField id="Account" value="{!contact.AccountId}" />
</apex:pageBlockSection>
</apex:PageBlock>
</apex:form>
</apex:page>
The Apex controller for the custom popup window is yours to customize. I know what you are thinking, “Free at last! Free at last! Thank God Almighty, we are free at last!” This class has all of your custom search functionality plus the method to create a new account. This is demo code so the search is very limited and does not prevent soql injections.
public with sharing class CustomAccountLookupController {
public Account account {get;set;} // new account to create
public List<Account> results{get;set;} // search results
public string searchString{get;set;} // search keyword
public CustomAccountLookupController() {
account = new Account();
// get the current search string
searchString = System.currentPageReference().getParameters().get('lksrch');
runSearch();
}
// performs the keyword search
public PageReference search() {
runSearch();
return null;
}
// prepare the query and issue the search command
private void runSearch() {
// TODO prepare query string for complex serarches & prevent injections
results = performSearch(searchString);
}
// run the search and return the records found.
private List<Account> performSearch(string searchString) {
String soql = 'select id, name from account';
if(searchString != '' && searchString != null)
soql = soql + ' where name LIKE \'%' + searchString +'%\'';
soql = soql + ' limit 25';
System.debug(soql);
return database.query(soql);
}
// save the new account record
public PageReference saveAccount() {
insert account;
// reset the account
account = new Account();
return null;
}
// used by the visualforce page to send the link to the right dom element
public string getFormTag() {
return System.currentPageReference().getParameters().get('frm');
}
// used by the visualforce page to send the link to the right dom element for the text box
public string getTextBox() {
return System.currentPageReference().getParameters().get('txt');
}
}
Any finally the Visualforce page for the popup itself. It contains a tabbed interface easily allowing a user to search for records and create new ones. Make sure you look at the code for the second tab for creating a new record. I have better things to do than change the fields on the input form every time a new field is created or something is made required. The solution is to use field sets! So when an administrator makes a change, they can simply update the field set and the popup reflects the change accordingly. Life is good.
I'm a Senior Technical Consultant at Appirio specializing in cloud-based applications using Salesforce.com, Google App Engine and VMware.
You should be a foster and/or adoptive parent. I'm both and it's fun!! Ask me how.
Buy My Books!
By Jeff Douglas & Wes Nolte
By Jeff Douglas &Kyle Roche
Favorite Quotes
"Any sufficiently advanced technology is indistinguishable from magic."
"Those who say it cannot be done should not interrupt the person doing it."