Tuesday, November 15, 2011

Eclipse: make sure your unit test uses the project's classpath

I was getting this error when trying to run a unit test through Eclipse (Indigo 3.7).

java.lang.NoSuchMethodError: org.slf4j.helpers.MessageFormatter.arrayFormat(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
 at ch.qos.logback.classic.spi.LoggingEvent.getFormattedMessage(LoggingEvent.java:282)
   ...

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/MyProject/lib/logback-classic-0.9.15.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/MyProject/lib/logback-classic-0.9.27.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: The requested version 1.5.6 by your slf4j binding is not compatible with [1.6]
SLF4J: See http://www.slf4j.org/codes.html#version_mismatch for further details.

Turns out the key line was SLF4J: Class path contains multiple SLF4J bindings - it was finding StaticLoggerBinder classes in multiple places (specifically logback-classic-0.9.15.jar and logback-classic-0.9.27.jar). In our project, we kept both versions of the jar in our lib directory, but were only referencing the latest one in the .classpath file. Unfortunately, by default, the JUnit Run Configuration ignores your project classpath and instead just scans for jars.

To stop this from happening, go Run > Run Configurations > MyTestName > Classpath tab > User entries > click on myappname (default classpath) > Edit > tick "Only include exported entries" > OK > Apply > Run. Now the unit test's run configuration will use your project's classpath.

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

Friday, September 23, 2011

Get HTTPS to work for localhost on Tomcat

I needed to test how my app works with HTTPS locally, so I began going through the Tomcat 7 HOW-TO for setting up SSL. I got the basics out of it, but it didn't work locally and being a security n00b I really didn't understand what I was doing anyway. So I made a StackOverflow post, SSL in Tomcat 7 where EJP helped me get it to work. And thus I document it here so that I can repeat these steps in a year's time when I have completely forgotten what I did to get it working. I will note below what bits the Tomcat 7 HOW-TO didn't make clear with this marker: HOW-TO Note.

I am using Tomcat 7, Windows 7, JDK 6 - and I have %JAVA_HOME%\bin correctly set in my path.

1. Make a Key Pair

Use the Java keytool utility to generate a key pair (a public key and associated private key). If you haven't already created a keystore, this command will create one for you (by default in user home, so for Windows 7 the keystore created is the file C:\Users\USERNAME\.keystore).

The following shows what input I used for the command suggested by the HOW-TO: keytool -genkey -alias tomcat -keyalg RSA.

keytool -genkey -alias tomcat -keyalg RSA
Enter keystore password:  changeit
Re-enter new password: changeit
What is your first and last name?
  [Unknown]:  localhost
What is the name of your organizational unit?
  [Unknown]:  Dev
What is the name of your organization?
  [Unknown]:  MyBusiness
What is the name of your City or Locality?
  [Unknown]:  Melbourne
What is the name of your State or Province?
  [Unknown]:  Victoria
What is the two-letter country code for this unit?
  [Unknown]:  AU
Is CN=localhost, OU=Dev, O=MyBusiness, L=Melbourne, ST=Victoria, C=AU correct?
  [no]:  yes

Enter key password for         (RETURN if same as keystore password):

HOW-TO Note: in response to the question What is your first and last name? enter localhost, not your name!

If you have never run this command before, it will output a single file: C:\Users\USERNAME\.keystore. If you like, confirm that the aliased key pair has been made with a command such as keytool -list -alias tomcat, shown below.

keytool -list -alias tomcat
Enter keystore password: changeit
tomcat, 23/09/2011, PrivateKeyEntry,
Certificate fingerprint (SHA1): B2:B9:DB:B8:C3:9F:7D:2B:F8:82:A2:A2:C7:A2:9A:94:90:65:69:6E

2. Create Connector in server.xml

Open up the server.xml file for your Tomcat. I am running a Tomcat instance through Eclipse so for me the file is not the one stored in the Tomcat install directory (TOMCAT-INSTALL-DIR\conf\server.xml) but ECLIPSE-WORKSPACE\Servers\Tomcat v7.0 Server at localhost-config\server.xml. Look for the comment that includes the text Define a SSL HTTP/1.1 Connector on port 8443, and add the below XML after that comment.

<Connector port="8443" maxThreads="200"
        scheme="https" secure="true" SSLEnabled="true"
        keystoreFile="${user.home}/.keystore" keystorePass="changeit"
        clientAuth="false" sslProtocol="TLS"/>

Now restart Tomcat.

3. Load the App, Trust the Certificate

HOW-TO Note: the HOW-TO doesn't mention anything about this. Probably they didn't have localhost in mind, or maybe it's because I am not a trusted source of certificates.

  1. Load up your app in IE: https://localhost:8443/path/to/your/app (note the use of port 8443 - as per the Connector XML above).
  2. You will see an error page (There is a problem with this website's security certificate....). Click Continue to this website (not recommended).
  3. The address bar will be red and have the text "Certificate Error". Click on it. See the message: The security certificate presented by this website was not issued by a trusted certificate authority.
  4. Click View Certificates. See the below dialogue box.
  5. Click Install Certificate > Next
  6. Select Place all certificates in the following store > Browse > select Trusted Root Certificate Authorities > click OK.
  7. Click Next > click Finish. See this:
    .
  8. Click Yes > see the message The import was successful > click OK > click OK.
  9. Re-load https://localhost:8443/path/to/your/app in IE and it should work without showing any certificate error!

Unresolved Issues

This isn't perfect though. There are two problems I haven't worked out yet.

It doesn't work if I install the certificate first. You can export the certificate like this.

keytool -export -alias tomcat -file tomcatcertfile.cer
Enter keystore password:  changeit
Certificate stored in file 

This will output a file in the same directory as the keystore file, by default C:\Users\USERNAME\tomcatcertfile.cer.

  1. Go Start Menu, type "Internet Options" > select Content tab > click Certificates.
  2. Select Trusted Root Certification Authorities tab.
  3. Click Import > click Next > enter File Name: C:\Users\USERNAME\tomcatcertfile.cer > click Next
  4. Click Next > click Finish > click OK > click close > click OK.

But after I did this, I still see a certificate error in IE.

It doesn't work in Chrome or Firefox - although in Firefox you can add an exception. In Chrome, I see the following error, which I have found no workaround for.

Pages that helped me with this post.

Monday, September 19, 2011

Unsupported major.minor version 51.0 while patching component in LiveCycle ES2

Got this when patching a component in my local LiveCycle ES 2.

When trying to patch the component I got the following unhelpful error from (Eclipse based) Adobe LiveCycle Workbench ES2:

ALC-DSC-027-000: com.adobe.idp.dsc.registry.component.IncompatiblePatchException: Cannot patch component: au.com.blah.NotMyRealComponentName with component having id of: au.com.blah.NotMyRealComponentName
   at com.adobe.idp.dsc.registry.component.impl.ComponentRegistryImpl._update(ComponentRegistryImpl.java:563)
   at com.adobe.idp.dsc.registry.component.impl.ComponentRegistryImpl.modify(ComponentRegistryImpl.java:517)
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   ... snip *yawn*

I tried un-installing and then re-installing the component, and got an equally unhelpful error from Workbench:

ALC-DSC-000-000: com.adobe.idp.dsc.DSCRuntimeException: Internal error.
   at com.adobe.idp.dsc.transaction.impl.ejb.adapter.EjbTransactionBMTAdapterBean.doRequiresNew(EjbTransactionBMTAdapterBean.java:249)
   at sun.reflect.GeneratedMethodAccessor644.invoke(Unknown Source)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
   at java.lang.reflect.Method.invoke(Unknown Source)
... snip *yawn*

Finally I remembered to look in server.log and found the smoking gun: Unsupported major.minor version 51.0.

2011-09-19 15:03:27,064 ERROR [org.jboss.ejb.plugins.LogInterceptor] RuntimeException in method: public abstract java.lang.Object com.adobe.idp.dsc.transaction.impl.ejb.adapter.EjbTransactionBMTAdapterLocal.doBMT(com.adobe.idp.dsc.transaction.TransactionCallback) throws com.adobe.idp.dsc.DSCException:
ALC-DSC-000-000: com.adobe.idp.dsc.DSCRuntimeException: Internal error.
   at com.adobe.idp.dsc.transaction.impl.ejb.adapter.EjbTransactionBMTAdapterBean.doRequiresNew(EjbTransactionBMTAdapterBean.java:249)
   at sun.reflect.GeneratedMethodAccessor644.invoke(Unknown Source)
... snip
Caused by: java.lang.UnsupportedClassVersionError: au/com/blah/datatype/PukErrorListCombo : Unsupported major.minor version 51.0
   at java.lang.ClassLoader.defineClass1(Native Method)
   at java.lang.ClassLoader.defineClass(Unknown Source)
... snip

From this old java.net post called Unsupported major.minor version 51.0: It means that you compiled your classes under a specific JDK, but then try to run them under older version of JDK. So, you can't run classes compiled with JDK 6.0 under JDK 5.0. The same with classes compiled under JDK 7.0 when you try to run them under JDK 6.0.

I have had this before, when I had to switch major versions e.g. revert from using JDK5 for development to JDK4 because the end server used that version. In my case, it was a lot narrower. I was building the component jar using JDK 1.6.0_26 whereas the LiveCycle ES2 uses JDK 1.6.0_14 (which I found by looking at what Java it installed: C:\Adobe\Adobe LiveCycle ES2\Java\jdk1.6.0_14\bin\javaw.exe). So I reverted my build script to use the earlier JDK.

The next problem I found is that merely patching the component still fails. I had to un-install and then re-install the component to get it to work.

Eclipse and SVN on 64-Bit Windows

I am running the following components.

  • JDK 1.7.0 (build 1.7.0-b147), Java HotSpot(TM) 64-Bit Server VM (build 21.0-b17, mixed mode)
  • Eclipse eclipse-jee-indigo-win32-x86_64
  • Subclipse 1.6.18
  • Windows 7 64-bit
  • CollabNet Subversion Command-Line Client v1.6.17 (for Windows)

And I get the below error from Eclipse every time I try to use SVN for the first time (since opening Eclipse). The operation still works, but this error is annoying.

Subclipse talks to Subversion via a Java API that requires access to native libraries. Those libraries were either not found or an incompatible version was loaded. The errors are displayed below. Informaction on getting a proper version installed and visible to Eclipse is available here: http://subclipse.tigris.org/wiki/JavaHL

Failed to load JavaHL Library.
These are the errors that were encountered:
C:\Program Files (x86)\CollabNet\Subversion Client\libapr-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libapriconv-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libeay32.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\ssleay32.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libaprutil-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libsasl.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libsvn_subr-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libsvn_delta-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libsvn_diff-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libsvn_wc-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libsvn_fs-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libsvn_repos-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libsvn_ra-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libsvn_client-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
C:\Program Files (x86)\CollabNet\Subversion Client\libsvnjavahl-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
no svnjavahl-1 in java.library.path
no svnjavahl in java.library.path
java.library.path = C:\Windows\system32;C:\Windows\Sun\Java\bin;C:\Windows\system32;C:\Windows;.;C:\cygwin\home\rbram\bin;C:\cygwin\usr\local\bin;C:\cygwin\bin;C:\Program Files (x86)\CollabNet\Subversion Client;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0;C:\Program Files\Intel\WiFi\bin;C:\Program Files\Common Files\Intel\WirelessCommon;C:\Program Files\Intel\DMIX;C:\Program Files\TortoiseSVN\bin;C:\Program Files (x86)\WinMerge;C:\Program Files (x86)\QuickTime\QTSystem;C:\Program Files (x86)\Microsoft SQL Server\90\Tools\binn;C:\Program Files (x86)\IDM Computer Solutions\UltraEdit;C:\Program Files\Intel\WiFi\bin;C:\Program Files\Common Files\Intel\WirelessCommon;C:\Users\rbram\Documents\Java\apache-ant-1.8.2\bin;C:\Program Files\7-Zip;C:\cygwin\bin;C:\cygwin\lib\lapack;.

When I look at the page the error mentions, it has a section on 64-Bit Windows that essentially says I need to install SlikSVN. So I installed Slik-Subversion-1.6.17-x64.msi and removed C:\Program Files (x86)\CollabNet\Subversion Client from my path. After a re-boot, I haven't seen the problem come back.

Sunday, September 18, 2011

Autohotkey script for HTML/XML - create closing tags

Note. Get the AHK script. Discuss it on the Autohotkey forum, or see discussion on Autohotkey for the best way to select a word.

UltraEdit is my favourite text editor: it has a very strong macro language and lets you write scripts in Javascript as well. Previously I wrote about a set of UltraEdit macros for creating XML/HTML tags: UltraEdit macro for HTML/XML - auto-create closing tags. I use these two macros so often in UltraEdit that I began to feel the pain whenever I was editing some Google Doc, forum post, or XML file in Eclipse and wanted the same functionality. This is where Autohotkey comes in - I have already written about my favourite Autohotkey scripts. Autohotkey is an amazing Windows scripting language that is flexible and relatively easy to grasp. So I set about translating these two UltraEdit macros into Autohotkey. Here is the result.

The first macro outputs an empty container tag, with the cursor left in the middle. For example, if I were to type out xmlTag| and press Alt+Control+Shift+r, I will be left with the following: <xmlTag>|</xmlTag>. The pipe character (|) indicates cursor position, i.e. when you invoke the macro, your cursor should be at the end of the word (after the "g" in this example) and will be left in between the two tags when the macro finishes. If you have a tag name that contains non alpha-numeric characters such as hyphens, underscores or (Maker forbid) spaces, select the entire tag name first.

; Make XML tag out of current token, leaving cursor in the middle.
; Usage GOOD (selects token with a control+shift LEFT):
;  token|
;  >token< entire "token" selected
;  >token-token< entire "token-token" selected
;  >token_token< entire "token_token" selected
; Usage BAD:
;  token-token|
;  token_token|
;  toke|n
;  |token
!^+r::
   ClipSaved := ClipboardAll  ; Save the entire clipboard to a variable of your choice.
   Clipboard := ; Clear the clipboard
   Send ^x
   if (clipboard == "") {
      Send, {CTRLDOWN}{SHIFTDOWN}{LEFT}{CTRLUP}{SHIFTUP}{CTRLDOWN}x{CTRLUP}
   }
   token := RegExReplace(Clipboard, "[[:space:]]")
   whiteSpace := RegExReplace(Clipboard, "\S")
   Send <%token%></%token%>%whiteSpace%
   x := StrLen(token) + 3 + StrLen(whiteSpace)
   Loop, %x% {
      Send, {Left}
   }
   Clipboard = %ClipSaved%   ; Restore the original clipboard. Note the use of Clipboard (not ClipboardAll).
   ClipSaved =   ; Free the memory in case the clipboard was very large.
return

The second macro does the same thing except that it will paste whatever is in your clipboard in between the two tags and leave the cursor at the end of the closing tag. For example, if I copied the text lorum ipsum baby! and then typed out xmlTag| and pressed Alt+Control+Shift+t, I will be left with the following: <xmlTag>lorum ipsum baby!</xmlTag>|. Again, the pipe character (|) indicates cursor position, i.e. when you invoke the macro, you cursor should be at the end of the word (after the "g" in this example) and will be left at the end of the closing tag when the macro finishes. If you have a tag name that contains non alpha-numeric characters, select the entire tag name first.

; Make XML tag out of current token, pasting clipboard contents in between and leaving cursor after the end of the closing tag.
; Usage GOOD (selects token with a control+shift LEFT):
;  token|
;  >token< entire "token" selected
;  >token-token< entire "token-token" selected
;  >token_token< entire "token_token" selected
; Usage BAD:
;  token-token|
;  token_token|
;  toke|n
;  |token
!^+t::
   tagContents = %clipboard%    ; Convert any copied files, HTML, or other formatted text to plain text.
   Clipboard := ; Clear the clipboard
   Send ^x
   if (clipboard == "") {
      Send, {CTRLDOWN}{SHIFTDOWN}{LEFT}{CTRLUP}{SHIFTUP}{CTRLDOWN}x{CTRLUP}
   }
   token := RegExReplace(Clipboard, "[[:space:]]")
   whiteSpace := RegExReplace(Clipboard, "\S")
   Clipboard = %tagContents%   ; Restore the original clipboard as text.
   Send <%token%>^v</%token%>%whiteSpace%
   x := StrLen(whiteSpace)
   Loop, %x% {
      Send, {Left}
   }
   tagContents =   ; Free the memory in case the clipboard was very large.
   clipLength :=
return

Not as good as UltraEdit

Unfortunately, this Autohotkey script is not as good as the UltraEdit macro - because the UltraEdit macro command SelectWord correctly handles "selecting current word under the cursor" irrespective of whether your cursor is at the start, end or in the middle of the word, or whether the word is at the start, end or somewhere in the middle of a line. In the Autohotkey script, I am using {CTRLDOWN}{SHIFTDOWN}{LEFT}{CTRLUP}{SHIFTUP} (control+shift+left) to select the word just typed (unless you have the entire word already selected). The control+shift+left action yields incorrect results if the cursor is not at the end of the word - and using control+right, control+shift+left yields incorrect results if the word is at the end of the line and the cursor is at the end of the word (because it will select the newline character as well). I don't know how to mimic the SelectWord behaviour properly in Autohotkey without resorting to imitating a double click (which is crazy - your mouse and cursor will be at different positions when you are typing) or using caret logic that won't work in all types of inputs that accept text.

As a result, I use the UltraEdit macros when in UltraEdit and the Autohotkey macros when in any other program - but I still have the same keyboard mappings for both! How do I do this? Because Autohotkey is so cool that you can even define what applications a script should be active for. I surround the Autohotkey script logic in a #IfWinNotActive, UltraEdit directive that will prevent it from working in UltraEdit. See below for an example of this.

#IfWinNotActive, UltraEdit
  ; ... Autohotkey script that will work in any app other than UltraEdit.
#IfWinNotActive

So I get functionality that is more or less the same everywhere, just more flexible in UltraEdit.

Saturday, September 17, 2011

Separate DDL and DML in Transact-SQL with Batches

I wrote about this problem in a Stackoverflow.com post called Problem with alter then update in try catch with tran using Transact-SQL where Mikael Eriksson gave me just the hint I needed. My problem came about when I tried to run the below Transact-SQL via sqlcmd (SQL Server 2005) (using the command: sqlcmd -S "localhost\SQLEXPRESS" -U sa -P password -i "C:\path\to\transform.sql").

USE PUK;
BEGIN TRANSACTION;
BEGIN TRY

   -- Modify the table RETRIEVAL_STAT
   alter table dbo.RETRIEVAL_STAT add
      SOURCE nvarchar(10) NULL,
      ACCOUNTNUMBER nvarchar(50) NULL,
      PUK nvarchar(20) NULL;

   -- Modify data in new columns of table RETRIEVAL_STAT
   update dbo.RETRIEVAL_STAT set
      SOURCE = 'XX',
      ACCOUNTNUMBER = 'XX',
      PUK = 'XX';

END TRY
BEGIN CATCH
    SELECT
        ERROR_NUMBER() AS ErrorNumber
        ,ERROR_SEVERITY() AS ErrorSeverity
        ,ERROR_STATE() AS ErrorState
        ,ERROR_PROCEDURE() AS ErrorProcedure
        ,ERROR_LINE() AS ErrorLine
        ,ERROR_MESSAGE() AS ErrorMessage;
    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;
END CATCH;
IF @@TRANCOUNT > 0
    COMMIT TRANSACTION;
GO

Note that it contains code that alters a table (DDL) followed by code that modifies data in a table (DML). Also note that the DML is meant to work on the new columns introduced by the DDL. This code generates the below error.

(0 rows affected)
Changed database context to 'PUK'.
Msg 207, Level 16, State 1, Server localhost\SQLEXPRESS, Line 13
Invalid column name 'SOURCE'.
Msg 207, Level 16, State 1, Server localhost\SQLEXPRESS, Line 13
Invalid column name 'ACCOUNTNUMBER'.
Msg 207, Level 16, State 1, Server localhost\SQLEXPRESS, Line 13
Invalid column name 'PUK'.

The problem is that Transact-SQL is compiled first, then executed. It means that when the compiler goes through my SQL, it finds that the second block - the SQL that modifies the new columns - cannot possibly work because those columns don't exist yet. (Because it hasn't executed the first block yet, it is still compiling the whole lot!) The solution is simple enough. Place the blocks in separate batches, which are compiled (and thus executed) separately, but still in order because I will still place the batches in the same file, one after the other, and run them with the same command. A batch is a series of Transact-SQL statements (that may include a try-catch) separated by the GO command.

This code works.

USE PUK;
BEGIN TRANSACTION;
BEGIN TRY

   -- Modify the table RETRIEVAL_STAT
   alter table dbo.RETRIEVAL_STAT add
      SOURCE nvarchar(10) NULL,
      ACCOUNTNUMBER nvarchar(50) NULL,
      PUK nvarchar(20) NULL;

END TRY
BEGIN CATCH
    SELECT
        ERROR_NUMBER() AS ErrorNumber
        ,ERROR_SEVERITY() AS ErrorSeverity
        ,ERROR_STATE() AS ErrorState
        ,ERROR_PROCEDURE() AS ErrorProcedure
        ,ERROR_LINE() AS ErrorLine
        ,ERROR_MESSAGE() AS ErrorMessage;
    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;
END CATCH;
IF @@TRANCOUNT > 0
    COMMIT TRANSACTION;
GO

USE PUK;
BEGIN TRANSACTION;
BEGIN TRY

   -- Modify data in new columns of table RETRIEVAL_STAT
   update dbo.RETRIEVAL_STAT set
      SOURCE = 'XX',
      ACCOUNTNUMBER = 'XX',
      PUK = 'XX';

END TRY
BEGIN CATCH
    SELECT
        ERROR_NUMBER() AS ErrorNumber
        ,ERROR_SEVERITY() AS ErrorSeverity
        ,ERROR_STATE() AS ErrorState
        ,ERROR_PROCEDURE() AS ErrorProcedure
        ,ERROR_LINE() AS ErrorLine
        ,ERROR_MESSAGE() AS ErrorMessage;
    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;
END CATCH;
IF @@TRANCOUNT > 0
    COMMIT TRANSACTION;
GO

Pages that helped me with this post.

Thursday, September 15, 2011

Windows: Delete a Service Definition when the files are gone

This happened to me wast week on my Windows 7 machine. I installed Adobe LiveCycle ES 2, something went wrong during the install and the un-installer didn't complete - so I simply deleted everything under C:\Adobe. I could not re-install Adobe LiveCycle ES 2 because the installer was telling me that the MySQL (and then the JBoss) services were already installed. These are services that the turnkey Adobe LiveCycle ES 2 installs. So in this case, none of the files were present (I deleted them) but the service definitions were still present in the Windows Control Panel > Services page. To delete them I ran the sc delete command as such:

sc delete "MySQL for Adobe LiveCycle ES2"
sc delete JBOSS_FOR_ADOBE_LIVECYCLE_ES2

Wednesday, September 07, 2011

JNDI binding in JBoss

How to get a JNDI name in a DSdotXML file (-ds.xml) with a matching application reference in JBoss. Thanks to Ketan, who is awesome!

I had big problems getting this to work. I wrote about in the Adobe LiveCycle Developers Google Group: Problem with JNDI binding in JBoss (LiveCycle turnkey) and on StackOverflow: Problem with JNDI binding in JBoss (LiveCycle turnkey), but it was my friend Ketan who pointed out the solution.

I needed a jboss-web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
   <resource-ref>
      <res-ref-name>RMB_DS</res-ref-name>
      <jndi-name>java:/RMB_DS</jndi-name>
   </resource-ref>
</jboss-web>

And changed the original web.xml to have:

<resource-ref>
   <description>DB Connection</description>
   <res-ref-name>RMB_DS</res-ref-name>
   <res-type>javax.sql.DataSource</res-type>
   <res-auth>Container</res-auth>
</resource-ref>

Which matches rmb-ds.xml

<datasources>
   <local-tx-datasource>
      <jndi-name>RMB_DS</jndi-name>
      <connection-url>jdbc:sqlserver://localhost\SQLEXPRESS;DatabaseName=RMB</connection-url>
      <driver-class>com.microsoft.sqlserver.jdbc.SQLServerDriver</driver-class>
      <user-name>sa</user-name>
      <password>password</password>
      <check-valid-connection-sql>SELECT 1 FROM sysobjects</check-valid-connection-sql>
      <!-- corresponding type-mapping in the standardjbosscmp-jdbc.xml (optional) -->
      <metadata>
         <type-mapping>MS SQLSERVER2000</type-mapping>
      </metadata>
   </local-tx-datasource>
</datasources>

Tuesday, September 06, 2011

Get JBoss Web Console and JMX Console back in Turnkey ES2

In my new work-place, we are using the turnkey version of ES2 (Adobe - LiveCycle Enterprise Suite - note that as of ES3, it is being renamed to ADEP, Adobe Digital Enterprise Platform). The turnkey project installs LiveCycle on JBoss with MySql.

I am having trouble finding out what JNDI datasources are present, so I looked for JBoss JMX Console or JBoss Web Console. But they are missing in the turnkey project - for security purposes. Thankfully, it is very easy to bring them back.

First, find out what version of JBoss you are using. I found this in the JBoss readme: C:\Adobe\Adobe LiveCycle ES2\jboss\readme.html (by default on Windows, ES2 installs itself to C:\Adobe\Adobe LiveCycle ES2). Once you know your version (4.2.1.GA for ES2), download the original zip file for that version of JBoss and unzip it somewhere. Copy the management directory (web console) and jmx-console.war war file (JMX console) into your JBoss' deploy directory. For me that meant copying C:\Temp\jboss-4.2.1.GA\server\all\deploy\management and C:\Temp\jboss-4.2.1.GA\server\all\deploy\jmx-console.war to C:\Adobe\Adobe LiveCycle ES2\jboss\server\lc_turnkey\deploy.

Now I have web-console: http://localhost:8080/web-console/ and JMX Console: http://localhost:8080/jmx-console/.

Making Sugar fields sortable and linkable

I am using Sugar Community Edition, Version 6.2.0RC3 (Build 6350).

I found that in the list views for custom modules in Sugar - and in the sub-panels Sugar creates when you make relationships between custom modules in Sugar - only name fields appear as links to the items being listed. Furthermore, it only allows name fields to be sortable by default. This is annoying default behaviour - particularly if you don't want to use a name field. Thankfully, it is relatively easy to fix.

Linking requires a different fix for list views and sub-panels. I came across a post in the Sugar forums that gave me the clues I needed for this: No list view link.

Linking in list views. You need to find the listviewdefs.php file for the particular module you are working on. This path will differ slightly depending on whether you have the module in a package or not (I do). In my example, assume I have a field called "FIELD_P" in a module called "Module_X" in a package called "Package_A". Look for the file <SUGAR_INSTANCE>\custom\modulebuilder\packages\Package_A\modules\Module_X\metadata\listviewdefs.php. (The path to SUGAR_INSTANCE will be a the unzipped Sugar install deployed to an Apache instance.) Within that file, find the array representing the field you want to make "linkable". It should look like this:

'FIELD_P' =>
   array (
      'type' => 'enum',
      'studio' => 'visible',
      'label' => 'LBL_FIELD_P',
      'sortable' => false,
      'width' => '10%',
      'default' => true,
   ),

All you need to do is add another array element, 'link' => true, like so:

'FIELD_P' =>
   array (
      'type' => 'enum',
      'studio' => 'visible',
      'label' => 'LBL_FIELD_P',
      'sortable' => false,
      'width' => '10%',
      'default' => true,
      'link' => true,
   ),

Linking in sub-panels. Find the PHP file for the subpanel. For my example, the default sub-panel will be in this file: <SUGAR_INSTANCE>\custom\modulebuilder\packages\Package_A\modules\Module_X\metadata\subpanels\default.php. Again, find the array representing the field you want to make "linkable". It should look like this:

'FIELD_P' =>
   array (
      'type' => 'enum',
      'studio' => 'visible',
      'label' => 'LBL_FIELD_P',
      'sortable' => false,
      'width' => '10%',
      'default' => true,
   ),

All you need to do is add another array element, 'widget_class' => 'SubPanelDetailViewLink', like so:

'FIELD_P' =>
   array (
      'type' => 'enum',
      'studio' => 'visible',
      'label' => 'LBL_FIELD_P',
      'sortable' => false,
      'width' => '10%',
      'default' => true,
      'widget_class' => 'SubPanelDetailViewLink',
   ),

Sorting is even easier, and you have already seen where to fix it for both sub-panels and list views - change 'sortable' => false, in the above two examples to 'sortable' => true,.

Final note: you are making changes to files that Sugar has generated. I noticed that whenever I requested further deployments and cache refreshes, the changes remained - but who can tell what sort of action will blow those changes away. For this reason, it is very important to back up your Sugar files regularly.

Friday, September 02, 2011

java.lang.ClassNotFoundException: No ClassLoaders found for: com.microsoft.sqlserver.jdbc.SQLServerDriver

Today I was making a JDBC call to an application running on JBoss and using MS SQL Server 2005 Express Edition (SP 4), when I received the below error.

Exception: Could not create connection; - nested throwable: (org.jboss.resource.JBossResourceException: Failed to register driver for: com.microsoft.sqlserver.jdbc.SQLServerDriver; - nested throwable: (java.lang.ClassNotFoundException: No ClassLoaders found for: com.microsoft.sqlserver.jdbc.SQLServerDriver)); - nested throwable: (org.jboss.resource.JBossResourceException: Could not create connection; - nested throwable: (org.jboss.resource.JBossResourceException: Failed to register driver for: com.microsoft.sqlserver.jdbc.SQLServerDriver; - nested throwable: (java.lang.ClassNotFoundException: No ClassLoaders found for: com.microsoft.sqlserver.jdbc.SQLServerDriver)))

It meant that JBoss didn't have the JDBC driver jar installed.

Microsoft has a page for JDBC Drivers, which includes a link to download Microsoft SQL Server JDBC Driver 3.0. Download and run sqljdbc_3.0.1301.101_enu.exe (which is just a ZIP archive). Copy the driver jar: Microsoft SQL Server JDBC Driver 3.0\sqljdbc_3.0\enu\sqljdbc4.jar to C:\Adobe\Adobe LiveCycle ES2\jboss\server\lc_turnkey\lib, then restart JBoss to make sure the changes take effect.

TCP/IP connection to the host 127.0.0.1, port 1433 has failed

I encountered this annoying error today when attempting to make a JDBC connection to MS SQL Server 2005 Express Edition (SP 4).

Exception: Could not create connection; - nested throwable: (com.microsoft.sqlserver.jdbc.SQLServerException: The TCP/IP connection to the host 127.0.0.1, port 1433 has failed. Error: "Connection refused: connect. Verify the connection properties, check that an instance of SQL Server is running on the host and accepting TCP/IP connections at the port, and that no firewall is blocking TCP connections to the port.".); - nested throwable: (org.jboss.resource.JBossResourceException: Could not create connection; - nested throwable: (com.microsoft.sqlserver.jdbc.SQLServerException: The TCP/IP connection to the host 127.0.0.1, port 1433 has failed. Error: "Connection refused: connect. Verify the connection properties, check that an instance of SQL Server is running on the host and accepting TCP/IP connections at the port, and that no firewall is blocking TCP connections to the port.".))

The problem was that TCP/IP connections were not enabled. To fix this, open SQL Server Configuration Manager, select SQL Server 2005 Network Configuration (32bit), select Protocols for SQLEXPRESS, mark TCP/IP as Enabled. Also, restart MS SQL Server to ensure the changes take effect.

For future reference, MS SQL JDBC connection strings look like this: jdbc:sqlserver://host\instanceName;DatabaseName=TheDbName - optionally include username and password with jdbc:sqlserver://host\instanceName;DatabaseName=TheDbName;user=xxx;password=xxx. You can find out host and instance name through Microsoft SQL Server Management Studio Express: right click on the DB and select Properties.

Also, MS SQL Server runs on port 1433 by default, but the connection string doesn't specify this. Why?

Thursday, August 18, 2011

SVN Keybindings in Eclipse Indigo (3.7)

This really got me today. I just setting up a new instance of Eclipse Indigo (3.7) and when I set up key bindings for SVN, they didn't work. I looked at the team menu and the shortcuts were shown. But they didn't work. Many nails were bitten.

I found the solution in this StackOverflow post answered by Russell Davis: SVN key bindings not working in Eclipse. The issue is due to a breaking change in Eclipse 3.6. The fix: in the "Customize Perspective" dialog, go to the "Command Groups Availability" tab and check "Team" and "SVN".

Wednesday, August 10, 2011

Limitations of Gmail on iPad

Updates. Wednesday 21 September 2011, 08:35:51 PM: yay, I am wrong! Use two fingers to scroll up/down a nested scrollable area. For Atomic users: disable 2 finger swipe up/down gestures first!

The iPad interface for Gmail is fantastic - but still, as a mobile interface it is very limited. Just like on the iPhone (and Android?) you don't get access to all of your settings. For example, you can't use the mobile interface to change what pop accounts Gmail connects to - nor can you do this through the Gmail HTML interface

A couple of days ago, I wrote about using Atomic Web browser on the iPad to get access to the desktop version of Google Checkout, which worked nicely. Unfortunately, it doesn't work as nicely for the Gmail desktop interface on Atomic Web Browser: I can get to the Accounts and Import settings page, but cannot scroll down to the section containing the pop accounts!


Sunday, August 07, 2011

Text macros in iOS - insert a date on the iPad

I have written before about how to insert dates into various windows applications and how this was solved instantly and globally and magically with Autohotkey. Today I worked out how to solve this - and so much more - on the iPad and iPhone.

The answer is TextExpander Touch - which gives you text based macros (called "snippets"), combined with the hopefully growing list of apps that support TextExpander. So far, I am regularly using BlogPress for writing blog posts (such as this one) and WriteRoom for text notes sync'd with DropBox.

J. Kevin Wolfe (that's Wolfe with an e) wrote the most useful review I have found for TextExpander: Review: iPad's TextExpander leaves you shorthanded (he is being ironic). He lists two of the most important snippet expansions, which I have copied below.

  • Paste contents of the clipboard: %clipboard
  • Place the cursor: %|

Here are the mappings I have made for dates.

  • Abbreviation: dd1; content: %date:EEEE d MMMM YYYY, hh:mm:ss a%; sample output: Sunday 7 August 2011, 01:18:36 AM
  • Abbreviation: dd2; content: %date:YYYYMMdd-HHmmss%; sample output: 20110807-012007

In this Smile blog post: More TextExpander Date and Time Formatting Options the creators of TextExpander write that it uses the Unicode Date Format Patterns, so go nuts and create your own!

My biggest gripe with this arrangement is that although TextExpander is uber-cool, it will only work with apps that come pre-baked with TextExpander support. This means it won't work in any of the "standard" iOS apps like Safari, Notes, Pages, SMS or Email. I personally doubt that Apple built apps will ever support TextExpander unless Apple buys the company (but I'm not starting any rumors). It means that you can only use TextExpander in one or more of the apps that support TextExpander, c&p'ing your content to an unsupported app as required. Personally, BlogPress suddenly became 100% cooler than I thought it was, and I will become very attached to WriteRoom from here on in.

One further note, don't forget to tap the + icon within Snippet Groups and tap Add Predefined - you can get predefined snippets for CSS, HTML and some good auto-corrections for common mis-spellings.

Saturday, August 06, 2011

Getting around mobile interface for Google Checkout on iOS

When I tried to renew my credit card details on Google Checkout today, I used my iPad - I am on holiday and don't have access to a PC. On my iPad, however, Google Checout only offers the mobile interface which doesn't have the pages needed to modify account/credit card details - nor does it offer a link to the Desktop interface.

Solution: download Atomic Web Browser, paid version ($0.99) > tap settings icon > select "Identify Browser As" > select Safari Desktop. Use this browser to access Google Checkout. Problem solved!

What this does is change the "User Agent String" which tells the server what browser you are using. I changed it to tell Google I am using Safari on a desktop rather than the mobile version of Safari on an iPad - so Google served me the full Desktop version of the website, with all the controls I needed to change my credit card details.

Note that while this worked in this case, it might not be appropriate for all uses - sometimes a website might require resources that simply aren't feasible on an iPad. An example would be a site with Flash content, or ActiveX scripts etc.


Thursday, June 30, 2011

Tomcat and Spring.. or a JEE Container?

I like this article: Is Tomcat enterprise ready? by Jeff Hanson, JavaWorld.com, 01/22/08. Even though it's from 2008, it pointed out the essential difference between Tomcat and an "real" JEE container like Glassfish - and how in many cases Tomcat and Spring will suffice.

Monday, June 20, 2011

Unable to load Module Dashlet Definition

I am using Sugar Community Edition, Version 6.2.0RC3 (Build 6350).

I had a bunch of modules in a custom package, renamed some of the modules and then tried to modify the Sugar Dashlet Layouts. I got this error message any time I tried to save the layout: Unable to load Module Dashlet Definition.

There is no easy fix. Prevention is better than the cure. Sorry. :(

From the post unable to load Module Dashlet Definition on the Sugar forums, you can supposedly fix this by renaming a bunch of files and modifying all references to those files. Perhaps because I have a package, I couldn't even find a match for the directory structures referenced in the post.

So, I am going to re-build the entire package. Luckily, I am still prototyping, so I have the scope for doing this.

What I have learned: don't rename a module, just change the label.

Damnit.

Friday, March 18, 2011

Using SELECT MAX(ID)+1 in an insert statement

I am working with the following Knowledge Tree table in MySQL - it holds reference data.
CREATE TABLE `document_types_lookup` (
    `id` INT(10) NOT NULL,
    `name` VARCHAR(100),
    `disabled` BIT DEFAULT'0' NOT NULL
);

ALTER TABLE `document_types_lookup` ADD PRIMARY KEY (`id`);
I needed to insert a few thousand rows, but since there is no auto-increment field for the ID, I needed SQL that could increment ID for me - using SQL only, no script. I found a bug report (Allow INSERT...SELECT MAX(x)+1 from the same table) that had the below snippet that used select (max(id)+1) to increment ID for an insert.
insert into foo(lfd) select (max(lfd)+1) from foo;
And while I could get that statement by itself to work (inserting only ID into a record), I could not not adapt it to add the rest of the record values.
insert into
   document_types_lookup(id, name, disabled)
values
   (select (max(id)+1) from document_types_lookup, 'a name', false);
I always got the below error.
Executing:

insert into document_types_lookup(id, name, disabled) values (select (max(id)+1) from document_types_lookup, 'a name', false)

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select (max(id)+1) from document_types_lookup, 'a name', false)' at line 1
[Error Code: 1064]
[SQL State: 42000]

So, my work-around is to use two statements instead of one: an insert followed by an update, as below.
insert into
   document_types_lookup(id)
   select (max(id)+1) from document_types_lookup;

update
   document_types_lookup
set
   name = 'CORP_HMN_000_CA_',
   disabled = false
where
   name is null;
OK, see any problems with this script? You should - it's a dangerous script for two big reasons. Firstly, it isn't thread safe, i.e. using select (max(id)+1) in an insert is dangerous if there are multiple people updating the table at the same time. You have no guarantee that between the time you have selected max ID + 1 and the time the insert actually happens, a new record might have been inserted by someone else, meaning that when your insert statement finally hits, your max ID + 1 will not be unique anymore. You can solve this by locking the table before your insert and releasing it afterwards. The second reason that this is a dangerous script is because it assumes there will only ever be one record where name is null; it does this as I could think of no other way to determine what record I just inserted.

So, this script works only because a) I knew I was the only person who would be updating the table at that time and b) I knew name would have a value for all records other than the one I had just inserted.

