Up: Part II

13 Third Party Integrations

In this chapter we’ll explore how you can integrate Lift with some well-known third party libraries and applications

13.1 OpenID Integration

The OpenID Foundation [V]  [V] http://openid.net/ explain OpenID as:
“OpenID eliminates the need for multiple usernames across different websites, simplifying your online experience.
You get to choose the OpenID Provider that best meets your needs and most importantly that you trust. At the same time, your OpenID can stay with you, no matter which Provider you move to. And best of all, the OpenID technology is not proprietary and is completely free. For businesses, this means a lower cost of password and account management, while drawing new web traffic. OpenID lowers user frustration by letting users have control of their login. For geeks, OpenID is an open, decentralized, free framework for user-centric digital identity. OpenID takes advantage of already existing internet technology (URI, HTTP, SSL, Diffie-Hellman) and realizes that people are already creating identities for themselves whether it be at their blog, photostream, profile page, etc. With OpenID you can easily transform one of these existing URIs into an account which can be used at sites which support OpenID logins.
OpenID is still in the adoption phase and is becoming more and more popular, as large organizations like AOL, Microsoft, Sun, Novell, etc. begin to accept and provide OpenIDs. Today it is estimated that there are over 160-million OpenID enabled URIs with nearly ten-thousand sites supporting OpenID logins.”
Lift provides openId support using onepID4Java [W]  [W] http://code.google.com/p/openid4java/. It provides two fundamental traits net.liftweb.openId.OpenIdVendor and net.liftweb.openId.OpenIdConsumer. OpenIdVendor contains variables such as:
Also the vendor trait contains the loginForm function that returns the login form containing an input text field for the OpenID identity and the submit button. The form will point to /<PathRoot>/<LoginPath> where PathRoot and LoginPath are the variables described above. Here is an example:
OpenID example
// Your template
​
<lift:OpenID.form>
  <openId:renderForm/>
</lift:OpenID.form>
​
// Your snippet
​
class OpenID {
​
  def renderForm(xhtml: NodeSeq) : NodeSeq = {
    SimpleOpenIdVendor.loginForm
  }
​
}
​
class Boot {
​
  ...
  // This is needed in order to process the login and logout requests and also to process
  // the response comming from OpenID provider
  LiftRules.dispatch.append(SimpleOpenIdVendor.dispatchPF)
  ...
​
}
​
​
That is pretty much all you need to add into your Lift application. The authentication flow is:
  1. User accesses your lift page that contains the OpenID form
  2. User enters his/her OpenID identity URL and submits the form. Note that you don’t have to use the default login form asyou can construct your own as long as the form is submitted to the correct path and contains the correct input text parameter name.
  3. The dispatchPF function that we appended above will process the /openid/login request and will send the authentication request to the Identity Provider site
  4. Identity Provider will validate the user and redirect back to your Lift application to /openid/response path.
  5. The response is validated using OpenId4Java library
  6. OpenIdConsumer.postLogin gets called.
The SimpleOpenIDVendor looks like:
SimpleOpenIDVendor
trait SimpleOpenIdVendor extends OpenIdVendor {   
  type UserType = Identifier   
  type ConsumerType = OpenIdConsumer[UserType]
  
  def currentUser = OpenIdUser.is
  def postLogin(id: Box[Identifier],res: VerificationResult): Unit = {
    id match {
      case Full(id) => S.notice("Welcome "+id)
      case _ => S.error("Failed to authenticate")
    }
    OpenIdUser(id)
  }
  def logUserOut() {
    OpenIdUser.remove   
  }
  def displayUser(in: UserType): NodeSeq = Text("Welcome "+in)
  def createAConsumer = new AnyRef with OpenIDConsumer[UserType]
}
object SimpleOpenIdVendor extends SimpleOpenIdVendor 
Note the postLogin implementation. Of course if you need a more complex post-login processing you can extend OpenIdVendor by yourself.
During this message exchange between the Identity Provider ans your Lift application, Lift utilizes a couple of SessionVars:

13.2 AMQP

AMQP stands for Advanced Message Queuing Protocol [X]  [X] http://jira.amqp.org/confluence/display/AMQP/Advanced+Message+Queuing+Protocol. It is an open Internet protocol for messaging. It is concepted as a binary representation of messages. Lift facilitates the work with AMQP using the RabbitMQ [Y]  [Y] http://www.rabbitmq.com/ Java implementation. There are two fundamental classes:
Let’s see how we can use Lift to send AMQP messages
AMQP sending messages example
​
  import net.liftweb.amqp._
  import com.rabbitmq.client._ 
​
  val params = new ConnectionParameters
  // All of the params, exchanges, and queues are all just example data.
  params.setUsername("guest")
  params.setPassword("guest")
  params.setVirtualHost("/")
  params.setRequestedHeartbeat(0)
  val factory = new ConnectionFactory(params)
  val amqp = new StringAMQPSender(factory, "localhost", 5672, "mult", "routeroute")
  amqp.start
  amqp ! AMQPMessage("hi") 
