/*
* 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.audit.providers.envers;
import java.util.Set;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.gvnix.addon.jpa.addon.audit.JpaAuditOperationsSPI;
import org.gvnix.addon.jpa.addon.audit.JpaAuditRevisionEntityMetadata;
import org.gvnix.addon.jpa.addon.audit.providers.RevisionLogMetadataBuilder;
import org.gvnix.addon.jpa.addon.audit.providers.RevisionLogProvider;
import org.gvnix.addon.jpa.addon.audit.providers.RevisionLogRevisionEntityMetadataBuilder;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.process.manager.MutableFile;
import org.springframework.roo.project.Dependency;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectMetadata;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Revision log provider based on Hibernate envers module
* <p/>
* This provider is only available on project which uses Hibernate as JPA
* implementation.
* <p/>
* On {@link #setup()} this module configures and install Hibernate envers an
* generates a custom entity for revision log. This entity differs on standard
* H-enver one in uses a long id and stores user name of every revision (if
* Spring Security is available and there is a user logged).
*
* @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>
* @since 1.3.0
*
*/
@Component
@Service
public class EnversRevisionLogProvider implements RevisionLogProvider {
private static final String STORE_DATA_AT_DELETE_PROP_NAME = "org.hibernate.envers.store_data_at_delete";
@SuppressWarnings("unused")
private static final Logger LOGGER = HandlerUtils
.getLogger(EnversRevisionLogProvider.class);
private static final Dependency HIBERNATE_DEPENDENCY = new Dependency(
"org.hibernate", "hibernate-entitymanager", null);
private static final Dependency HIBERNATE_ENVERS_DEPENDENCY = new Dependency(
"org.hibernate", "hibernate-envers", null);
private static final String HBER_PERS_PROV_CLS = "org.hibernate.jpa.HibernatePersistenceProvider";
private static String PROVIDER_NAME = "H-ENVERS";
private static String PROVIDER_DESCRIPTION = "Revision-log provider base on Hibernate envers module";
private static final String PERSISTENCE_XML_LOCATION = "META-INF/persistence.xml";
@Reference
private ProjectOperations projectOperations;
@Reference
private FileManager fileManager;
@Reference
private MetadataService metadataService;
/**
* {@inheritDoc} Checks Hibernate dependency to show it as available
*/
@Override
public boolean isAvailable() {
ProjectMetadata projectMetadata = getProjectMetadata();
// XXX check persistence.xml ???
return !getHibernateDependency(projectMetadata).isEmpty();
}
/**
* @return
*/
private ProjectMetadata getProjectMetadata() {
return projectOperations.getFocusedProjectMetadata();
}
/**
* Gets Hibernate dependencies declared on current project (excluding
* version)
*
* @param metadata of current project
* @return
*/
private Set<Dependency> getHibernateDependency(ProjectMetadata metadata) {
return metadata.getPom().getDependenciesExcludingVersion(
HIBERNATE_DEPENDENCY);
}
/**
* Checks if Hibernate is declared as dependency on current project
* (excluding version)
*
* @param metadata of current project
* @return
*/
private boolean isHibernateEnversInstalled(ProjectMetadata metadata) {
return metadata.getPom().hasDependencyExcludingVersion(
HIBERNATE_ENVERS_DEPENDENCY);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isActive() {
ProjectMetadata projectMetadata = getProjectMetadata();
if (!isHibernateEnversInstalled(projectMetadata)) {
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return PROVIDER_NAME;
}
/**
* {@inheritDoc}
*/
@Override
public String getDescription() {
return PROVIDER_DESCRIPTION;
}
/**
* {@inheritDoc}
*/
@Override
public boolean getDefaultValueOfRevisionLogAttribute() {
return true;
}
/**
* {@inheritDoc}
* <p/>
* This do this operations:
* <ul>
* <li>Install Hibernate evenrs depencency (uses the same hibernate-core
* version)</li>
* </ul>
*
*/
@Override
public void setup(JpaAuditOperationsSPI operations) {
// Installs envers dependency on pom.xml
installEnversDependency();
// Configure presistence.xml
configurePersistenceXML();
// Install revision entity class on project
operations.installRevisonEntity(null);
// Refresh all audited entity metadata
operations.refreshAuditedEntities();
}
/**
* Configure presistence.xml project file
*/
private void configurePersistenceXML() {
PathResolver pathResolver = projectOperations.getPathResolver();
String persistencePath = pathResolver.getIdentifier(
LogicalPath.getInstance(Path.SRC_MAIN_RESOURCES, ""),
PERSISTENCE_XML_LOCATION);
if (!fileManager.exists(persistencePath)) {
throw new IllegalStateException(
"persistence.xml not found: ".concat(persistencePath));
}
// Load xml file
MutableFile persistenceMutableFile = null;
Document persistenceXml;
try {
// Get orm.xml content and parse it
persistenceMutableFile = fileManager.updateFile(persistencePath);
persistenceXml = XmlUtils.getDocumentBuilder().parse(
persistenceMutableFile.getInputStream());
}
catch (Exception e) {
throw new IllegalStateException("Error loading file '".concat(
persistencePath).concat("'"), e);
}
// Search for provider to assure is hibernate
checkPersistenceProvider(persistencePath, persistenceXml);
// Search for properties element
Element propertiesElement = XmlUtils.findFirstElement(
"/persistence/persistence-unit/properties", persistenceXml);
// Check if 'org.hibernate.envers.store_data_at_delete' exists
Element storeDataAtDeletePropElement = XmlUtils.findFirstElement(
"/persistence/persistence-unit/properties/property[@name='"
.concat(STORE_DATA_AT_DELETE_PROP_NAME).concat("']"),
persistenceXml);
if (storeDataAtDeletePropElement != null) {
// Property already exists: nothing to do here
return;
}
// Create property element and appent to properties tag
storeDataAtDeletePropElement = persistenceXml.createElement("property");
storeDataAtDeletePropElement.setAttribute("name",
STORE_DATA_AT_DELETE_PROP_NAME);
storeDataAtDeletePropElement.setAttribute("value", "true");
propertiesElement.appendChild(storeDataAtDeletePropElement);
// write persistence.xml
XmlUtils.writeXml(persistenceMutableFile.getOutputStream(),
persistenceXml);
}
/**
* Check persistence.xml registered provider to assure it's hibernate
*
* @param persistencePath
* @param persistenceXml
*/
private void checkPersistenceProvider(String persistencePath,
Document persistenceXml) {
Element providerElement = XmlUtils.findFirstElement(
"/persistence/persistence-unit/provider", persistenceXml);
if (providerElement == null) {
throw new IllegalStateException("Error loading file '".concat(
persistencePath).concat(
"': /persistence/persistence-unit/provider tag not found"));
}
String provider = providerElement.getTextContent();
if (StringUtils.isBlank(provider)) {
throw new IllegalStateException("Error loading file '".concat(
persistencePath).concat(
"': /persistence/persistence-unit/provider tag is empty"));
}
provider = provider.trim();
if (!HBER_PERS_PROV_CLS.equals(provider)) {
throw new IllegalStateException(
String.format(
"Error loading file '%s': unexpected /persistence/persistence-unit/provider (expected: '%s' found: '%s')",
persistencePath, HBER_PERS_PROV_CLS, provider));
}
}
/**
* Install envers dependency on project
*/
private void installEnversDependency() {
// Locate hiberante dependency
ProjectMetadata projectMetadata = getProjectMetadata();
Set<Dependency> hibernateDependencies = getHibernateDependency(projectMetadata);
if (hibernateDependencies.isEmpty()) {
throw new IllegalStateException("No Hibernate dependency found");
}
if (hibernateDependencies.size() > 1) {
throw new IllegalStateException(
"Error on Hibernate dependency: > 1 found for "
.concat(HIBERNATE_DEPENDENCY.getSimpleDescription()));
}
// Install hibernate envers dependency
Dependency enversDependency = new Dependency(
HIBERNATE_ENVERS_DEPENDENCY.getGroupId(),
HIBERNATE_ENVERS_DEPENDENCY.getArtifactId(), null);
projectOperations.addDependency(
projectOperations.getFocusedModuleName(), enversDependency);
}
/**
* {@inheritDoc}
*/
@Override
public RevisionLogMetadataBuilder getMetadataBuilder(
JpaAuditOperationsSPI operations,
PhysicalTypeMetadata governorPhysicalTypeMetadata) {
String revisionEntityMetadatId = JpaAuditRevisionEntityMetadata
.createIdentifier(operations.getRevisionEntityJavaType(),
LogicalPath.getInstance(Path.SRC_MAIN_JAVA, ""));
JpaAuditRevisionEntityMetadata revisionEntityMetada = (JpaAuditRevisionEntityMetadata) metadataService
.get(revisionEntityMetadatId);
if (revisionEntityMetada == null) {
throw new IllegalStateException(
"Can't get RevisionEntityLog metadata: "
.concat(revisionEntityMetadatId));
}
return new EnversRevisionLogMetadataBuilder(
governorPhysicalTypeMetadata, revisionEntityMetada);
}
/**
* {@inheritDoc}
*/
@Override
public RevisionLogRevisionEntityMetadataBuilder getRevisonEntityMetadataBuilder(
JpaAuditOperationsSPI operations,
PhysicalTypeMetadata governorPhysicalTypeMetadata) {
return new EnversRevisionLogEntityMetadataBuilder(
governorPhysicalTypeMetadata);
}
}