Monday, March 14, 2011

UltraEdit macro for HTML/XML - auto-create closing tags

Here is another pair of UltraEdit macros that I use quite frequently - these two help me with creating container tags for HTML or XML; specifically, they automate the creation of the opening and closing tag: all I need to do is type out the tag name and run the macro with the cursor within (or at the left/right boundary of) the tag name.

For example, if I type out xmlTag and run the first macro, I will be left with the following: <xmlTag>|</xmlTag>. The I bar (|) indicates where the cursor is left when the macro finishes.
InsertMode
ColumnModeOff
HexOff
UltraEditReOn
Clipboard 9
IfSel
Cut
"<"
Paste
">"
Else
IfSel
Else
SelectWord
EndIf
StartSelect
Cut
"<"
Paste
">"
EndIf
Clipboard 0
Paste
Clipboard 9
"</"
Paste
">"
Clipboard 0
The second macro is the same except that it will paste whatever is in the clipboard at the time and leave the cursor at the end of the closing tag. For example, if I type and cut the following text: text in the clipboard, then type out xmlTag and run the second macro, I will be left with the following: <xmlTag>text in the clipboard</xmlTag>|. The I bar (|) indicates where the cursor is left when the macro finishes.
InsertMode
ColumnModeOff
HexOff
UltraEditReOn
Clipboard 9
IfSel
Cut
"<"
Paste
">"
Else
IfSel
Else
SelectWord
EndIf
StartSelect
Cut
"<"
Paste
">"
EndIf
Clipboard 0
Paste
Clipboard 9
"</"
Paste
">"
Clipboard 0
Note that both the macros above include instructions that are from another common UltraEdit macro I use, Alt+x to cut currently selected token (selected or if cursor is in the token).
IfSel
Else
SelectWord
EndIf
StartSelect
Cut