As you can see the AMQSender is leveraging Scala actors to send messages. Scala actors and AMQP messaging concepts play very well together.
Now let’s see how we can receive and process AMQP messages:
AMQP receiving messages example
​
import net.liftweb.amqp._
import com.rabbitmq.client._ 
​
/**
 * Example Dispatcher that listens on an example queue and exchange. Use this
 * as your guiding example for creating your own Dispatcher.
 *
 */
class ExampleSerializedAMQPDispatcher[T](factory: ConnectionFactory, host: String, port: Int)
    extends AMQPDispatcher[T](factory, host, port) {
​
  override def configure(channel: Channel) {
    // Get the ticket.
    val ticket = channel.accessRequest("/data")
    // Set up the exchange and queue
    channel.exchangeDeclare(ticket, "mult", "direct")
    channel.queueDeclare(ticket, "mult_queue")
    channel.queueBind(ticket, "mult_queue", "mult", "routeroute")
    // Use the short version of the basicConsume method for convenience.
    channel.basicConsume(ticket, "mult_queue", false, new SerializedConsumer(channel, this))
  }
}
​
/**
 * Example class that accepts Strings coming in from the
 * ExampleSerializedAMQPDispatcher.
 */
class ExampleStringAMQPListener {
  val params = new ConnectionParameters
  params.setUsername("guest")
  params.setPassword("guest")
  params.setVirtualHost("/")
  params.setRequestedHeartbeat(0)
  val factory = new ConnectionFactory(params)
  // thor.local is a machine on your network with rabbitmq listening on port 5672
  val amqp = new ExampleSerializedAMQPDispatcher[String](factory, "thor.local", 5672)
  amqp.start
​
  // Example Listener that just prints the String it receives.
  class StringListener extends Actor {
    def act = {
      react {
        case msg@AMQPMessage(contents: String) => println("received: " + msg); act
      }
    }
  }
  val stringListener = new StringListener()
  stringListener.start
  amqp ! AMQPAddListener(stringListener) 
} 
First of all don’t get scared about this. The above classes are already existent so you can just reuse them. However the point of showing them here is to understand how to use a AMQP consumer, how to configure it to match the client settings from the Listing 1.313.2↑. The key here is to see how the actual messages are consumed. Note the StringListener actor is consumming the AMQPMessage but the actor itself it provided to AMQPDispatcher. What happens is that when a real AMQP message is received by AMQPDispatcher it will just forward it to the user’sActor for actuall processing. SerializedConsumer class is actually doing the transformation of the raw data (array of byte-s) into AMQPMessage messages.

13.3 PayPal

Paypal [Z]  [Z] https://www.paypal.com is the notorious service that allows you to do online payment transactions. Lift supports both
PDT(Payment Data Transferr) [A]  [A] https://www.paypal.com/en_US/i/IntegrationCenter/scr/scr_ppPDTDiagram_513x282.gifas well as
IPN(Instant Payment Notification) [B]  [B] https://www.paypal.com/en_US/i/IntegrationCenter/scr/scr_ppIPNDiagram_555x310.gif API’ sprovided by PayPal. We won’t be getting into PayPal API details as this information can be found on PayPal site. However let’s see how we’d use PDT and IPN.
PDT Example
import net.liftweb.paypal._
​
object MyPayPalPDT extends PayPalPDT {
  override def pdtPath = "paypal_complete"
  def paypalAuthToken = Props.get("paypal.authToken") openOr "cannot find auth token from props file"
​
  def pdtResponse: PartialFunction[(PayPalInfo, Req), LiftResponse] = {
    case (info, req) => println("— in pdtResponse"); DoRedirectResponse("/account_admin/index");
  }
}
​
// in Boot
​
def boot(){
  ...
  LiftRules.statelessDispatchTable.append(MyPayPalPDT)
  ...
}
​
That is pretty much it. pdtResponse function allows you to determine the behavior of you application upon receiving the reponse from PayPal.
IPN Example
import net.liftweb.paypal._
​
object MyPayPalIPN extends PayPalIPN {
  def actions = {
    case (ClearedPayment, info, req) => // do your processing here
    case (RefundedPayment, info, req) => // do refund processing 
  }
}
​
// in Boot
​
def boot(){
  ...
  LiftRules.statelessDispatchTable.append(MyPayPalIPN)
  ...
}
​
As you can see everything is pretty strightforward. Just pattern match on the PaypalTransactionStatus. It is worth to note sthat IPN is a ’machine-to-machine’ API which happens in the background without the end user interraction.

13.4 Facebook

