Wednesday, December 8, 2010

Mapping Dates and Time Zones with Hibernate, Part 2: Few Solutions

Go back to Part 1 of this article

In the first part of this article I was talking about the problems when mapping date/time to a database table using Hibernate. In part two I will talk about the solutions.

The quick Way: Use Property Access Type and implement the Setter

Tell Hibernate to use setter and getter methods for field access, instead of using reflection to modify the entity object's fields directly. To do so, you have to put the Hibernate annotations above the getter methods, instead of the class attributes. You MUST move the annotation of the @Id field to the getter to enable property access. But you should do it with all field annotations, for better clarity. You can find a more detailed discussion of Hibernate property access here and here.
Once you did so, you can implement a setter method for the calendar field, which takes the calendar object provided by Hibernate, and creates a new object with the right time zone and date information from it:

package entity;

...

public class GMTDateEntity implements Serializable {

   
    ...

    private Integer pk;

    private Calendar calendar;

    @Id
    @Column(name = "pk", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Integer getPk() {
        return pk;
    }



    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "calendartime")
    public Calendar getCalendar() {
         return calendar;
    }

    public void setCalendar(Calendar calendar) {
         //create new calendar in GMT time zone
        this.calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
       
        //set calendar fields
        this.calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
        this.calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
        this.calendar.set(Calendar.DATE, calendar.get(Calendar.DATE));
        this.calendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY));
        this.calendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE));
        this.calendar.set(Calendar.SECOND, calendar.get(Calendar.SECOND));
        this.calendar.set(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND));
       
        //recalculate calendar time millis
        this.calendar.getTime();
     }


     ...
}

Because the time zone information is the only thing that is wrong in Hibernate's calendar object, we simply create a new calendar object in the correct time zone, and then copy all required fields from Hibernate's object to our new object. The final call to getTime() will recalculate the calendars internal time milliseconds based on the field and time zone information. Unfortunately, the actual recalculation-methods are protected in java.util.Calendar for reasons I just don't know. So we have to use this less elegant workaround.

The elegant Way: Create a custom Hibernate User Type 

If for any reason you cannot use Hibernate property access, you will have to stick to the advanced art of programming and create a custom user type, which does the mapping from DB result set to Java object for you. Explanation of user type implementation would be beyond topic of this article. But you can find a very good introduction here. Just be aware, that you have to create a mutable user type, as java.util.Calendars are mutable objects.
To put everything short, here is the user type implementation that will serve our purpose:
package usertypes;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;

public class GmtCalendarUserType implements UserType, Serializable {

    private static final long serialVersionUID = 1L;
  
    //The interesting methods:

    @Override
    public void nullSafeSet(PreparedStatement statement, Object value, int index)
            throws HibernateException, SQLException {


        // we have to be null-safe
        if (value == null) {
          Hibernate.CALENDAR.nullSafeSet(statement, null, index); 
          return;
        } 
 
        // we have a Calendar here
        Calendar cal = (Calendar) value;
        // cut millis, as SQL Server uses only 1/300 precision
        long millis = cal.getTimeInMillis();
        millis = millis - (millis % 1000);
        cal.setTimeInMillis(millis);
        // simply delegate to hibernate's built in method
        Hibernate.CALENDAR.nullSafeSet(statement, cal, index);
    }

    @Override
    public Object nullSafeGet(ResultSet resultSet, String[] columnNames,
            Object owner) throws HibernateException, SQLException {

        // we cannot do it like this, because it would initialize the calendar
        // in the jvm's default timezone:
        // Calendar cal = (Calendar) Hibernate.CALENDAR.nullSafeGet(resultSet,
        // columnNames);
        // return cal;

        // We have to create the Calendar object from the DB date string
        String timeString = (String) Hibernate.STRING.nullSafeGet(resultSet,
                columnNames);
        if (timeString == null)
            return null;

        try {
            Date date = this.parseDbDateString(timeString);
            // Init calendar in GMT
            Calendar retValue = new GregorianCalendar(
                    TimeZone.getTimeZone("GMT"));
            retValue.setTime(date);
            // calculate calendar fields
            retValue.getTime();
            return retValue;
        } catch (ParseException e) {
            throw new HibernateException("Could not parse datestring from DB.",
                    e);
        }
    }

