/*
* gvNIX is an open source tool for rapid application development (RAD).
* Copyright (C) 2010 Generalitat Valenciana
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gvnix.addon.jpa.addon.geo.providers.hibernatespatial;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.gvnix.addon.jpa.addon.JpaOperations;
import org.gvnix.addon.jpa.addon.geo.FieldGeoTypes;
import org.gvnix.addon.jpa.addon.geo.providers.GeoProvider;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.jpa.annotations.activerecord.RooJpaActiveRecord;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.TypeManagementService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.FieldMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.operations.jsr303.FieldDetails;
import org.springframework.roo.model.JavaPackage;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.ReservedWords;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.support.util.FileUtils;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a
* href="http://www.dgti.gva.es">General Directorate for Information
* Technologies (DGTI)</a>
*/
@Component
@Service
public class HibernateSpatialGeoProvider implements GeoProvider {
private static final JavaType JPA_ACTIVE_RECORD_ANNOTATION = new JavaType(
RooJpaActiveRecord.class);
// ------------ OSGi component attributes ----------------
private BundleContext context;
private FileManager fileManager;
private PathResolver pathResolver;
private TypeLocationService typeLocationService;
private TypeManagementService typeManagementService;
private ProjectOperations projectOperations;
private JpaOperations jpaOperations;
/**
* DECLARING CONSTANTS
*/
public static final String NAME = "HIBERNATE_SPATIAL";
public static final String DESCRIPTION = "Use HibernateSpatial in your project";
private static final Logger LOGGER = Logger
.getLogger(HibernateSpatialGeoProvider.class.getName());
private static final JavaType HIBERNATE_TYPE_ANNOTATION = new JavaType(
"org.hibernate.annotations.Type");
private static final JavaType GVNIX_ENTITY_MAP_LAYER_ANNOTATION = new JavaType(
"org.gvnix.addon.jpa.annotations.geo.providers.hibernatespatial.GvNIXEntityMapLayer");
protected void activate(ComponentContext cContext) {
context = cContext.getBundleContext();
}
/**
* If HIBERNATE is setted as persistence provider, the command will be
* available
*/
@Override
public boolean isAvailablePersistence(FileManager fileManager,
PathResolver pathResolver) {
return HibernateSpatialGeoUtils.isHibernateProviderPersistence(
getFileManager(), getPathResolver());
}
/**
* This method checks if field geo is available to execute
*/
@Override
public boolean isGeoPersistenceInstalled(FileManager fileManager,
PathResolver pathResolver) {
return HibernateSpatialGeoUtils.isHibernateSpatialPersistenceInstalled(
getFileManager(), getPathResolver(), getClass());
}
/**
* This method configure your project to works using hibernate spatial
*/
@Override
public void setup() {
// Setup gvNIX JPA
getJpaOperations().setup();
// Updating Persistence dialect
updatePersistenceDialect();
// Adding hibernate-spatial dependencies
addHibernateSpatialDependencies();
}
/**
* This method adds new file to the specified Entity.
*
* @param JavaSymbolName
* @param fieldGeoTypes
* @param entity
*
*/
@Override
public void addField(JavaSymbolName fieldName, FieldGeoTypes fieldGeoType,
JavaType entity) {
final ClassOrInterfaceTypeDetails cid = getTypeLocationService()
.getTypeDetails(entity);
final String physicalTypeIdentifier = cid.getDeclaredByMetadataId();
// Getting fieldType and fieldDetails
JavaType fieldType = new JavaType(fieldGeoType.toString());
FieldDetails fieldDetails = new FieldDetails(physicalTypeIdentifier,
fieldType, fieldName);
// Checking not reserved words on fieldName
ReservedWords
.verifyReservedWordsNotPresent(fieldDetails.getFieldName());
// Generating package-info.java if necessary
JavaPackage entityPackage = entity.getPackage();
HibernateSpatialGeoUtils.generatePackageInfoIfNotExists(entityPackage,
getPathResolver(), getFileManager());
// Adding Annotation @Type
List<AnnotationMetadataBuilder> fieldAnnotations = new ArrayList<AnnotationMetadataBuilder>();
AnnotationMetadataBuilder typeAnnotation = new AnnotationMetadataBuilder(
HIBERNATE_TYPE_ANNOTATION);
typeAnnotation.addStringAttribute("type",
"org.hibernate.spatial.GeometryType");
fieldAnnotations.add(typeAnnotation);
fieldDetails.setAnnotations(fieldAnnotations);
// Adding Modifier
fieldDetails.setModifiers(Modifier.PRIVATE);
final FieldMetadataBuilder fieldBuilder = new FieldMetadataBuilder(
fieldDetails);
// Adding field to entity
getTypeManagementService().addField(fieldBuilder.build());
}
/**
* This method checks all Entities with GEO fields and annotate it with @GvNIXEntityMapLayer
*/
@Override
public void addFinderGeoAll() {
// Getting all entities annotated with @RooJpaActiveRecord
Set<ClassOrInterfaceTypeDetails> entities = getTypeLocationService()
.findClassesOrInterfaceDetailsWithAnnotation(
JPA_ACTIVE_RECORD_ANNOTATION);
for (ClassOrInterfaceTypeDetails entity : entities) {
annotateEntityWithGeoFields(entity);
}
}
/**
* This method add @GvNIXEntityMapLayer annotation to selected Entity
*/
@Override
public void addFinderGeoAdd(JavaType entity) {
// Validating not null entity
Validate.notNull(entity, "The entity specified, '%s', doesn't exist",
entity);
// Validate @RooJpaActiveRecord annotation
ClassOrInterfaceTypeDetails entityDetails = getTypeLocationService()
.getTypeDetails(entity);
AnnotationMetadata activeRecordAnnotation = entityDetails
.getAnnotation(JPA_ACTIVE_RECORD_ANNOTATION);
Validate.notNull(
activeRecordAnnotation,
String.format(
"The entity specified, %s doesn't have @RooJpaActiveRecord annotation",
entity));
// Annotate entity with @GvNIXEntityMapLayer
boolean isValid = annotateEntityWithGeoFields(entityDetails);
Validate.isTrue(
isValid,
String.format(
"The entity specified, %s doesn't have geo fields. Use \"field geo\" command to add new geo fields on current entity",
entity));
}
/**
* This method checks if entity has Geo field and annotate with @GvNIXEntityMapLayer
* if necessary
*
* @param entity
*/
public boolean annotateEntityWithGeoFields(
ClassOrInterfaceTypeDetails entity) {
// Getting all entityFields
List<? extends FieldMetadata> entityFields = entity.getDeclaredFields();
for (FieldMetadata field : entityFields) {
// Getting field type to get package
JavaType fieldType = field.getFieldType();
JavaPackage fieldPackage = fieldType.getPackage();
// If has jts field, annotate entity
if (fieldPackage.toString().equals("com.vividsolutions.jts.geom")) {
// Generating annotation
ClassOrInterfaceTypeDetailsBuilder detailsBuilder = new ClassOrInterfaceTypeDetailsBuilder(
entity);
AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder(
GVNIX_ENTITY_MAP_LAYER_ANNOTATION);
// Add annotation to target type
detailsBuilder.updateTypeAnnotation(annotationBuilder.build());
// Save changes to disk
getTypeManagementService().createOrUpdateTypeOnDisk(
detailsBuilder.build());
return true;
}
}
return false;
}
/**
* This method adds hibernate spatial dependencies and repositories to
* pom.xml
*/
public void addHibernateSpatialDependencies() {
// Parse the configuration.xml file
final Element configuration = XmlUtils.getConfiguration(getClass());
// Add POM Repositories
HibernateSpatialGeoUtils.updateRepositories(configuration,
"/configuration/repositories/repository",
getProjectOperations());
// Add POM dependencies
HibernateSpatialGeoUtils.updateDependencies(configuration,
"/configuration/dependencies/dependency",
getProjectOperations());
}
/**
* This method update Pesistence Dialect to use the required one to the
* selected database.
*
*/
public void updatePersistenceDialect() {
boolean runTime = false;
// persistence.xml file
final String persistenceFile = getPathResolver().getFocusedIdentifier(
Path.SRC_MAIN_RESOURCES, "META-INF/persistence.xml");
// if persistence.xml doesn't exists show a WARNING
if (getFileManager().exists(persistenceFile)) {
// Getting document
final Document persistenceXmlDocument = XmlUtils
.readXml(getFileManager().getInputStream(persistenceFile));
final Element persistenceElement = persistenceXmlDocument
.getDocumentElement();
// Getting all persistence-unit
NodeList nodes = persistenceElement.getChildNodes();
// Saving in variables, which nodes could be changed
int totalModified = 0;
for (int i = 0; i < nodes.getLength(); i++) {
Node persistenceUnit = nodes.item(i);
// Get all items of current persistence-unit
NodeList childNodes = persistenceUnit.getChildNodes();
for (int x = 0; x < childNodes.getLength(); x++) {
Node childNode = childNodes.item(x);
String nodeName = childNode.getNodeName();
if ("properties".equals(nodeName)) {
// Getting all properties
NodeList properties = childNode.getChildNodes();
for (int y = 0; y < properties.getLength(); y++) {
Node property = properties.item(y);
// Getting attribute properties
NamedNodeMap attributes = property.getAttributes();
if (attributes != null) {
Node propertyName = attributes
.getNamedItem("name");
String propertyNameValue = propertyName
.getNodeValue();
if ("hibernate.dialect"
.equals(propertyNameValue)) {
Node propertyValue = attributes
.getNamedItem("value");
String value = propertyValue.getNodeValue();
// If is replaced on runtime,
// we alert to the user to change manually
if (value.startsWith("${")) {
runTime = true;
showRuntimeMessage(value);
}
else {
final Element configuration = XmlUtils
.getConfiguration(getClass());
if (!HibernateSpatialGeoUtils
.isGeoDialect(configuration,
value)) {
// Transform current Dialect to
// valid
// GEO dialect depens of the
// selected
// Database
// Parse the configuration.xml file
String geoDialect = HibernateSpatialGeoUtils
.convertToGeoDialect(
configuration,
value);
// If geo Dialect exists, modify
// value
// with
// the
// valid GEO dialect
if (geoDialect != null) {
propertyValue
.setNodeValue(geoDialect);
totalModified++;
}
}
else {
// If is geo dialect, mark as
// modified
totalModified++;
}
}
}
}
}
}
}
}
if (totalModified != 0) {
getFileManager().createOrUpdateTextFileIfRequired(
persistenceFile,
XmlUtils.nodeToString(persistenceXmlDocument), false);
// Showing WARNING informing that if you install a different
// persistence, you must to execute this
// command again
LOGGER.log(
Level.INFO,
"WARNING: If you install a new persistence, you must to execute 'jpa geo setup' again to modify Persistence Dialects.");
}
else if (!runTime) {
showRuntimeMessage("");
}
}
else {
throw new RuntimeException(
"ERROR: Error getting persistence.xml file");
}
}
/**
* This method creates types.xml file into src/main/resources/*
*
* TODO: Improve <!ENTITY declaration on DOCTYPE
*
* @param entity
*/
private void addTypesXmlFile(JavaType entity) {
// Getting current entity package
String entityPackage = entity.getPackage()
.getFullyQualifiedPackageName();
String entityPackageFolder = entityPackage.replaceAll("[.]", "/");
// Setting types.xml location using entity package
final String typesXmlPath = getPathResolver().getFocusedIdentifier(
Path.SRC_MAIN_RESOURCES,
String.format("/%s/types.xml", entityPackageFolder));
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = FileUtils.getInputStream(getClass(), "types.xml");
if (!getFileManager().exists(typesXmlPath)) {
outputStream = getFileManager().createFile(typesXmlPath)
.getOutputStream();
}
if (outputStream != null) {
IOUtils.copy(inputStream, outputStream);
}
}
catch (final IOException ioe) {
throw new IllegalStateException(ioe);
}
finally {
IOUtils.closeQuietly(inputStream);
if (outputStream != null) {
IOUtils.closeQuietly(outputStream);
}
}
// Modifying created file
if (outputStream != null) {
PrintWriter writer = new PrintWriter(outputStream);
writer.println(" <!DOCTYPE hibernate-mapping PUBLIC");
writer.println("\"-//Hibernate/Hibernate Mapping DTD 3.0//EN\"");
writer.println("\"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\" [");
writer.println(String.format(
"<!ENTITY types SYSTEM \"classpath://%s/types.xml\">",
entityPackageFolder));
writer.println("]>");
writer.println("");
writer.println(String.format("<hibernate-mapping package=\"%s\">",
entityPackage));
writer.println("<typedef name=\"geometry\" class=\"org.hibernate.spatial.GeometryType\"/>");
writer.println("</hibernate-mapping>");
writer.flush();
writer.close();
}
}
/**
* Method to show which possibilities has the developer to implement new
* dialect
*
* @param value
*/
public void showRuntimeMessage(String value) {
if (StringUtils.isBlank(value)) {
LOGGER.log(
Level.INFO,
"There's not any valid database to apply GEO persistence support. GEO is only supported for POSTGRES, ORACLE, MYSQL and MSSQL databases. You must change it following the next instructions:");
}
else {
LOGGER.log(
Level.INFO,
String.format(
"Cannot replace '%s' on 'src/main/resources/META-INF/persistence.xml' with a valid Hibernate Spatial Dialect. You must change it manually following the next instructions:",
value));
}
LOGGER.log(Level.INFO, "");
LOGGER.log(Level.INFO,
"Replace your current dialect with the correct one: ");
LOGGER.log(Level.INFO, "");
LOGGER.log(
Level.INFO,
"org.hibernate.dialect.H2Dialect ==> org.hibernate.spatial.dialect.h2geodb.GeoDBDialect");
LOGGER.log(
Level.INFO,
"org.hibernate.dialect.PostgreSQLDialect ==> org.hibernate.spatial.dialect.postgis.PostgisDialect");
LOGGER.log(
Level.INFO,
"org.hibernate.dialect.MySQLDialect ==> org.hibernate.spatial.dialect.mysql.MySQLSpatialDialect");
LOGGER.log(
Level.INFO,
"org.hibernate.dialect.MySQL5Dialect ==> org.hibernate.spatial.dialect.mysql.MySQLSpatialDialect");
LOGGER.log(
Level.INFO,
"org.hibernate.dialect.MySQLInnoDBDialect ==> org.hibernate.spatial.dialect.mysql.MySQLSpatialInnoDBDialect");
LOGGER.log(
Level.INFO,
"org.hibernate.dialect.MySQL5InnoDBDialect ==> org.hibernate.spatial.dialect.mysql.MySQLSpatialInnoDBDialect");
LOGGER.log(
Level.INFO,
"org.hibernate.dialect.MySQL5DBDialect ==> org.hibernate.spatial.dialect.mysql.MySQLSpatial56Dialect");
LOGGER.log(
Level.INFO,
"org.hibernate.dialect.MySQLSpatial56Dialect ==> org.hibernate.spatial.dialect.mysql.MySQLSpatial5InnoDBDialect");
LOGGER.log(
Level.INFO,
"org.hibernate.dialect.Oracle10gDialect ==> org.hibernate.spatial.dialect.oracle.OracleSpatial10gDialect");
LOGGER.log(
Level.INFO,
"org.hibernate.dialect.OracleDialect ==> org.hibernate.spatial.dialect.oracle.OracleSpatial10gDialect");
LOGGER.log(
Level.INFO,
"org.hibernate.dialect.SQLServerDialect ==> org.hibernate.spatial.dialect.SQLServer2008Dialect");
}
/**
* PROVIDER CONFIGURATION METHODS
*/
@Override
public String getName() {
return NAME;
}
@Override
public String getDescription() {
return DESCRIPTION;
}
public FileManager getFileManager() {
if (fileManager == null) {
// Get all Services implement FileManager interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(FileManager.class.getName(),
null);
for (ServiceReference<?> ref : references) {
return (FileManager) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load FileManager on HibernateSpatialGeoProvider.");
return null;
}
}
else {
return fileManager;
}
}
public PathResolver getPathResolver() {
if (pathResolver == null) {
// Get all Services implement PathResolver interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(PathResolver.class.getName(),
null);
for (ServiceReference<?> ref : references) {
return (PathResolver) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load PathResolver on HibernateSpatialGeoProvider.");
return null;
}
}
else {
return pathResolver;
}
}
public TypeLocationService getTypeLocationService() {
if (typeLocationService == null) {
// Get all Services implement TypeLocationService interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
TypeLocationService.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (TypeLocationService) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load TypeLocationService on HibernateSpatialGeoProvider.");
return null;
}
}
else {
return typeLocationService;
}
}
public TypeManagementService getTypeManagementService() {
if (typeManagementService == null) {
// Get all Services implement TypeManagementService interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
TypeManagementService.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (TypeManagementService) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load TypeManagementService on HibernateSpatialGeoProvider.");
return null;
}
}
else {
return typeManagementService;
}
}
public ProjectOperations getProjectOperations() {
if (projectOperations == null) {
// Get all Services implement ProjectOperations interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
ProjectOperations.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (ProjectOperations) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load ProjectOperations on HibernateSpatialGeoProvider.");
return null;
}
}
else {
return projectOperations;
}
}
public JpaOperations getJpaOperations() {
if (jpaOperations == null) {
// Get all Services implement ProjectOperations interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(JpaOperations.class.getName(),
null);
for (ServiceReference<?> ref : references) {
return (JpaOperations) this.context.getService(ref);
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load gvnix JpaOperations on HibernateSpatialGeoProvider.");
return null;
}
}
else {
return jpaOperations;
}
}
}