package org.springframework.roo.addon.layers.repository.jpa.addon.finder; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Service; import org.osgi.framework.BundleContext; import org.osgi.service.component.ComponentContext; import org.springframework.roo.addon.layers.repository.jpa.addon.RepositoryJpaLocator; 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.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.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.ArrayAttributeValue; import org.springframework.roo.classpath.details.annotations.NestedAnnotationAttributeValue; import org.springframework.roo.classpath.scanner.MemberDetails; import org.springframework.roo.classpath.scanner.MemberDetailsScanner; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.model.RooJavaType; import org.springframework.roo.project.FeatureNames; import org.springframework.roo.project.ProjectOperations; import org.springframework.roo.support.logging.HandlerUtils; import org.springframework.roo.support.osgi.ServiceInstaceManager; /** * Implementation of {@link FinderOperations}. * * @author Stefan Schmidt * @autor Juan Carlos GarcĂ­a * @since 1.0 */ @Component @Service public class FinderOperationsImpl implements FinderOperations { private static final Logger LOGGER = HandlerUtils.getLogger(FinderOperationsImpl.class); // ------------ OSGi component attributes ---------------- private BundleContext context; private ServiceInstaceManager serviceInstaceManager = new ServiceInstaceManager(); protected void activate(final ComponentContext context) { this.context = context.getBundleContext(); serviceInstaceManager.activate(this.context); } public void installFinder(final JavaType entity, final JavaSymbolName finderName, JavaType formBean, JavaType returnType) { Validate.notNull(entity, "ERROR: Entity type required to generate finder."); Validate.notNull(finderName, "ERROR: Finder name required to generate finder."); final String id = getTypeLocationService().getPhysicalTypeIdentifier(entity); if (id == null) { LOGGER.warning("Cannot locate source for '" + entity.getFullyQualifiedTypeName() + "'"); return; } // Check if provided entity is annotated with @RooJpaEntity ClassOrInterfaceTypeDetails entityDetails = getTypeLocationService().getTypeDetails(entity); AnnotationMetadata entityAnnotation = entityDetails.getAnnotation(RooJavaType.ROO_JPA_ENTITY); Validate.notNull(entityAnnotation, "ERROR: Provided entity must be annotated with @RooJpaEntity"); // Getting repository that manages current entity ClassOrInterfaceTypeDetails repository = getRepositoryJpaLocator().getRepository(entity); // Entity must have a repository that manages it, if not, shows an error Validate .notNull( repository, "ERROR: You must generate a repository to the provided entity before to add new finder. You could use 'repository jpa' commands."); // Check if provided formBean contains the field names and types indicated as finder params if (formBean != null) { MemberDetails entityMemberDetails = getMemberDetailsScanner().getMemberDetails(this.getClass().getName(), getTypeLocationService().getTypeDetails(entity)); PartTree finderPartTree = new PartTree(finderName.getSymbolName(), entityMemberDetails); checkDtoFieldsForFinder(getTypeLocationService().getTypeDetails(formBean), finderPartTree, repository.getType()); } // Get repository annotation AnnotationMetadata repositoryAnnotation = repository.getAnnotation(RooJavaType.ROO_REPOSITORY_JPA); AnnotationMetadataBuilder repositoryAnnotationBuilder = new AnnotationMetadataBuilder(repositoryAnnotation); // Create list that will include finders to add List<AnnotationAttributeValue<?>> finders = new ArrayList<AnnotationAttributeValue<?>>(); // Check if new finder name to be included already exists in @RooRepositoryJpa annotation AnnotationAttributeValue<?> currentFinders = repositoryAnnotation.getAttribute("finders"); if (currentFinders != null) { List<?> values = (List<?>) currentFinders.getValue(); Iterator<?> it = values.iterator(); while (it.hasNext()) { NestedAnnotationAttributeValue finder = (NestedAnnotationAttributeValue) it.next(); if (finder.getValue() != null && finder.getValue().getAttribute("value") != null) { if (finder.getValue().getAttribute("value").getValue().equals(finderName.getSymbolName())) { LOGGER.log( Level.WARNING, String.format("ERROR: Finder '%s' already exists on entity '%s'", finderName.getSymbolName(), entity.getSimpleTypeName())); return; } finders.add(finder); } } } // Create @RooFinder AnnotationMetadataBuilder singleFinderAnnotation = new AnnotationMetadataBuilder(RooJavaType.ROO_FINDER); // Add finder attribute singleFinderAnnotation.addStringAttribute("value", finderName.getSymbolName()); // Add returnType attribute singleFinderAnnotation.addClassAttribute("returnType", returnType); // Prevent errors validating if the return type contains a valid module if (returnType.getModule() != null) { getProjectOperations().addModuleDependency(repository.getName().getModule(), returnType.getModule()); } // Add formBean attribute if (formBean != null) { singleFinderAnnotation.addClassAttribute("formBean", formBean); getProjectOperations().addModuleDependency(repository.getName().getModule(), formBean.getModule()); } NestedAnnotationAttributeValue newFinder = new NestedAnnotationAttributeValue(new JavaSymbolName("value"), singleFinderAnnotation.build()); // If not exists current finder, include it finders.add(newFinder); // Add finder list to currentFinders ArrayAttributeValue<AnnotationAttributeValue<?>> newFinders = new ArrayAttributeValue<AnnotationAttributeValue<?>>(new JavaSymbolName("finders"), finders); // Include finder name to finders attribute repositoryAnnotationBuilder.addAttribute(newFinders); // Update @RooRepositoryJpa annotation final ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder(repository); // Update annotation cidBuilder.updateTypeAnnotation(repositoryAnnotationBuilder); // Save changes on disk getTypeManagementService().createOrUpdateTypeOnDisk(cidBuilder.build()); } public boolean isFinderInstallationPossible() { return getProjectOperations().isFocusedProjectAvailable() && getProjectOperations().isFeatureInstalled(FeatureNames.JPA); } public SortedSet<String> listFindersFor(final JavaType typeName, final Integer depth) { return null; } /** * Build a Map<String, String> with form bean type field names and "path" field names * and adds it to the typesFieldMaps, typeFieldMetadataMap and finderParametersMap. * * @param entity the entity containing the FieldMetadata used as finder parameters. * The fields can be fields from other related entity * @param formBeanType the bean class containing the values for which to look up. * @param typesFieldMaps the map with field names and "path" field names mappings. * Can be null. * @param typeFieldMetadataMap the map with field names and FieldMetadata mappings. * Can be null. * @param finderName the name of the finder to build mappings. * @param finderParametersList the list of FinderParameters for each finder name. */ public void buildFormBeanFieldNamesMap(JavaType entity, JavaType formBeanType, Map<JavaType, Map<String, String>> typesFieldMaps, Map<JavaType, Map<String, FieldMetadata>> typeFieldMetadataMap, JavaSymbolName finderName, Map<JavaSymbolName, List<FinderParameter>> finderParametersMap) { // Get all entity fields ClassOrInterfaceTypeDetails entityCid = getTypeLocationService().getTypeDetails(entity); MemberDetails entityMemberDetails = getMemberDetailsScanner().getMemberDetails(this.getClass().getName(), entityCid); List<FieldMetadata> allEntityFields = entityMemberDetails.getFields(); // Create inner Maps Map<String, String> fieldNamesMap = null; if (typesFieldMaps != null) { if (typesFieldMaps.get(formBeanType) == null) { fieldNamesMap = new HashMap<String, String>(); } else { fieldNamesMap = typesFieldMaps.get(formBeanType); } } Map<String, FieldMetadata> fieldMetadataMap = null; if (typeFieldMetadataMap != null) { if (typeFieldMetadataMap.get(formBeanType) == null) { fieldMetadataMap = new HashMap<String, FieldMetadata>(); } else { fieldMetadataMap = typeFieldMetadataMap.get(formBeanType); } } // Get finder fields PartTree partTree = new PartTree(finderName.getSymbolName(), entityMemberDetails); List<FinderParameter> finderParameters = partTree.getParameters(); // Create list of parameters for this finder List<FinderParameter> finderParametersList = new ArrayList<FinderParameter>(); // Get all DTO fields if form bean is a DTO or entity fields if not List<FieldMetadata> allFormBeanFields = new ArrayList<FieldMetadata>(); if (getTypeLocationService().getTypeDetails(formBeanType) != null && getTypeLocationService().getTypeDetails(formBeanType).getAnnotation(RooJavaType.ROO_DTO) != null) { allFormBeanFields = getMemberDetailsScanner().getMemberDetails(this.getClass().getName(), getTypeLocationService().getTypeDetails(formBeanType)).getFields(); } // Iterate over all specified fields for (FinderParameter finderParameter : finderParameters) { JavaSymbolName fieldName = finderParameter.getName(); JavaType fieldType = finderParameter.getType(); boolean found = false; // Iterate over all entity fields for (FieldMetadata field : allEntityFields) { if (field.getFieldName().equals(fieldName) && field.getFieldType().equals(fieldType)) { // Field found, build field "path" name and add it to map // Path name for DTO's should be the path to entity's fields String fieldPathName = String.format("%s.%s", StringUtils.uncapitalize(entity.getSimpleTypeName()), field.getFieldName()); if (typesFieldMaps != null) { fieldNamesMap.put(fieldName.getSymbolName(), fieldPathName); } // Add FieldMetadata from DTO to fieldMetadataMap if (!allFormBeanFields.isEmpty()) { boolean fieldFoundInDto = false; for (FieldMetadata dtoField : allFormBeanFields) { if (dtoField.getFieldName().equals(fieldName) && dtoField.getFieldType().equals(fieldType)) { if (typeFieldMetadataMap != null) { fieldMetadataMap.put(fieldName.getSymbolName(), dtoField); } fieldFoundInDto = true; } } Validate.isTrue(fieldFoundInDto, "Couldn't find a field with same name and type that %s on DTO %s", fieldName.getSymbolName(), formBeanType.getSimpleTypeName()); } else { fieldMetadataMap.put(fieldName.getSymbolName(), field); } found = true; break; } } if (!found) { // The field isn't in the entity, should be in one of its relations for (FieldMetadata field : allEntityFields) { found = findDtoFieldRecursivelyAndAddToMappings(entity, fieldNamesMap, fieldMetadataMap, found, field, fieldName, fieldType, allFormBeanFields, formBeanType.getSimpleTypeName()); } } if (!found) { // Field not found in its throw new IllegalArgumentException(String.format( "Field %s couldn't be located in DTO %s. Please, be sure that it is well " + "written and exists in %s or its related entities.", fieldName, formBeanType.getSimpleTypeName(), entity.getSimpleTypeName())); } finderParametersList.add(finderParameter); } // Add dto mappings to domain type mappings if (typesFieldMaps != null) { typesFieldMaps.put(formBeanType, fieldNamesMap); } if (typeFieldMetadataMap != null) { typeFieldMetadataMap.put(formBeanType, fieldMetadataMap); } // Add finder params to Map finderParametersMap.put(finderName, finderParametersList); } /** * Validates that all dto fields matches with parameters on finder of a repository * * @param formBeanDetails * @param finder * @param repository */ private void checkDtoFieldsForFinder(ClassOrInterfaceTypeDetails formBeanDetails, PartTree finder, JavaType repository) { final JavaType dtoType = formBeanDetails.getName(); final String finderName = finder.getOriginalQuery(); FieldMetadata paramField, dtoField; JavaType paramType, dtoFieldType; for (FinderParameter param : finder.getParameters()) { paramField = param.getPath().peek(); dtoField = formBeanDetails.getField(paramField.getFieldName()); Validate.notNull(dtoField, "Field '%s' not found on DTO %s for finder '%s' of %s", paramField.getFieldName(), dtoType, finderName, repository); paramType = paramField.getFieldType().getBaseType(); dtoFieldType = dtoField.getFieldType().getBaseType(); Validate.isTrue(paramType.equals(dtoFieldType), "Type missmatch for field '%s' on DTO %s for finder '%s' of %s: excepted %s and got %s", dtoField.getFieldName(), dtoType, finderName, repository, paramType, dtoFieldType); } } private boolean findDtoFieldRecursivelyAndAddToMappings(JavaType entity, Map<String, String> fieldNamesMap, Map<String, FieldMetadata> fieldMetadataMap, boolean found, FieldMetadata field, JavaSymbolName finderFieldName, JavaType finderFieldType, List<FieldMetadata> allDtoFields, String dtoSimpleName) { JavaType currentEntity; if (getTypeLocationService().getTypeDetails(field.getFieldType()) != null && getTypeLocationService().getTypeDetails(field.getFieldType()).getAnnotation( RooJavaType.ROO_JPA_ENTITY) != null) { // Change current entity currentEntity = field.getFieldType(); // Modify pathName with one more level String pathName = StringUtils.uncapitalize(entity.getSimpleTypeName()).concat(".") .concat(field.getFieldName().getSymbolName()); List<FieldMetadata> relatedEntityFields = getMemberDetailsScanner().getMemberDetails(this.getClass().getName(), getTypeLocationService().getTypeDetails(currentEntity)).getFields(); for (FieldMetadata relatedField : relatedEntityFields) { if (relatedField.getFieldName().equals(finderFieldName) && relatedField.getFieldType().equals(finderFieldType)) { // Add FieldMetadata from DTO to fieldMetadataMap boolean fieldFoundInDto = false; for (FieldMetadata dtoField : allDtoFields) { if (dtoField.getFieldName().equals(finderFieldName) && dtoField.getFieldType().equals(finderFieldType)) { fieldMetadataMap.put(finderFieldName.getSymbolName(), dtoField); fieldFoundInDto = true; } } Validate.isTrue(fieldFoundInDto, "Couldn't find a field with same name and type that %s on DTO %s", finderFieldName.getSymbolName(), dtoSimpleName); fieldNamesMap.put(finderFieldName.getSymbolName(), pathName.concat(".").concat(relatedField.getFieldName().getSymbolName())); found = true; break; } else { findDtoFieldRecursivelyAndAddToMappings(currentEntity, fieldNamesMap, fieldMetadataMap, found, relatedField, finderFieldName, finderFieldType, allDtoFields, dtoSimpleName); } } } return found; } private MemberDetailsScanner getMemberDetailsScanner() { return serviceInstaceManager.getServiceInstance(this, MemberDetailsScanner.class); } private ProjectOperations getProjectOperations() { return serviceInstaceManager.getServiceInstance(this, ProjectOperations.class); } private TypeLocationService getTypeLocationService() { return serviceInstaceManager.getServiceInstance(this, TypeLocationService.class); } private TypeManagementService getTypeManagementService() { return serviceInstaceManager.getServiceInstance(this, TypeManagementService.class); } private RepositoryJpaLocator getRepositoryJpaLocator() { return serviceInstaceManager.getServiceInstance(this, RepositoryJpaLocator.class); } }