Friday, March 11, 2011

dotCMS thumbnail error: java.lang.IncompatibleClassChangeError

In our dotCMS 1.7 environments, we found - on just one environment (PROD, of all places!) - that image thumbnails were broken. For example, referencing the image itself worked fine: http://example.com/path/to/image.jpg, but referencing the thumbnail fails: http://example.com/thumbnail?inode=60732&w=75&h=50.

In the logs, I noticed the below error.
[07/03/11 04:39:53:053 EST] ERROR [/].[ThumbnailServlet]: Servlet.service() for
servlet ThumbnailServlet threw exception
java.lang.IncompatibleClassChangeError: Found class com.sun.image.codec.jpeg.JPEGImageEncoder, but interface was expected
at com.dotmarketing.util.Thumbnail.resizeImage(Thumbnail.java:114)
at com.dotmarketing.servlets.image.ThumbnailImage.service(ThumbnailImage.java:203)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.dotmarketing.filters.CMSFilter.doFilter(CMSFilter.java:104)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
After some Googling, I found the below description of the error on this page: 'Interface was expected' for JPEGImageEncoder.
Root Cause
If you found this error appears in the log file, the biggest possibility and maybe the only reason that this exception being thrown is you are using the OpenJDK instead of Sun JDK. Well, the exception message is obvious to tell you that the com.sun.image.codec.jpeg.JPEGImageEncoder is a class in OpenJDK whereas it is implemented as an interface in Sun JDK.
I then confirmed that PROD did indeed have a different JRE than the other environments. On DEV:
java -version
java version "1.6.0_21"
Java(TM) SE Runtime Environment (build 1.6.0_21-b06)
Java HotSpot(TM) Client VM (build 17.0-b16, mixed mode, sharing)
But on PROD:
java -version
java version "1.6.0_17"
OpenJDK Runtime Environment (IcedTea6 1.7.5) (rhel-1.16.b17.el5-x86_64)
OpenJDK 64-Bit Server VM (build 14.0-b16, mixed mode)
So... we had the wrong JDK. Overnight, we installed Java HotSpot on PROD, changed PATH and JAVA_HOME to point to the new JRA, and the issue was resolved.