Facebook [C]  [C] http://www.facebook.com is the well known site that simply allows people to easily interract, build social graphs share photos etc. Facebook also exposes HTTP API’s [D]  [D] http://wiki.developers.facebook.com/index.php/API that allows other applications to interract with it. Lift framework allows your application to easily interract with Facebook by providing an abstraction layer over the Facebook API. Here is an example:
Facebook example
​
import net.liftweb.ext_api.facebook._
​
FacebookRestApi.apiKey = <your API key>;
FacebookRestApi.secret = <your secret>;
​
// The api key is ontained from System.getProperty("com.facebook.api_key")
// The secreat is obtained from System.setProperty("com.facebook.secret", key) 
​
​
// Invoke stateless calls
val respNode: Node = FacebookClient !? AuthCreateToken
val authToken = // extract authToken from respNode
​
// Obtain a stateful client based on the authToken
val faceBookClient = FacebookClient fromAuthToken(authToken)
​
faceBookClient !? GetFriendLists
​
Once you have the FacebookClient you can invoke numerous API methods described by FacebookMethod or SessionlessFacebookMethod. In the above examplewe are creating the FaceBook context by first obtaining an authToken and then obtaining a faceBookClient reference bound to the newly created session. After that we’re just ontaining the friends list.

13.5 XMPP

XMPP [E]  [E] http://xmpp.org/ stands for eXtensible Messaging and Presence Protocol. It is an XML-based protocol used for presence and realtime communication such as instant messaging (Jabber and GoogleTalk being two of the more famous users). It is developed by the Jabber [F]  [F] http://xmpp.org/about/jabber.shtml open-source community. Lift provides an XMPP dispatcher implementation that your application can use to receive instant messages, manage rosters etc. This support relies on the Smack  [G]  [G] http://www.igniterealtime.org/downloads/index.jsp XMPP client library and utilizes Scala actors for the interface. Here is an example:
XMPP Example
import net.liftweb.xmpp._
​
/**
 * An example Chat application that prints to stdout.
 *
 * @param username is the username to login to at Google Talk: format: something@gmail.com
 * @param password is the password for the user account at Google Talk.
 */
class ConsoleChatActor(val username: String, val password: String) extends Actor {
  def connf() = new ConnectionConfiguration("talk.google.com", 5222, "gmail.com")
  def login(conn: XMPPConnection) = conn.login(username, password)
  val xmpp = new XMPPDispatcher(connf, login)
  xmpp.start
​
  val chats: Map[String, List[Message]] = new HashMap[String, List[Message]]
  val rosterMap: HashMap[String, Presence] = new HashMap[String, Presence]
  var roster: Roster = null
  def act = loop
​
  def loop { 
   react {
      case Start => {
        xmpp ! AddListener(this)
        xmpp ! SetPresence(new Presence(Presence.Type.available))
        loop
      }
      case NewChat(c) => {
        chats += (c.getParticipant -> Nil)
        loop
      }
      case RecvMsg(chat, msg) => {
        println("RecvMsg from: " + msg.getFrom + ": " + msg.getBody);
        loop 
      }
      case NewRoster(r) => {
        println("getting a new roster: " + r)
        this.roster = r
        val e: Array[Object] = r.getEntries.toArray.asInstanceOf[Array[Object]]
        for (entry <- e) {
          val user: String = entry.asInstanceOf[RosterEntry].getUser
          rosterMap += (user -> r.getPresence(user))
        }
        loop
      }
​
      case RosterPresenceChanged(p) => { 
        val user = StringUtils.parseBareAddress(p.getFrom)
        println("Roster Update: " + user + " " + p)
        // It’s best practice to ask the roster for the presence. This is because
        // multiple presences can exist for one user and the roster knows which one 
        // has priority. 
        rosterMap += (user -> roster.getPresence(user))
        loop
      }
      case RosterEntriesDeleted(e) => {
        println(e) 
        loop 
      } 
      case RosterEntriesUpdated(e) => {
        println(e)
        loop
      }
      case RosterEntriesAdded(e) => { 
        println(e)
        loop 
      }
      case a => println(a); loop
    }
  }
  def createChat(to: String) {
    xmpp ! CreateChat(to)
  }
  def sendMessage(to: String, msg: String) {
    xmpp ! SendMsg(to, msg)
  }
​
 /**
  * @returns an Iterable of all users who aren’t unavailable along with their Presence
  */
 def availableUsers: Iterable[(String, Presence)] = {
   rosterMap.filter((e) => e._2.getType() != Presence.Type.unavailable)
 }
}
​
object ConsoleChatHelper {
  /**
   * @param u is the username
   * @param p is the password
   */
  def run(u: String, p: String) = {
    val ex = new ConsoleChatActor(u, p)
    ex.start 
    ex ! Start 
    ex 
  }
}
​
// To start the dispatcher just call:
​
ConsoleChatHelper.run(userName, password);
​
...
The above is an example how you can integrate your application with an XMPP server and how messages are pocessed.

13.6 Lucene/Compass Integration

This chapter is still under active development. The contents will change.
Up: Part II

(C) 2012 Lift 2.0 EditionWritten by Derek Chen-Becker, Marius Danciu and Tyler Weir