    private Date parseDbDateString(String dateString) throws ParseException {
        // create gmt time zone
        TimeZone gmtZone = TimeZone.getTimeZone("GMT");
        // create db date format (cut millis)
        String pattern = "yyyy-MM-dd HH:mm:ss";
        SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
        dateFormat.setTimeZone(gmtZone);
        return dateFormat.parse(dateString);
    }

    //The other UserType methods:
  
    @Override
    public Object assemble(Serializable cached, Object owner)
            throws HibernateException {
        return this.deepCopy(cached);
    }

    @Override
    public Object deepCopy(Object object) throws HibernateException {
        if (object == null)
            return null;
        // we have a calendar here
        Calendar cal = (Calendar) object;
        return cal.clone();
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) this.deepCopy(value);
    }

    @Override
    public boolean equals(Object object1, Object object2)
            throws HibernateException {
        if (object1 == object2) {
            return true;
        }
        if ((object1 == null) || (object2 == null))
            return false;
        return object1.equals(object2);
    }

    @Override
    public int hashCode(Object value) throws HibernateException {
        return value.hashCode();
    }

    @Override
    public boolean isMutable() {
        // Calendar is mutable
        return true;
    }

    @Override
    public Object replace(Object original, Object target, Object owner)
            throws HibernateException {
        return this.deepCopy(original);
    }

    @Override
    public Class returnedClass() {
        return Calendar.class;
    }

    @Override
    public int[] sqlTypes() {
        return new int[] { Types.TIMESTAMP };
    }
}



The nullSafeSet method will inject our Java object into the SQL statement. As Hibernate handles Calendars correctly when persisting, you simply delegate this task to Hibernate here. But since I'm using MS SQL Server in this example, I have to deal with the different precisions of Java dates (1 millisecond prec.) and SQL Server datetime types (1/300 second prec.). I choose the simple way here and simply cut the millis.

The nullSafeGet method does a little more of tweaking. This is where the result set is mapped to our Java Calendar object. If we would use Hibernate's built in mapping here (as in the commented block in this method), we would get the Calendar with the JVM time zone set. So we have to treat the datetime from the SQL result set as a string, and parse the date in GMT from it and create a new GMT GregorianCalendar.

Once we've finished our UsertType, all we have to do is to annotate our GMTDateEntity object to use it:
package entity;

...


@Entity
@Table(name = "datetestgmt")

//Declare the UserType on the class
@TypeDef(name = "gmtCalendar", typeClass = GmtCalendarUserType.class)
public class GMTDateEntity implements Serializable {



...




    
    //Use the UserType on the attribute
    @Type(type = "gmtCalendar")
    @Column(name = "calendartime")
    private Calendar calendar;
 



...

}

Go back to Part 1 of this article



Tuesday, November 23, 2010

Valuable Tools for Developers

This is a list of useful tools to support Java and Objective-C development. It will be updated continuously.

HermesJMS (Java)
JMS Console to browse, read from, and write to JMS queues and topics.
http://www.hermesjms.com



JarFinder (Java)
Java Application that lets you search local folders and JAR files for specific classes.
http://sourceforge.net/projects/jarfinder/

Opacity (Mac OS X)
Easy to use program to create buttons and screen graphics for web, iOS, and any other applications.
http://likethought.com/opacity/


StartExplorer (Java/Eclipse)
Eclipse plugin that opens the location of a file or folder from within an Eclipse project in the system's file explorer.
http://startexplorer.sourceforge.net/



Friday, November 19, 2010

Mapping Dates and Time Zones with Hibernate, Part 1: Plenty of Obstacles

Go ahead to part 2 of this article
 
Have you ever tried to persist a date with Hibernate, which is supposed to
be in a different time zone from the one of the system your application
is running on? I'm pretty sure that you will get into trouble while
trying this. There's a number of obstacles to pass. Let's get them on
one by one and figure out appropriate solutions.


The Setup

