Google App Engine + JAVA + JDO = Simple Search

Objective

This article will show you how to implement a simple search in Google App Engine using JDO engine including searching in child objects.

The Problem Domain

In my application I need to search for data and you probably need to do the same in yours. In Google App Engine you can not query for properties of the child objects. In SQL world this means you can not use “where” clause. So how we can not construct a query which looks at child objects. So how can we implement search in such restrictive environment?

The Solution

I came up with a very simple solution – I created an “index” property where I store all the data that I need to search on. Let’s say we have a Customer bean that has multiple addresses and phones. We want to be able to search on address, phone and customer name. Here are our beans:

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")
public class Customer implements Serializable {
    private static final long serialVersionUID = -3665292067832675548L;

    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;
    @Persistent
    private String name;
    @Persistent
    private String contactName;
    @Persistent
    private String comments;

    @Persistent(mappedBy = "customer")
    @Element(dependent = "true")
    private List<Address> addresses = new ArrayList<Address>();

    @Persistent(mappedBy = "customer")
    @Element(dependent = "true")
    private List<Phone> phones = new ArrayList<Phone>();

    @Persistent
    private List<String> index;

    // Getters and Setter go here...
}

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")
public class Address {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key id;
    @Persistent
    private String type;
    @Persistent
    private String name;
    @Persistent
    private String line1;
    @Persistent
    private String line2;
    @Persistent
    private String city;
    @Persistent
    private String state;
    @Persistent
    private String zip;
    @Persistent
    private Customer customer;

    // Getters and Setter go here...
}

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")
public class Phone {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key id;
    @Persistent
    private String type;
    @Persistent
    private Number phone;
    @Persistent
    private Customer customer;

    // Getters and Setter go here...
}

In the Customer bean we defined an “index” property which is a String array. We will put all our searchable words int this index, so later we can construct a query. Before we save our customer we want to populate index property like this:

public void rebuildIndex() {
	index = new ArrayList<String>();
		
	Iterator<Address> it = this.addresses.iterator();
	while( it.hasNext() ) {
		Address ad = it.next();
		if( ad.getLine1() != null ){
			String[] idx = ad.getLine1().split(" ");
			for( int i = 0 ; i < idx.length ; i++ )
				index.add( idx[i] );
		}
		if( ad.getLine2() != null ){
			String[] idx = ad.getLine2().split(" ");
			for( int i = 0 ; i < idx.length ; i++ )
				index.add( idx[i] );
		}
	}

	Iterator<Phone> it2 = this.phones.iterator();
	while( it2.hasNext() ) {
		Phone ph = it2.next();
		if( ph.getPhone() != null )
			index.add( ph.getPhone().toString() );
	}

	String[] idx = name.split(" ");
	for( int i = 0 ; i < idx.length ; i++ )
		index.add( idx[i] );
		
	idx = contactName.split(" ");
	for( int i = 0 ; i < idx.length ; i++ )
		index.add( idx[i] );
		
}

Now we have all our searchable words from child beans and parent bean in one property we can search on. Here is how my search method looks like:

@SuppressWarnings("unchecked")
@ModelAttribute("custs")
@RequestMapping(value = "/{search}", method = RequestMethod.GET)
public List<Customer> searchCustomers( @PathVariable String search, HttpSession session)
                                          throws IOException {
    PersistenceManager pm = pmf.getManager();
    Query query = pm.newQuery(Customer.class);
    query.setFilter("index == searchParam");
    query.declareParameters("String searchParam");
    List<Customer> custs;
    try {
        custs = (List<Customer>) query.execute( search );
    } finally {
        query.closeAll();
    }
    return custs;
}

Conclusion

We found an easy way around the big table JDO implementation limitation in Google App Engine. It is a simple and strait forward workaround, but it has couple drawbacks: we can only search on full words and we duplicate data using more storage space. In my book storage is cheap so that is not a big concern for me.

As I progress more with my projects on Google App Engine, I will surely revisit the search topic and post about other solutions to this challenge.

If you have a better solution please post it in the comments, I would love to give it a try!

3 thoughts on “Google App Engine + JAVA + JDO = Simple Search

  1. Hi Tomas,

    Thanks for the great posts. I’ve read them all today and have found them extremely helpful.

    I just wanted to ask about the Key property. You seem to use it for some primary keys and you use Long for some others. Is there a reason for this?

    thanks

  2. The reason you have to use Key is mandated for the child objects. Key is actually combination of 2 ids: parent and child. You can also use it for parent objects, but I like Long for simplicity. If you try using Long for child key Data Nucleus will complain very loudly :)

    Tomas

Leave a Reply

Your email address will not be published. Required fields are marked *