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.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.PartTree; import org.springframework.roo.addon.layers.repository.jpa.annotations.RooJpaRepositoryCustom; 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.itd.AbstractItdTypeDetailsProvidingMetadataItem; import org.springframework.roo.classpath.operations.Cardinality; import org.springframework.roo.metadata.MetadataIdentificationUtils; import org.springframework.roo.model.DataType; 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.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Metadata for {@link RooJpaRepositoryCustom}. * * @author Paula Navarro * @since 2.0 */ public class RepositoryJpaCustomMetadata extends AbstractItdTypeDetailsProvidingMetadataItem { private static final JavaSymbolName PAGEABLE_PARAMETER_NAME = new JavaSymbolName("pageable"); private static final JavaSymbolName GOBAL_SEARCH_PARAMETER_NAME = new JavaSymbolName( "globalSearch"); private static final AnnotatedJavaType PAGEABLE_PARAMETER = new AnnotatedJavaType( SpringJavaType.PAGEABLE); private static final AnnotatedJavaType GLOBAL_SEARCH_PARAMETER = AnnotatedJavaType .convertFromJavaType(SpringletsJavaType.SPRINGLETS_GLOBAL_SEARCH); private static final String PROVIDES_TYPE_STRING = RepositoryJpaCustomMetadata.class.getName(); private static final String PROVIDES_TYPE = MetadataIdentificationUtils .create(PROVIDES_TYPE_STRING); private final JavaType defaultReturnType; private final JavaType identifierType; private final Map<FieldMetadata, MethodMetadata> referencedFieldsFindAllMethods; private final List<Pair<MethodMetadata, PartTree>> customFinderMethods; private final List<Pair<MethodMetadata, PartTree>> customCountMethods; private final MethodMetadata findAllGlobalSearchMethod; private final MethodMetadata findAllByIdsInGlobalSearchMethod; 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 details) { final LogicalPath logicalPath = PhysicalTypeIdentifier.getPath(details.getDeclaredByMetadataId()); return createIdentifier(details.getType(), logicalPath); } 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 identifierType the type of the entity's identifier field * (required) * @param domainType entity referenced on interface * @param searchResult the java type o the search result returned by findAll finder * @param repositoryMetadata list of information of fields which entity is child part * @param relationsAsChild */ public RepositoryJpaCustomMetadata(final String identifier, final JavaType aspectName, final PhysicalTypeMetadata governorPhysicalTypeMetadata, final RepositoryJpaCustomAnnotationValues annotationValues, final JavaType identifierType, final JavaType domainType, final RepositoryJpaMetadata repositoryMetadata, List<Pair<FieldMetadata, RelationInfo>> relationsAsChild) { super(identifier, aspectName, governorPhysicalTypeMetadata); Validate.notNull(annotationValues, "Annotation values required"); Validate.notNull(repositoryMetadata, "Referenced fields could be empty but not null"); this.defaultReturnType = repositoryMetadata.getDefaultReturnType(); this.identifierType = identifierType; this.finderMethodsAndCounts = new HashMap<JavaSymbolName, MethodMetadata>(); ArrayList<Pair<MethodMetadata, PartTree>> tmpCustomFinderMethods = new ArrayList<Pair<MethodMetadata, PartTree>>(); ArrayList<Pair<MethodMetadata, PartTree>> tmpCustomCountMethods = new ArrayList<Pair<MethodMetadata, PartTree>>(); Map<FieldMetadata, MethodMetadata> tempTeferencedFieldsFindAllMethods = new HashMap<FieldMetadata, MethodMetadata>(relationsAsChild.size()); boolean composition = false; // Generate findAllMethod for every referencedFields for (Pair<FieldMetadata, RelationInfo> referencedField : relationsAsChild) { if (referencedField.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; } MethodMetadata method = getFindAllMethodByReferencedField(referencedField.getLeft(), referencedField.getValue()); ensureGovernorHasMethod(new MethodMetadataBuilder(method)); tempTeferencedFieldsFindAllMethods.put(referencedField.getLeft(), method); } referencedFieldsFindAllMethods = Collections.unmodifiableMap(tempTeferencedFieldsFindAllMethods); // Generate findAll method if (!composition) { findAllGlobalSearchMethod = getFindAllGlobalSearchMethod(); ensureGovernorHasMethod(new MethodMetadataBuilder(findAllGlobalSearchMethod)); findAllByIdsInGlobalSearchMethod = getFindAllByIdsInGlobalSearchMethod(); ensureGovernorHasMethod(new MethodMetadataBuilder(findAllByIdsInGlobalSearchMethod)); } else { findAllGlobalSearchMethod = null; findAllByIdsInGlobalSearchMethod = null; } // Prepare a list of all finder and count methods already declared on // repository. While generate new methods, this list will be ground. ArrayList<MethodMetadata> allCountMethods = new ArrayList<MethodMetadata>(); allCountMethods.addAll(repositoryMetadata.getCountMethods()); ArrayList<MethodMetadata> allFinderMethods = new ArrayList<MethodMetadata>(); allCountMethods.addAll(repositoryMetadata.getFindersGenerated()); // Generate finder methods if any if (repositoryMetadata.getFindersToAddInCustom() != null && !repositoryMetadata.getFindersToAddInCustom().isEmpty()) { FinderMethod finderMethod; for (Pair<FinderMethod, PartTree> finderInfo : repositoryMetadata.getFindersToAddInCustom()) { finderMethod = finderInfo.getKey(); JavaType fromBean = finderMethod.getParameters().get(0).getType(); MethodMetadata method = getCustomFinder(finderMethod.getReturnType(), finderMethod.getMethodName(), fromBean); if (!isAlreadyDeclaredMethod(method, allFinderMethods)) { ensureGovernorHasMethod(new MethodMetadataBuilder(method)); tmpCustomFinderMethods.add(Pair.of(method, finderInfo.getRight())); } allFinderMethods.add(method); // Generate a count method for each custom finder if they aren't count methods MethodMetadata countMethod = null; if (!StringUtils.startsWith(finderMethod.getMethodName().getSymbolName(), "count")) { countMethod = getCustomCount(fromBean, finderMethod.getMethodName()); if (!isAlreadyDeclaredMethod(countMethod, allCountMethods)) { ensureGovernorHasMethod(new MethodMetadataBuilder(countMethod)); tmpCustomCountMethods.add(Pair.of(countMethod, finderInfo.getRight())); } allCountMethods.add(countMethod); } finderMethodsAndCounts.put(method.getMethodName(), countMethod); } } customCountMethods = Collections.unmodifiableList(tmpCustomCountMethods); customFinderMethods = Collections.unmodifiableList(tmpCustomFinderMethods); // 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; } /** * Method that generates the findAll method on current interface. * * @return */ private MethodMetadata getFindAllGlobalSearchMethod() { // Define method parameter types and parameter names List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>(); List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); //Global search parameter parameterTypes.add(GLOBAL_SEARCH_PARAMETER); parameterNames.add(GOBAL_SEARCH_PARAMETER_NAME); // Pageable parameter parameterTypes.add(PAGEABLE_PARAMETER); parameterNames.add(PAGEABLE_PARAMETER_NAME); // Method name JavaSymbolName methodName = new JavaSymbolName("findAll"); // Return type JavaType returnType = new JavaType("org.springframework.data.domain.Page", 0, DataType.TYPE, null, Arrays.asList(defaultReturnType)); // Use the MethodMetadataBuilder for easy creation of MethodMetadata MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(getId(), Modifier.PUBLIC + Modifier.ABSTRACT, methodName, returnType, parameterTypes, parameterNames, null); return methodBuilder.build(); // Build and return a MethodMetadata } /** * Method that generates the findAllByIdsIn method on current interface. * * @return */ private MethodMetadata getFindAllByIdsInGlobalSearchMethod() { // Define method parameter types and parameter names List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>(); List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); // Identifiers parameter parameterTypes.add(AnnotatedJavaType.convertFromJavaType(JavaType.wrapperOf(JavaType.LIST, identifierType))); parameterNames.add(new JavaSymbolName("ids")); //Global search parameter parameterTypes.add(GLOBAL_SEARCH_PARAMETER); parameterNames.add(GOBAL_SEARCH_PARAMETER_NAME); // Pageable parameter parameterTypes.add(PAGEABLE_PARAMETER); parameterNames.add(PAGEABLE_PARAMETER_NAME); // Method name JavaSymbolName methodName = new JavaSymbolName("findAllByIdsIn"); // Return type JavaType returnType = new JavaType("org.springframework.data.domain.Page", 0, DataType.TYPE, null, Arrays.asList(defaultReturnType)); // Use the MethodMetadataBuilder for easy creation of MethodMetadata MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(getId(), Modifier.PUBLIC + Modifier.ABSTRACT, methodName, returnType, parameterTypes, parameterNames, null); return methodBuilder.build(); // Build and return a MethodMetadata } /** * Method that generates the findAll method for provided referenced field on current interface. * * @param referencedField * @param identifierType * * @return */ private MethodMetadata getFindAllMethodByReferencedField(FieldMetadata referencedField, RelationInfo relationInfo) { // Define method name String findPattern = "findBy%s"; if (relationInfo.cardinality == Cardinality.MANY_TO_MANY) { findPattern = "findBy%sContains"; } // Method name JavaSymbolName methodName = new JavaSymbolName(String.format(findPattern, referencedField.getFieldName() .getSymbolNameCapitalisedFirstLetter())); // Define method parameter types and parameter names List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>(); final JavaType paramType = referencedField.getFieldType().getBaseType(); parameterTypes.add(AnnotatedJavaType.convertFromJavaType(paramType)); parameterTypes.add(GLOBAL_SEARCH_PARAMETER); parameterTypes.add(PAGEABLE_PARAMETER); List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); parameterNames.add(new JavaSymbolName(StringUtils.uncapitalize(referencedField.getFieldName() .getSymbolName()))); parameterNames.add(GOBAL_SEARCH_PARAMETER_NAME); parameterNames.add(PAGEABLE_PARAMETER_NAME); // Return type JavaType returnType = new JavaType(SpringJavaType.PAGE.getFullyQualifiedTypeName(), 0, DataType.TYPE, null, Arrays.asList(defaultReturnType)); // Use the MethodMetadataBuilder for easy creation of MethodMetadata MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(getId(), Modifier.PUBLIC + Modifier.ABSTRACT, methodName, returnType, parameterTypes, parameterNames, null); return methodBuilder.build(); // Build and return a MethodMetadata } /** * Method that generates finder methods whose return types are projections. * * @param finderReturnType * @param finderName * @param parameterType * * @return */ private MethodMetadata getCustomFinder(JavaType finderReturnType, JavaSymbolName finderName, JavaType parameterType) { // Define method parameter types and parameter names List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>(); parameterTypes.add(AnnotatedJavaType.convertFromJavaType(parameterType)); parameterTypes.add(GLOBAL_SEARCH_PARAMETER); parameterTypes.add(PAGEABLE_PARAMETER); List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); parameterNames.add(new JavaSymbolName("formBean")); parameterNames.add(GOBAL_SEARCH_PARAMETER_NAME); parameterNames.add(PAGEABLE_PARAMETER_NAME); // Return type JavaType returnType = new JavaType(SpringJavaType.PAGE.getFullyQualifiedTypeName(), 0, DataType.TYPE, null, Arrays.asList(finderReturnType)); // Use the MethodMetadataBuilder for easy creation of MethodMetadata MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(getId(), Modifier.PUBLIC + Modifier.ABSTRACT, finderName, returnType, parameterTypes, parameterNames, null); return methodBuilder.build(); // Build and return a MethodMetadata } /** * Method that generates count methods for custom finders. * * @param formBean the object containing the properties to search to * @param javaSymbolName the finder name * @return */ private MethodMetadata getCustomCount(JavaType formBean, JavaSymbolName finderName) { // Define method parameter types and parameter names List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>(); parameterTypes.add(AnnotatedJavaType.convertFromJavaType(formBean)); List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); parameterNames.add(new JavaSymbolName("formBean")); // Create count method name String countName = finderName.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(); // Build and return a MethodMetadata } @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); return builder.toString(); } public JavaType getDefaultReturnType() { return defaultReturnType; } /** * This method returns all findAll methods for * referenced fields * * @return */ public Map<FieldMetadata, MethodMetadata> getReferencedFieldsFindAllMethods() { return referencedFieldsFindAllMethods; } /** * This method returns all finder methods which return a projection * * @return */ public List<Pair<MethodMetadata, PartTree>> getCustomFinderMethods() { return customFinderMethods; } /** * Returns all count methods of all custom finder methods * * @return */ public List<Pair<MethodMetadata, PartTree>> getCustomCountMethods() { return customCountMethods; } /** * Return the finder name methods and the related count method if exists. * * @return */ public Map<JavaSymbolName, MethodMetadata> getFinderMethodsAndCounts() { return finderMethodsAndCounts; } /** * * @return method findAll declared for this repository */ public MethodMetadata getCurrentFindAllGlobalSearchMethod() { return findAllGlobalSearchMethod; } /** * * @return method findAll declared for this repository */ public MethodMetadata getCurrentFindAllByIdsInGlobalSearchMethod() { return findAllByIdsInGlobalSearchMethod; } }