Friday, August 21, 2009

Escaping database column name when using Hibernate

Changing the database engine when using Hibernate turned out to be not as easy as changing the .hbm.xml file.
At first I used Apache Derby (client-server) as the database engine, just for testing purposes. I tried to change it to Microsoft SQL Server, by using the JDBC driver provided and changing the .hbm.xml file. A problem happened, a String field whose length I set to 40000, cannot be handled, so I need to truncate it to 8000. Later when I change it to Oracle, there cannot be 2 columns that have LONG VARCHAR2 type in a single table, so I need to further reduce it to 4000 characters.
The problem does not stop there. I use Hibernate Annotations, so every field is configured in the source code (instead of in the .hbm.xml file). I can define the column names if I want:
@Entity
public class Entry {
  String message;

  @Column(name = "log_time")
  Date time;
}
In that case, the message field will be stored in a database column called message, but the time field will be stored in a database column called log_time since I have defined a @Column annotation.
The sad truth is that Hibernate does not escape column names mentioned in queries, so some DBMS'es regard them as keywords. Example include exception in Oracle, size in Derby, and so on.
We can force Hibernate to escape identifier names on the SQL queries by putting backticks (`) on the column name. The backticks will be converted to different identifier escaper for different database systems. For example, `column` in MySQL, [column] in Microsoft SQL, and "column" in Apache Derby.
So, our entity class becomes something like:
@Entity
public class LogEntry {
  @Column(name = "`level`")
  String level;

  @Column(name = "`exception`", length = 4000)
  String exception;

  // etc.
}
Unfortunately, we must duplicate the name (one on the field, and one on the annotations), so it's not so easy to maintain (e.g. we do a rename-refactoring to the field name, we may not notice that the annotation's column name is not changed together).
I still wonder why doesn't Hibernate always escape the column name.

Tuesday, August 11, 2009

Excluding specific folder from directory crawlers

Shortly: I want to have a directory D:\bekap\mybackup that cannot be traversed.
I made a backup program that backs up data from a specified directory to another directory. (The program has much more features such as detecting duplicate data and to have efficient incremental backup.) I wanted to backup the whole D: drive, but that is the only partition available for the backup destination. Therefore I can only backup D: to let's say D:\bekap\mybackup.
The problem is, when the program traverses or walks through D: to find available files, in the end it will also go to D:\bekap\mybackup and it would try to back up files inside D:\bekap\mybackup to itself. (It's a similar problem with tar -cf archive.tar .).
So I want to make D:\bekap\mybackup not detectable except by directly accessing it through the path name. It is similar as if you have a non-linked web page at http://example.com/private_photos/xyz/; only people you give the address will be able to access it.
After trying through several options, I found a way to do that:
  • Go to the Security properties of D:\bekap (in Explorer: right click, properties, you may need to disable the Use simple file sharing in Folder Options)
  • Click Advanced button, then you will see entries similar to these:
  • Click Add, fill in your username, then click Check Names, then OK.
  • Tick the Deny column for the entry List Folder / Read Data.
Now you cannot access D:\bekap, but if you type D:\bekap\mybackup, you can open the contents!
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

D:\>cd bekap

D:\bekap>dir
Volume in drive D is Dadu
Volume Serial Number is 1293-1777

Directory of D:\bekap

File Not Found

D:\bekap>cd mybackup

D:\bekap\mybackup>dir
Volume in drive D is Dadu
Volume Serial Number is 1293-1777

Directory of D:\bekap\mybackup

2009-08-11  15:22    <DIR>          .
2009-08-11  15:22    <DIR>          ..
2009-08-11  15:22                 0 .bekapkeren2
2009-08-11  15:22        35,616,973 .cache.ser
2009-08-07  17:43    <DIR>          data
2009-08-11  15:21    <DIR>          fs
          2 File(s)     35,616,973 bytes
          4 Dir(s)  82,628,956,160 bytes free

Thursday, August 6, 2009

Multiple Return Values in Java

How can we have multiple return values in Java? We cannot formally have it, since each method can only have no return value (void) or single return value (int, long, Object, String, etc.)

One of the solution to emulate it is to create a class that contains two fields, one for each return value. But that will make you tired because you need to create it every time you need it (not including the time used to think what the class name should be).

In my case, I solved this problem by using a Pointer class. It works similar to C in which you can have multiple return values by passing a pointer to a value that will be modified to the function arguments like: size = fread(pbuf, size, count, pfile);

The pointer class is as follows:

public class Pointer<T> {
  public T value;
  public static <T> Pointer<T> create() {
    return new Pointer<T>();
  }
}

Let's say you have a method that needs to return two values: result and error code. The method for our example will be:

byte[] downloadFile(String url, Pointer<Integer> errorCode) {
  ...(really download)...
  if (errorCode != null) {
    errorCode.value = 200; // example only
  }
}

To use the method, we first create the pointer to store the error code as follows:

Pointer<Integer> errorCode = Pointer.create();
byte[] file = downloadFile("http://biginteger.blogspot.com/", errorCode);
System.out.printf("File downloaded (%d bytes) with error code %d", file.length, errorCode.value);

The reason of the create() is to eliminate repeated typing of the type parameter:

Pointer<Integer> errorCode = new Pointer<Integer>();

Wednesday, August 5, 2009

Setting GWT locale using cookies

GWT represents locale as a client property whose value can be set either using a meta tag embedded in the host page or in the query string of the host page's URL.
I wanted to set the locale of my GWT application stored somewhere, so that I don't need to always append ?locale=ja or &locale=ja to the URL for every page on the site. Putting the meta tag every time the page is requested is also not an option, since you still need to let the server know the preferences of the user (session management).
For example, if I have http://www.ngambek.com/ page in Indonesian, but I want to set it to Japanese, I need to set the address to http://www.ngambek.com/?locale=ja to let the GWT system use the Japanese version of the page. Later when there is a link to http://www.ngambek.com/12345678, I would need to make it http://www.ngambek.com/12345678?locale=ja. What a burden.
I decided to use cookie to store the preferred locale, then let a javascript snippet run to set a meta tag dynamically. When the GWT module is loaded, the locale will be detected because the appropriate meta tag will have been written (in browser's memory).
So, before the GWT module script is loaded, I put these lines onto the HTML file:
var getCookie = function(c_name) {
  if (document.cookie.length > 0) {
    c_start = document.cookie.indexOf(c_name + "=");
    if (c_start != -1) {
      c_start = c_start + c_name.length+1;
      c_end = document.cookie.indexOf(";", c_start);
      if (c_end == -1) c_end = document.cookie.length;
      return unescape(document.cookie.substring(c_start,c_end));
    }
  }
  return "";
}

var locale = getCookie("locale");
if (! locale) {
  locale = "id";
}

document.write("<meta name='gwt:property' content='locale=" + locale + "' />");
Then, in order to set the locale, it's just a matter of setting the locale cookie and reloading the page:
function setLocale(locale) {
  document.cookie = "locale=" + escape(locale);
  document.location.href = document.location.href;
}
By the way, the internationalization feature of GWT is great! Try it if you haven't tried it before.