I reported this issue on the dotCMS Yahoo group here: Thumbnails not showing - java.lang.IncompatibleClassChangeError. In a response to that post, Maria Ahues Bouza noted that dotCMS only supports Sun Java (Oracle now I guess) as it's defined on their technology requirements page.

Thursday, March 10, 2011

DOS Batch Script to upload file to multiple dotCMS environments via cadaver

Update: Friday 11 March 2011, 11:56:08 AM, added instructions for use with Eclipse. I also announced this script on the dotCMS Yahoo Group.
Friday 11 March 2011, 03:20:15 PM added section about STATUS_ACCESS_VIOLATION error.

In dotCMS, one of the most tedious activities for a developer is to use the dotCMS Admin UI to navigate to a file (.vtl for example), open it for editing, make your change and then publish the change. Depending on how snappy your environment is, everything bar the actual editing can take a minute or more. Now multiply that by the 30, 40, 50, 100 edits you might need to make in one work day, especially during a test-debug cycle.

It is for this reason that in my current project I have used .vtl files everywhere possible to store content/code that is a developer's responsibility. This means two things.
  1. It is very easy to use Cadaver and a script to upload new and changed files.
  2. I can use my favourite text editor or IDE to edit the VTL files.
    1. Eclipse has a plugin for editing Velocity files: Veloeclipse. Unfortunately, apart from syntax highlighting and HTML auto-complete, none of the other functions seem to work since Galileo (at least), like auto-complete for macros or Velocity/Java objects.
    2. UltraEdit has a wordfile for Velocity on its extras page (to allow for syntax highlighting).
