I’ve been using Play! since Dreamforce 11 when Heroku announced support for it and I have to say that I’m addicted to it. If you love Java (and who doesn’t) but hate developing Java applications with all of the crap that goes along with it (Maven, XML config files, deployments, etc.) then the Play! framework my just be your savior.
I did a small demo with salesforce.com integration with Heroku and Play! if you would like some sample code or to run it for yourself.
Here’s what I like the most about Play!: it makes me productive. The development style is similar to Rails and I can simply get stuff done. I don’t have to save my Java code, restart Tomcat, wait for Hibernate to fire up and then see if my code runs as expected. With Play! I make my modifications, hit refresh in the browser and presto! my new app is either up and running or there is a nice, pretty error message.
Running Play! is a breeze. The framework comes bundled with Jetty so I don’t have to install and maintain Tomcat locally and when I’m ready to put the app into production, I just push it to Heroku and they take care of the rest.
If you want a great intro to Play! and why you might to use it, check out the first 10 minutes of this video from Dreamforce 11, “Introducing Play! Framework: Painless Java and Scala Web Applications”. The reset of the preso gets into actually building an app so if you want to see some code, feel free to continue watching.
The new System Log (aka Apex CSI) is a cool new part of Winter 12 that significantly reduces the pain of debugging Force.com applications. This is a great video from Dreamforce 11 showing you how to use the new console to debug code, set “breakpoints” and examine heap variables and more.
I’m working on some of the Apex REST services for our CloudSpokes org and needed some code to fetch field level metadata using Apex Describe. I poked around and realized that there isn’t really much out there. So I decided to write something up and hopefully people find it useful or instructional. Perhaps it should be part of apex-lang?
If you’ve ever worked with Apex Describe before you’ll quickly realize that it’s not the easiest thing to work with. You’ll want to take a peek at the docs. Don’t get me wrong, it’s power, fast and very handy. But it is rather confusing and cumbersome to work with at first. So I wanted some code that returns the metadata for specific fields in an object so that I could look at field types and lengths and perform “stuff” accordingly.
So here’s what I can up with. You pass the method the DescribeFieldResult for an object and a collection of fields that you want metadata for. The method returns a map where the field names are the keys and the values contain the metadata for the corresponding field. It looks pretty hard but that’s the great thing about utility methods, they encapsulate and hide the complexity of the implementation.
public static Map<String, Schema.DescribeFieldResult> getFieldMetaData(
Schema.DescribeSObjectResult dsor, Set<String> fields) {
// the map to be returned with the final data
Map<String,Schema.DescribeFieldResult> finalMap =
new Map<String, Schema.DescribeFieldResult>();
// map of all fields in the object
Map<String, Schema.SObjectField> objectFields = dsor.fields.getMap();
// iterate over the requested fields and get the describe info for each one.
// add it to a map with field name as key
for(String field : fields){
// skip fields that are not part of the object
if (objectFields.containsKey(field)) {
Schema.DescribeFieldResult dr = objectFields.get(field).getDescribe();
// add the results to the map to be returned
finalMap.put(field, dr);
}
}
return finalMap;
}
So now in my code I can call this static method in my Utils class and access the field metadata easily by fetching it from the map by it’s field name:
1
2
3
4
5
6
7
8
9
10
11
12
// field to return -- skips fields not actually part of the sobject
Set<String> fields = new Set<String>{'name','annualrevenue','BADFIELD'};
Map<String, Schema.DescribeFieldResult> finalMap =
Utils.getFieldMetaData(Account.getSObjectType().getDescribe(), fields);
// only print out the 'good' fields
for (String field : new Set<String>{'name','annualrevenue'}) {
System.debug(finalMap.get(field).getName()); // field name
System.debug(finalMap.get(field).getType()); // field type
System.debug(finalMap.get(field).getLength()); // field length
}
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);
}
}
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."