/*
* 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.occ.roo.addon.addon;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.gvnix.occ.roo.addon.annotations.GvNIXEntityOCCChecksum;
import org.gvnix.support.dependenciesmanager.DependenciesVersionManager;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.jpa.addon.JpaOperations;
import org.springframework.roo.addon.jpa.addon.activerecord.JpaActiveRecordMetadata;
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.MemberFindingUtils;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
import org.springframework.roo.classpath.persistence.PersistenceMemberLocator;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.project.Property;
import org.springframework.roo.project.Repository;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Element;
/**
* gvNIX OCCChecksum operation service implementation
*
* @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 OCCChecksumOperationsImpl implements OCCChecksumOperations {
private static final Logger logger = HandlerUtils
.getLogger(OCCChecksumOperationsImpl.class);
@Reference
private MetadataService metadataService;
@Reference
private ProjectOperations projectOperations;
@Reference
private JpaOperations entityOperations;
@Reference
private TypeLocationService typeLocationService;
@Reference
private PersistenceMemberLocator persistenceMemberLocator;
@Reference
private TypeManagementService typeManagementService;
protected void activate(ComponentContext context) {
// TODO:
}
protected void deactivate(ComponentContext context) {
// TODO:
}
/*
* (non-Javadoc)
*
* @see
* org.gvnix.occ.roo.addon.OCCChecksumOperations#isOCCChecksumAvailable()
*/
public boolean isOCCChecksumAvailable() {
return entityOperations.isPersistentClassAvailable();
}
/*
* (non-Javadoc)
*
* @see org.gvnix.occ.roo.addon.OCCChecksumOperations#addOccToEntity(org.
* springframework.roo.model.JavaType, java.lang.String, java.lang.String)
*/
public void addOccToEntity(JavaType entity, String fieldName,
String digestMethod) {
addGvNIXAnnotationsDependecy();
this.doAddOccToEntity(entity, fieldName, digestMethod, true);
}
/**
* Annotates given entity of its parent with GvNIXEntityOCCChecksum. If
* <code>annotatedParent</code> is set false, if @javax.persistence.Version
* field is declared in a parent class of the given one the method returns
* without annotate any class. When is set true, the method will annotate
* the class where @javax.persistence.Version field is declared or the given
* one if field is not declared yet.
*
* @param entity
* @param fieldName
* @param digestMethod
* @param annotateParent
*/
private void doAddOccToEntity(JavaType entity, String fieldName,
String digestMethod, boolean annotateParent) {
// Check if given entity has a @Version field declared
// Maybe the given entity extends of a class declaring the @Version
// field, in this case we must to annotate parent class
String entityMetadataKey = JpaActiveRecordMetadata.createIdentifier(
entity, LogicalPath.getInstance(Path.SRC_MAIN_JAVA, ""));
JpaActiveRecordMetadata entityMetadata = (JpaActiveRecordMetadata) metadataService
.get(entityMetadataKey, true);
if (entityMetadata == null) {
// entity JavaType is not a valid Entity. Nothing to do
return;
}
// Check if target entity has got a @Version field and it is in the same
// class or in a parent one. If @Version field is declared in a parent
// class, this will be the target class for annotate with
// @GvNIXEntityOCCChecksum
FieldMetadata versionField = persistenceMemberLocator
.getVersionField(entity);
if (versionField != null) {
String declaredByType = versionField.getDeclaredByMetadataId()
.substring(
versionField.getDeclaredByMetadataId().lastIndexOf(
"?") + 1);
if (!entityMetadataKey.endsWith(declaredByType)) {
// Annotate parent instead of given class just if it's requested
if (annotateParent) {
// @Version field is declared in a parent class, so annotate
// the parent instead of given entity
JavaType entityToAnnotate = new JavaType(declaredByType);
logger.info("Entity ".concat(entityToAnnotate
.getFullyQualifiedTypeName()
.concat(" will be annotated instead of: "
.concat(entity.getFullyQualifiedTypeName()))));
// Exchange target class
entity = entityToAnnotate;
}
else {
// We could annotate parent class with declared @Version
// field, but invoker doesn't want this.
return;
}
}
}
// Load class details. If class not found an exception will be raised.
ClassOrInterfaceTypeDetails tmpDetails = typeLocationService
.getTypeDetails(entity);
// Checks if it's mutable
Validate.isInstanceOf(ClassOrInterfaceTypeDetails.class, tmpDetails,
"Can't modify " + tmpDetails.getName());
ClassOrInterfaceTypeDetailsBuilder mutableTypeDetailsBuilder = new ClassOrInterfaceTypeDetailsBuilder(
(ClassOrInterfaceTypeDetails) tmpDetails);
List<? extends AnnotationMetadata> entityAnnotations = mutableTypeDetailsBuilder
.build().getAnnotations();
// Looks for @GvNIXEntityOCCChecksumAnnotation and @RooJpaActiveRecord
AnnotationMetadata occAnnotation = MemberFindingUtils
.getAnnotationOfType(entityAnnotations, new JavaType(
GvNIXEntityOCCChecksum.class.getName()));
AnnotationMetadata rooEntityAnnotation = MemberFindingUtils
.getAnnotationOfType(entityAnnotations, new JavaType(
RooJpaActiveRecord.class.getName()));
if (rooEntityAnnotation != null) {
if (occAnnotation != null) {
// Already set annotation. Nothing to do
logger.info("Entity ".concat(entity
.getFullyQualifiedTypeName()
.concat(" is already annotated with "
.concat(GvNIXEntityOCCChecksum.class.getName()))));
return;
}
// Prepares GvNIXEntityOCCChecksum attributes
List<AnnotationAttributeValue<?>> occAnnotationAttributes = new ArrayList<AnnotationAttributeValue<?>>(
2);
if (fieldName != null) {
occAnnotationAttributes.add(new StringAttributeValue(
new JavaSymbolName("fieldName"), fieldName));
}
if (digestMethod != null) {
occAnnotationAttributes.add(new StringAttributeValue(
new JavaSymbolName("digestMethod"), digestMethod));
}
// Creates GvNIXEntityOCCChecksum
occAnnotation = new AnnotationMetadataBuilder(new JavaType(
GvNIXEntityOCCChecksum.class.getName()),
occAnnotationAttributes).build();
// Adds GvNIXEntityOCCChecksum to the entity
mutableTypeDetailsBuilder.addAnnotation(occAnnotation);
typeManagementService
.createOrUpdateTypeOnDisk(mutableTypeDetailsBuilder.build());
}
}
/*
* (non-Javadoc)
*
* @see
* org.gvnix.occ.roo.addon.OCCChecksumOperations#addOccAll(java.lang.String,
* java.lang.String)
*/
public void addOccAll(String fieldName, String digestMethod) {
addGvNIXAnnotationsDependecy();
// Look for classes annotated with @RooJpaActiveRecord
for (JavaType type : typeLocationService
.findTypesWithAnnotation(new JavaType(RooJpaActiveRecord.class
.getName()))) {
doAddOccToEntity(type, fieldName, digestMethod, false);
}
}
/*
* (non-Javadoc)
*
* @see
* org.gvnix.occ.roo.addon.OCCChecksumOperations#addGvNIXAnnotationsDependecy
* ()
*/
public void addGvNIXAnnotationsDependecy() {
// Install the add-on Google code repository and dependency needed to
// get the annotations
Element conf = XmlUtils.getConfiguration(this.getClass());
// Install repositories
List<Element> repos = XmlUtils.findElements(
"/configuration/gvnix/repositories/repository", conf);
for (Element repo : repos) {
projectOperations.addRepository(projectOperations
.getFocusedModuleName(), new Repository(repo));
}
// Install properties
List<Element> properties = XmlUtils.findElements(
"/configuration/gvnix/properties/*", conf);
for (Element property : properties) {
projectOperations.addProperty(projectOperations
.getFocusedModuleName(), new Property(property));
}
// Install dependencies
List<Element> depens = XmlUtils.findElements(
"/configuration/gvnix/dependencies/dependency", conf);
DependenciesVersionManager.manageDependencyVersion(metadataService,
projectOperations, depens);
}
}