Personally, I edit VTLs in UltraEdit and upload them using Cadaver scripts from Cygwin - and now from UltraEdit directly. Thanks to UltraEdit's ability to run DOS commands with a keyboard shortcut, I can upload the VTL file to all the dotCMS environments at the same time, without even alt+tabbing to Cygwin.

Prerequsites.
  1. You are using a local project (directory structure) to store files (such as VTLs, JS, CSS etc) which you then upload to dotCMS. Your project directory structure must match the folder structure you are using in dotCMS (if not, stop reading this post because my script will not help you). It would also be really good if you are using a source control system (good like you being off Santa's nice list if you don't).
  2. Have Cygwin installed and, um, be running Windows. :)
  3. Make sure that your Cygwin install includes Cadaver. Cadaver is now included with Cygwin, so it should already be there if you recently installed/updated Cygwin. Make sure by entering the command cadaver. If you see command not found, then grab cadaver and unpack it into /usr/bin.
  4. Make sure you have set up your .netrc file with credentials for each environment as described in my post Using webDAV and Cadaver with dotCMS.
  5. Save the script below as a DOS batch file - into the root of your project directory - and edit only the section underneath the line :: Change these to suit your project.
    • environmentLabelX and environmentX. Add as many environmentLabelX and environmentX pairs as you want - one for each environment you want files to be uploaded to. Make sure to increment X for each pair.
    • max. Edit the line set max=3 to make the number match the number of environments you have just defined.
    • PATH_SUFFIX. Within dotCMS, we have put all of our content inside a "root" directory that is not matched by our project directory structure. For example, our CSS file would be http://dev.example.com/rootDir/css/styles.css but in our project directory, this file would be found at PROJECT_DIR/css/styles.css. If you have such a root, store it in PATH_SUFFIX. Otherwise, leave it blank if you put all your files into the highest folder level within dotCMS.
  6. This script can be used by itself (it is a DOS Batch after all, and the only argument it needs is the absolute path to the file being uploaded) or in conjunction with an editor/IDE that knows how to execute external commands. Both UltaEdit and Eclipse can do this. Below are the instructions for using the script with UltraEdit or Eclipse.
    • To use it with UltraEdit, follow these instructions to add it to the Tool Configuration.
      1. In the menu bar, select Advanced > Tool Configuraiton > Insert.
      2. On the Command tab enter the following.
        • Menu item name: Upload current file with Cadaver.
        • Command line: C:\path\to\your\script.bat %f.
      3. On the Options tab, enter the following.
        • Program Type: DOS program.
        • Tick "Save active file"
      4. On the Output tab, enter the following.
        • Command Output (DOS Commands): Output to list box.
        • Tick "Show DOS box"
        • Tick "Capture Output"
        • Replace selected text with: No replace.
      5. Now, after you have edited a VTL file (or CSS, JS etc) in UltraEdit, select Advanced > Upload current file with Cadaver or just use the keyboard shortcut UltraEdit gives to each of those (a control+shift+x shortcut).
    • To use it with Eclipse, follow these instructions to add it as an external tool.
      1. In the menu bar, select Run > External Tools > External Tools Configuration.
      2. Click the icon for New Configuration.
      3. Enter name: Upload current file with Cadaver.
      4. Enter location: c:/path/to/your/batch.bat.
      5. Enter arguments: ${resource_loc}.
      6. Click close.
      7. In the menu bar, select Run > External Tools > Oragnize Favourites...
      8. Click add.
      9. Tick the Launch Configuration you just created.
      10. Click OK.
      11. If you have multiple External Tools Favourites, you can change the order in which they appear here. This will affect the keyboard combination you use to access each one - changing the number used for the last key, 1-9 etc.
      12. Click OK.
      13. Now, after you have edited a VTL file (or CSS, JS etc) in Eclipse, select Run > External Tools > Upload current file with Cadaver or just use the keyboard combination Eclipse gives to each of those (Alt, R, E, 1-9).
