Sunday, April 10, 2011

Queries in Sling

Here is how I use JCR queries in my .esp pages.
The sample below lists all child nodes of the current node sorted by price property.


<%
var sql = "select * from [nt:base] where ischildnode('" + currentNode + "') order by price asc";
var query = currentSession.workspace.queryManager.createQuery(sql, Packages.javax.jcr.query.Query.JCR_SQL2);
var result = query.execute();
var iter = result.nodes;
while (iter.hasNext()) {
var node = iter.nextNode();
%>

// render each node from the result

<% } %>


Notice that when a Node is converted to string its path is used.
Also here you can see how Packages object is used to access static members of Java classes. This is a Rhino feature. You can read more about it here.

Resizing windows in Ubuntu

A major usability paint point in Ubuntu's default theme is that it is very difficult to grab the window border to resize it. The reason is that the borders are very thin, I think just one pixel.
So I have been using Alt-F8 shortcut (see System > Preferences > Keyboard Shortcuts) to resize windows. But this is not very convenient either since you can resize in only one direction. To resize in another direction you have to press Alt-F8 again. Also the direction is chosen from the initial mouse movement after Alt-F8. Well, just try it.
Now I got irritated by this obvious malfunction and did a quick search and found this thread. It turns out you can also resize windows with Alt-(middle mouse button). Well, this is much easier. There is also a link to a bug report where they say this will be addressed in the coming 11.04 release. It seems like there will be 3 pixel wide invisible area around the window edges where you can grab and resize.

Some other useful shortcuts:
  • Alt-F7 - move active window (very convenient to get back a window from a second display/TV which is currently turned off)
  • Alt-(left mouse button) - move window

Friday, March 25, 2011

Log in Sling

The default Sling user is anonymous and it has read-only access to the repository. So if this user attempts to change anything in the repository he gets an error like this

javax.jcr.AccessDeniedException: <path>: not allowed to add or modify item

So in order to change the data they have to login with a user that has proper permissions. But we still want to allow anonymous visitors to read the content of our site (like search engines). We ask them to log in only if they choose to edit the content. One way to do this in Sling is like this:

<%
if (!currentSession.hasPermission(currentNode.path, "set_property"))
    response.sendRedirect(request.requestURL + "?sling:authRequestLogin=BASIC");

%>

Put this code at the beginning of an ESP page that performs content changes.

Wednesday, February 9, 2011

Posting non-ASCII characters in web forms

I just hit this issue. You write some non-ASCII text (e.g. in Cyrillic) in a form and when submitted the text appears garbled on the server side.

It turns out this is a well known glitch in web development especially when done in Java. The main reasons for the mess are
  1. Web browsers do not specify the encoding of posted data
  2. Java Servlet specification says that default request encoding should be ISO-8859-1 in contrast to UTF-8 which is universally used nowadays
You can find a good description of this issue here HTTP Form Character Sets and Related Problems.

Tomcat FAQ recommends creating a filter to set the request encoding.

But when using Wicket there is no such issue as they fix the request encoding to UTF-8 as described in How to change the character encoding.

Unfortunately Sling guys did it in another way (SLING-508) which requires me to put a hidden input named _charset_ with the value UTF-8 in all my forms. So they have adopted the ugly IE hack. :(
This is also described in Sling documentation.

I wish Sling had a way to set this to UTF-8 in one place and get rid of it.

Sometimes web development is so frustrating.

Update: Mar 2nd
After picking up this discussion on Sling mailing list the guys there decided after all to make this configurable, see SLING-1998.
Great! My forms now work without the _charset_ hack.

Monday, November 29, 2010

Sling gotcha

A content-driven application normally implements the basic CRUD operations. Using Sling this should be done relatively easy.
As part of the Read operation you normally have to implement some listing of available resources. Being familiar with JCR API  it would be natural to do it with this JavaScript code in your ESP page

var iter = currentNode.getNodes();
while (iter.hasNext()) {
    var childNode = iter.nextNode();
    ...


Yes, but no! The result is a big fat exception

org.mozilla.javascript.EcmaError: TypeError: hasNext is not a function, it is org.mozilla.javascript.Undefined. (/apps/catalog/html.esp#11)


Hm! currentNode is a JCR Node so getNodes() should return a javax.jcr.NodeIterator which is a java.util.Iterator. So the code looks correct but somehow iter here is not a Java object at all.

I struggled with this several hours. I was very puzzled to see similar code in espblog sample working just fine. The only difference is that espblog uses QueryResult.getNodes() which still returns NodeIterator.

Finally I found the answer in this thread. It turns out Sling wraps Node.getNodes() to return a JavaScript object which has one property for each child node. Probably the idea was to easily iterate over that object with a for-each loop

for each (var childNode in  currentNode.getNodes()) {
    ...


Another solution is to use the property name instead of the getter method

var iter = currentNode.nodes;
while (iter.hasNext()) {
    var childNode = iter.nextNode();
    ...


This seems to circumvent the wrapping done by Sling.

This example illustrates a cute feature of Rhino which allows easy access of JavaBeans properties.

Yes, server side JavaScript can be fun.

Monday, November 1, 2010

Dynamic Class Loading in OSGi

Sometimes you need to load an arbitrary class which you don't know in advance. In a normal Java application you would do that with Class.forName(String className). But in OSGi this will work only if your bundle imports explicitly the package of the class that you wish to load. In OSGi each bundle has its own class loader. OSGi services will not help if the class you wish to load is not exposed as a service, which is more likely the case.
It would be great if at run-time you could find the bundle exporting the package of the desired class and ask that bundle to load the class using its own class loader.
It turns out this is possible via PackageAdmin service.

org.osgi.service.packageadmin.PackageAdmin packageAdmin;
...
Class clazz = packageAdmin.getExportedPackage(packageName)
  .getExportingBundle().loadClass(className);

Here packageName is the package name and className is the full class name.
This way you can load any class from any package exported by any active bundle and still your bundle is independent from the bundle providing the class. This could be very useful when implementing some generic functionality like object persistence.

Wednesday, September 1, 2010

Ant & Xerces

Have you seen errors like this when parsing XML from Ant tasks?

org.xml.sax.SAXParseException: Invalid encoding name "Cp1252".
  at org.apache.xerces.parsers.DOMParser.parse(Unknown Source)
  at org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source)
  at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:208)
  ...

The problem is in the encoding specified in the xml header
<?xml version="1.0" encoding="Cp1252"?>
This often appears in xml files generated by Java programs.

Bug 4665105 is filed for this issue but it is closed as "Not a defect".

It turns out that the same XML file is parsed fine by the JRE libraries, but ant uses its own xml libraries - xml-apis.jar and xercesImpl.jar found under ant/lib. If I delete these two files, xml parsing in ant works fine.
Luckily this issue seems solved in latest Ant 1.8.1. From the release notes:
* Ant no longer ships with Apache Xerces-J or the XML APIs but relies
on the Java runtime to provide a parser and matching API versions.

A workaround.
When parsing the xml file instead of
DocumentBuilder.parse(file)
use
DocumentBuilder.parse(new InputSource(new FileReader(file)))
This tells the parser to ignore the encoding specified in the xml header.