PageRenderTime 28ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/addon-jpa/addon/src/main/java/org/springframework/roo/addon/jpa/addon/dod/JpaDataOnDemandCreator.java

http://github.com/SpringSource/spring-roo
Java | 401 lines | 265 code | 63 blank | 73 comment | 31 complexity | 77178465373283abb00df674084cbfa4 MD5 | raw file
  1. package org.springframework.roo.addon.jpa.addon.dod;
  2. import static org.springframework.roo.model.JpaJavaType.ENTITY;
  3. import java.lang.reflect.Modifier;
  4. import java.util.ArrayList;
  5. import java.util.Iterator;
  6. import java.util.List;
  7. import java.util.Set;
  8. import org.apache.commons.lang3.Validate;
  9. import org.apache.felix.scr.annotations.Component;
  10. import org.apache.felix.scr.annotations.Reference;
  11. import org.apache.felix.scr.annotations.Service;
  12. import org.springframework.roo.addon.jpa.addon.entity.JpaEntityMetadata;
  13. import org.springframework.roo.addon.jpa.addon.entity.JpaEntityMetadata.RelationInfo;
  14. import org.springframework.roo.addon.test.providers.DataOnDemandCreatorProvider;
  15. import org.springframework.roo.classpath.PhysicalTypeCategory;
  16. import org.springframework.roo.classpath.PhysicalTypeIdentifier;
  17. import org.springframework.roo.classpath.TypeLocationService;
  18. import org.springframework.roo.classpath.TypeManagementService;
  19. import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
  20. import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder;
  21. import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
  22. import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
  23. import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
  24. import org.springframework.roo.classpath.details.annotations.ClassAttributeValue;
  25. import org.springframework.roo.classpath.scanner.MemberDetails;
  26. import org.springframework.roo.classpath.scanner.MemberDetailsScanner;
  27. import org.springframework.roo.metadata.MetadataService;
  28. import org.springframework.roo.model.JavaSymbolName;
  29. import org.springframework.roo.model.JavaType;
  30. import org.springframework.roo.model.RooJavaType;
  31. import org.springframework.roo.project.Dependency;
  32. import org.springframework.roo.project.DependencyScope;
  33. import org.springframework.roo.project.DependencyType;
  34. import org.springframework.roo.project.LogicalPath;
  35. import org.springframework.roo.project.Path;
  36. import org.springframework.roo.project.Plugin;
  37. import org.springframework.roo.project.ProjectOperations;
  38. import org.springframework.roo.project.maven.Pom;
  39. import org.springframework.roo.support.util.XmlUtils;
  40. import org.w3c.dom.Element;
  41. /**
  42. * Implementation of {@link DataOnDemandOperations}, based on old
  43. * DataOnDemandOperationsImpl.
  44. *
  45. * @author Alan Stewart
  46. * @author Sergio Clares
  47. * @since 2.0
  48. */
  49. @Component
  50. @Service
  51. public class JpaDataOnDemandCreator implements DataOnDemandCreatorProvider {
  52. private static final Dependency VALIDATION_API_DEPENDENCY = new Dependency("javax.validation",
  53. "validation-api", null);
  54. private static final Dependency SPRING_BOOT_TEST_DEPENDENCY = new Dependency(
  55. "org.springframework.boot", "spring-boot-test", null, DependencyType.JAR,
  56. DependencyScope.TEST);
  57. private static final String MAVEN_JAR_PLUGIN = "maven-jar-plugin";
  58. @Reference
  59. private MemberDetailsScanner memberDetailsScanner;
  60. @Reference
  61. private MetadataService metadataService;
  62. @Reference
  63. private ProjectOperations projectOperations;
  64. @Reference
  65. private TypeLocationService typeLocationService;
  66. @Reference
  67. private TypeManagementService typeManagementService;
  68. @Override
  69. public boolean isValid(JavaType javaType) {
  70. ClassOrInterfaceTypeDetails cid = typeLocationService.getTypeDetails(javaType);
  71. return cid.getAnnotation(RooJavaType.ROO_JPA_ENTITY) != null;
  72. }
  73. @Override
  74. public JavaType createDataOnDemand(JavaType entity) {
  75. Validate.notNull(entity, "Entity to produce a data on demand provider for is required");
  76. JavaType dodClass = getDataOnDemand(entity);
  77. if (dodClass != null) {
  78. return dodClass;
  79. }
  80. // Add plugin to generate test jar
  81. addMavenJarPlugin(entity.getModule());
  82. // Create the JavaType for DoD class
  83. JavaType name =
  84. new JavaType(entity.getPackage().getFullyQualifiedPackageName().concat(".dod.")
  85. .concat(entity.getSimpleTypeName()).concat("DataOnDemand"), entity.getModule());
  86. // Obatain test path for the module of the new class
  87. final LogicalPath path = LogicalPath.getInstance(Path.SRC_TEST_JAVA, name.getModule());
  88. Validate.notNull(path, "Location of the new data on demand provider is required");
  89. // Create DoD configuration class
  90. createDataOnDemandConfiguration(entity.getModule());
  91. // Create entity factories for the given entity and its related entities
  92. createEntityFactory(entity);
  93. // Create data on demand class
  94. return newDataOnDemandClass(entity, name);
  95. }
  96. @Override
  97. public JavaType createDataOnDemandConfiguration(String moduleName) {
  98. // Check if alreafy exists
  99. JavaType dodConfig = getDataOnDemandConfiguration();
  100. if (dodConfig != null) {
  101. return dodConfig;
  102. }
  103. // Add spring-boot-test dependency with test scope
  104. projectOperations.addDependency(moduleName, SPRING_BOOT_TEST_DEPENDENCY);
  105. // Get Pom
  106. final Pom module = projectOperations.getPomFromModuleName(moduleName);
  107. // Get test Path for module
  108. final LogicalPath path = LogicalPath.getInstance(Path.SRC_TEST_JAVA, moduleName);
  109. // Create the JavaType for the configuration class
  110. JavaType dodConfigurationClass =
  111. new JavaType(String.format("%s.dod.DataOnDemandConfiguration",
  112. typeLocationService.getTopLevelPackageForModule(module), moduleName));
  113. final String declaredByMetadataId =
  114. PhysicalTypeIdentifier.createIdentifier(dodConfigurationClass, path);
  115. if (metadataService.get(declaredByMetadataId) != null) {
  116. // The file already exists
  117. return new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId).getName();
  118. }
  119. // Create the CID builder
  120. ClassOrInterfaceTypeDetailsBuilder cidBuilder =
  121. new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId, Modifier.PUBLIC,
  122. dodConfigurationClass, PhysicalTypeCategory.CLASS);
  123. cidBuilder.addAnnotation(new AnnotationMetadataBuilder(
  124. RooJavaType.ROO_JPA_DATA_ON_DEMAND_CONFIGURATION));
  125. // Write changes to disk
  126. final ClassOrInterfaceTypeDetails configDodCid = cidBuilder.build();
  127. typeManagementService.createOrUpdateTypeOnDisk(configDodCid);
  128. return configDodCid.getName();
  129. }
  130. @Override
  131. public JavaType createEntityFactory(JavaType currentEntity) {
  132. Validate.notNull(currentEntity, "Entity to produce a data on demand provider for is required");
  133. // Verify the requested entity actually exists as a class and is not
  134. // abstract
  135. final ClassOrInterfaceTypeDetails cid = getEntityDetails(currentEntity);
  136. Validate.isTrue(cid.getPhysicalTypeCategory() == PhysicalTypeCategory.CLASS,
  137. "Type %s is not a class", currentEntity.getFullyQualifiedTypeName());
  138. Validate.isTrue(!Modifier.isAbstract(cid.getModifier()), "Type %s is abstract",
  139. currentEntity.getFullyQualifiedTypeName());
  140. // Check if the requested entity is a JPA @Entity
  141. final MemberDetails memberDetails =
  142. memberDetailsScanner.getMemberDetails(JpaDataOnDemandCreator.class.getName(), cid);
  143. final AnnotationMetadata entityAnnotation = memberDetails.getAnnotation(ENTITY);
  144. Validate.isTrue(entityAnnotation != null, "Type %s must be a JPA entity type",
  145. currentEntity.getFullyQualifiedTypeName());
  146. // Get related entities
  147. List<JavaType> entities = getEntityAndRelatedEntitiesList(currentEntity);
  148. // Get test Path for module
  149. final LogicalPath path = LogicalPath.getInstance(Path.SRC_TEST_JAVA, currentEntity.getModule());
  150. JavaType currentEntityFactory = null;
  151. for (JavaType entity : entities) {
  152. // Create the JavaType for the configuration class
  153. JavaType factoryClass =
  154. new JavaType(String.format("%s.dod.%sFactory", entity.getPackage()
  155. .getFullyQualifiedPackageName(), entity.getSimpleTypeName()), entity.getModule());
  156. final String declaredByMetadataId =
  157. PhysicalTypeIdentifier.createIdentifier(factoryClass, path);
  158. if (metadataService.get(declaredByMetadataId) != null) {
  159. // The file already exists
  160. continue;
  161. }
  162. // Create the CID builder
  163. ClassOrInterfaceTypeDetailsBuilder cidBuilder =
  164. new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId, Modifier.PUBLIC,
  165. factoryClass, PhysicalTypeCategory.CLASS);
  166. // Add @RooEntityFactory annotation
  167. AnnotationMetadataBuilder entityFactoryAnnotation =
  168. new AnnotationMetadataBuilder(RooJavaType.ROO_JPA_ENTITY_FACTORY);
  169. entityFactoryAnnotation.addClassAttribute("entity", entity);
  170. cidBuilder.addAnnotation(entityFactoryAnnotation);
  171. // Write changes to disk
  172. typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());
  173. // First entity is current entity
  174. if (currentEntityFactory == null) {
  175. currentEntityFactory = cidBuilder.getName();
  176. }
  177. }
  178. return currentEntityFactory;
  179. }
  180. @Override
  181. public JavaType getDataOnDemand(JavaType entity) {
  182. Set<ClassOrInterfaceTypeDetails> dataOnDemandCids =
  183. typeLocationService
  184. .findClassesOrInterfaceDetailsWithAnnotation(RooJavaType.ROO_JPA_DATA_ON_DEMAND);
  185. JavaType typeToReturn = null;
  186. for (ClassOrInterfaceTypeDetails cid : dataOnDemandCids) {
  187. if (entity.equals((JavaType) cid.getAnnotation(RooJavaType.ROO_JPA_DATA_ON_DEMAND)
  188. .getAttribute("entity").getValue())) {
  189. typeToReturn = cid.getName();
  190. break;
  191. }
  192. }
  193. return typeToReturn;
  194. }
  195. @Override
  196. public JavaType getDataOnDemandConfiguration() {
  197. Set<JavaType> dodConfigurationTypes =
  198. typeLocationService
  199. .findTypesWithAnnotation(RooJavaType.ROO_JPA_DATA_ON_DEMAND_CONFIGURATION);
  200. if (!dodConfigurationTypes.isEmpty()) {
  201. return dodConfigurationTypes.iterator().next();
  202. }
  203. return null;
  204. }
  205. @Override
  206. public JavaType getDataOnDemandConfiguration(String moduleName) {
  207. Set<JavaType> dodConfigurationTypes =
  208. typeLocationService
  209. .findTypesWithAnnotation(RooJavaType.ROO_JPA_DATA_ON_DEMAND_CONFIGURATION);
  210. Iterator<JavaType> it = dodConfigurationTypes.iterator();
  211. while (it.hasNext()) {
  212. JavaType dodConfigType = it.next();
  213. if (dodConfigType.getModule().equals(moduleName)) {
  214. return dodConfigType;
  215. }
  216. }
  217. return null;
  218. }
  219. @Override
  220. public JavaType getEntityFactory(JavaType entity) {
  221. Set<ClassOrInterfaceTypeDetails> dataOnDemandCids =
  222. typeLocationService
  223. .findClassesOrInterfaceDetailsWithAnnotation(RooJavaType.ROO_JPA_ENTITY_FACTORY);
  224. JavaType typeToReturn = null;
  225. for (ClassOrInterfaceTypeDetails cid : dataOnDemandCids) {
  226. if (entity.equals((JavaType) cid.getAnnotation(RooJavaType.ROO_JPA_ENTITY_FACTORY)
  227. .getAttribute("entity").getValue())) {
  228. typeToReturn = cid.getName();
  229. break;
  230. }
  231. }
  232. return typeToReturn;
  233. }
  234. /**
  235. * Add maven-jar-plugin to provided module.
  236. *
  237. * @param moduleName the name of the module.
  238. */
  239. private void addMavenJarPlugin(String moduleName) {
  240. // Add plugin maven-jar-plugin
  241. Pom module = projectOperations.getPomFromModuleName(moduleName);
  242. // Stop if the plugin is already installed
  243. for (final Plugin plugin : module.getBuildPlugins()) {
  244. if (plugin.getArtifactId().equals(MAVEN_JAR_PLUGIN)) {
  245. return;
  246. }
  247. }
  248. final Element configuration = XmlUtils.getConfiguration(getClass());
  249. final Element plugin = XmlUtils.findFirstElement("/configuration/plugin", configuration);
  250. // Now install the plugin itself
  251. if (plugin != null) {
  252. projectOperations.addBuildPlugin(moduleName, new Plugin(plugin), false);
  253. }
  254. }
  255. /**
  256. * Creates a new data-on-demand provider for an entity. Silently returns
  257. * if the DoD class already exists.
  258. *
  259. * @param entity to produce a DoD provider for
  260. * @param name the name of the new DoD class
  261. */
  262. private JavaType newDataOnDemandClass(JavaType entity, JavaType name) {
  263. Validate.notNull(entity, "Entity to produce a data on demand provider for is required");
  264. Validate.notNull(name, "Name of the new data on demand provider is required");
  265. final LogicalPath path = LogicalPath.getInstance(Path.SRC_TEST_JAVA, name.getModule());
  266. Validate.notNull(path, "Location of the new data on demand provider is required");
  267. // Add javax validation dependency
  268. projectOperations.addDependency(name.getModule(), VALIDATION_API_DEPENDENCY);
  269. // Verify the requested entity actually exists as a class and is not
  270. // abstract
  271. final ClassOrInterfaceTypeDetails cid = getEntityDetails(entity);
  272. Validate.isTrue(cid.getPhysicalTypeCategory() == PhysicalTypeCategory.CLASS,
  273. "Type %s is not a class", entity.getFullyQualifiedTypeName());
  274. Validate.isTrue(!Modifier.isAbstract(cid.getModifier()), "Type %s is abstract",
  275. entity.getFullyQualifiedTypeName());
  276. // Check if the requested entity is a JPA @Entity
  277. final MemberDetails memberDetails =
  278. memberDetailsScanner.getMemberDetails(JpaDataOnDemandCreator.class.getName(), cid);
  279. final AnnotationMetadata entityAnnotation = memberDetails.getAnnotation(ENTITY);
  280. Validate.isTrue(entityAnnotation != null, "Type %s must be a JPA entity type",
  281. entity.getFullyQualifiedTypeName());
  282. // Everything is OK to proceed
  283. final String declaredByMetadataId = PhysicalTypeIdentifier.createIdentifier(name, path);
  284. if (metadataService.get(declaredByMetadataId) != null) {
  285. // The file already exists
  286. return new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId).getName();
  287. }
  288. final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
  289. final List<AnnotationAttributeValue<?>> dodConfig =
  290. new ArrayList<AnnotationAttributeValue<?>>();
  291. dodConfig.add(new ClassAttributeValue(new JavaSymbolName("entity"), entity));
  292. annotations.add(new AnnotationMetadataBuilder(RooJavaType.ROO_JPA_DATA_ON_DEMAND, dodConfig));
  293. final ClassOrInterfaceTypeDetailsBuilder cidBuilder =
  294. new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId, Modifier.PUBLIC, name,
  295. PhysicalTypeCategory.CLASS);
  296. cidBuilder.setAnnotations(annotations);
  297. // Write changes on disk
  298. final ClassOrInterfaceTypeDetails dodClassCid = cidBuilder.build();
  299. typeManagementService.createOrUpdateTypeOnDisk(dodClassCid);
  300. return cid.getName();
  301. }
  302. /**
  303. * Searches the related entities of provided entity and returns a
  304. * {@link List} with all the related entities plus the provided entity.
  305. *
  306. * @param entity
  307. * the entity JavaType to search for its related entities.
  308. * @return a List with all the related entities.
  309. */
  310. private List<JavaType> getEntityAndRelatedEntitiesList(JavaType entity) {
  311. ClassOrInterfaceTypeDetails entityDetails = getEntityDetails(entity);
  312. JpaEntityMetadata entityMetadata =
  313. metadataService.get(JpaEntityMetadata.createIdentifier(entityDetails));
  314. List<JavaType> entitiesToCreateFactories = new ArrayList<JavaType>();
  315. entitiesToCreateFactories.add(entity);
  316. // Get related child entities
  317. for (RelationInfo info : entityMetadata.getRelationInfos().values()) {
  318. // Add to list
  319. if (!entitiesToCreateFactories.contains(info.childType)) {
  320. entitiesToCreateFactories.add(info.childType);
  321. }
  322. }
  323. return entitiesToCreateFactories;
  324. }
  325. /**
  326. * Returns the {@link ClassOrInterfaceTypeDetails} for the provided entity.
  327. *
  328. * @param entity
  329. * the entity to lookup required
  330. * @return the ClassOrInterfaceTypeDetails type details (never null; throws
  331. * an exception if it cannot be obtained or parsed)
  332. */
  333. private ClassOrInterfaceTypeDetails getEntityDetails(final JavaType entity) {
  334. final ClassOrInterfaceTypeDetails cid = typeLocationService.getTypeDetails(entity);
  335. Validate.notNull(cid, "Java source code details unavailable for type '%s'", entity);
  336. return cid;
  337. }
  338. }