package org.springframework.roo.addon.layers.repository.jpa.addon;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.roo.addon.jpa.addon.entity.JpaEntityMetadata;
import org.springframework.roo.addon.jpa.addon.entity.JpaEntityMetadata.RelationInfo;
import org.springframework.roo.addon.jpa.annotations.entity.JpaRelationType;
import org.springframework.roo.addon.layers.repository.jpa.addon.finder.parser.FinderMethod;
import org.springframework.roo.addon.layers.repository.jpa.addon.finder.parser.FinderParameter;
import org.springframework.roo.addon.layers.repository.jpa.addon.finder.parser.PartTree;
import org.springframework.roo.addon.layers.repository.jpa.annotations.RooJpaRepository;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.FieldMetadata;
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.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.itd.AbstractItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.operations.Cardinality;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.SpringJavaType;
import org.springframework.roo.model.SpringletsJavaType;
import org.springframework.roo.project.LogicalPath;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Metadata for {@link RooJpaRepository}.
*
* @author Stefan Schmidt
* @author Andrew Swan
* @since 1.2.0
*/
public class RepositoryJpaMetadata extends AbstractItdTypeDetailsProvidingMetadataItem {
private static final JavaSymbolName SAVE_METHOD_NAME = new JavaSymbolName("save");
private static final JavaSymbolName FIND_ONE_METHOD_NAME = new JavaSymbolName("findOne");
private static final JavaSymbolName FIND_ALL_ITERATOR_METHOD_NAME = new JavaSymbolName("findAll");
private static final String PROVIDES_TYPE_STRING = RepositoryJpaMetadata.class.getName();
private static final String PROVIDES_TYPE = MetadataIdentificationUtils
.create(PROVIDES_TYPE_STRING);
private final Map<FieldMetadata, MethodMetadata> countMethodByReferencedFields;
private final FieldMetadata compositionField;
private final RelationInfo compositionInfo;
private final MethodMetadata compositionCountMethod;
private final JavaType customRepository;
private final JavaType entity;
private final JavaType defaultReturnType;
private final List<FinderMethod> findersDeclared;
private final List<MethodMetadata> findersGenerated;
private final List<MethodMetadata> countMethods;
private final List<Pair<FinderMethod, PartTree>> findersToAddInCustom;
private final List<String> declaredFinderNames;
private Map<JavaSymbolName, MethodMetadata> finderMethodsAndCounts;
public static String createIdentifier(final JavaType javaType, final LogicalPath path) {
return PhysicalTypeIdentifierNamingUtils.createIdentifier(PROVIDES_TYPE_STRING, javaType, path);
}
public static String createIdentifier(ClassOrInterfaceTypeDetails repositoryDetails) {
final LogicalPath repositoryLogicalPath =
PhysicalTypeIdentifier.getPath(repositoryDetails.getDeclaredByMetadataId());
return createIdentifier(repositoryDetails.getType(), repositoryLogicalPath);
}
public static JavaType getJavaType(final String metadataIdentificationString) {
return PhysicalTypeIdentifierNamingUtils.getJavaType(PROVIDES_TYPE_STRING,
metadataIdentificationString);
}
public static String getMetadataIdentiferType() {
return PROVIDES_TYPE;
}
public static LogicalPath getPath(final String metadataIdentificationString) {
return PhysicalTypeIdentifierNamingUtils.getPath(PROVIDES_TYPE_STRING,
metadataIdentificationString);
}
public static boolean isValid(final String metadataIdentificationString) {
return PhysicalTypeIdentifierNamingUtils.isValid(PROVIDES_TYPE_STRING,
metadataIdentificationString);
}
/**
* Constructor
*
* @param identifier the identifier for this item of metadata (required)
* @param aspectName the Java type of the ITD (required)
* @param governorPhysicalTypeMetadata the governor, which is expected to
* contain a {@link ClassOrInterfaceTypeDetails} (required)
* @param annotationValues (required)
* @param entityMetadata boolean
* @param readOnlyRepository JavaType
* @param customRepository
* @param relationsAsChild
* @param findersToAdd
* @param findersToAddInCustom
* @param declaredFinderNames
* @param referenceFields Map<JavaType, JavaType> that contains referenceField type
* and its identifier type
*/
public RepositoryJpaMetadata(final String identifier, final JavaType aspectName,
final PhysicalTypeMetadata governorPhysicalTypeMetadata,
final RepositoryJpaAnnotationValues annotationValues, final JpaEntityMetadata entityMetadata,
final JavaType readOnlyRepository, final JavaType customRepository,
final JavaType defaultReturnType, List<Pair<FieldMetadata, RelationInfo>> relationsAsChild,
List<FinderMethod> findersToAdd, List<Pair<FinderMethod, PartTree>> findersToAddInCustom,
List<String> declaredFinderNames) {
super(identifier, aspectName, governorPhysicalTypeMetadata);
Validate.notNull(annotationValues, "Annotation values required");
final FieldMetadata identifierField = entityMetadata.getCurrentIndentifierField();
final JavaType identifierType = identifierField.getFieldType();
this.entity = annotationValues.getEntity();
this.defaultReturnType = defaultReturnType;
this.findersToAddInCustom = Collections.unmodifiableList(findersToAddInCustom);
this.customRepository = customRepository;
this.countMethodByReferencedFields = new HashMap<FieldMetadata, MethodMetadata>();
this.declaredFinderNames = Collections.unmodifiableList(declaredFinderNames);
this.finderMethodsAndCounts = new HashMap<JavaSymbolName, MethodMetadata>();
// Iterate over fields which are child fields
boolean composition = false;
MethodMetadata countMethod = null;
MethodMetadata compositionCountMethod = null;
Pair<FieldMetadata, RelationInfo> compositionFieldInfo = null;
for (Pair<FieldMetadata, RelationInfo> fieldInfo : relationsAsChild) {
countMethod = getCountMethodByField(fieldInfo.getLeft(), fieldInfo.getRight());
ensureGovernorHasMethod(new MethodMetadataBuilder(countMethod));
countMethodByReferencedFields.put(fieldInfo.getLeft(), countMethod);
// Check composition relation
if (fieldInfo.getRight().type == JpaRelationType.COMPOSITION) {
// check for more than one part of compositions as child part
Validate.isTrue(!composition,
"Entity %s has defined more than one relations as child part whit type composition.",
aspectName);
composition = true;
ensureGovernorHasMethod(new MethodMetadataBuilder(getFindOneMethod(entity, identifierField)));
ensureGovernorHasMethod(new MethodMetadataBuilder(getFindAllIteratorMethod(entity,
identifierField)));
ensureGovernorHasMethod(new MethodMetadataBuilder(getSaveMethod(entity)));
compositionCountMethod = countMethod;
compositionFieldInfo = fieldInfo;
}
}
if (composition) {
this.compositionField = compositionFieldInfo.getLeft();
this.compositionInfo = compositionFieldInfo.getRight();
this.compositionCountMethod = compositionCountMethod;
} else {
this.compositionField = null;
this.compositionInfo = null;
this.compositionCountMethod = null;
}
// Add Repository interface
JavaType interfaceType = null;
if (entityMetadata.isReadOnly()) {
// If readOnly, extends ReadOnlyRepository
interfaceType = readOnlyRepository;
} else {
// Extends JpaRepository
interfaceType = SpringletsJavaType.SPRINGLETS_DETACHABLE_JPA_REPOSITORY;
}
ensureGovernorExtends(JavaType.wrapperOf(interfaceType, annotationValues.getEntity(),
identifierType));
// If has some RepositoryCustom associated, add extends
ensureGovernorExtends(customRepository);
// All repositories are generated with @Transactional(readOnly = true)
AnnotationMetadataBuilder transactionalAnnotation =
new AnnotationMetadataBuilder(SpringJavaType.TRANSACTIONAL);
transactionalAnnotation.addBooleanAttribute("readOnly", true);
ensureGovernorIsAnnotated(transactionalAnnotation);
// Prepare list of ALL finders and count declared
List<MethodMetadata> findersTmp = new ArrayList<MethodMetadata>();
List<MethodMetadata> countMethodsTmp = new ArrayList<MethodMetadata>();
if (compositionCountMethod != null) {
countMethodsTmp.add(compositionCountMethod);
}
for (MethodMetadata declared : countMethodByReferencedFields.values()) {
countMethodsTmp.add(declared);
}
// Including finders and count methods
for (FinderMethod finderMethod : findersToAdd) {
MethodMetadata method = getFinderMethod(finderMethod).build();
if (!isAlreadyDeclaredMethod(method, findersTmp)) {
ensureGovernorHasMethod(new MethodMetadataBuilder(method));
}
findersTmp.add(method);
// Generate a count method for each finder if they aren't count methods
if (!StringUtils.startsWith(finderMethod.getMethodName().getSymbolName(), "count")) {
countMethod = getCountMethod(finderMethod).build();
if (!isAlreadyDeclaredMethod(countMethod, countMethodsTmp)) {
ensureGovernorHasMethod(new MethodMetadataBuilder(countMethod));
}
countMethodsTmp.add(countMethod);
}
finderMethodsAndCounts.put(method.getMethodName(), countMethod);
}
this.findersDeclared = Collections.unmodifiableList(new ArrayList<FinderMethod>(findersToAdd));
this.findersGenerated = Collections.unmodifiableList(findersTmp);
this.countMethods = Collections.unmodifiableList(countMethodsTmp);
// Build the ITD
itdTypeDetails = builder.build();
}
private boolean isAlreadyDeclaredMethod(MethodMetadata method,
List<MethodMetadata> alreadyDeclaredMethods) {
for (MethodMetadata declared : alreadyDeclaredMethods) {
if (method.matchSignature(declared)) {
return true;
}
}
return false;
}
private MethodMetadata getFindOneMethod(JavaType entity, FieldMetadata identifierFieldMetadata) {
// Define method parameter type and name
List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
parameterTypes
.add(AnnotatedJavaType.convertFromJavaType(identifierFieldMetadata.getFieldType()));
parameterNames.add(identifierFieldMetadata.getFieldName());
MethodMetadata existingMethod =
getGovernorMethod(FIND_ONE_METHOD_NAME,
AnnotatedJavaType.convertFromAnnotatedJavaTypes(parameterTypes));
if (existingMethod != null) {
return existingMethod;
}
// Use the MethodMetadataBuilder for easy creation of MethodMetadata
return new MethodMetadataBuilder(getId(), Modifier.PUBLIC + Modifier.ABSTRACT,
FIND_ONE_METHOD_NAME, entity, parameterTypes, parameterNames, null).build();
}
private MethodMetadata getSaveMethod(JavaType entity) {
// Define method parameter type and name
List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
parameterTypes.add(AnnotatedJavaType.convertFromJavaType(entity));
parameterNames.add(new JavaSymbolName(StringUtils.uncapitalize(entity.getSimpleTypeName())));
MethodMetadata existingMethod =
getGovernorMethod(SAVE_METHOD_NAME,
AnnotatedJavaType.convertFromAnnotatedJavaTypes(parameterTypes));
if (existingMethod != null) {
return existingMethod;
}
// Use the MethodMetadataBuilder for easy creation of MethodMetadata
return new MethodMetadataBuilder(getId(), Modifier.PUBLIC + Modifier.ABSTRACT,
SAVE_METHOD_NAME, entity, parameterTypes, parameterNames, null).build();
}
private MethodMetadata getFindAllIteratorMethod(JavaType entity,
FieldMetadata identifierFieldMetadata) {
// Define method parameter type and name
List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
parameterTypes.add(AnnotatedJavaType.convertFromJavaType(JavaType
.iterableOf(identifierFieldMetadata.getFieldType())));
parameterNames.add(identifierFieldMetadata.getFieldName());
MethodMetadata existingMethod =
getGovernorMethod(FIND_ALL_ITERATOR_METHOD_NAME,
AnnotatedJavaType.convertFromAnnotatedJavaTypes(parameterTypes));
if (existingMethod != null) {
return existingMethod;
}
// Use the MethodMetadataBuilder for easy creation of MethodMetadata
return new MethodMetadataBuilder(getId(), Modifier.PUBLIC + Modifier.ABSTRACT,
FIND_ALL_ITERATOR_METHOD_NAME, JavaType.listOf(entity), parameterTypes, parameterNames,
null).build();
}
/**
* Method that generates method "countByField" method.
*
* @param relationInfo
* @param identifierType
*
* @return field
*/
public MethodMetadata getCountMethodByField(FieldMetadata field, RelationInfo relationInfo) {
// Define method name
String countPattern = "countBy%s";
if (relationInfo.cardinality == Cardinality.MANY_TO_MANY) {
countPattern = "countBy%sContains";
}
final JavaSymbolName methodName =
new JavaSymbolName(String.format(countPattern, field.getFieldName()
.getSymbolNameCapitalisedFirstLetter()));
// Define method parameter type and name
List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
final JavaType paramType = field.getFieldType().getBaseType();
parameterTypes.add(AnnotatedJavaType.convertFromJavaType(paramType));
parameterNames.add(new JavaSymbolName(StringUtils.uncapitalize(field.getFieldName()
.getSymbolName())));
MethodMetadata existingMethod =
getGovernorMethod(methodName,
AnnotatedJavaType.convertFromAnnotatedJavaTypes(parameterTypes));
if (existingMethod != null) {
return existingMethod;
}
// Use the MethodMetadataBuilder for easy creation of MethodMetadata
MethodMetadataBuilder methodBuilder =
new MethodMetadataBuilder(getId(), Modifier.PUBLIC + Modifier.ABSTRACT, methodName,
JavaType.LONG_PRIMITIVE, parameterTypes, parameterNames, null);
return methodBuilder.build();
}
/**
* Method that generates finder method on current interface
*
* @param finderMethod
* @return
*/
private MethodMetadataBuilder getFinderMethod(FinderMethod finderMethod) {
// Define method parameter types and parameter names
List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
for (FinderParameter param : finderMethod.getParameters()) {
parameterTypes.add(AnnotatedJavaType.convertFromJavaType(param.getType()));
parameterNames.add(param.getName());
}
// Add additional Pageable method
parameterTypes.add(AnnotatedJavaType.convertFromJavaType(SpringJavaType.PAGEABLE));
parameterNames.add(new JavaSymbolName("pageable"));
// Use the MethodMetadataBuilder for easy creation of MethodMetadata
MethodMetadataBuilder methodBuilder =
new MethodMetadataBuilder(getId(), Modifier.PUBLIC + Modifier.ABSTRACT,
finderMethod.getMethodName(), finderMethod.getReturnType(), parameterTypes,
parameterNames, null);
return methodBuilder; // Build and return a MethodMetadata
// instance
}
/**
* Method that generates finder method on current interface
*
* @param finderMethod
* @return
*/
private MethodMetadataBuilder getCountMethod(FinderMethod finderMethod) {
// Define method parameter types and parameter names
List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
for (FinderParameter param : finderMethod.getParameters()) {
parameterTypes.add(AnnotatedJavaType.convertFromJavaType(param.getType()));
parameterNames.add(param.getName());
}
// Create count method name
String countName = finderMethod.getMethodName().getSymbolName();
if (StringUtils.startsWith(countName, "find")) {
countName = StringUtils.removeStart(countName, "find");
} else if (StringUtils.startsWith(countName, "query")) {
countName = StringUtils.removeStart(countName, "query");
} else if (StringUtils.startsWith(countName, "read")) {
countName = StringUtils.removeStart(countName, "read");
}
countName = "count".concat(countName);
JavaSymbolName countMethodName = new JavaSymbolName(countName);
// Use the MethodMetadataBuilder for easy creation of MethodMetadata
MethodMetadataBuilder methodBuilder =
new MethodMetadataBuilder(getId(), Modifier.PUBLIC + Modifier.ABSTRACT, countMethodName,
JavaType.LONG_PRIMITIVE, parameterTypes, parameterNames, null);
return methodBuilder; // Build and return a MethodMetadata
// instance
}
/**
* This method returns all generated countMethodByReferencedFields
*
* @return Map with key that identifies referenced field and method metadata
*/
public Map<FieldMetadata, MethodMetadata> getCountMethodByReferencedFields() {
return countMethodByReferencedFields;
}
/**
* @return composition count method
*/
public MethodMetadata getCompositionCountMethod() {
return compositionCountMethod;
}
/**
* @return composition field
*/
public FieldMetadata getCompositionField() {
return compositionField;
}
/**
* @return composition relation info
*/
public RelationInfo getCompositionInfo() {
return compositionInfo;
}
/**
* @return list of related custom repositories
*/
public JavaType getCustomRepository() {
return customRepository;
}
/**
* @return true if entity is composition
*/
public boolean isComposition() {
return compositionField != null;
}
/**
* @return entity handles by current repository
*/
public JavaType getEntity() {
return entity;
}
/**
* @return finders definition which should be add in custom repository
*/
public List<Pair<FinderMethod, PartTree>> getFindersToAddInCustom() {
return findersToAddInCustom;
}
/**
* @return all countMethods declared in repository
*/
public List<MethodMetadata> getCountMethods() {
return countMethods;
}
/**
* @return finders definition declared to implement in repository
*/
public List<FinderMethod> getFindersDeclared() {
return findersDeclared;
}
/**
* @return finders method generated in repository
*/
public List<MethodMetadata> getFindersGenerated() {
return findersGenerated;
}
/**
* @return all declared finder names
*/
public List<String> getDeclaredFinderNames() {
return declaredFinderNames;
}
/**
* Return defaultReturnType specified value (if any)
*
* @return defaultReturnType or entity type if value is not set
*/
public JavaType getDefaultReturnType() {
return defaultReturnType;
}
/**
* Return the finder name methods and the related count method if exists.
*
* @return
*/
public Map<JavaSymbolName, MethodMetadata> getFinderMethodsAndCounts() {
return finderMethodsAndCounts;
}
@Override
public int hashCode() {
// Override hashCode to propagate changes in non-modified-itd changes
// (as finders which will generate on RepositoryCustom)
int result = super.hashCode();
StringBuilder sb = new StringBuilder("");
for (Pair<FinderMethod, PartTree> finder : findersToAddInCustom) {
sb.append(finder.getLeft().getMethodName().getSymbolName());
}
// Add hashCode changes for normal finders as they change an annotation
// inside the annotation which triggers this metadata
for (FinderMethod finder : findersDeclared) {
sb.append(finder.getMethodName().getSymbolName());
}
// Combine hashCodes
result += sb.toString().hashCode();
return result;
}
@Override
public String toString() {
final ToStringBuilder builder = new ToStringBuilder(this);
builder.append("identifier", getId());
builder.append("valid", valid);
builder.append("aspectName", aspectName);
builder.append("destinationType", destination);
builder.append("governor", governorPhysicalTypeMetadata.getId());
builder.append("itdTypeDetails", itdTypeDetails);
builder.append("entity", entity);
return builder.toString();
}
}