Wednesday, October 05, 2011

Connection Properties and Adobe LiveCycle ES 2.5

I am working on an app that needs to authenticate a login against LiveCycle ES 2.5 users. To do this I need to make a connection to LiveCycle from my external app. I found Adobe's Setting connection properties page to be a bit confusing at first, because it didn't explain a few basic concepts up front. Hopefully, this post will rectify that.

If you need to connect to a LiveCycle instance via an external app - i.e. code that is not running under a LiveCycle process - you first need to know if your app is running on the same JVM as LiveCycle i.e. is it deployed to the same server instance? If your app is running on the same JVM, the code required to connect is really easy.

// Build a factory to obtain various clients for the LiveCycle service.
ServiceClientFactory myFactory = ServiceClientFactory.createInstance();
// Create a client that can authenticate users against LiveCycle.
AuthenticationManagerServiceClient authClient =
      new AuthenticationManagerServiceClient(myFactory);

// Authenticate user with username/password parameters entered in a form.
String username = request.getParameter("username");
String password = request.getParameter("password");
AuthResult authResult = authClient.authenticate(username,
      password.getBytes());

// Get the authenticated user's information
User authUser = authResult.getAuthenticatedUser();

if (authUser != null && !authUser.isLocked() && !authUser.isDisabled()) {
   // Login successful.. carry on!
} else {
   // Login failed.
}

