Primitives are handled in Jofti by wrapping the primitive in its equivalent Java wrapper class, so an int becomes an Integer, a float a Float and so on. The user does not have to make any changes to the classes themselves.
However, when using the convenience query classes you must use
the wrapper
class equivalent. So if your property value is an int you would use:
Map results = new MatchQuery
("com.foo.bah.TestClass",privateField,new Integer(20));
This does not apply for the Query language version where the parser will
automatically convert the String representation to the correct wrapper
type.
// select ClassName where PropertyName [=,!=,>=,<=] Value
Map results = index.query(
new SQLQuery("select com.foo.bah.TestClass where privateField = 20 ));
// or
Map results = index.query(
new EJBQuery("select c from com.foo.bah.TestClass as c where
c.privateField = 20 ));
One primitive type does not conform to this rule and that is boolean.
Because the
wrapper class Boolean was not made Comparable until Java 1.5 it is
necessary to
supply a Jofti wrapper class to the convenience queries.
For example
Map results = new MatchQuery("com.foo.bah.TestClass",privateField,
new com.jofti.model.ComparableBoolean(true));
NOTE: This does NOT mean that you have to use this class in your
objects -
you can continue to use boolean or Boolean fields, or top level Boolean
Objects.
It is
just the convenience methods that require this class. The Query language
will
convert true and false to the correct type.
// select ClassName where PropertyName [=,!=,>=,<=] Value
Map results = index.query(
new SQLQuery("select com.foo.bah.TestClass where privateField = true ));
Similarly you can pass a Boolean object as a named or positional parameter in an EJB format query and Jofti will treat it as a primitive
if the type of the attribute is a primitive boolean.
Map results = index.query(
new SQLQuery("select c FROM com.foo.bah.TestClass where
c.privateField = ?1" ).setParameter(1,Boolean.TRUE);
A number of Java objects which are Comparable are excluded from the
index. These
are objects which roughly conform to BLOB type
fields in databases and are:
Dates are always awkward in Java and this is not really an exception.
For the
convenience queries and the fields and objects in parameterised queries you
can just use the Date classes as in Java.
For example
Map results = new MatchQuery("com.foo.bah.TestClass",privateField,
new Date());
However, the query language needs to be able to parse dates correctly
from the
query String. To this end Jofti uses the
default locale encoding Date format for the JVM it is running in.
For the UK examples would be:
The time and timezone elements are optional and can have a variable format. Check Java's documentation for more details and how you would specify the date in your own JVM encoding.
So for example to use dates in the query language in the UK encoding you
would
write something similar to the examples below:
Map results = index.query(new SQLQuery
("select java.util.Date where value <'4/10/2001 12:23:00'"));
//or
Map results = index.query(new SQLQuery
("select java.util.Date where value <'4/10/2001'"));
//or
Map results = index.query(new SQLQuery
("select java.util.Date where value <'10 April 2001'"));
//or EJB format
Map results = index.query(new EJBQuery
("select d from java.util.Date as d where d.value <'4/10/2001 12:23:00'"));
Map results = index.query(new EJBQuery
("select d from java.util.Date as d where d.value <'4/10/2001'"));
//or
Map results = index.query(new EJBQuery
("select d from java.util.Date as d where d.value <'10 April 2001'"));
Note: You must surround a date literal with single quotesIn order to find out the formats that JOFTI accepts for your locale use the following:
System.out.println("short " +DateFormat.getDateTimeInstance
(DateFormat.SHORT, DateFormat.SHORT).format(new Date()));
System.out.println("short " +DateFormat.getDateTimeInstance
(DateFormat.SHORT, DateFormat.MEDIUM).format(new Date()));
System.out.println("short " +DateFormat.getDateTimeInstance
(DateFormat.SHORT, DateFormat.LONG).format(new Date()));
System.out.println("medium " +DateFormat.getDateTimeInstance
(DateFormat.MEDIUM, DateFormat.SHORT).format(new Date()));
System.out.println("medium " +DateFormat.getDateTimeInstance
(DateFormat.MEDIUM, DateFormat.MEDIUM).format(new Date()));
System.out.println("medium " +DateFormat.getDateTimeInstance
(DateFormat.MEDIUM, DateFormat.LONG).format(new Date()));
System.out.println("long " +DateFormat.getDateTimeInstance
(DateFormat.LONG, DateFormat.SHORT).format(new Date()));
System.out.println("long " +DateFormat.getDateTimeInstance
(DateFormat.LONG, DateFormat.MEDIUM).format(new Date()));
System.out.println("long " +DateFormat.getDateTimeInstance
(DateFormat.LONG, DateFormat.LONG).format(new Date()));
The current OSX Java (1.4.x and 1.5.x rc) implementations do not correctly pick up the locale from
your machine. You must programmatically set it as a work around
The java.sql.Timstamp class is similar in usage to Date. The difference
is that
to construct a timestamp in a
query the format for the String must be in a similar form to
yyyy-mm-dd
hh:mm:ss.fffffffff (nanosecond part of String is optional).
For example
Map results = index.query(new SQLQuery
("select java.sql.Timestamp where
value >'2005-08-08 11:00:00.03'" ));
// or
index.query(new EJBQuery
("select t from java.sql.Timestamp as t where
t.value >'2005-08-08 11:00:00.03'" ));
Interface implementation, interface hierarchies and concrete class hierarchies present a particular problem for indexing. Namely, one of how to decide what class to index under. E.g. if we place an object in the cache that implements an interface, or is part of class heirarchy do we want it to be indexed under its concrete type, its interface or the parent definition. And if it has an interface hierarchy which one do we choose?
Jofti tackles this problem by indexing objects by all interfaces, parent classes and superinterfaces that are declared in the config file.
So:
So assume we had two classes that implement the same interface:
public interface Foo {
String getBar() throws Exception;
}
public class FooImpl implements Foo {
String getBar() throws Exception {
// ...
}
}
public class AnotherImpl implements Foo {
String getBar() throws Exception {
// ...
}
}
and we had a classes definition like the following.
<classes>
<class name="package.Foo">
<property>bar</property>
</class>
<class name="package.AnotherImpl">
<property>bar</property>
</class>
</classes>
We would find that the Class AnotherImpl, because it implements the Interface Foo, would be indexed under its own type, and the interface Foo. FooImpl however, will be indexed only under the interface Foo as its concrete class is not mentioned in the classes definition.
So if we queried against the types we would find the following results.
index.put("key1",new AnotherImpl("test"));
index.put("key2", new FooImpl("test"));
// would return 2 results
Map results = index.query(new MatchQuery("package.Foo",bar,
"test"));
// would return 1 result
results = index.query(new MatchQuery("package.AnotherImpl",bar,
"test"));
This is so we can simply declare interfaces in our index and not have to worry about concrete types at run time, they will all be indexed under the appropriate Interfaces.Now this can be taken further to deal with interface hierarchies.
Assume, as before we have a class which has two interfaces, but these are represented as
a hierarchy.
public interface Foo {
String getBar() throws Exception;
}
public interface Something extends Foo {
String getFoo() throws Exception;
}
public class OurImpl implements Something {
String getBar() throws Exception {
// ...
}
String getFoo() throws Exception {
// ...
}
}
The object OurImpl will be indexed under the interface it implements and the super interface Foo.
<classes>
<class name="package.Foo">
<property>bar</property>
</class>
<class name="package.Bar">
<property>foo</property>
</class>
</classes>
The ourImpl object will now be searchable under both interfaces - but not the concrete class, as this is not mentioned in the classes definition.Object heirarchies without interfaces obey the same rules, but for concrete classes.
Dynamic proxies are reasonably similar to interfaces (as you would expect), but have a couple of differences. One is that you cannot index a concrete class under its class definition if it is proxied. Jofti will not be able to apply Rule 1, or Rule 4 from above to a proxied class (as its actual class type is something similar to $Proxy0). Therefore, only rules 2 and 3 are applied to a dynamically proxied class.
For instance:
public class TestProxy implements
java.lang.reflect.InvocationHandler
{
private Object obj;
public static Object newInstance(Object obj) {
return java.lang.reflect.Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new TestProxy(obj));
}
private TestProxy(Object obj) {
this.obj = obj;
}
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable
{
Object result;
try {
System.out.println("before method " + m.getName());
result = m.invoke(obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException("
unexpected invocation exception: " +
e.getMessage());
} finally {
System.out.println("after method " + m.getName());
}
return result;
}
}
If we create a proxied instance of OurImpl class:,
Foo foo = (Foo) TestProxy.newInstance(new OurImpl());
index.put("tester",foo);
Using class mappings of:
<classes>
<class name="package.Foo">
<property>bar</property>
</class>
<class name="package.OurImpl">
<property>bar</property>
</class>
</classes>
We could find the proxied class by searching for instances of Foo, but not the OurImpl type.
Access to Inner classes in Jofti is quite straightforward as long as you understand how the JVM names such classes.
For example, assuming a class:
public class OuterClass {
public class InnerClass {
private String privateField ="inner";
public synchronized String getPrivateField() {
return privateField;
}
public synchronized void setPrivateField
(String privateField) {
this.privateField = privateField;
}
}
}
We can define an index entry as :
<classes>
<class name="package.OuterClass$InnerClass">
<property>privateField</property>
</class>
</classes>
Therefore adding the inner class to the index, we can find it as follows:
index.put("test", new OuterClass().new InnerClass() );
index.query(new SQLQuery("select package.OuterClass$InnerClass
where privateField =inner"));
//or
index.query(new EJBQuery("select oi from package.OuterClass$InnerClass as oi
where oi.privateField =inner"));
NULL in java is a special value and is treated the same way in Jofti. Objects that are themselves NULL are ignored by the index, however, NULL attributes of an Object that is indexed is recorded in the index. These can be queried against, providing the following conventions are met.
No code changes are required when inserting an object with an indexed attribute that is NULL.
For a convenience query (such as MatchQuery) you would do the following:
ObjectWithNullAttribute foo = new ObjectWithNullAttribute();
index.put("tester",foo);
Map results = index.query(new MatchQuery("package.ObjectWithNullAttribute",
bar,null));
Notice how we use the Java value null as the attribute value. So far no real changes are required. However, for the query language there is an important difference.
If you use:
ObjectWithNullAttribute foo = new ObjectWithNullAttribute();
index.put("tester",foo);
Map results = index.query(new SQLQuery("select package.ObjectWithNullAttribute
where bar = null"));
Instead you MUST use the operators 'IS' and 'NOT' as shown in the following code.
ObjectWithNullAttribute foo = new ObjectWithNullAttribute();
index.put("tester",foo);
Map results = index.query(new SQLQuery("select package.ObjectWithNullAttribute
where bar IS null"));
// or
Map results = index.query(new EJBQuery("select oa from
package.ObjectWithNullAttribute as oa where oa.bar IS null"));
ObjectWithNullAttribute foo = new ObjectWithNullAttribute();
index.put("tester",foo);
Map results = index.query(new SQLQuery("select
package.ObjectWithNullAttribute where bar NOT null"));
// or
Map results = index.query(new EJBQuery("select o from package.ObjectWithNullAttribute
as o where o.bar NOT null"));
The IS and NOT operators may only be used with null, you canot use them in normal searches.
Using Jofti to index arrays that are themselves cache objects (top level arrays) is much like using non-array objects with attributes. Jofti does not index the array as a single entity, rather it indexes the individual elements. Jofti will use the configuration to work out which type of objects it will index from inside the array.
This notation for refering to Arrays (and Collections) is not part of either SQL or the EJB3 spec, but we feel that it is relatively concise, expressive and fits naturally into the navigational grammar of each language.
Essentially, there are a few ways of dealing with an array and this depends on whether it is an array of a built-in types, an array of primitives, an array of user defined objects or an Object array (which will be addressed in the Object[] section.
An array of a built in type (such as String) or a primitive (such as int) is inserted into the index in the same manner as a normal object, you do not need to configure anything, it is only the query fomat which is different.
String[] temp = new String[2];
temp[0] = "stuff";
temp[1] = "from";
index.put("test",temp);
Map results = index.query(new SQLQuery("select java.lang.String[]
where [element] ='stuff'"));
//or
Map results = index.query(new EJBQuery("select star from java.lang.String[] as star
where star.[element] ='stuff'"));
The difference here is that for elements in an array of built in types or primitives we MUST use the field name as "[element]". This literal tells Jofti that the type of the element is the same as the Array type.
(it is is also a shorthand so we do not have to write select java.lang.String[] where [java.lang.String]=....)
Jofti does NOT support the idea of queryable positions in arrays, so you cannot specify:
Map results = index.query(new SQLQuery("select java.lang.String[] where
[1] ='stuff'"));
This will not work.
Primitives differ only in the identifier used for the array. For example:
int[] temp = new int[2];
temp[0] = 2;
temp[1] = 3;
index.put("test",temp);
Map results = index.query(new SQLQuery("select int[] where [element] =3"));
// or
Map results = index.query(new EJBQuery("select iar from int[]
as iar where iar.[element] =3"));
Notice how the primitve array "int[]" has no java.lang package as the class does not have one in Java. This is similar for all the primitive types.
Note: that primitive arrays like "int[]" and the primitive wrapper array equivalent "java.lang.Integer[]" are not searchable as a single type as they are indexed separately in Jofti.
Arrays of user defined objects.
User defined object arrays are very similar to built in types, except you have to configure them before they can be indexed.
For Instance to configure from the config file you would write:
<!--
Classes to be indexed
-->
<classes>
<class name="com.test.TestClass[]">
<property>[element].privateField</property>
</class>
<class name="com.foo.bar.SomeClass">
<property>bahField</property>
</class>
</classes>
The interesting line here is that TestClass is defined to be an array (TestClass[]) and the property privateField (an integer) that we are interested in is accessed by the "[element].privateField" property. This tells Jofti that for each TestClass in an array of TestClass types we should index the privateField from each element.
To then search we would write:
TestClass[] temp = new TestClass[2];
temp[0] = new TestClass();
temp[1] = new TestClass();
index.put("test",temp);
Map results = index.query(new SQLQuery("select com.test.TestClass[]
where [element].privateField =3"));
//or
Map results = index.query(new EJBQuery("select t from com.test.TestClass[] as t
where t.[element].privateField =3"));
Notice how the propertyname [element].privateField is used in the search as it is specified in the config.
Using a top level Object array is really similar in concept to using a user defined type array. The reason that an Object array is not treated like a built-in type is that Jofti needs to know in advance what type of Objects it should be looking for in a top level Object array, so it is required as a runtime configuration. Therefore, the type of Object(s) that you want to be indexed in an Object[] must be specified as a property.
To configure Jofti to deal with the Object[] type we would use something like:
<!--
Classes to be indexed
-->
<classes>
<class name="java.lang.Object[]">
<property>[java.lang.String]</property>
<property>[java.lang.Integer]</property>
</class>
<class name="com.foo.bar.SomeClass">
<property>bahField</property>
</class>
</classes>
We can see here that the Object[] property does not use the [element] keyword we use for typed arrays. This is because we need to know which Object types from the Object[] we should be indexing. From the above config you can see that we are interested in Strings and Integer, everything else in the array will be ignored by Jofti.
So to search we can do this:
Object[] objs = new Object[3];
objs[0] = "from";
objs[1] = new Integer(3);
objs[2] = new Object();
index.put("testObs",objs);
Map results = cache.query(new SQLQuery(" select java.lang.Object[]
where [java.lang.Integer] = 3"));
//or
Map results = cache.query(new EJBQuery(" select o from java.lang.Object[] as o
where o.[java.lang.Integer] = 3"));
The search shown above says get all Object[] instances in the cache that have a java.lang.Integer element that == 3. The search will NOT search for String elements in the Object[] array that =="3". To achieve this we could use the AND/OR clause and add a second query predicate (as detailed in the Query section above).
Notice that we do not use the [element] keyword as we need to know what type of object we are looking for. In order to use a top level Object[] you must follow this mechanism, you cannot use [element] and it must be configured.
It is worth noting that the configuration is used for all instances of Object[]. It is not instance specific, so you cannot configure Object[] more than once for a specific index. The attrbutes do not determine the Object indexing. So it is NOT legal to do this:
<!--
Classes to be indexed
-->
<classes>
<class name="java.lang.Object[]">
<property>[java.lang.String]</property>
<property>[java.lang.Integer]</property>
</class>
<class name="java.lang.Object[]">
<property>[java.lang.Long]</property>
</class>
</classes>
There is no way in Jofti to say that one set of Object[] instances will have Strings and Integers, and another will have Longs. The Class type is the
index root and there can only be one entry for each Object type as a direct index. However, this is not true if the array is itself an attribute of another Object, as it is the containing object that is the root type, so we can have as many array definitions as we want providing
they are different attributes on the same or different Classes. (see next section).
The use of arrays (of any type) that are returned as an attribute of another Object is a natural extension of the usage of top level arrays.
So, for instance, assume we had an object that had an attribute that returned a String[] :
public class ArrayTester
{
private String[] temp = new String[5];
public ArrayTester(){
temp[0]="value";
temp[1]="other";
}
public String[] getTemp(){
return temp;
}
}
We need to configure Jofti to look inside the array returned from attribute temp. For example:
<!--
Classes to be indexed
-->
<classes>
<class name="com.package.ArrayTester">
<property>temp.[element]</property>
</class>
</classes>
It should be obvious that although we have not specified the String[] per se - we have specified that the return object for the property temp will return an array that we should index only the [element] objects. This makes it clear that the array type must be built in type or of a user defined type. It cannot be an Object array.
So to search for instances of ArrayTester where its String[] contained the String "other", we would write.
index.put("test",new ArrayTester());
Map results = index.query(new SQLQuery(" select com.package.ArrayTester
where temp.[element] = 'other'"));
//or
Map results = index.query(new EJBQuery(" select a from com.package.ArrayTester as a
where a.temp.[element] = 'other'"));
Similarly, as to top level Object[] types we would access elements of an Object[] using the type information as part of the query.
So given a class:
public class ObjectArrayTester
{
private Object[] temp = new Object[5];
public ObjectArrayTester(){
temp[0]="value";
temp[1]=new Integer(0);
}
public Object[] getTemp(){
return temp;
}
}
And a config:
<!--
Classes to be indexed
-->
<classes>
<class name="com.package.ObjectArrayTester">
<property>temp.[java.lang.String]</property>
<property>temp.[java.lang.Integer]</property>
</class>
</classes>
We would access it in the expected manner of:
index.put("test",new ObjectArrayTester());
Map results = index.query(new SQLQuery(" select com.package.ObjectArrayTester
where temp.[java.lang.String] = 'other'"));
// or
Map results = index.query(new EJBQuery(" select o from
com.package.ObjectArrayTester as o
where o.temp.[java.lang.String] = 'other'"));
If an object contains an array that is an attribute that has elements that are objects, that themselves have attributes that are arrays, we can chain the
attribute.[element].attribute.[element] calls to an arbitrary depth.
Collections, and the sub types of List, Set Vector (etc) are a generalised case of Object Array for Jofti. The configuration and
query formats are extremely similar to Object[]. For example:
<!--
Classes to be indexed
-->
<classes>
<class name="java.util.ArrayList">
<property>[java.lang.String]</property>
</class>
</classes>
We can see here that any top level ArrayList will have any String inside it indexed. Other types will be ignored.
We can query this accordingly:
ArrayList list = new ArrayList();
list.add("test");
list.add("other");
index.put("test",list);
Map results = index.query(new SQLQuery(" select java.util.ArrayList
where [java.lang.String] = 'test'"));
//or
Map results = index.query(new EJBQuery(" select a from java.util.ArrayList as a
where a.[java.lang.String] = 'test'"));
Similarly to Arrays there is no way to refer to the indices in the List. Also similar to Object[], the elements in the list MUST be referenced by type as there is no way for Jofti to guess the type of the Object.
As List is an interface in Java we can also use Jofti's capability for indexing by Interfaces.
For example:
<!--
Classes to be indexed
-->
<classes>
<class name="java.util.ArrayList">
<property>[java.lang.String]</property>
</class>
<class name="java.util.List">
<property>[java.lang.String]</property>
<property>[java.lang.Integer]</property>
</class>
</classes>
Given the above config we can search the ArrayList we have put into the cache as an ArrayList or as a List. This means that any subtype of List will be accessable under the
List interface but only ArrayLists can be searched using that particular subtype.
ArrayList list = new ArrayList();
list.add("test");
list.add("other");
index.put("test",list);
// returns 1 List
Map results = index.query(new SQLQuery(" select java.util.List
where [java.lang.String] = 'test'"));
// or
Map results = index.query(new EJBQuery(" select l from java.util.List as l
where l.[java.lang.String] = 'test'"));
LinkedList linkedList = new LinkedList();
list.add("test");
list.add("linked");
index.put("linkedList",list);
// returns both Lists
results = index.query(new SQLQuery(" select java.util.List
where [java.lang.String] = 'test'"));
// or
results = index.query(new EJBQuery(" select l from java.util.List as l
where l.[java.lang.String] = 'test'"));
// returns ArrayList only
results = index.query(new SQLQuery(" select java.util.ArrayList
where [java.lang.String] = 'test'"));
//or
results = index.query(new EJBQuery(" select al from java.util.ArrayList as al
where al.[java.lang.String] = 'test'"));
The above query shows that we can use the List super interface (specified in the config) to be able to search both the ArrayList and LinkedList types. The results from the second query will return both List Objects. The third query uses the concrete class so Jofti limits its search to only those concrete class types.
This can be further generalised to use the Collection interface so we can search Lists and sets with the same query.
For instance:
<!--
Classes to be indexed
-->
<classes>
<class name="java.util.ArrayList">
<property>[java.lang.String]</property>
</class>
<class name="java.util.List">
<property>[java.lang.String]</property>
<property>[java.lang.Integer]</property>
</class>
<class name="java.util.Collection">
<property>[java.lang.String]</property>
<property>[java.lang.Integer]</property>
</class>
</classes>
We can then use the index in the following manner:
ArrayList list = new ArrayList();
list.add("test");
list.add("other");
index.put("test",list);
// returns 1 List
Map results = index.query(new SQLQuery(" select java.util.List
where [java.lang.String] = 'test'"));
// or
Map results = index.query(new EJBQuery(" select l from java.util.List as l
where l.[java.lang.String] = 'test'"));
LinkedList linkedList = new LinkedList();
list.add("test");
list.add("linked");
index.put("linkedList",list);
// returns both Lists
results = index.query(new SQLQuery(" select java.util.List
where [java.lang.String] = 'test'"));
// or
results = index.query(new EJBQuery(" select l from java.util.List as l
where l.[java.lang.String] = 'test'"));
// returns ArrayList only
results = index.query(new SQLQuery(" select java.util.ArrayList
where [java.lang.String] = 'test'"));
//or
results = index.query(new EJBQuery(" select al from java.util.ArrayList as al
where al.[java.lang.String] = 'test'"));
Set set = new HashSet();
set.add("test");
set.add("set val");
cache.put("testSet",set);
// returns both Lists and the Set
results = index.query(new SQLQuery(" select java.util.Collection
where [java.lang.String] = 'test'"));
// or
results = index.query(
new EJBQuery("select c from java.util.Collection as c
where c.[java.lang.String] = 'test'"));
The above example shows that we can easily specify the type of searching we want based on the interfaces implemented by the Collection.
It is interesting to note that even though we have NOT specified the Set or HashSet classes in the config they will still be picked up using the Collection Interface.
Like Object[] the Collection does not need to have objects that are all the same type and Jofti will just ignore Objects that are not of a configured type. So it is perfectly fine to do this:
ArrayList list = new ArrayList();
list.add("test");
list.add(new Integer(5));
list.add(new Object());
index.put("test",list);
// returns 1 List
Map results = index.query(new SQLQuery(" select java.util.List
where [java.lang.String] = 'test'"));
// or
Map results = index.query(new EJBQuery(" select l from java.util.List as l
where l.[java.lang.String] = 'test'"));
Interestingly, the EJB QL format capability of returning attributes (not achievable using the SQL format) from Objects would allow us to return all Strings (or some other type) as Object[]s from any Collection object that matched a query (this only applies to top-level Collections)
ArrayList list = new ArrayList();
list.add("test");
list.add("other");
index.put("test",list);
LinkedList linkedList = new LinkedList();
list.add("test");
list.add("linked");
index.put("linkedList",list);
Set set = new HashSet();
set.add("test");
set.add("set val");
cache.put("testSet",set);
Map results = index.query(
new EJBQuery("select c.[java.lang.String] from java.util.Collection as c
where c.[java.lang.String] = 'test'"));
Using Collections as attributes is pretty straight forward and builds on the way we use top level Collections. The main difference is that for attributes we never specify the Collection type, only the Objects we are interested in from within the Collection. This is identical to Object[]s in Jofti (in fact the notation for using Collections as attributes is identical to using an Object[] as an attribute.
As a way of illustrating a the use of Collections that are attributes of other Objects we shall take a slightly more complex example.
Given a Class Person which has a List of Address objects, suppose we want to query the Person objects by the postcode (the UK equivalent of Zip code) attribute in the address.
The class structure is:
public class Person {
private List addresses = new ArrayList();
public List getAddresses() {
return addresses;
}
public void setAddresses(List addresses) {
this.addresses = addresses;
}
}
public class Address {
private String postcode =null;
public String getPostcode() {
return postcode;
}
public void setPostcode(String postcode) {
this.postcode = postcode;
}
}
We configure Jofti to index all the postcode fields of the Address objects that are in the List of Addresses in Person.
<!--
Classes to be indexed
-->
<classes>
<class name="com.package.Person">
<property>addresses.[com.package.Address].postcode</property>
</class>
</classes>
It should be obvious from the above config that we do not specify the type of Collection when it is an attribute. In fact Jofti does not care, and the interest is actually the type of the Object we want to retrieve from the Collection (in this case com.package.Address). From this object we are then indexing the postcode field.
In order to use this in a query we would do something similar to:
List temp = new ArrayList();
Address address = new Address();
address.setPostcode("z1 1ru");
temp.add(address);
Address address2 = new Address();
address2.setPostcode("x1 1ru");
temp.add(address2);
Person person1 = new Person();
person1.setAddresses(temp);
List temp2 = new ArrayList();
temp2.add(address2);
Person person2 = new Person();
person2.setAddresses(temp2);
index.put("person1",person1);
index.put("person2",person2);
Map results = index.query(new SQLQuery(" select com.package.Person
where addresses.[com.package.Address].postcode = 'z1 1ru'"));
// or
Map results = index.query(new EJBQuery(" select p from com.package.Person as p
where p.addresses.[com.package.Address].postcode = 'z1 1ru'"));
This will return 1 result (person1).
To use a user defined type in Jofti as an indexable attribute there are a couple of restrictions that must be met.
1.The class must implement Comparable.
2. The class must have equals() and hashCode() implemented.
3. The class must be a field of another object. It cannot be a top level
object
by itself
4. The class must have a String constructor if you want to use it in the
SQL format query
language.
If you are only using the convenience query classes or the EJB3 QL format queries then the last
restriction can
be waived.
We can address this in the EJB3 format query by using a named or a positional parameter:
If there is no String constructor then when we come to use it in the
query
language we have
to solve the following problem in the SQL format query:
MyUserDefinedType userType = new MyUserDefinedType();
.... do some stuff here
Map results = index.query(new MatchQuery
("com.foo.bah.MyClass", "userDefinedField", userType));
what is supplied in place of the question marks?
MyUserDefinedType userType = new MyUserDefinedType();
.... do some stuff here
Map results = index.query(new SQLQuery
(select com.foo.bah.MyClass where userDefinedField = ????));
MyUserDefinedType userType = new MyUserDefinedType();
.... do some stuff here
Map results = index.query(new EJBQuery
(select m from com.foo.bah.MyClass as m where m.userDefinedField
= :param).setParameter("param",userType));
//or
Map results = index.query(new EJBQuery
(select m from com.foo.bah.MyClass as m where m.userDefinedField
= ?1).setParameter(1,userType));