I'm using JBoss 5.1 with Hibernate 3 and JPA for this tutorial. The data base is
Microsoft SQL Server Express 2008 with the JDBC driver from
sqljdbc4.jar. Both, JBoss and SQL Server are running on the same
machine in time zone Europe/Berlin with Central European Time (CET) as
active time schedule. We want to persist a Date to the data base wich shows up there in GMT time.


We will need two Java classes. First there's the entity data object to be
persisted to the DB. As we want to persist GMT date records, it is called
GMTDateEntity:

package entity;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

public class GMTDateEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    //DB primary key.
    @Id
    @Column(name = "pk", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer pk;

     //The actual Date record.
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "datetime")
    private Date date;

    public Integer getPk() {
        return pk;
    }

    public Date getDate() {
        return this.date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public void setPk(Integer pk) {
        this.pk = pk;
    }
}

The second class is the Data Access Object used to persist and retrieve our data object:
package dao;

import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import entity.GMTDateEntity;

@Stateless
@Remote(IDateTestDao.class)

public class DateTestDao implements IDateTestDao {

    //References section in hibernate config persistence.xml
    @PersistenceContext(unitName = "hibernate-date-test")
    private EntityManager entityManager;

    @Override
    public GMTDateEntity retrieveGMTDateEntity(Integer pk) {
        return this.entityManager.find(GMTDateEntity.class, pk);
    }

    @Override
    public GMTDateEntity persistGMTDateEntity(GMTDateEntity entity) {
        return this.entityManager.merge(entity);
    }
}


1. Obstacle: Hibernate uses system's time zone

Now let's persist a date object to the DB using our two classes. For this example I will create a java.util.Date representing the 9. November 2010, 12:00h GMT which we want to store to the DB. Now we do something like this:

String dateStringGMT = "2010-11-09T12:00:00";
//Parse the String using GMT time zone
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

Date date = dateFormat.parse(dateStringGMT);
GMTDateEntity entity = new GMTDateEntity();
entity.setDate(date);
dateTestDao.persistGMTDateEntity(entity);





After executing the code we will have a new record in our DB:




What happened? The time is shifted by an hour!
This is because java.util.Date stores dates in a time zone independent fashion, but for SQL data bases there dosn't exists the notion of time zone independent dates. You can store date records to a SQL DBMS only with a specific time zone. So what Hibernate does, is using the time zone schedule of its JVM (which runs in CET) to convert the time zone independent java.util.Date to the time zone specific date string in our DB record. Because CET is one hour ahead of GMT, one hour has been added to our DB date.
Unfortunately, there exists neither an Hibernate or JPA annotation nor any other simple way to tell Hibernate in which time zone to persist java.util.Date objects. So if we cannot set the time zone schedule of our JVM to GMT, we must figure out other ways to tell Hibernate what we want.


2. Obstacle: Hibernate doesn't use Calendar's time zone for reading
 
The sensible way for attaching time zone information to dates in Java is the use of java.util.Calendar. So let's tweak our entity class a bit and add a Calendar field:

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "calendartime")
private Calendar calendar;

And now we extend our testing code to persist the calendar field, too:
String dateStringGMT = "2010-11-09T12:00:00";
//Parse the String using GMT time schedule
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

Date date = dateFormat.parse(dateStringGMT);
GMTDateEntity entity = new GMTDateEntity();
entity.setDate(date);

//Create Calendar object in GMT
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTime(date);
entity.setCalendar(cal);
dateTestDao.persistGMTDateEntity(entity);


After code execution our DB will look like this:




As you can see, the calendartime column perfectly displays our date in GMT! But still we're not satisfied. To be a little bitchy, we add the following lines to our testing code:

 String dateStringGMT = "2010-11-09T12:00:00";
//Parse the String using GMT time schedule
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

Date date = dateFormat.parse(dateStringGMT);
GMTDateEntity entity = new GMTDateEntity();
entity.setDate(date);

//Create Calendar object in GMT
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTime(date); 
entity.setCalendar(cal); 
dateTestDao.persistGMTDateEntity(entity);

