/addon-jpa/addon/src/main/java/org/springframework/roo/addon/jpa/addon/dod/JpaDataOnDemandCreator.java
Java | 401 lines | 265 code | 63 blank | 73 comment | 31 complexity | 77178465373283abb00df674084cbfa4 MD5 | raw file
- package org.springframework.roo.addon.jpa.addon.dod;
- import static org.springframework.roo.model.JpaJavaType.ENTITY;
- import java.lang.reflect.Modifier;
- import java.util.ArrayList;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Set;
- 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.springframework.roo.addon.jpa.addon.entity.JpaEntityMetadata;
- import org.springframework.roo.addon.jpa.addon.entity.JpaEntityMetadata.RelationInfo;
- import org.springframework.roo.addon.test.providers.DataOnDemandCreatorProvider;
- import org.springframework.roo.classpath.PhysicalTypeCategory;
- import org.springframework.roo.classpath.PhysicalTypeIdentifier;
- 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.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.ClassAttributeValue;
- import org.springframework.roo.classpath.scanner.MemberDetails;
- import org.springframework.roo.classpath.scanner.MemberDetailsScanner;
- import org.springframework.roo.metadata.MetadataService;
- import org.springframework.roo.model.JavaSymbolName;
- import org.springframework.roo.model.JavaType;
- import org.springframework.roo.model.RooJavaType;
- import org.springframework.roo.project.Dependency;
- import org.springframework.roo.project.DependencyScope;
- import org.springframework.roo.project.DependencyType;
- import org.springframework.roo.project.LogicalPath;
- import org.springframework.roo.project.Path;
- import org.springframework.roo.project.Plugin;
- import org.springframework.roo.project.ProjectOperations;
- import org.springframework.roo.project.maven.Pom;
- import org.springframework.roo.support.util.XmlUtils;
- import org.w3c.dom.Element;
- /**
- * Implementation of {@link DataOnDemandOperations}, based on old
- * DataOnDemandOperationsImpl.
- *
- * @author Alan Stewart
- * @author Sergio Clares
- * @since 2.0
- */
- @Component
- @Service
- public class JpaDataOnDemandCreator implements DataOnDemandCreatorProvider {
- private static final Dependency VALIDATION_API_DEPENDENCY = new Dependency("javax.validation",
- "validation-api", null);
- private static final Dependency SPRING_BOOT_TEST_DEPENDENCY = new Dependency(
- "org.springframework.boot", "spring-boot-test", null, DependencyType.JAR,
- DependencyScope.TEST);
- private static final String MAVEN_JAR_PLUGIN = "maven-jar-plugin";
- @Reference
- private MemberDetailsScanner memberDetailsScanner;
- @Reference
- private MetadataService metadataService;
- @Reference
- private ProjectOperations projectOperations;
- @Reference
- private TypeLocationService typeLocationService;
- @Reference
- private TypeManagementService typeManagementService;
- @Override
- public boolean isValid(JavaType javaType) {
- ClassOrInterfaceTypeDetails cid = typeLocationService.getTypeDetails(javaType);
- return cid.getAnnotation(RooJavaType.ROO_JPA_ENTITY) != null;
- }
- @Override
- public JavaType createDataOnDemand(JavaType entity) {
- Validate.notNull(entity, "Entity to produce a data on demand provider for is required");
- JavaType dodClass = getDataOnDemand(entity);
- if (dodClass != null) {
- return dodClass;
- }
- // Add plugin to generate test jar
- addMavenJarPlugin(entity.getModule());
- // Create the JavaType for DoD class
- JavaType name =
- new JavaType(entity.getPackage().getFullyQualifiedPackageName().concat(".dod.")
- .concat(entity.getSimpleTypeName()).concat("DataOnDemand"), entity.getModule());
- // Obatain test path for the module of the new class
- final LogicalPath path = LogicalPath.getInstance(Path.SRC_TEST_JAVA, name.getModule());
- Validate.notNull(path, "Location of the new data on demand provider is required");
- // Create DoD configuration class
- createDataOnDemandConfiguration(entity.getModule());
- // Create entity factories for the given entity and its related entities
- createEntityFactory(entity);
- // Create data on demand class
- return newDataOnDemandClass(entity, name);
- }
- @Override
- public JavaType createDataOnDemandConfiguration(String moduleName) {
- // Check if alreafy exists
- JavaType dodConfig = getDataOnDemandConfiguration();
- if (dodConfig != null) {
- return dodConfig;
- }
- // Add spring-boot-test dependency with test scope
- projectOperations.addDependency(moduleName, SPRING_BOOT_TEST_DEPENDENCY);
- // Get Pom
- final Pom module = projectOperations.getPomFromModuleName(moduleName);
- // Get test Path for module
- final LogicalPath path = LogicalPath.getInstance(Path.SRC_TEST_JAVA, moduleName);
- // Create the JavaType for the configuration class
- JavaType dodConfigurationClass =
- new JavaType(String.format("%s.dod.DataOnDemandConfiguration",
- typeLocationService.getTopLevelPackageForModule(module), moduleName));
- final String declaredByMetadataId =
- PhysicalTypeIdentifier.createIdentifier(dodConfigurationClass, path);
- if (metadataService.get(declaredByMetadataId) != null) {
- // The file already exists
- return new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId).getName();
- }
- // Create the CID builder
- ClassOrInterfaceTypeDetailsBuilder cidBuilder =
- new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId, Modifier.PUBLIC,
- dodConfigurationClass, PhysicalTypeCategory.CLASS);
- cidBuilder.addAnnotation(new AnnotationMetadataBuilder(
- RooJavaType.ROO_JPA_DATA_ON_DEMAND_CONFIGURATION));
- // Write changes to disk
- final ClassOrInterfaceTypeDetails configDodCid = cidBuilder.build();
- typeManagementService.createOrUpdateTypeOnDisk(configDodCid);
- return configDodCid.getName();
- }
- @Override
- public JavaType createEntityFactory(JavaType currentEntity) {
- Validate.notNull(currentEntity, "Entity to produce a data on demand provider for is required");
- // Verify the requested entity actually exists as a class and is not
- // abstract
- final ClassOrInterfaceTypeDetails cid = getEntityDetails(currentEntity);
- Validate.isTrue(cid.getPhysicalTypeCategory() == PhysicalTypeCategory.CLASS,
- "Type %s is not a class", currentEntity.getFullyQualifiedTypeName());
- Validate.isTrue(!Modifier.isAbstract(cid.getModifier()), "Type %s is abstract",
- currentEntity.getFullyQualifiedTypeName());
- // Check if the requested entity is a JPA @Entity
- final MemberDetails memberDetails =
- memberDetailsScanner.getMemberDetails(JpaDataOnDemandCreator.class.getName(), cid);
- final AnnotationMetadata entityAnnotation = memberDetails.getAnnotation(ENTITY);
- Validate.isTrue(entityAnnotation != null, "Type %s must be a JPA entity type",
- currentEntity.getFullyQualifiedTypeName());
- // Get related entities
- List<JavaType> entities = getEntityAndRelatedEntitiesList(currentEntity);
- // Get test Path for module
- final LogicalPath path = LogicalPath.getInstance(Path.SRC_TEST_JAVA, currentEntity.getModule());
- JavaType currentEntityFactory = null;
- for (JavaType entity : entities) {
- // Create the JavaType for the configuration class
- JavaType factoryClass =
- new JavaType(String.format("%s.dod.%sFactory", entity.getPackage()
- .getFullyQualifiedPackageName(), entity.getSimpleTypeName()), entity.getModule());
- final String declaredByMetadataId =
- PhysicalTypeIdentifier.createIdentifier(factoryClass, path);
- if (metadataService.get(declaredByMetadataId) != null) {
- // The file already exists
- continue;
- }
- // Create the CID builder
- ClassOrInterfaceTypeDetailsBuilder cidBuilder =
- new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId, Modifier.PUBLIC,
- factoryClass, PhysicalTypeCategory.CLASS);
- // Add @RooEntityFactory annotation
- AnnotationMetadataBuilder entityFactoryAnnotation =
- new AnnotationMetadataBuilder(RooJavaType.ROO_JPA_ENTITY_FACTORY);
- entityFactoryAnnotation.addClassAttribute("entity", entity);
- cidBuilder.addAnnotation(entityFactoryAnnotation);
- // Write changes to disk
- typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());
- // First entity is current entity
- if (currentEntityFactory == null) {
- currentEntityFactory = cidBuilder.getName();
- }
- }
- return currentEntityFactory;
- }
- @Override
- public JavaType getDataOnDemand(JavaType entity) {
- Set<ClassOrInterfaceTypeDetails> dataOnDemandCids =
- typeLocationService
- .findClassesOrInterfaceDetailsWithAnnotation(RooJavaType.ROO_JPA_DATA_ON_DEMAND);
- JavaType typeToReturn = null;
- for (ClassOrInterfaceTypeDetails cid : dataOnDemandCids) {
- if (entity.equals((JavaType) cid.getAnnotation(RooJavaType.ROO_JPA_DATA_ON_DEMAND)
- .getAttribute("entity").getValue())) {
- typeToReturn = cid.getName();
- break;
- }
- }
- return typeToReturn;
- }
- @Override
- public JavaType getDataOnDemandConfiguration() {
- Set<JavaType> dodConfigurationTypes =
- typeLocationService
- .findTypesWithAnnotation(RooJavaType.ROO_JPA_DATA_ON_DEMAND_CONFIGURATION);
- if (!dodConfigurationTypes.isEmpty()) {
- return dodConfigurationTypes.iterator().next();
- }
- return null;
- }
- @Override
- public JavaType getDataOnDemandConfiguration(String moduleName) {
- Set<JavaType> dodConfigurationTypes =
- typeLocationService
- .findTypesWithAnnotation(RooJavaType.ROO_JPA_DATA_ON_DEMAND_CONFIGURATION);
- Iterator<JavaType> it = dodConfigurationTypes.iterator();
- while (it.hasNext()) {
- JavaType dodConfigType = it.next();
- if (dodConfigType.getModule().equals(moduleName)) {
- return dodConfigType;
- }
- }
- return null;
- }
- @Override
- public JavaType getEntityFactory(JavaType entity) {
- Set<ClassOrInterfaceTypeDetails> dataOnDemandCids =
- typeLocationService
- .findClassesOrInterfaceDetailsWithAnnotation(RooJavaType.ROO_JPA_ENTITY_FACTORY);
- JavaType typeToReturn = null;
- for (ClassOrInterfaceTypeDetails cid : dataOnDemandCids) {
- if (entity.equals((JavaType) cid.getAnnotation(RooJavaType.ROO_JPA_ENTITY_FACTORY)
- .getAttribute("entity").getValue())) {
- typeToReturn = cid.getName();
- break;
- }
- }
- return typeToReturn;
- }
- /**
- * Add maven-jar-plugin to provided module.
- *
- * @param moduleName the name of the module.
- */
- private void addMavenJarPlugin(String moduleName) {
- // Add plugin maven-jar-plugin
- Pom module = projectOperations.getPomFromModuleName(moduleName);
- // Stop if the plugin is already installed
- for (final Plugin plugin : module.getBuildPlugins()) {
- if (plugin.getArtifactId().equals(MAVEN_JAR_PLUGIN)) {
- return;
- }
- }
- final Element configuration = XmlUtils.getConfiguration(getClass());
- final Element plugin = XmlUtils.findFirstElement("/configuration/plugin", configuration);
- // Now install the plugin itself
- if (plugin != null) {
- projectOperations.addBuildPlugin(moduleName, new Plugin(plugin), false);
- }
- }
- /**
- * Creates a new data-on-demand provider for an entity. Silently returns
- * if the DoD class already exists.
- *
- * @param entity to produce a DoD provider for
- * @param name the name of the new DoD class
- */
- private JavaType newDataOnDemandClass(JavaType entity, JavaType name) {
- Validate.notNull(entity, "Entity to produce a data on demand provider for is required");
- Validate.notNull(name, "Name of the new data on demand provider is required");
- final LogicalPath path = LogicalPath.getInstance(Path.SRC_TEST_JAVA, name.getModule());
- Validate.notNull(path, "Location of the new data on demand provider is required");
- // Add javax validation dependency
- projectOperations.addDependency(name.getModule(), VALIDATION_API_DEPENDENCY);
- // Verify the requested entity actually exists as a class and is not
- // abstract
- final ClassOrInterfaceTypeDetails cid = getEntityDetails(entity);
- Validate.isTrue(cid.getPhysicalTypeCategory() == PhysicalTypeCategory.CLASS,
- "Type %s is not a class", entity.getFullyQualifiedTypeName());
- Validate.isTrue(!Modifier.isAbstract(cid.getModifier()), "Type %s is abstract",
- entity.getFullyQualifiedTypeName());
- // Check if the requested entity is a JPA @Entity
- final MemberDetails memberDetails =
- memberDetailsScanner.getMemberDetails(JpaDataOnDemandCreator.class.getName(), cid);
- final AnnotationMetadata entityAnnotation = memberDetails.getAnnotation(ENTITY);
- Validate.isTrue(entityAnnotation != null, "Type %s must be a JPA entity type",
- entity.getFullyQualifiedTypeName());
- // Everything is OK to proceed
- final String declaredByMetadataId = PhysicalTypeIdentifier.createIdentifier(name, path);
- if (metadataService.get(declaredByMetadataId) != null) {
- // The file already exists
- return new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId).getName();
- }
- final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
- final List<AnnotationAttributeValue<?>> dodConfig =
- new ArrayList<AnnotationAttributeValue<?>>();
- dodConfig.add(new ClassAttributeValue(new JavaSymbolName("entity"), entity));
- annotations.add(new AnnotationMetadataBuilder(RooJavaType.ROO_JPA_DATA_ON_DEMAND, dodConfig));
- final ClassOrInterfaceTypeDetailsBuilder cidBuilder =
- new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId, Modifier.PUBLIC, name,
- PhysicalTypeCategory.CLASS);
- cidBuilder.setAnnotations(annotations);
- // Write changes on disk
- final ClassOrInterfaceTypeDetails dodClassCid = cidBuilder.build();
- typeManagementService.createOrUpdateTypeOnDisk(dodClassCid);
- return cid.getName();
- }
- /**
- * Searches the related entities of provided entity and returns a
- * {@link List} with all the related entities plus the provided entity.
- *
- * @param entity
- * the entity JavaType to search for its related entities.
- * @return a List with all the related entities.
- */
- private List<JavaType> getEntityAndRelatedEntitiesList(JavaType entity) {
- ClassOrInterfaceTypeDetails entityDetails = getEntityDetails(entity);
- JpaEntityMetadata entityMetadata =
- metadataService.get(JpaEntityMetadata.createIdentifier(entityDetails));
- List<JavaType> entitiesToCreateFactories = new ArrayList<JavaType>();
- entitiesToCreateFactories.add(entity);
- // Get related child entities
- for (RelationInfo info : entityMetadata.getRelationInfos().values()) {
- // Add to list
- if (!entitiesToCreateFactories.contains(info.childType)) {
- entitiesToCreateFactories.add(info.childType);
- }
- }
- return entitiesToCreateFactories;
- }
- /**
- * Returns the {@link ClassOrInterfaceTypeDetails} for the provided entity.
- *
- * @param entity
- * the entity to lookup required
- * @return the ClassOrInterfaceTypeDetails type details (never null; throws
- * an exception if it cannot be obtained or parsed)
- */
- private ClassOrInterfaceTypeDetails getEntityDetails(final JavaType entity) {
- final ClassOrInterfaceTypeDetails cid = typeLocationService.getTypeDetails(entity);
- Validate.notNull(cid, "Java source code details unavailable for type '%s'", entity);
- return cid;
- }
- }