The ServiceClientFactory is a very important class for connecting to and interacting with LiveCycle via an external app. You use the ServiceClientFactory to create clients to the LiveCycle service - clients such as the AuthenticationManagerServiceClient (for logging in as a LiveCycle user) or the more simply named ServiceClient (for actually invoking LiveCycle operations.

If your app is running on a different JVM than the one LiveCycle is running on, you need to know what type of J2EE application server it is (WebSphere, JBoss and WebLogic appear to be the only supported choices). You also need to know the host name if it is running on a different machine. You then need to decide whether to connect via EJB or SOAP. EJB offers better performance, so it should be your default choice. Use SOAP if there is a firewall between your app and LiveCycle (blocking the ports used by EJB) or if your app is not running within a J2EE application server (i.e. doesn't support EJB).

For example, let's say your app is running on a different JVM - WebLogic - but on the same machine. We use EJB and the host is localhost. The code will look like this:


// Get username/password parameters entered in a form.
String username = request.getParameter("username");
String password = request.getParameter("password");

// Build connection properties specific to the server type.
Properties connectionProps = new Properties();
connectionProps.setProperty(
      ServiceClientFactoryProperties.DSC_DEFAULT_EJB_ENDPOINT,
      "t3://localhost:7001");
connectionProps.setProperty(
      ServiceClientFactoryProperties.DSC_TRANSPORT_PROTOCOL,
      ServiceClientFactoryProperties.DSC_EJB_PROTOCOL);
connectionProps.setProperty(
      ServiceClientFactoryProperties.DSC_SERVER_TYPE, "WebLogic");
connectionProps.setProperty(
      ServiceClientFactoryProperties.DSC_CREDENTIAL_USERNAME,
      username);
connectionProps.setProperty(
      ServiceClientFactoryProperties.DSC_CREDENTIAL_PASSWORD,
      password);

// Build a factory to obtain various clients for the LiveCycle service.
ServiceClientFactory myFactory = ServiceClientFactory.createInstance(connectionProps);
// Create a client that can authenticate users against LiveCycle.
AuthenticationManagerServiceClient authClient =
      new AuthenticationManagerServiceClient(myFactory);

// Authenticate the user.
AuthResult authResult = authClient.authenticate(username,
      password.getBytes());

if (authUser != null && !authUser.isLocked() && !authUser.isDisabled()) {
   // Login successful.. carry on!
} else {
   // Login failed.
}

See Adobe's Setting connection properties page to see the properties you need to use for JBoss or WebSphere.

There are a few errors to look for when debugging this type of code. Here is an error I got when using JBoss configuration (jnp) against a WebLogic server.

[30 Sep 2011 11:47:14,722] au.com.btss.puk.web.servlet.LoginServlet  - LiveCycle authentication error occurred: com.adobe.idp.um.api.UMException| [com.adobe.livecycle.usermanager.client.AuthenticationManagerServiceClient] errorCode:16385 errorCodeHEX:0x4001 message:Exception thrown is NOT a DSCException : UnExpected From DSC chainedException:ALC-DSC-031-000: com.adobe.idp.dsc.net.DSCNamingException: Remote EJBObject lookup failed for ejb/Invocation providerchainedExceptionMessage:Remote EJBObject lookup failed for ejb/Invocation provider chainedException trace:ALC-DSC-031-000: com.adobe.idp.dsc.net.DSCNamingException: Remote EJBObject lookup failed for ejb/Invocation provider
	at com.adobe.idp.dsc.provider.impl.ejb.EjbMessageDispatcher.initialise(EjbMessageDispatcher.java:101)
	at com.adobe.idp.dsc.provider.impl.ejb.EjbMessageDispatcher.doSend(EjbMessageDispatcher.java:141)
...
Caused by: javax.naming.ServiceUnavailableException [Root exception is java.net.UnknownHostException: Unknown protocol: 'JNP']
	at weblogic.jndi.internal.ExceptionTranslator.toNamingException(ExceptionTranslator.java:34)
	at weblogic.jndi.WLInitialContextFactoryDelegate.toNamingException(WLInitialContextFactoryDelegate.java:788)
...
Caused by: java.net.UnknownHostException: Unknown protocol: 'JNP'
	at weblogic.rjvm.RJVMManager.findOrCreateRemoteInternal(RJVMManager.java:216)
...

And this is an error I got when trying to use connection properties in a situation where I should have been using no connection properties i.e. I hadn't realised that if the app was running on the same JVM as LiveCycle, I didn't need connection properties.

[30 Sep 2011 12:20:09,057] ERROR au.com.btss.puk.web.servlet.LoginServlet  - LiveCycle authentication error occurred: com.adobe.idp.um.api.UMException| [com.adobe.livecycle.usermanager.client.AuthenticationManagerServiceClient] errorCode:16385 errorCodeHEX:0x4001 message:Exception thrown is NOT a DSCException : UnExpected From DSC chainedException:ALC-DSC-031-000: com.adobe.idp.dsc.net.DSCNamingException: Remote EJBObject lookup failed for ejb/Invocation providerchainedExceptionMessage:Remote EJBObject lookup failed for ejb/Invocation provider chainedException trace:ALC-DSC-031-000: com.adobe.idp.dsc.net.DSCNamingException: Remote EJBObject lookup failed for ejb/Invocation provider
	at com.adobe.idp.dsc.provider.impl.ejb.EjbMessageDispatcher.initialise(EjbMessageDispatcher.java:101)
	at com.adobe.idp.dsc.provider.impl.ejb.EjbMessageDispatcher.doSend(EjbMessageDispatcher.java:141)
...
Caused by: javax.naming.NameNotFoundException: Unable to resolve 'ejb.Invocation'. Resolved 'ejb' [Root exception is javax.naming.NameNotFoundException: Unable to resolve 'ejb.Invocation'. Resolved 'ejb']; remaining name 'Invocation'
	at weblogic.rjvm.ResponseImpl.unmarshalReturn(ResponseImpl.java:234)
	at weblogic.rmi.cluster.ClusterableRemoteRef.invoke(ClusterableRemoteRef.java:348)
	...
Caused by: javax.naming.NameNotFoundException: Unable to resolve 'ejb.Invocation'. Resolved 'ejb'
	at weblogic.jndi.internal.BasicNamingNode.newNameNotFoundException(BasicNamingNode.java:1139)
	at weblogic.jndi.internal.BasicNamingNode.lookupHere(BasicNamingNode.java:252)
	...

One final note: use DSC_CREDENTIAL_USERNAME and DSC_CREDENTIAL_PASSWORD if security is enabled on the service you are calling. By default, security is enabled on every LiveCycle service a.k.a. process you create. To turn if off for a given process, use the Admin UI to set "Require callers to authenticate:" to "No."