/* * 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.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.gvnix.addon.jpa.addon.audit.JpaAuditUserServiceMetadata; import org.gvnix.addon.jpa.addon.audit.providers.RevisionLogRevisionEntityMetadataBuilder; import org.gvnix.addon.jpa.annotations.audit.GvNIXJpaAuditRevisionEntity; import org.gvnix.support.ItdBuilderHelper; import org.gvnix.support.ItdBuilderHelper.GET_FIELD_EXISTS_ACTION; import org.springframework.roo.classpath.PhysicalTypeCategory; import org.springframework.roo.classpath.PhysicalTypeMetadata; 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.ItdTypeDetailsBuilder; import org.springframework.roo.classpath.details.MethodMetadata; import org.springframework.roo.classpath.details.MethodMetadataBuilder; import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType; import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue; import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder; import org.springframework.roo.classpath.details.annotations.ClassAttributeValue; import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.model.JdkJavaType; import org.springframework.roo.model.JpaJavaType; /** * ITD builder of {@link EnversRevisionLogEntityMetadataBuilder} for the entity * with stores revision identifiers (annotated * {@link GvNIXJpaAuditRevisionEntity}) * * @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 * */ public class EnversRevisionLogEntityMetadataBuilder implements RevisionLogRevisionEntityMetadataBuilder { private static final JavaSymbolName ID_FIELD = new JavaSymbolName("id"); private static final JavaSymbolName TIMESTAMP_FIELD = new JavaSymbolName( "timestamp"); private static final JavaSymbolName USER_FIELD = new JavaSymbolName( "revisonUser"); private static final JavaSymbolName TO_STRING_METHOD = new JavaSymbolName( "toString"); private static final JavaSymbolName REVISION_DATE_TRANSIENT_FIELD = new JavaSymbolName( "revisionDate"); private static final JavaSymbolName EQUALS_METHOD = new JavaSymbolName( "equals"); private static final JavaSymbolName HASH_CODE_METHOD = new JavaSymbolName( "hashCode"); private static final JavaSymbolName NEW_REVISION_METHOD = new JavaSymbolName( "newRevision"); private static final JavaType TO_STRING_BUILDER = new JavaType( "org.apache.commons.lang3.builder.ToStringBuilder"); private static final JavaType HASH_CODE_BUILDER = new JavaType( "org.apache.commons.lang3.builder.HashCodeBuilder"); private static final JavaType REVISION_ENTITY = new JavaType( "org.hibernate.envers.RevisionEntity"); private static final JavaType REVISION_NUMBER = new JavaType( "org.hibernate.envers.RevisionNumber"); private static final JavaType REVISION_TIMESTAMP = new JavaType( "org.hibernate.envers.RevisionTimestamp"); private static final JavaType REVISION_LISTENER = new JavaType( "org.hibernate.envers.RevisionListener"); private final PhysicalTypeMetadata governorPhysicalTypeMetadata; private final ClassOrInterfaceTypeDetails governorTypeDetails; private JavaType revisionListenerType; private FieldMetadata idField; private FieldMetadata timestampField; private FieldMetadata userField; private MethodMetadata idFieldGetter; private MethodMetadata idFieldSetter; private MethodMetadata timestampFieldGetter; private MethodMetadata timestampFieldSetter; private MethodMetadata userNameFieldGetter; private MethodMetadata userNameFieldSetter; private MethodMetadata revsionDateGetter; private ItdTypeDetailsBuilder builder; private Context context; private ItdBuilderHelper helper; public EnversRevisionLogEntityMetadataBuilder( PhysicalTypeMetadata governorPhysicalTypeMetadata) { this.governorPhysicalTypeMetadata = governorPhysicalTypeMetadata; final Object physicalTypeDetails = governorPhysicalTypeMetadata .getMemberHoldingTypeDetails(); if (physicalTypeDetails instanceof ClassOrInterfaceTypeDetails) { // We have reliable physical type details governorTypeDetails = (ClassOrInterfaceTypeDetails) physicalTypeDetails; } else { throw new IllegalArgumentException( "Invalid governorPhysicalTypeMetadata"); } } @Override public void initialize(ItdTypeDetailsBuilder builder, Context context) { if (this.builder != null || idField != null) { throw new IllegalStateException(); } this.builder = builder; this.context = context; this.helper = context.getHelper(); } @Override public void done() { this.builder = null; this.context = null; this.helper = null; } @Override public void fillEntity() { this.revisionListenerType = new JavaType(governorPhysicalTypeMetadata .getType().getFullyQualifiedTypeName() .concat(".RevisionLogEntityListener")); // Add @Entity if needed if (governorTypeDetails.getAnnotation(JpaJavaType.ENTITY) == null) { builder.addAnnotation(new AnnotationMetadataBuilder( JpaJavaType.ENTITY)); } // Add @RevisionEntity if needed if (governorTypeDetails.getAnnotation(JpaJavaType.ENTITY) == null) { Collection<AnnotationAttributeValue<?>> values = new ArrayList<AnnotationAttributeValue<?>>(); values.add(new ClassAttributeValue(new JavaSymbolName("value"), revisionListenerType)); builder.addAnnotation(AnnotationMetadataBuilder.getInstance( REVISION_ENTITY, values)); } builder.addField(getIdField()); builder.addMethod(getIdFieldGetter()); builder.addMethod(getIdFieldSetter()); builder.addField(getTimestampField()); builder.addMethod(getTimestampGetter()); builder.addMethod(getTimestampSetter()); builder.addField(getUserField()); builder.addMethod(getUserGetter()); builder.addMethod(getUserNameSetter()); builder.addMethod(getRevisionDate()); builder.addMethod(getToStringMethod()); builder.addMethod(getEqualsMethod()); builder.addMethod(getHashCodeMethod()); builder.addInnerType(getRevisionListener()); } private MethodMetadata getRevisionDate() { if (revsionDateGetter == null) { JavaSymbolName methodName = helper .getGetterMethodNameForField(REVISION_DATE_TRANSIENT_FIELD); // Define method parameter types List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>( 0); // Check if a method exist in type final MethodMetadata method = helper.methodExists(methodName, parameterTypes); if (method != null) { // If it already exists, just return the method return method; } // Define method annotations (none in this case) List<AnnotationMetadataBuilder> annotations = helper .toAnnotationMetadata(JpaJavaType.TRANSIENT); // Define method throws types (none in this case) List<JavaType> throwsTypes = new ArrayList<JavaType>(); // Define method parameter names (none in this case) List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>( 0); // Create the method body InvocableMemberBodyBuilder body = new InvocableMemberBodyBuilder(); // return new Date(this.timestamp); body.appendFormalLine(String.format("return new %s(this.%s);", helper.getFinalTypeName(JdkJavaType.DATE), TIMESTAMP_FIELD)); // Use the MethodMetadataBuilder for easy creation of MethodMetadata MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder( context.getMetadataId(), Modifier.PUBLIC, methodName, JdkJavaType.DATE, parameterTypes, parameterNames, body); methodBuilder.setAnnotations(annotations); methodBuilder.setThrowsTypes(throwsTypes); revsionDateGetter = methodBuilder.build(); // Build and return a // MethodMetadata } return revsionDateGetter; } private MethodMetadata getToStringMethod() { // Define method parameter types List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>( 0); // Check if a method exist in type final MethodMetadata method = helper.methodExists(TO_STRING_METHOD, parameterTypes); if (method != null) { // If it already exists, just return the method return method; } // Define method annotations (none in this case) List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); // Define method throws types (none in this case) List<JavaType> throwsTypes = new ArrayList<JavaType>(); // Define method parameter names (none in this case) List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(0); // Create the method body InvocableMemberBodyBuilder body = new InvocableMemberBodyBuilder(); // return new ToStringBuilder(this).append("id", // id).append("revisonDate", getRevisionDate()).append("userName", // userName).toString(); body.appendFormalLine(String .format("return new %s(this).append(\"%s\", this.%s).append(\"%s\", this.%s()).append(\"%s\", this.%s).toString();", helper.getFinalTypeName(TO_STRING_BUILDER), ID_FIELD, ID_FIELD, REVISION_DATE_TRANSIENT_FIELD, getRevisionDate().getMethodName(), USER_FIELD, USER_FIELD)); // Use the MethodMetadataBuilder for easy creation of MethodMetadata MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder( context.getMetadataId(), Modifier.PUBLIC, TO_STRING_METHOD, JavaType.STRING, parameterTypes, parameterNames, body); methodBuilder.setAnnotations(annotations); methodBuilder.setThrowsTypes(throwsTypes); return methodBuilder.build(); // Build and return a MethodMetadata } private ClassOrInterfaceTypeDetails getRevisionListener() { // Check class exists ClassOrInterfaceTypeDetails innerClass = governorTypeDetails .getDeclaredInnerType(revisionListenerType); if (innerClass != null) { // If class exists (already push-in) we can do nothing return innerClass; } // Create inner class ClassOrInterfaceTypeDetailsBuilder classBuilder = new ClassOrInterfaceTypeDetailsBuilder( context.getMetadataId(), Modifier.PUBLIC + Modifier.STATIC, revisionListenerType, PhysicalTypeCategory.CLASS); classBuilder.addImplementsType(REVISION_LISTENER); classBuilder.addMethod(getNewRevisionMethod()); return classBuilder.build(); } private MethodMetadata getNewRevisionMethod() { // Define method parameter types List<AnnotatedJavaType> parameterTypes = helper .toAnnotedJavaType(JavaType.OBJECT); // Check if a method exist in type final MethodMetadata method = helper.methodExists(TO_STRING_METHOD, parameterTypes); if (method != null) { // If it already exists, just return the method return method; } // Define method annotations (none in this case) List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>( 0); // Define method throws types (none in this case) List<JavaType> throwsTypes = new ArrayList<JavaType>(); // Define method parameter names (none in this case) List<JavaSymbolName> parameterNames = helper .toSymbolName("revisionEntity"); // Create the method body InvocableMemberBodyBuilder body = new InvocableMemberBodyBuilder(); buildNewRevisionMethodBody(body); // Use the MethodMetadataBuilder for easy creation of MethodMetadata MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder( context.getMetadataId(), Modifier.PUBLIC, NEW_REVISION_METHOD, JavaType.VOID_PRIMITIVE, parameterTypes, parameterNames, body); methodBuilder.setAnnotations(annotations); methodBuilder.setThrowsTypes(throwsTypes); return methodBuilder.build(); // Build and return a MethodMetadata } private void buildNewRevisionMethodBody(InvocableMemberBodyBuilder body) { // HistoryRevisionEntity revision = (HistoryRevisionEntity) // revisionEntity; body.appendFormalLine(String.format( "%s revision = (%s) revisionEntity;", context.getEntity() .getSimpleTypeName(), context.getEntity() .getSimpleTypeName())); // revison.setUserNsame(AuditUserService.getUser()); body.appendFormalLine(String.format("revision.%s(%s.%s());", helper.getSetterMethodNameForField(USER_FIELD), helper.getFinalTypeName(context.getUserService()), JpaAuditUserServiceMetadata.GET_USER_METHOD)); } private MethodMetadata getHashCodeMethod() { // Define method parameter types List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>( 0); // Check if a method exist in type final MethodMetadata method = helper.methodExists(TO_STRING_METHOD, parameterTypes); if (method != null) { // If it already exists, just return the method return method; } // Define method annotations (none in this case) List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); // Define method throws types (none in this case) List<JavaType> throwsTypes = new ArrayList<JavaType>(); // Define method parameter names (none in this case) List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(0); // Create the method body InvocableMemberBodyBuilder body = new InvocableMemberBodyBuilder(); body.appendFormalLine(String.format( "return new %s(17, 31).append(%s).append(%s).toHashCode();", helper.getFinalTypeName(HASH_CODE_BUILDER), ID_FIELD, TIMESTAMP_FIELD)); // Use the MethodMetadataBuilder for easy creation of MethodMetadata MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder( context.getMetadataId(), Modifier.PUBLIC, HASH_CODE_METHOD, JavaType.INT_PRIMITIVE, parameterTypes, parameterNames, body); methodBuilder.setAnnotations(annotations); methodBuilder.setThrowsTypes(throwsTypes); return methodBuilder.build(); // Build and return a MethodMetadata } private MethodMetadata getEqualsMethod() { // Define method parameter types List<AnnotatedJavaType> parameterTypes = helper .toAnnotedJavaType(JavaType.OBJECT); // Check if a method exist in type final MethodMetadata method = helper.methodExists(TO_STRING_METHOD, parameterTypes); if (method != null) { // If it already exists, just return the method return method; } // Define method annotations (none in this case) List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>( 0); // Define method throws types (none in this case) List<JavaType> throwsTypes = new ArrayList<JavaType>(); // Define method parameter names (none in this case) List<JavaSymbolName> parameterNames = helper.toSymbolName("object"); // Create the method body InvocableMemberBodyBuilder body = new InvocableMemberBodyBuilder(); buildEqualsMethodBody(body); // Use the MethodMetadataBuilder for easy creation of MethodMetadata MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder( context.getMetadataId(), Modifier.PUBLIC, EQUALS_METHOD, JavaType.BOOLEAN_PRIMITIVE, parameterTypes, parameterNames, body); methodBuilder.setAnnotations(annotations); methodBuilder.setThrowsTypes(throwsTypes); return methodBuilder.build(); // Build and return a MethodMetadata } private void buildEqualsMethodBody(InvocableMemberBodyBuilder body) { // if (this == object) { body.appendFormalLine("if (this == object) {"); body.indent(); // return true; body.appendFormalLine("return true;"); // } body.indentRemove(); body.appendFormalLine("}"); // if (!(object instanceof RevisionEntity)) { body.appendFormalLine(String.format("if (!(object instanceof %s)) {", context.getEntity())); body.indent(); // return false; body.appendFormalLine("return false;"); // } body.indentRemove(); body.appendFormalLine("}"); // HistoryRevisionEntity that = (HistoryRevisionEntity) object; body.appendFormalLine(String.format("%s that = (%s) object;", context.getEntity(), context.getEntity())); // if (id != that.id) { body.appendFormalLine(String.format("if (this.%s == that.%s) {", ID_FIELD, ID_FIELD)); body.indent(); // return false; body.appendFormalLine("return false;"); // } body.indentRemove(); body.appendFormalLine("}"); // if (timestamp != that.timestamp) { body.appendFormalLine(String.format("if (this.%s == that.%s) {", TIMESTAMP_FIELD, TIMESTAMP_FIELD)); body.indent(); // return false; body.appendFormalLine("return false;"); // } body.indentRemove(); body.appendFormalLine("}"); // return true; body.appendFormalLine("return true;"); } private MethodMetadata getUserGetter() { if (userNameFieldGetter == null) { userNameFieldGetter = helper.getGetterMethod(getUserField(), null); } return userNameFieldGetter; } private MethodMetadata getUserNameSetter() { if (userNameFieldSetter == null) { userNameFieldSetter = helper.getSetterMethod(getUserField(), null); } return userNameFieldSetter; } private MethodMetadata getTimestampGetter() { if (timestampFieldGetter == null) { timestampFieldGetter = helper.getGetterMethod(getTimestampField(), null); } return timestampFieldGetter; } private MethodMetadata getTimestampSetter() { if (timestampFieldSetter == null) { timestampFieldSetter = helper.getSetterMethod(getTimestampField(), null); } return timestampFieldSetter; } private MethodMetadata getIdFieldGetter() { if (idFieldGetter == null) { idFieldGetter = helper.getGetterMethod(getIdField(), null); } return idFieldGetter; } private MethodMetadata getIdFieldSetter() { if (idFieldSetter == null) { idFieldSetter = helper.getSetterMethod(getIdField(), null); } return idFieldSetter; } private FieldMetadata getUserField() { if (userField == null) { List<AnnotationMetadataBuilder> annotations = null; if (context.getUserTypeIsEntity()) { annotations = new ArrayList<AnnotationMetadataBuilder>(1); annotations.add(new AnnotationMetadataBuilder( AnnotationMetadataBuilder.JPA_MANY_TO_ONE_ANNOTATION)); } userField = helper .getField( USER_FIELD, Modifier.PRIVATE, context.getUserType(), annotations, GET_FIELD_EXISTS_ACTION.RETURN_EXISTING_IF_ANNOTATION_MATCH); } return userField; } private FieldMetadata getTimestampField() { if (timestampField == null) { List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); annotations.add(new AnnotationMetadataBuilder(REVISION_TIMESTAMP)); timestampField = helper .getField( TIMESTAMP_FIELD, Modifier.PRIVATE, JavaType.LONG_OBJECT, annotations, GET_FIELD_EXISTS_ACTION.RETURN_EXISTING_IF_ANNOTATION_MATCH); } return timestampField; } /** * @param helper * @return */ private FieldMetadata getIdField() { if (idField == null) { List<AnnotationMetadataBuilder> annotations = helper .toAnnotationMetadata(JpaJavaType.ID, JpaJavaType.GENERATED_VALUE, REVISION_NUMBER); idField = helper .getField( ID_FIELD, Modifier.PRIVATE, JavaType.LONG_OBJECT, annotations, GET_FIELD_EXISTS_ACTION.RETURN_EXISTING_IF_ANNOTATION_MATCH); } return idField; } public JavaSymbolName getRevisionIdGetterName() { return getIdFieldGetter().getMethodName(); } public JavaSymbolName getRevisionDateGetterName() { return getRevisionDate().getMethodName(); } public JavaSymbolName getRevisonUserGetterName() { return getUserGetter().getMethodName(); } }