Thursday, October 13, 2011

Platforms and Accessibility

If you are a developer, stop what you are doing and read this now: Steve Yegge's Google Platform rant.

Then ask:

  • Why can't Google Reader work with Google+?
  • Why can't Flipboard work with Google+?
  • Why is Google senior management not using Google+?
  • Why can't I change the default font size in Chrome? Yes you can - but for a long time you couldn't. Whew. (But another lesson for me: too many sites control the font size when they shouldn't.)
  • Why can't Google Docs support user created styles?
  • Why does the Google Docs mobile UI suck. SUCK.

It's all about Platforms and Accessibility.

Never before has this been made so clear to me. It has changed the way I think about my job.

Steve deleted the original post because it was meant to be an internal communication only, but I think what he says is important. It points out the most important thing that Amazon does right and Google does wrong. And it has quite changed the way I think about SOA.

Friday, October 07, 2011

Creating database, schema, user and login with TSQL

Here is how to create a database, schema, user and login in MS SQL - this should work equally for MS SQL 2005/2008 at least. In MS SQL, there is a distinction between a USER and a LOGIN; a user is attached to databases can be assigned various roles and permissions, and a login can be mapped to different users.

CREATE DATABASE TEST;
GO
USE TEST;
GO
CREATE SCHEMA TEST;
GO
CREATE LOGIN TEST_USER WITH PASSWORD = 'password';
EXEC sp_defaultdb @loginame='TEST_USER', @defdb='TEST'
CREATE USER TEST_USER FOR LOGIN TEST_USER WITH DEFAULT_SCHEMA = TEST;
EXEC sp_addrolemember 'db_owner','TEST_USER'
GO

Microsoft's Transact SQL (or TSQL) and Oracle's PL/SQL are both proprietary procedural SQL languages that let you write programs in what is essentially SQL with control statements etc. If you put the above code into a file, you could run it with a command such as this.

sqlcmd -S "localhost\SQLEXPRESS" -U sa -P password -i "C:\path\to\script.sql"

The line EXEC sp_addrolemember 'db_owner','TEST_USER' is used to make the new user an owner of the new database. A database owner has permission to create tables, procedures and views etc within the given database. In general, a role is a collection of permissions that will be granted or denied to whichever user is assigned that role. There is an important distinction about the db_owner role in particular: an owner cannot have any permission denied to it. The ownership role is thus very powerful and should be used sparingly. In production systems it would be more advisable to create a new role with specific permission assigned or denied.

Lastly, here is how to roll it all back. Users belong to databases, so if you drop the database - and the user only belongs to that one database - you also delete the user.

-- Roll it back..
DROP DATABASE TEST;
DROP SCHEMA TEST;
DROP LOGIN TEST_USER;
GO

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."