And finally, here is the DOS Batch file.
:: Change these to suit your project.

set environmentLabel1=DEV
set environmentLabel2=Staging
set environmentLabel3=PROD
set environment1=http://dev.example.com/webdav/autopub/dev.example.com
set environment2=http://staging.example.com/webdav/autopub/staging.example.com
set environment3=http://prod.example.com/webdav/autopub/prod.example.com
SET MAX=3
SET PATH_SUFFIX=rootDir/


:: Do not change below here.
SET WORKING_DIR=%cd%
SET WORKING_DIR=%WORKING_DIR:\=/%
SET FILENAME=%~nx1
SET FULL_PATH=%~dp1
SET FULL_PATH=%FULL_PATH:\=/%
SET RELATIVE_PATH=!FULL_PATH:%WORKING_DIR%/=!

echo File: %1
echo FTP file name: %FILENAME%
echo Using relative path: %RELATIVE_PATH%
echo Using full path: %FULL_PATH%

For /L %%i in (1,1,%MAX%) Do (
   echo.
   echo.
   echo.
   echo.
   echo =======================================================================
   echo Transferring to !ENV_LABEL_%%i!
   echo open !ENV_%%i! > %temp%\%~n0.cadaver
   echo lcd %FULL_PATH% >> %temp%\%~n0.cadaver
   echo cd %PATH_SUFFIX%%RELATIVE_PATH% >> %temp%\%~n0.cadaver
   echo mput %FILENAME% >> %temp%\%~n0.cadaver
   echo bye >> %temp%\%~n0.cadaver
   c:\cygwin\bin\cadaver.exe --tolerant < %temp%\%~n0.cadaver
)
EndLocal
So there you have it. A DOS Batch script that you can use to upload a file to multiple dotCMS environments at the same time. Use it by itself on the command line or use with a tool like UltraEdit or Eclipse that understands how to run external commands.

