9/27/2011

JPA, Eclipselink and Lotus Domino Integration

You might be curious, what my last post about doing geo-spatial queries in JPA has to do with Lotus Domino development - here is the answer, and a question:

After developing the classes for my spatial search along a specified route (tested perfectly well in the eclipse environment), now is the time to intregrate this beast with Lotus Domino server - this should be pretty simple - but only on first sight.

JPA stores its configuration inside a persistence.xml file in the /META-INF directory - but this does not seem to be available to the runtime - causing this error:

No Persistence provider for EntityManager named myPersistenceUnit
(LDD Bug Report X-Pages Forum Report)

Now - after some searching, it could be done theoretically (but does not work for me, and its russian, too - google translates it into a funny description):

Lotus Domino + JPA + GUICE

A rough description of the procedure:
  • Use Guice to inject your dependencies to the JPA Framework Implementation (eclipse in this case). Put all the code into a WEB-INF/src folder which is in the build path.
  • Put the persistence.xml and orm.xml into WEB-IF/src/META-INF
  • Put all the libraries you need into WEB-INF/lib
  • Remove all security restrictions from java.policy in c:\Domino\jvm\lib\security by using

grant {

    permission java.security.AllPermission;

};


The downsides:

If you do all this, it works (but is still very hard to handle while develop, test and deploy), very slow to compile because all data has to be put to and from the database, not secure and not really possible to deploy in a productive customer environment.
Addidtionally, the whole server has to be restarted for every tiny change.

Is there any other possibility, to deploy this via jar?

So i guess it's time to set up a Glassfish Server along Domino to make it happen in a convenient way.

More work to do...

9/19/2011

Spatial Queries for MS SQL in Eclipselink JPA Expressions

The Problem: How to query Microsoft SQL Server 2008 Geography Data Type with Eclipselink Expressions?

Eclipselink provides an easy, extensible way of mapping relational data to java objects. But: it does not yet implement direct mapping of the geography data type that is available on Microsoft SQL Server 2008 R2 (byte array mapping is possible though).

Now this is  easy with a native query like this simple query, that selects all entries, that are within a buffer of 500m within a given route - the route being provided in the "Well Known Text" format:

SELECT * FROM [Address]  WHERE (GEOGRAPHY::Parse(
'LINESTRING (27.45 51.34, 25.45 50.214)'
).STBuffer( 500 ).STIntersects([LocationData])) = 1

This is ok, if you just want to query that specific case. I'd like to show, how we could make use of the object relational features, that the object oriented query would provide such as automatic mapping between different entities (joining the mapped tables), querying by example and dynamic building of queries with the ExpressionBuilder object.

The Approach: Extend JPA in a way that makes spatial queries possible

I chose to extend eclipselink in a way, so that the expression builder can be used to build queries with integrated searching for geospatial data. The next step would be to find a way to integrate the used functions into commonly known functions that can be used inside JPQL (Java Persistence Query Language) queries. The main point, why this is not possible at the moment is the "." in the notation (SELECT * FROM [Address]  WHERE (GEOGRAPHY::Parse(?).STBuffer(?).STIntersects([LocationData])) = 1) that uses .NET class objects that provide the spatial datatypes in the SQL Server at the moment. Eclipslink just throws a parse error when trying to use functions like ".STBuffer", which is pretty understandable in most cases. Please send me an email, if you can provide further info, on how to use functions like that.

The solution: Create an own Extension to org.eclipse.persistence.platform.database.SQLServerPlatform and use it

The solution I came up with is: extend the database platform and integrate a new function that takes three arguments as a vector. This advanced technique seems to circumvent the extensive syntax checking when using the getFunction method of the eclipselink expressions. This pattern can be easily extended to provide those basic functions that are missing in your current environment. If you switch the platform, you just have to implement those patters for the new database you are switching to. To use this new DatabasePlatform definition, you just have to specify it inside the persistence unit properties in your persistence.xml file:

..
<property name="javax.persistence.jdbc.url" value="jdbc:jtds:sqlserver://someserver:someport/somedatabas;instance=someintstance" />
<property name="eclipselink.target-database" value="de.itbalance.persistence.platform.database.SQLServerSpatialPlatform"/>
..

This is the code for the new DatabasePlatform extension:

public class SQLServerSpatialPlatform extends SQLServerPlatform {
    
    /**
     * ID for a function GEOGRAPHY::Parse( ? ).STBuffer( ? ).STIntersects( ? )
     *
     * where arguments are
     *
     * ?1: "LINESTRING (27.45 51.34, 25.45 50.214)" (OGS WKT - Well Know Text - format)
     * ?2: 500
     * ?3: <GEOGRAPHY-FIELD-NAME> in database table (MS SQL Geography Data Type)
     *
     * result is 1, if ?3 is inside the range of a buffer of ?2 meters around the geometric figure described in ?1
     *
     */
    public static final int FUNC_ISNEARGEO = 14000;
    
    
    /**
     *
     */
    private static final long serialVersionUID = -4972647627903866454L;
    
    public SQLServerSpatialPlatform() {
        super();
    }

    protected void initializePlatformOperators() {
        super.initializePlatformOperators();

        // create user-defined functions
        
        Vector<String> opStrings = new Vector<String>();
        opStrings.add("GEOGRAPHY::Parse(");
        opStrings.add(").STBuffer(");
        opStrings.add(").STIntersects(");
        opStrings.add(")");
        ExpressionOperator op = new ExpressionOperator();
        op.setSelector(FUNC_ISNEARGEO); //ExpressionOperator.*
        op.printsAs(opStrings);
        //op.bePrefix();
        op.setNodeClass(FunctionExpression.class);
    
        // make it available to this platform (only!)
        addOperator(op);
    }

}

The corresponding junit test function, that demonstrates the useage is:
  
    @SuppressWarnings("unchecked")
    @Test
    public void testExpressionsWithDatabasePlatform(){
        
        //pre-selections
        String lineString="LINESTRING (27.45 51.34, 25.45 50.214)";
        int buffer = 500;
        
        
        //get session and execute
        EntityManagerFactory factorySpatial;
        EntityManager emSpatial;
        factorySpatial = Persistence.createEntityManagerFactory("spatialBlogDemo");
        emSpatial = factorySpatial.createEntityManager();
        JpaEntityManager emImpl = (JpaEntityManager)emSpatial.getDelegate();

        //generate query with an expression builder
        ReadAllQuery query = new ReadAllQuery(Address.class);
        ExpressionBuilder builder = query.getExpressionBuilder();
        
        //prepare arguments for FUNC_ISNEARGEO
        Vector<Object> args = new Vector<Object>();
        args.add(lineString);
        args.add(Integer.toString(buffer));
        args.add(builder.getField("Location"));
    
        //set selection criteria
        query.setSelectionCriteria(builder.getFunction(SQLServerSpatialPlatform.FUNC_ISNEARGEO, args).equal(1));
        
        //create query
        TypedQuery<Address> jpaQuery = (TypedQuery<Address>) emImpl.createQuery(query);
        List<Address> list = jpaQuery.getResultList();
        
        //assert test result
        assertEquals(list.size()>0,true);
        emSpatial.close();
    }

So, this was pretty in-depht. Contact me, if you have any questions.