//Now read back what we persisted, and store it again
GMTDateEntity readEntity = dateTestDao.retrieveGMTDateEntity(entity.getPk());
GMTDateEntity newEntity = new GMTDateEntity();
//just to be sure we set the time zone to GMT again
readEntity.getCalendar().setTimeZone(TimeZone.getTimeZone("GMT"));
newEntity.setCalendar(readEntity.getCalendar());
newEntity.setDate(readEntity.getCalendar().getTime());
dateTestDao.persistGMTDateEntity(newEntity);
 

The DB now looks like this:





What the hell?! Obviously we got a Calendar out of the DB which is different from the one we previously stored to it! And that's true. When reading from DB, HIbernate creates a completely new Calendar object, and uses the system's time zone again for converting the date.

3. Obstacle: There are solutions, but it won't be easy!

Well, if you're as naive as I am, you might just shout out: "Of cause it didn't work! We didn't tell HIbernate about the target time zone of our entity class!" And then you would go and tweak the GMTDateEntity like this:

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "calendartime")
private Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT"));

Just to shorten this: It will not work! Hibernate uses Reflection to set the entity's fields, and when it comes to do so, the date conversion is already done. So instead of asking yout entity's field: "What kind of time zone would be welcome, Sir?", it just creates a new Calendar instance in system's time zone and squeezes the DB date into it.

However, there are some more complex solutions to this problem, which I will explain in the second part of this article.

Go ahead to part 2 of this article



Saturday, September 18, 2010

PicUpp User Manual


Add new Picasa Account

Add Picasa Account Tap
the [+] button in the upper right corner to open the Add Account
view. If no accounts are added, yet, you can even simply tap anywhere
on the screen to bring up this view.
Enter Picasa Login Name Enter
your Picasa login name. If you want to save your password permanently,
switch on the Save Password switch.
Enter Picasa Password Enter
your password and tap the [Save] button. Your password is saved
securely in the iPhone keychain.

Delete Picasa Account

Delete Picasa Account Tap
the [Edit] button or swipe over an account horizontally to delete it.

Edit Picasa Account

Edit Picasa Account Tap
the disclosure button to the right of an account to open the Edit
Account
view.

Use Picasa Account for Upload

Use Picasa Account for Upload Tap
somewhere on the account name to select it for upload and open the Upload
Queue
view.

Add Photos to Upload Queue

Add photos Tap the [+] button in
upper right corner to bring up the image selector. If there are no
images in the upload queue, yet, you can even simply tap somewhere on
the screen to bring up the
photo picker.
Choose photo ot image

Choose the
photo/image you want to upload from the photo picker.


Enter photo caption Once you selected a
photo from the photo picker, you can enter a photo caption and then tap
the [Add to upload Queue] button. 
Finished adding photos to PicUpp queue

When
you're finished adding all desired photos to the upload queue, tap the
[Done]
button in the upper right corner of the photo picker.
You can
add a maximum of 20 photos to the upload queue.

Remove a Photo from Upload Queue

Remove a photo from PicUpp queue Swipe over a photo horizontally to reveal the
[Delete] button.

Select Picasa Album to upload to

Tap select album button Tap the [Select
Album] button to bring up the Picasa Albums Selector. This is the time
when the app connects to Picasa and tries to log you in. All previous
steps you could even execute without an internet connection. 
Enter Picasa password

If you did not
save your account password, PicUpp will ask you to enter it here.
Select Picasa album On
the Picasa Album Selector, use the picker wheel to select the album you
want to upload your photos to. If you'd like to use a new album
instead, enter the new album name in the textfield at the bottom. The
new album will be created as a private album.
To confirm album selection or creation, tap the [Select Album] button.

Upload your photos

Tap Launch Upload button Once you added
photos, and selected the Picasa album, the [Launch Uppload!] button
appears. Tap it to start uploading the photos.
Photo uploading to Picasa The photo currently
uploading is displayed on the upload screen. If you want to cancel the
upload, tap the [Cancel Uppload] button.






Saturday, August 14, 2010

Howto use the Keychain in iPhone SDK to store and retrieve secure Data