STATUS_ACCESS_VIOLATION

Occasionally, I see this error from running the script.
1 [unknown (0xCA8)] cadaver 7944 exception::handle: Exception: STATUS_ACCESS_VIOLATION
- 557182 [unknown (0xCA8)] cadaver 7944 open_stackdumpfile: Dumping stack trace to cadaver.exe.stackdump
dav:!> open http://staging.example.com/webdav/autopub/staging.example.com
It also leaves me with a cadaver.exe.stackdump just under 1MB. I can just re-run the command and clear up the stackdump, but I sure would like to know if there is a way to stop them from happening.

Tuesday, February 22, 2011

Formula to detect duplicate cells in Excel

For when you have a column with values that may or may not contain duplicates. Sort the column A-Z etc and apply the following formula to the column next over.

=IF(C2=C3, "copy", "unique")

Monday, February 21, 2011

UltraEdit macro: go to next/previous white space

In UltraEdit, you can control what delimiter characters are used when you double click or use control+left/right - do it through the Advanced > Configuration dialog, as per below.


I got rid of the underscore character so that my copy/cut/paste macros would work on variable names that include it.

However, sometimes I really just want to jump to the next white space character, ignoring all the delimiters. Most commonly I want this when I am editing something with/a/lot/of/paths. Instead of hitting control+right arrow nine times, I would much rather hit a keyboard shortcut once.

So, here are the two shortcuts I use for this. I map this one to Control+Alt Right Arrow.
InsertMode
ColumnModeOff
HexOff
PerlReOn
Find RegExp " |\t|
"
Key RIGHT ARROW
Key LEFT ARROW
I map this one to Control+Alt Left Arrow.
InsertMode
ColumnModeOff
HexOff
PerlReOn
Find RegExp Up " |\t|
"
Key LEFT ARROW
Key RIGHT ARROW

Windows: one clipboard is not enough!

Too often I have found the need for more than one clipboard in Windows (a clipboard is what you use when you copy, cut and paste). There are various application specific solutions. UltraEdit has 9 inbuilt clipboards (control 0-9, 0 being the default, Windows clipboard). Eclipse has a very cool multi clipboard plugin. But I want something that will work across all applications.

Then I found out about the amazing Autohotkey utilities created by Florian Winkelbauer when they were written up on LifeHacker: DropPub Sends Any File to Dropbox’s Public Folder and Copies the Link to Your Clipboard. DropPub is a brilliant utility to send a file to your DropBox public folder, but the utility that I found more interesting was 4Clip - which gives you 4 extra clipboards linked to F1-F4.

The basic functions are: select some text and press F1+c to copy, F1+v to paste, or F2, F3, F4 - each F key is linked to a different buffer or clipboard. It means you can now "keep in memory" five things at once - the normal Windows clipboard plus four more pieces of text linked to F1 - F4.

Best thing to do is put the exe (or a shortcut to it) in your Windows Startup folder (C:\Documents and Settings\...your username ...\Start Menu\Programs\Startup) so that it will start up automatically with Windows. It's a small utility - taking 4.6 kb on my Windows XP. See the readme.txt included to see other functions.

Florian gives you a zip file including the source and exe file, but the exe is all you really need. I have been using this pretty much daily since I read about it on LifeHacker. But when I asked Florian if he could make it use all the F keys (12 clipboards) he responded within hours with 12Clip.zip (link gone for now). So, extra props to you Florian - thank you very much!

Insert a date into any program using Autohotkey on Windows

Update. Friday 8 March 2013, 01:00:40 AM. If you like a date picker that outputs any date/time in any format, check out Generic Date Picker Version 2 by Paul Moss - and my adaptation of it in response to a request below by Sanjay Mehta.

I previously wrote a post on how insert a date into various programs using built in short cuts. Well, since then I have discovered the joys of AutoHotkey scripts for windows, which let you do just about everything. So, here is an AutoHotkey script for inserting a date - what I like most about it is that you can adjust the format of your date so easily. This one uses control+F12.

; - Insert Date Time stamp
^$F12::
   FormatTime, xx,, dddd d MMMM yyyy ; This is one type of the date format
   FormatTime, zz,, hh:mm:ss tt ; This is one type of the time format
   SendInput, %xx%, %zz%
Return

The script above will insert a date like this: Monday 21 February 2011, 12:03:26 PM. See the AutoHotkey Help page on FormatTime to see how to make your own format.

Thanks to Mama on the AutoHotkey forum: Input system date and/or time, when u press a hot key.

To use this, you must install AutoHotkey and set up a script in which you can include this snippet.

Wednesday, February 16, 2011

Lucene search fails against decimal numbers in dotCMS

Update: 17/02/2011 11:30:17 AM, Christopher F. Falzone shows how to filter out extraneous results.

When running a Lucene Query, searching/filtering by any number with decimals doesn't work against a field with float data type. For example, if a float field has a value of "3.0" or "3.24", searching for "3.0" or "3.24" respectively will not work. Any search term that has a decimal point will fail against a float field.

This affects both the Admin UI when searching for or filtering content, and in a front end page when running a macro like #pullContent() for example.

This bug affects dotCMS prior to version 1.9.2. The bug report for it is filed under Can't Filter Content by Field Stored as Decimal. I stumbled across this bug in my own project work against dotCMS 1.7 and reported in the Yahoo forum post Searching for decimal via Lucene?

There is no fix apart from either upgrading (ugh) or porting across the fixed Java code (if you can find it and if there are no other dependencies). Instead, I used the following work-around in my code: rip off the decimal portion of the number and tack on an asterisk to do a wild card search using only the integral portion of the number. For example, see below.

#set($indexVal = $floatSearchTerm.indexOf("."))
#if($indexVal > 0)
   #set($floatSearchTerm = $floatSearchTerm.substring(0, $indexVal))
#end
#pullContent("+text2:$floatSearchTerm*" "0" "text1")

This means that instead of searching for "3.0" or "3.24", I will be searching for "3*" in both cases. Sure, you might be getting more results, but at least those results will include the one you need. One sticking point is if or how do you tell the user that you have changed their search term? Perhaps you might re-display their search term as "3" instead of "3.0" or "3.24". In my case, I am not doing anything and hoping that the users will be satisfied just by getting relevant results.

