Database schemaThis one is pretty straightforward. When defining a table column that needs to store geographical data, you can use the MDSYS.SDO_GEOMETRY type. For example:
CREATE TABLE RESIDENCE(
RESIDENCE_ID INT NOT NULL,
NAME VARCHAR2(100) NOT NULL,
CONSTRAINT PK_RESIDENCE PRIMARY KEY (RESIDENCE_ID)
You can use the SDO_UTIL package to insert GML into SDO_GEOMETRY types using the SDO_UTIL.FROM_GMLGEOMETRY and SDO_UTIL.FROM_GML311GEOMETRY functions.
See the Oracle Spatial Developer's Guide for more information on the SDO_GEOMETRY type.
ORM layerIn the ORM layer we map database table rows to POJOs and vice versa using JPA. JPA implementations such as EclipseLink provide out-of-the-box mappings between most common Java data types and database columns. To map more exotic and user-defined Java objects you can use Converters in EclipseLink. You can either use an out-of-the-box converter that is shipped with EclipseLink, or code one yourself by implementing EclipseLink's Converter interface. For more information, see this blogpost by Doug Clarke.
In this case we need to map the SDO_GEOMETRY database objects to some sort of Java geometry object. Luckily, EclipseLink ships with an out-of-the-box Converter that maps the SDO_GEOMETRY type to a JGeometry object. The JGeometry class provides all kinds of convenience methods and attributes for working with geographical data. This class is part of the Oracle Spatial Java functionality. It can be used only for Oracle Spatial's SQL type MDSYS.SDO_GEOMETRY and supports Oracle JDBC Driver version 8.1.7 or higher.
To implement the mapping for geographical data we need to do the following:
- Add the required JARs to the classpath;
- Annotate the JPA entities and attributes.
The JGeometry class and associated Java classes are contained in the sdoapi.jar and sdoutl.jar files. They can be found in the library directory of your Oracle RDBMS installation. Also add the ojdbc JAR to the classpath.
Add a Convert annotation to the geometry attributes in your JPA entities that need to map to the SDO_GEOMETRY database types:
private JGeometry geometry;
Next, add the StructConverter annotation to the JPA entities containing geometry attributes. The StructConverter is a specific type of EclipseLink converter that provides out-of-the-box mappings to Oracle RDBMS struct types.
@Table(name = "RESIDENCE")
@StructConverter(name = "JGeometry", converter = "org.eclipse.persistence.platform.database.oracle.converters.JGeometryConverter")
public class Residence implements Serializable
The org.eclipse.persistence.platform.database.oracle.converters.JGeometryConverter provides the actual mapping logic. The name attribute of the StructConverter needs to be same as the attribute value for the Convert annotation.
Web Service layerSince GML is an XML format we can use JAXB to generate Java classes for the GML elements that are part of the input and output values of the Web Service operations. There are several ways to generate the JAXB classes including Maven plugins for JAXB or the command-line tool xjc. A simple example of running xjc is shown by the following command:
xjc -d [target dir of generated classes] [XSD root directory]
In our use case, we had a predefined Web Service interface and used a top-down approach to generate Java classes based on the existing interface. You can use the wsimport tool to generate the JAX-WS artifacts including the Java WebService class from the WSDL.
Note that in this end-to-end scenario the service is exposed as SOAP Web Service. It is simple to expose the same functionality as a RESTful service. You can use JAX-RS annotations instead of JAX-WS annotations to create a RESTful service that exposes geographical data in GML format. See the following example that shows how JPA and JAX-RS can be combined to create RESTful services.
Business logic layerThis layer, among others, provides the logic to map between the JAXB generated classes for the GML elements and the JGeometry objects.
For the conversion from JGeometry objects to JAXB generated classes for GML elements this involves:
- Use the static methods of the oracle.spatial.util.GML3 class to generate a String containing the textual GML representation of the geographical object;
- Unmarshall the GML String into the JAXB generated classes.
JAXBContext jaxbContext = JAXBContext.newInstance("net.opengis.gml");
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
JAXBElement jaxbGeometry = null;
String gml = GML3.to_GML3Geometry(jGeometry);
ByteArrayInputStream bais = new ByteArrayInputStream(gml.getBytes());
JAXBElement jaxbGeometry = (JAXBElement) unmarshaller.unmarshal(bais);
The GML3 class and supporting code can also be found in the sdoapi.jar and sdoutl.jar files.
For the conversion from JAXB generated classes for GML elements to JGeometry objects you need to retrieve the geographical data from the GML elements and use the static methods of the JGeometry class to instantiate the proper JGeometry object. For example:
JGeometry geometry = JGeometry.createLinearPolygon(coordinates, spatialDimension, spatialReference);
Read about the deployment specifics for this geometry-based Web Service on Oracle WebLogic Server in part 3 of this blog.