When you need to store data securely from within your iPhone application, sooner or later you will step over the iPhone keychain. This is the super-duper built-in iPhone safe, kindly provided to you by the fruit company.
Now - take your time to praise Apple for being so wise and foresighted to supply you with the cooked equipment for secure data storage...
..did you do well? Did you rag on some dump Windows users? Fine, then here is one more thing: The stuff ain't that simple to use. Of course, as a blessed Apple user you would expect a simple interface in the fashion of "[keychain getMySecretData] [keychain storeMySecretData]". But when you take a closer look at it you will find that it is rather half-baked - and implementing keychain access might take a little more than a one-buttoned mouse.
First of all, there ain't an easy-to-use keychain API in any of the frameworks delivered with the iPhone SDK. You would have to deal with weird C function calls. For instance, to retrieve data from the keychain you have to invoke something called SecItemCopyMatching, passing in a dictionary as parameter. Completely self explanatory, isn't it? If you're getting curious, you can dive into the secrets of keychain services here. Though in this programming guide the Apple guys are not getting bored emphasizing the straightforwardness and ease of use of the keychain API - there seems to be a little doubt, though.  Cause in the code examples accompanying the guidelines, they build an object-oriented wrapper class around all this cute C-function magic. And this is where we join the game..

The complete example application provided by Apple can be found here. But the only thing of interest to us is the KeyChainItemWrapper. You can think of a keychain item as a record stored in the keychain database. Each keychain item consists of unencrypted attributes, and the actual data, which is encrypted. For instance, a password keychain item could have the account name set as one of its attributes, and the password itself be stored as keychain item data. Apple defines these types (aka classes) of keychain items:
  • Generic Password
  • Internet Password
  • Certificate
  • Cryptographic Key
  • Digital Identity
Keychain item classes differ in the attributes and structure of item data.
The KeyChainItemWrapper is a kind of object-oriented wrapper around a Generic Password keychain item, providing methods for item creation and modification:

- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup

Creates a Generic Password keychain item. If an item with provided identifier is already stored in the keychain data base, it will load its data. If not, it will create a new one and initialize it with empty strings. The identifier should be a string identifying the account the password stored in the keychain item belongs to.
The access group can be used to share the keychain item among different applications. So in most cases, you will not need it, and it can be set to nil.

- (void)setObject:(id)inObject forKey:(id)key

Depending on the value you provide for key, it stores the inObject as keychain item attribute, or encrypted keychain item data. To gain reasonable results, you MUST use the constants defined in the keychain services API for the key. For example, to store an ecrypted password, kSecValueData must be the key, and the actual password must be the inObject. To store a login name as keychain item attribute, use kSecAttrAccount for key, and the login name for inObject.

No later than here you recognize, that even the "object-oriented" KeyChainItemWrapper is not object-oriented, but at most "objective". In a serious object-oriented language, they would have given us methods like [keyChainItem setPassword] and [keyChainItem setAccount]. In reality, we have to tell what we want by passing cryptic constants to generic methods. Well, you will have to get used to it: It's the Apple guys - they don't care about user requirements, they define them themselves.

- (id)objectForKey:(id)key

Get an attribute or data out of the keychain. Just as mentioned in the previous section, you will have to use the pre-defined keychain services API constants for key.

- (void)resetKeychainItem

Delete all attributes and data of the keychain item, and initialize them with empty strings. Note: This does not delete the keychain item itself! Deleting a keychain item is not implemented in the Apple example.
 
Here is a small code example of how to write to and read from the keychain:

- (void) savePassword: (NSString*) password {
    [self.keychainItemWrapper setObject:password forKey:
           (id)kSecValueData];
}

- (void) retrievePassword {
    return (NSString*) [self.keychainItemWrapper objectForKey:
           (id)kSecValueData];
}




Well, now you should be equipped with the basic knowledge to integrate a secure, keychain-based password storage into your iPhone application. 

Don't forget to add the Security.framework to your Xcode project if you want to use the KeyChainItemWrapper!

One important thing to mention is, that with the latest version 1.2, the KeyChainItemWrapper also works on the iPhone simulator. In prior versions it did not. The programmers just forgot to mention this in the revision history. The only thing you cannot use on the simulator is the ability to share keychain items among different applications using an accessGroup. 
I think, you'll get over it.. 

You can find a very good, more detailed overview of the keychain API in this blog post.