Note that in terms of filtering content in the Admin UI, just leave off the decimal portion and the Admin UI will add the wild card to the end of your search term by default.


Update: 17/02/2011 11:30:17 AM. Christopher F. Falzone posted in the dotCMS Yahoo forum an update noting that it is not too hard to cycle through your results and remove those results that don't exactly match the original search term. Here is the code he adjusted to do that.

#set($origFloatTerm = $floatSearchTerm)
#set($indexVal = $floatSearchTerm.indexOf("."))
#if($indexVal > 0)
   #set($floatSearchTerm = $floatSearchTerm.substring(0, $indexVal))
#end
#pullContent("+text2:$floatSearchTerm*" "0" "text1")

#foreach($content in $list)
   #if($content.floatField.equals($origFloatTerm)
   ...
   #end
#end

Of course, this won't work if you are relying on #pageContent(), but I will address that with a later post.

PostgreSQL - current transaction is aborted, commands ignored until end of transaction block

When running SQL against PostgreSQL, I sometimes stuff up my SQL and get an error. I can't just fix my SQL and re-run the query though: I get this error instead:

ERROR: current transaction is aborted, commands ignored until end of transaction block
[SQL State: 25P02]

I have found two ways around this. Either disconnect and re-connect to clear this error, or run the command rollback;

dotCMS SQL to see history of a contentlet item

Here is some SQL that will output the history of a particular contentlet item. You need to know the identifier for the record (which you can see from the Admin UI).

select c.*
from contentlet c
inner join inode 
   on inode.identifier = 11235 
   and inode.inode = c.inode
order by c.title, c.inode;

Tuesday, February 08, 2011

dotCMS multi-select - don't use commas

When creating the CODE (value/label pairs) for a multi-select in dotCMS, make sure not to have any commas in the labels or values. It screws up search.

Thursday, February 03, 2011

Using SELECT field values from a dotCMS Structure to build a client side SELECT

Update. 6/02/2011 1:51:57 PM. Added detailed explanation of the code sample.

Sometimes you create a Structure with a SELECT or MULTISELECT whose value/label pairs you want to use on the front end too. For example, let's say I have a Structure called Book with a SELECT called "medium" that has the following values (CODE):

Hardback|0
Softcover|1
Audio|2
eBook|3

Now I want to build a book search page with medium as a criteria (as a SELECT). I could write the SELECT with hard-coded values, but what if I ever need to add new medium options to the Book Structure? I would like my search page to update itself without me having to do it - so instead I will create the SELECT with options drawn from the values stored in the Book#medium field.

There is no Lucene query that I know of that will query field values - but the SQL to do so is easy. First, you need to know the inode for the field. Use SQL such as below to find the inode.

select inode, structure_inode, field_name
from field
where field_name like '%medium%';
  1. Use like '%medium%' if you are not entirely sure of what the field is called (percentages are wild cards in SQL). If you are sure, use = 'medium' instead.
  2. Remember that SQL is case sensitive.
  3. Remember that field names do not have to be unique across the entire database, so the above query might give you multiple results. That's why you select the other values as well - to help you identify which result is the one you actually want.

Once you have that, the Velocity code to build a SELECT from the field values is easy.

#getSQLResults('select field_values from field where inode = 49640')
#if($UtilMethods.isSet($SQLError))
   <p>Unable to load values. Please report this to the site administrator.</p>
#else
   <select>
      #set($labelsValuesField = $results.get(0).field_values)
      #set($labelsValuesArray = $labelsValuesField.split("\r"))
      #foreach ($labelValue in $labelsValuesArray)
         #set($labelValueArray = $labelValue.split("[\\|]"))
         #set($label = $listTool.get($labelValueArray, 0).trim())
         #set($value = $listTool.get($labelValueArray, 1).trim())
         <option value="$value">$label</option>
      #end
   </select>
#end

Some explanation for the above code.

#getSQLResults('select field_values from field where inode = 49640')
#if($UtilMethods.isSet($SQLError))
   <p>Unable to load values. Please report this to the site administrator.</p>
#else

Run SQL to retrieve the text used to generate the SELECT OPTION tags (on the back-end and now in our front-end). If the SQLError variable is set, something went wrong and there is no use trying anything further. Warn the user appropriately.

   <select>
      #set($labelsValuesField = $results.get(0).field_values)

The SQL we ran will only ever return 1 result, which will occupy the first position in the results object.

      #set($labelsValuesArray = $labelsValuesField.split("\r"))

The text used to generate the SELECT OPTION tags is first separated by newline characters (\r). Each line contains the label and value for one OPTION. Thus, we create an array where each cell in the array stores a single line.

      #foreach ($labelValue in $labelsValuesArray)

Go through each entry in the array we just created i.e. iterate over each line.

         #set($labelValueArray = $labelValue.split("[\\|]"))

Each line has two values - the first being for the OPTION label and the second being for the OPTION value. They are separated by the pipe character (|). Create another array where the first cell is the label and the second cell is the value.

         #set($label = $listTool.get($labelValueArray, 0).trim())

Get the contents for the first cell - the label.

         #set($value = $listTool.get($labelValueArray, 1).trim())

Get the contents of the second cell - the value.

         <option value="$value">$label</option>

Put the label and value together to create an OPTION tag within the SELECT.

      #end
   </select>
#end

Don't bother with standards compliance in email templates

My friend Ketan was writing an email template: being diligent, he uses DIVs for layouts, not TABLEs. Then he finds that Outlook Webmail presented in IE8 (not Outlook Standalone) screws it all up! The only fix is to use TABLEs. Oh well.

UltraEdit macro to help output debugging Velocity code in HTML

UltraEdit is my text editor of choice, and right now I am working with Velocty scripting for dotCMS. Like Bash or jQuery, it uses the dollar sign $ to denote variables. Frequently I want to debug some variable by outputting it's value in HTML. For example:

<p>Rob here with variableName [$variableName]</p>

I do this often enough that I have created an UltraEdit macro to make is easier. Make sure you have the variable name (e.g. variableName copied to the clipboard and then run this macro. It will ouput variableName [$variableName]

InsertMode
ColumnModeOff
HexOff
Paste
" [$"
Paste
"]"

If you appreciate this, you might like to have a look at the other UltraEdit macros I use.

Friday, January 21, 2011

javax.naming.NameNotFoundException: Name java:comp is not bound in this Context

Logs are filled with the same error, over and over.

[21/01/11 12:05:56:056 EST] ERROR db.DbConnectionFactory: ---------- DBConnectionFactory: error getting dbconnection jdbc/dotCMSPool
javax.naming.NameNotFoundException: Name java:comp is not bound in this Context
        at org.apache.naming.NamingContext.lookup(NamingContext.java:770)
        at org.apache.naming.NamingContext.lookup(NamingContext.java:153)
        at javax.naming.InitialContext.lookup(InitialContext.java:392)
        at com.liferay.util.JNDIUtil.lookup(JNDIUtil.java:88)
        at com.dotmarketing.db.DbConnectionFactory.getDataSource(DbConnectionFactory.java:37)
        at com.dotmarketing.db.DbConnectionFactory.getConnection(DbConnectionFactory.java:81)
        at com.dotmarketing.db.HibernateUtil.getSession(HibernateUtil.java:498)
        at com.dotmarketing.db.HibernateUtil.sessionCleanupAndRollback(HibernateUtil.java:596)
        at com.dotmarketing.db.HibernateUtil.rollbackTransaction(HibernateUtil.java:580)
        at com.dotmarketing.portlets.contentlet.business.ReindexThread.run(ReindexThread.java:107)

I should have known: I have seen this type of error before in other projects. So here is the answer again - let's hope I will remember next time.

The server wasn't shut down properly. Kill all java processes and restart the server.

The post I made in search of the answer this time: NameNotFoundException: Name java:comp is not bound in this Context. The post I found that reminded me of the answer to this type of error: error getting dbconnection jdbc/dotCMSPool.

Shutdown, kill, start and look at logs.

sudo $DOTCMS_ROOT/bin/shutdown.sh
sudo killall java
sudo $DOTCMS_ROOT/bin/startup.sh
tail -f $DOTCMS_ROOT/logs/dotcms.log