package org.springframework.roo.addon.jpa.addon;
import static org.springframework.roo.model.JavaType.OBJECT;
import static org.springframework.roo.model.JpaJavaType.COLUMN;
import static org.springframework.roo.model.JpaJavaType.EMBEDDABLE;
import static org.springframework.roo.model.JpaJavaType.EMBEDDED_ID;
import static org.springframework.roo.model.JpaJavaType.GENERATED_VALUE;
import static org.springframework.roo.model.JpaJavaType.GENERATION_TYPE;
import static org.springframework.roo.model.JpaJavaType.ID;
import static org.springframework.roo.model.JpaJavaType.SEQUENCE_GENERATOR;
import static org.springframework.roo.model.JpaJavaType.VERSION;
import static org.springframework.roo.model.RooJavaType.ROO_EQUALS;
import static org.springframework.roo.model.RooJavaType.ROO_IDENTIFIER;
import static org.springframework.roo.model.RooJavaType.ROO_JAVA_BEAN;
import static org.springframework.roo.model.RooJavaType.ROO_SERIALIZABLE;
import static org.springframework.roo.model.RooJavaType.ROO_TO_STRING;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.Pair;
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.jpa.addon.entity.IdentifierStrategy;
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.application.config.ApplicationConfigService;
import org.springframework.roo.classpath.ModuleFeatureName;
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.FieldDetails;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.FieldMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.operations.InheritanceType;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.EnumDetails;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.JdkJavaType;
import org.springframework.roo.model.RooJavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.Dependency;
import org.springframework.roo.project.FeatureNames;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.project.Property;
import org.springframework.roo.project.maven.Pom;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.osgi.ServiceInstaceManager;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Implementation of {@link JpaOperations}.
*
* @author Stefan Schmidt
* @author Alan Stewart
* @author Juan Carlos García
* @author Paula Navarro
* @author Jose Manuel Vivó
* @author Sergio Clares
* @since 1.0
*/
@Component
@Service
public class JpaOperationsImpl implements JpaOperations {
protected final static Logger LOGGER = HandlerUtils.getLogger(JpaOperationsImpl.class);
// ------------ OSGi component attributes ----------------
private BundleContext context;
private static final String DATASOURCE_PREFIX = "spring.datasource";
private static final String DATABASE_DRIVER = "driver-class-name";
private static final String DATABASE_PASSWORD = "password";
private static final String DATABASE_URL = "url";
private static final String DATABASE_USERNAME = "username";
private static final String JNDI_NAME = "jndi-name";
private static final String HIBERNATE_NAMING_STRATEGY = "spring.jpa.hibernate.naming.strategy";
private static final String HIBERNATE_NAMING_STRATEGY_VALUE =
"org.hibernate.cfg.ImprovedNamingStrategy";
static final String POM_XML = "pom.xml";
private ServiceInstaceManager serviceManager = new ServiceInstaceManager();
private static final Property SPRINGLETS_VERSION_PROPERTY = new Property("springlets.version",
"1.2.0.RC1");
private static final Dependency SPRINGLETS_DATA_JPA_STARTER = new Dependency("io.springlets",
"springlets-data-jpa", "${springlets.version}");
private static final Dependency SPRINGLETS_DATA_COMMONS_STARTER = new Dependency("io.springlets",
"springlets-data-commons", "${springlets.version}");
private static final Dependency SPRINGLETS_CONTEXT_DEPENDENCY = new Dependency("io.springlets",
"springlets-context", "${springlets.version}");
protected void activate(final ComponentContext context) {
this.context = context.getBundleContext();
this.serviceManager.activate(this.context);
}
@Override
public void configureJpa(final OrmProvider ormProvider, final JdbcDatabase jdbcDatabase,
final Pom module, final String jndi, final String hostName, final String databaseName,
final String userName, final String password, final String profile, final boolean force) {
Validate.notNull(module, "Module required");
Validate.notNull(ormProvider, "ORM provider required");
if (StringUtils.isBlank(jndi)) {
Validate.notNull(jdbcDatabase, "JDBC database or JNDI data source required");
}
// Parse the configuration.xml file
final Element configuration = XmlUtils.getConfiguration(getClass());
// Get the first part of the XPath expressions for unwanted databases
// and ORM providers
if (jdbcDatabase != null) {
final String databaseXPath = getDbXPath(getUnwantedDatabases(jdbcDatabase));
final String providersXPath = getProviderXPath(getUnwantedOrmProviders(ormProvider));
final String startersXPath = getStarterXPath(getUnwantedOrmProviders(ormProvider));
// Updating pom.xml including necessary properties, dependencies and Spring Boot starters
updateDependencies(module, configuration, ormProvider, jdbcDatabase, startersXPath,
providersXPath, databaseXPath, profile);
}
// Update Spring Config File with spring.datasource.* domain properties
updateApplicationProperties(module.getModuleName(), ormProvider, jdbcDatabase, hostName,
databaseName, userName, password, jndi, profile, force);
}
@Override
public boolean isJpaInstallationPossible() {
return getProjectOperations().isFocusedProjectAvailable();
}
@Override
public void newEmbeddableClass(final JavaType name, final boolean serializable) {
Validate.notNull(name, "Embeddable name required");
final String declaredByMetadataId =
PhysicalTypeIdentifier.createIdentifier(name,
getPathResolver().getFocusedPath(Path.SRC_MAIN_JAVA));
final List<AnnotationMetadataBuilder> annotations =
new ArrayList<AnnotationMetadataBuilder>(Arrays.asList(new AnnotationMetadataBuilder(
ROO_JAVA_BEAN), new AnnotationMetadataBuilder(ROO_TO_STRING),
new AnnotationMetadataBuilder(EMBEDDABLE)));
if (serializable) {
annotations.add(new AnnotationMetadataBuilder(ROO_SERIALIZABLE));
}
final int modifier = Modifier.PUBLIC;
final ClassOrInterfaceTypeDetailsBuilder cidBuilder =
new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId, modifier, name,
PhysicalTypeCategory.CLASS);
cidBuilder.setAnnotations(annotations);
getTypeManagementService().createOrUpdateTypeOnDisk(cidBuilder.build());
getProjectOperations().addDependency(name.getModule(),
new Dependency("org.springframework.boot", "spring-boot-starter-data-jpa", null));
}
@Override
public void newEntity(final JavaType name, final boolean createAbstract,
final JavaType superclass, final JavaType implementsType, final String identifierField,
final JavaType identifierType, final String identifierColumn, final String sequenceName,
final IdentifierStrategy identifierStrategy, final String versionField,
final JavaType versionType, final String versionColumn,
final InheritanceType inheritanceType, final List<AnnotationMetadataBuilder> annotations) {
Validate.notNull(name, "Entity name required");
Validate.isTrue(!JdkJavaType.isPartOfJavaLang(name.getSimpleTypeName()),
"Entity name '%s' must not be part of java.lang", name.getSimpleTypeName());
getProjectOperations().setModule(getProjectOperations().getPomFromModuleName(name.getModule()));
// Add springlets-context dependency
getProjectOperations().addDependency(name.getModule(), SPRINGLETS_CONTEXT_DEPENDENCY);
getProjectOperations().addProperty("", SPRINGLETS_VERSION_PROPERTY);
int modifier = Modifier.PUBLIC;
if (createAbstract) {
modifier |= Modifier.ABSTRACT;
}
final String declaredByMetadataId =
PhysicalTypeIdentifier.createIdentifier(name,
getPathResolver().getFocusedPath(Path.SRC_MAIN_JAVA));
final ClassOrInterfaceTypeDetailsBuilder cidBuilder =
new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId, modifier, name,
PhysicalTypeCategory.CLASS);
if (!superclass.equals(OBJECT)) {
final ClassOrInterfaceTypeDetails superclassClassOrInterfaceTypeDetails =
getTypeLocationService().getTypeDetails(superclass);
if (superclassClassOrInterfaceTypeDetails != null) {
cidBuilder.setSuperclass(new ClassOrInterfaceTypeDetailsBuilder(
superclassClassOrInterfaceTypeDetails));
//Add dependency with superclass module
getProjectOperations().addModuleDependency(superclass.getModule());
}
}
cidBuilder.setExtendsTypes(Arrays.asList(superclass));
if (implementsType != null) {
final Set<JavaType> implementsTypes = new LinkedHashSet<JavaType>();
final ClassOrInterfaceTypeDetails typeDetails =
getTypeLocationService().getTypeDetails(declaredByMetadataId);
if (typeDetails != null) {
implementsTypes.addAll(typeDetails.getImplementsTypes());
}
implementsTypes.add(implementsType);
cidBuilder.setImplementsTypes(implementsTypes);
//Add dependency with implementsType modules
getProjectOperations().addModuleDependency(implementsType.getModule());
}
// Set annotations to new entity
cidBuilder.setAnnotations(annotations);
// Write entity on disk
ClassOrInterfaceTypeDetails entityDetails = cidBuilder.build();
getTypeManagementService().createOrUpdateTypeOnDisk(entityDetails);
// If a parent is defined, it must provide the identifier field.
// Adding identifier and version fields
if (superclass.equals(OBJECT)) {
getTypeManagementService().addField(
getIdentifierField(name, identifierField, identifierType, identifierColumn, sequenceName,
identifierStrategy, inheritanceType), true);
getTypeManagementService().addField(
getVersionField(name, versionField, versionType, versionColumn), true);
}
// Add persistence dependencies to entity module if necessary
// Don't need to add them if spring-boot-starter-data-jpa is present, often in single module project
if (!getProjectOperations().getFocusedModule().hasDependencyExcludingVersion(
new Dependency("org.springframework.boot", "spring-boot-starter-data-jpa", null))) {
List<Dependency> dependencies = new ArrayList<Dependency>();
dependencies.add(new Dependency("org.springframework", "spring-aspects", null));
dependencies.add(new Dependency("org.springframework", "spring-context", null));
dependencies.add(new Dependency("org.springframework.data", "spring-data-jpa", null));
dependencies.add(new Dependency("org.springframework.data", "spring-data-commons", null));
dependencies.add(new Dependency("org.eclipse.persistence", "javax.persistence", null));
getProjectOperations().addDependencies(getProjectOperations().getFocusedModuleName(),
dependencies);
}
}
@Override
public void updateEmbeddableToIdentifier(final JavaType identifierType,
final String identifierField, final String identifierColumn) {
Validate.notNull(identifierType, "Identifier type required");
// Get details from existing JavaType
ClassOrInterfaceTypeDetailsBuilder cidBuilder =
new ClassOrInterfaceTypeDetailsBuilder(getTypeLocationService().getTypeDetails(
identifierType));
// Create @RooIdentifier with getters and setters
AnnotationMetadataBuilder rooIdentifier = new AnnotationMetadataBuilder(ROO_IDENTIFIER);
rooIdentifier.addBooleanAttribute("settersByDefault", true);
final List<AnnotationMetadataBuilder> identifierAnnotations =
Arrays.asList(new AnnotationMetadataBuilder(ROO_TO_STRING), new AnnotationMetadataBuilder(
ROO_EQUALS), rooIdentifier);
cidBuilder.setAnnotations(identifierAnnotations);
// Set implement Serializable
List<JavaType> implementTypes = new ArrayList<JavaType>();
implementTypes.add(JdkJavaType.SERIALIZABLE);
cidBuilder.setImplementsTypes(implementTypes);
getTypeManagementService().createOrUpdateTypeOnDisk(cidBuilder.build());
}
@Override
public SortedSet<String> getDatabaseProperties(String profile) {
return getApplicationConfigService().getPropertyKeys(DATASOURCE_PREFIX, true, profile);
}
@Override
public boolean isJpaInstalled() {
return getProjectOperations().isFeatureInstalled(FeatureNames.JPA);
}
private String getConnectionString(final JdbcDatabase jdbcDatabase, String hostName,
final String databaseName) {
String connectionString = jdbcDatabase.getConnectionString();
if (connectionString.contains("TO_BE_CHANGED_BY_ADDON")) {
connectionString =
connectionString.replace("TO_BE_CHANGED_BY_ADDON",
StringUtils.isNotBlank(databaseName) ? databaseName : getProjectOperations()
.getProjectName(""));
} else {
if (StringUtils.isNotBlank(databaseName)) {
// Oracle uses a different connection URL - see ROO-1203
final String dbDelimiter = jdbcDatabase == JdbcDatabase.ORACLE ? ":" : "/";
connectionString += dbDelimiter + databaseName;
}
}
if (StringUtils.isBlank(hostName)) {
hostName = "localhost";
}
return connectionString.replace("HOST_NAME", hostName);
}
private String getDbXPath(final List<JdbcDatabase> databases) {
final StringBuilder builder = new StringBuilder("/configuration/databases/database[");
for (int i = 0; i < databases.size(); i++) {
if (i > 0) {
builder.append(" or ");
}
builder.append("@id = '");
builder.append(databases.get(i).getKey());
builder.append("'");
}
builder.append("]");
return builder.toString();
}
private String getProviderXPath(final List<OrmProvider> ormProviders) {
final StringBuilder builder = new StringBuilder("/configuration/ormProviders/provider[");
for (int i = 0; i < ormProviders.size(); i++) {
if (i > 0) {
builder.append(" or ");
}
builder.append("@id = '");
builder.append(ormProviders.get(i).name());
builder.append("'");
}
builder.append("]");
return builder.toString();
}
private String getStarterXPath(final List<OrmProvider> ormProviders) {
final StringBuilder builder = new StringBuilder("/configuration/starter/provider[");
for (int i = 0; i < ormProviders.size(); i++) {
if (i > 0) {
builder.append(" or ");
}
builder.append("@id = '");
builder.append(ormProviders.get(i).name());
builder.append("'");
}
builder.append("]");
return builder.toString();
}
private List<JdbcDatabase> getUnwantedDatabases(final JdbcDatabase jdbcDatabase) {
final List<JdbcDatabase> unwantedDatabases = new ArrayList<JdbcDatabase>();
for (final JdbcDatabase database : JdbcDatabase.values()) {
if (!database.getKey().equals(jdbcDatabase.getKey())
&& !database.getDriverClassName().equals(jdbcDatabase.getDriverClassName())) {
unwantedDatabases.add(database);
}
}
return unwantedDatabases;
}
private List<OrmProvider> getUnwantedOrmProviders(final OrmProvider ormProvider) {
final List<OrmProvider> unwantedOrmProviders =
new LinkedList<OrmProvider>(Arrays.asList(OrmProvider.values()));
unwantedOrmProviders.remove(ormProvider);
return unwantedOrmProviders;
}
public boolean hasDatabaseProperties() {
SortedSet<String> databaseProperties =
getApplicationConfigService().getPropertyKeys(DATASOURCE_PREFIX, false, null);
return !databaseProperties.isEmpty();
}
private void updateApplicationProperties(final String moduleName, final OrmProvider ormProvider,
final JdbcDatabase jdbcDatabase, final String hostName, final String databaseName,
String userName, final String password, String jndi, String profile, boolean force) {
// Check if jndi is blank. If is blank, include database properties on
// application.properties file
if (StringUtils.isBlank(jndi)) {
final String connectionString = getConnectionString(jdbcDatabase, hostName, databaseName);
// Getting current properties
final String driver =
getApplicationConfigService().getProperty(moduleName, DATASOURCE_PREFIX, DATABASE_DRIVER,
profile);
final String url =
getApplicationConfigService().getProperty(moduleName, DATASOURCE_PREFIX, DATABASE_URL,
profile);
final String uname =
getApplicationConfigService().getProperty(moduleName, DATASOURCE_PREFIX,
DATABASE_USERNAME, profile);
final String pwd =
getApplicationConfigService().getProperty(moduleName, DATASOURCE_PREFIX,
DATABASE_PASSWORD, profile);
boolean hasChanged = !jdbcDatabase.getDriverClassName().equals(driver);
hasChanged |= !connectionString.equals(url);
hasChanged |= !StringUtils.stripToEmpty(userName).equals(uname);
hasChanged |= !StringUtils.stripToEmpty(password).equals(pwd);
if (!hasChanged) {
LOGGER.log(Level.INFO, "INFO: No changes are needed.");
return;
}
// Write changes to Spring Config file
Map<String, String> props = new HashMap<String, String>();
props.put(DATABASE_URL, connectionString);
props.put(DATABASE_DRIVER, jdbcDatabase.getDriverClassName());
if (userName != null) {
props.put(DATABASE_USERNAME, StringUtils.stripToEmpty(userName));
} else {
getApplicationConfigService().removeProperty(moduleName, DATASOURCE_PREFIX,
DATABASE_USERNAME, profile);
}
if (password != null) {
props.put(DATABASE_PASSWORD, StringUtils.stripToEmpty(password));
} else {
getApplicationConfigService().removeProperty(moduleName, DATASOURCE_PREFIX,
DATABASE_PASSWORD, profile);
}
getApplicationConfigService().addProperties(moduleName, DATASOURCE_PREFIX, props, profile,
force);
// Remove jndi property
getApplicationConfigService().removeProperty(moduleName, DATASOURCE_PREFIX, JNDI_NAME,
profile);
} else {
final String jndiProperty =
getApplicationConfigService().getProperty(moduleName, DATASOURCE_PREFIX, JNDI_NAME);
boolean hasChanged =
jndiProperty == null || !jndiProperty.equals(StringUtils.stripToEmpty(jndi));
if (!hasChanged) {
// No changes from existing database configuration so exit now
return;
}
// Write changes to Spring Config file defined in profile
Map<String, String> props = new HashMap<String, String>();
props.put(JNDI_NAME, jndi);
getApplicationConfigService().addProperties(moduleName, DATASOURCE_PREFIX, props, profile,
force);
// Remove old properties if existing
getApplicationConfigService().removeProperty(moduleName, DATASOURCE_PREFIX, DATABASE_URL,
profile);
getApplicationConfigService().removeProperty(moduleName, DATASOURCE_PREFIX, DATABASE_DRIVER,
profile);
getApplicationConfigService().removeProperty(moduleName, DATASOURCE_PREFIX,
DATABASE_USERNAME, profile);
getApplicationConfigService().removeProperty(moduleName, DATASOURCE_PREFIX,
DATABASE_PASSWORD, profile);
}
// Add Hibernate naming strategy property
if (ormProvider.toString().equals(OrmProvider.HIBERNATE.toString())) {
getApplicationConfigService().addProperty(moduleName, HIBERNATE_NAMING_STRATEGY,
HIBERNATE_NAMING_STRATEGY_VALUE, profile, force);
}
// Add dev properties
getApplicationConfigService().addProperty(moduleName, "spring.jpa.show-sql", "true", "dev",
true);
getApplicationConfigService().addProperty(moduleName,
"spring.jpa.properties.hibernate.format_sql", "true", "dev", true);
getApplicationConfigService().addProperty(moduleName,
"spring.jpa.properties.hibernate.generate_statistics", "true", "dev", true);
getApplicationConfigService().addProperty(moduleName, "logging.level.org.hibernate.stat",
"DEBUG", "dev", true);
getApplicationConfigService().addProperty(moduleName,
"logging.level.com.querydsl.jpa.impl.JPAQuery", "DEBUG", "dev", true);
getApplicationConfigService().addProperty(moduleName, "logging.pattern.level",
"%5p - QP:%X{querydsl.parameters} -", "dev", true);
}
/**
* Updates the POM with the dependencies required for the given database and
* ORM provider, removing any other persistence-related dependencies
*
* @param configuration
* @param ormProvider
* @param jdbcDatabase
* @param startersXPath
* @param profile
*/
private void updateDependencies(final Pom module, final Element configuration,
final OrmProvider ormProvider, final JdbcDatabase jdbcDatabase, final String startersXPath,
final String providersXPath, final String databaseXPath, String profile) {
final List<Dependency> requiredDependencies = new ArrayList<Dependency>();
final List<Element> starterDependencies =
XmlUtils.findElements("/configuration/starter/provider[@id = '" + ormProvider.name()
+ "']/dependencies/dependency", configuration);
for (final Element dependencyElement : starterDependencies) {
requiredDependencies.add(new Dependency(dependencyElement));
}
// Add database dependencies
final List<Element> databaseDependencies =
XmlUtils.findElements(jdbcDatabase.getConfigPrefix() + "/dependencies/dependency",
configuration);
for (final Element dependencyElement : databaseDependencies) {
requiredDependencies.add(new Dependency(dependencyElement));
}
if (jdbcDatabase.toString().equals(JdbcDatabase.ORACLE.toString())) {
LOGGER
.warning("Oracle drivers aren't in Maven public repositories!! You should include them manually in your local Maven repository.");
}
final List<Element> ormDependencies =
XmlUtils.findElements(ormProvider.getConfigPrefix() + "/dependencies/dependency",
configuration);
for (final Element dependencyElement : ormDependencies) {
requiredDependencies.add(new Dependency(dependencyElement));
}
// Hard coded to JPA & Hibernate Validator for now
final List<Element> jpaDependencies =
XmlUtils.findElements(
"/configuration/persistence/provider[@id = 'JPA']/dependencies/dependency",
configuration);
for (final Element dependencyElement : jpaDependencies) {
requiredDependencies.add(new Dependency(dependencyElement));
}
final List<Element> springDependencies =
XmlUtils.findElements("/configuration/spring/dependencies/dependency", configuration);
for (final Element dependencyElement : springDependencies) {
requiredDependencies.add(new Dependency(dependencyElement));
}
final List<Element> commonDependencies =
XmlUtils.findElements("/configuration/common/dependencies/dependency", configuration);
for (final Element dependencyElement : commonDependencies) {
requiredDependencies.add(new Dependency(dependencyElement));
}
// Add properties
List<Element> properties = XmlUtils.findElements("/configuration/properties/*", configuration);
for (Element property : properties) {
getProjectOperations().addProperty("", new Property(property));
}
// Add dependencies used by other profiles, excluding the current profile
List<String> profiles =
getApplicationConfigService().getApplicationProfiles(module.getModuleName());
profiles.remove(profile);
for (String applicationProfile : profiles) {
// Extract database
final String driver =
getApplicationConfigService().getProperty(module.getModuleName(), DATASOURCE_PREFIX,
DATABASE_DRIVER, applicationProfile);
for (JdbcDatabase database : JdbcDatabase.values()) {
if (database.getDriverClassName().equals(driver)) {
for (final Element dependencyElement : XmlUtils.findElements(database.getConfigPrefix()
+ "/dependencies/dependency", configuration)) {
requiredDependencies.add(new Dependency(dependencyElement));
}
break;
}
}
}
// Remove redundant dependencies
final List<Dependency> redundantDependencies = new ArrayList<Dependency>();
redundantDependencies.addAll(getDependencies(databaseXPath, configuration));
redundantDependencies.addAll(getDependencies(startersXPath, configuration));
redundantDependencies.addAll(getDependencies(providersXPath, configuration));
// Don't remove any we actually need
redundantDependencies.removeAll(requiredDependencies);
// Update the POM
getProjectOperations().removeDependencies(module.getModuleName(), redundantDependencies);
getProjectOperations().addDependencies(module.getModuleName(), requiredDependencies);
// Add database test dependency to repository module if it is multimodule project
// and some repository has been already added
if (getProjectOperations().isMultimoduleProject()) {
Set<JavaType> repositoryTypes =
getTypeLocationService().findTypesWithAnnotation(RooJavaType.ROO_REPOSITORY_JPA);
if (!repositoryTypes.isEmpty()) {
Iterator<JavaType> repositoryIterator = repositoryTypes.iterator();
while (repositoryIterator.hasNext()) {
JavaType repositoryType = repositoryIterator.next();
String moduleName = repositoryType.getModule();
// Remove redundant dependencies from modules with repository classes
getProjectOperations().removeDependencies(moduleName, redundantDependencies);
// Add new database dependencies
addDatabaseDependencyWithTestScope(moduleName, profile, jdbcDatabase.getConfigPrefix());
}
}
}
// Include Springlets Starter project dependencies and properties
getProjectOperations().addProperty("", SPRINGLETS_VERSION_PROPERTY);
// If current project is a multimodule project, include dependencies
// first
// on dependencyManagement and then on current module
getProjectOperations().addDependency(module.getModuleName(), SPRINGLETS_DATA_JPA_STARTER);
getProjectOperations().addDependency(module.getModuleName(), SPRINGLETS_DATA_COMMONS_STARTER);
}
/**
* Add datasource dependency for testing purposes in a module with
* repository classes. This method can be called when installing/changing
* persistence database or when adding repositories to the project.
*
* @param repositoryModuleName
* the module name where the dependency should be added.
* @param profile
* the profile used to obtain the datasource property from spring
* config file.
* @param databaseConfigPrefix
* the database prefix used to find the right dependency in the
* configuration file. It could be null if called from repository
* commands.
*/
public void addDatabaseDependencyWithTestScope(String repositoryModuleName, String profile,
String databaseConfigPrefix) {
// Get configuration Element from configuration.xml
final Element configuration = XmlUtils.getConfiguration(getClass());
// If databaseConfigPrefix is null, get prefix from properties file
if (databaseConfigPrefix == null) {
// Get application module where properties file should be located
List<Pom> modules =
(List<Pom>) getTypeLocationService().getModules(ModuleFeatureName.APPLICATION);
if (modules.size() == 0) {
throw new RuntimeException(String.format("ERROR: Not found a module with %s feature",
ModuleFeatureName.APPLICATION));
}
if (profile == null) {
// Add the database dependency of each profile
List<String> profiles =
getApplicationConfigService().getApplicationProfiles(modules.get(0).getModuleName());
for (String applicationProfile : profiles) {
// // Find the driver name to obtain the right dependency to add
final String driver =
getApplicationConfigService().getProperty(modules.get(0).getModuleName(),
DATASOURCE_PREFIX, DATABASE_DRIVER, applicationProfile);
for (JdbcDatabase database : JdbcDatabase.values()) {
if (database.getDriverClassName().equals(driver)) {
databaseConfigPrefix = database.getConfigPrefix();
addTestScopedDependency(repositoryModuleName, databaseConfigPrefix, configuration);
}
}
}
} else {
// Find the driver name to obtain the right dependency to add
String driver =
getApplicationConfigService().getProperty(modules.get(0).getModuleName(),
DATASOURCE_PREFIX, DATABASE_DRIVER, profile);
// Find the prefix value from JdbcDatabase enum
JdbcDatabase[] jdbcDatabaseValues = JdbcDatabase.values();
for (JdbcDatabase database : jdbcDatabaseValues) {
if (database.getDriverClassName().equals(driver)) {
databaseConfigPrefix = database.getConfigPrefix();
addTestScopedDependency(repositoryModuleName, databaseConfigPrefix, configuration);
}
}
}
} else {
// No need to find the driver name to obtain database prefix
addTestScopedDependency(repositoryModuleName, databaseConfigPrefix, configuration);
}
}
/**
* Gets database dependency from config file and adds it with test scope
*
* @param moduleName
* the module which dependency should be added
* @param databaseConfigPrefix
* the prefix name for choosing the dependency to add
* @param configuration
* the configuration file with the dependencies to copy from
*/
private void addTestScopedDependency(String moduleName, String databaseConfigPrefix,
final Element configuration) {
final List<Element> databaseDependencies =
XmlUtils.findElements(databaseConfigPrefix + "/dependencies/dependency", configuration);
for (final Element dependencyElement : databaseDependencies) {
// Change scope from provided to test
NodeList childNodes = dependencyElement.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
final Node node = childNodes.item(i);
if (node != null && node.getNodeType() == Node.ELEMENT_NODE
&& node.getNodeName().equals("scope")) {
node.setTextContent("test");
}
}
// Add dependency
getProjectOperations().addDependency(moduleName, new Dependency(dependencyElement));
}
}
private List<Dependency> getDependencies(final String xPathExpression, final Element configuration) {
final List<Dependency> dependencies = new ArrayList<Dependency>();
for (final Element dependencyElement : XmlUtils.findElements(xPathExpression
+ "/dependencies/dependency", configuration)) {
final Dependency dependency = new Dependency(dependencyElement);
dependencies.add(dependency);
}
return dependencies;
}
@Override
public void deleteEntity(JavaType entity) {
final String entityFilePathIdentifier =
getPathResolver().getCanonicalPath(entity.getModule(), Path.SRC_MAIN_JAVA, entity);
if (getFileManager().exists(entityFilePathIdentifier)) {
getFileManager().delete(entityFilePathIdentifier);
}
}
@Override
public Pair<FieldMetadata, RelationInfo> getFieldChildPartOfCompositionRelation(
ClassOrInterfaceTypeDetails entityCdi) {
JavaType domainType = entityCdi.getType();
List<Pair<FieldMetadata, RelationInfo>> relations = getFieldChildPartOfRelation(entityCdi);
if (relations.isEmpty()) {
return null;
}
JpaEntityMetadata parent;
JavaType parentType;
RelationInfo info;
List<Pair<FieldMetadata, RelationInfo>> compositionRelation =
new ArrayList<Pair<FieldMetadata, RelationInfo>>();
for (Pair<FieldMetadata, RelationInfo> field : relations) {
if (field.getRight().type == JpaRelationType.COMPOSITION) {
compositionRelation.add(field);
}
}
Validate.isTrue(compositionRelation.size() <= 1,
"Entity %s has more than one relations of composition as child part: ", domainType,
StringUtils.join(getFieldNamesOfRelationList(compositionRelation), ","));
if (compositionRelation.isEmpty()) {
return null;
}
return compositionRelation.get(0);
}
private List<String> getFieldNamesOfRelationList(
List<Pair<FieldMetadata, RelationInfo>> compositionRelation) {
List<String> names = new ArrayList<String>(compositionRelation.size());
for (Pair<FieldMetadata, RelationInfo> pair : compositionRelation) {
names.add(pair.getLeft().getFieldName().getSymbolName());
}
return names;
}
@Override
public List<Pair<FieldMetadata, RelationInfo>> getFieldChildPartOfRelation(JavaType entity) {
ClassOrInterfaceTypeDetails entityDetails = getTypeLocationService().getTypeDetails(entity);
return getFieldChildPartOfRelation(entityDetails);
}
@Override
public List<Pair<FieldMetadata, RelationInfo>> getFieldChildPartOfRelation(
ClassOrInterfaceTypeDetails entityCdi) {
JavaType domainType = entityCdi.getType();
JpaEntityMetadata entityMetadata = getJpaEntityMetadata(entityCdi);
Validate.notNull(entityMetadata, "%s should be a Jpa Entity", domainType);
Map<String, FieldMetadata> relations = entityMetadata.getRelationsAsChild();
List<Pair<FieldMetadata, RelationInfo>> childRelations =
new ArrayList<Pair<FieldMetadata, RelationInfo>>();
JpaEntityMetadata parent;
JavaType parentType;
RelationInfo info;
for (Entry<String, FieldMetadata> fieldEntry : relations.entrySet()) {
parentType = fieldEntry.getValue().getFieldType().getBaseType();
parent = getJpaEntityMetadata(parentType);
Validate.notNull(parent,
"Can't get information about Entity %s which is declared as parent in %s.%s field",
parentType, domainType, fieldEntry.getKey());
info = parent.getRelationInfosByMappedBy(domainType, fieldEntry.getKey());
if (info != null) {
childRelations.add(Pair.of(fieldEntry.getValue(), info));
}
}
return childRelations;
}
@Override
public Pair<FieldMetadata, RelationInfo> getFieldChildPartOfCompositionRelation(JavaType entity) {
return getFieldChildPartOfCompositionRelation(getTypeLocationService().getTypeDetails(entity));
}
/**
* This method generates the identifier field using the provided values.
*
* @param entity
* @param identifierField
* @param identifierType
* @param identifierColumn
* @param sequenceName
* @param identifierStrategy
* @param inheritanceType
* @return
*/
private FieldMetadata getIdentifierField(final JavaType entity, String identifierField,
final JavaType identifierType, final String identifierColumn, final String sequenceName,
IdentifierStrategy identifierStrategy, InheritanceType inheritanceType) {
final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
final boolean hasIdClass = !(identifierType.isCoreType());
final JavaType annotationType = hasIdClass ? EMBEDDED_ID : ID;
annotations.add(new AnnotationMetadataBuilder(annotationType));
if (StringUtils.isEmpty(identifierField)) {
identifierField = "id";
}
// Compute the column name, as required
if (!hasIdClass) {
if (!"".equals(sequenceName)) {
// ROO-3719: Add SEQUENCE as @GeneratedValue strategy
// Check if provided identifierStrategy is valid
boolean isValidIdentifierStrategy = false;
if (identifierStrategy != null) {
for (IdentifierStrategy identifierStrategyType : IdentifierStrategy.values()) {
if (identifierStrategyType.name().equals(identifierStrategy.name())) {
isValidIdentifierStrategy = true;
break;
}
}
}
if (!isValidIdentifierStrategy) {
identifierStrategy = IdentifierStrategy.AUTO;
}
// ROO-746: Use @GeneratedValue(strategy = GenerationType.TABLE)
// If the root of the governor declares @Inheritance(strategy =
// InheritanceType.TABLE_PER_CLASS)
if (IdentifierStrategy.AUTO.name().equals(identifierStrategy.name())) {
if (inheritanceType != null) {
if ("TABLE_PER_CLASS".equals(inheritanceType.name())) {
identifierStrategy = IdentifierStrategy.TABLE;
}
}
}
final AnnotationMetadataBuilder generatedValueBuilder =
new AnnotationMetadataBuilder(GENERATED_VALUE);
generatedValueBuilder.addEnumAttribute("strategy", new EnumDetails(GENERATION_TYPE,
new JavaSymbolName(identifierStrategy.name())));
if (StringUtils.isNotBlank(sequenceName)) {
final String sequenceKey = StringUtils.uncapitalize(entity.getSimpleTypeName()) + "Gen";
generatedValueBuilder.addStringAttribute("generator", sequenceKey);
final AnnotationMetadataBuilder sequenceGeneratorBuilder =
new AnnotationMetadataBuilder(SEQUENCE_GENERATOR);
sequenceGeneratorBuilder.addStringAttribute("name", sequenceKey);
sequenceGeneratorBuilder.addStringAttribute("sequenceName", sequenceName);
annotations.add(sequenceGeneratorBuilder);
}
annotations.add(generatedValueBuilder);
}
// User has specified alternative columnName
if (StringUtils.isNotBlank(identifierColumn)) {
final AnnotationMetadataBuilder columnBuilder = new AnnotationMetadataBuilder(COLUMN);
columnBuilder.addStringAttribute("name", identifierColumn);
annotations.add(columnBuilder);
}
}
FieldDetails identifierFieldDetails =
new FieldDetails(getTypeLocationService().getPhysicalTypeIdentifier(entity),
identifierType, new JavaSymbolName(identifierField));
identifierFieldDetails.setModifiers(Modifier.PRIVATE);
identifierFieldDetails.addAnnotations(annotations);
return new FieldMetadataBuilder(identifierFieldDetails).build();
}
/**
* This method generates the version field using the provided values
*
* @param entity
* @param versionField
* @param versionType
* @param versionColumn
* @return
*/
private FieldMetadata getVersionField(final JavaType entity, String versionField,
final JavaType versionType, final String versionColumn) {
if (StringUtils.isEmpty(versionField)) {
versionField = "version";
}
final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
annotations.add(new AnnotationMetadataBuilder(VERSION));
if (StringUtils.isNotEmpty(versionColumn)) {
final AnnotationMetadataBuilder columnBuilder = new AnnotationMetadataBuilder(COLUMN);
columnBuilder.addStringAttribute("name", versionColumn);
annotations.add(columnBuilder);
}
FieldDetails versionFieldDetails =
new FieldDetails(getTypeLocationService().getPhysicalTypeIdentifier(entity), versionType,
new JavaSymbolName(versionField));
versionFieldDetails.setModifiers(Modifier.PRIVATE);
versionFieldDetails.addAnnotations(annotations);
return new FieldMetadataBuilder(versionFieldDetails).build();
}
/**
* Gets JpaEntityMetadata by a javaType
* @param domainType
* @return
*/
private JpaEntityMetadata getJpaEntityMetadata(JavaType domainType) {
ClassOrInterfaceTypeDetails entityDetails = getTypeLocationService().getTypeDetails(domainType);
return getJpaEntityMetadata(entityDetails);
}
/**
* Gets JpaEntityMetadata by a javaType
* @param domainType
* @return
*/
private JpaEntityMetadata getJpaEntityMetadata(ClassOrInterfaceTypeDetails domainTypeDetails) {
final String entityMetadataId = JpaEntityMetadata.createIdentifier(domainTypeDetails);
JpaEntityMetadata entityMetadata = getMetadataService().get(entityMetadataId);
return entityMetadata;
}
private MetadataService getMetadataService() {
return serviceManager.getServiceInstance(this, MetadataService.class);
}
private FileManager getFileManager() {
return serviceManager.getServiceInstance(this, FileManager.class);
}
private PathResolver getPathResolver() {
return serviceManager.getServiceInstance(this, PathResolver.class);
}
private ProjectOperations getProjectOperations() {
return serviceManager.getServiceInstance(this, ProjectOperations.class);
}
private TypeLocationService getTypeLocationService() {
return serviceManager.getServiceInstance(this, TypeLocationService.class);
}
private TypeManagementService getTypeManagementService() {
return serviceManager.getServiceInstance(this, TypeManagementService.class);
}
private ApplicationConfigService getApplicationConfigService() {
return serviceManager.getServiceInstance(this, ApplicationConfigService.class);
}
/**
* FEATURE Methods
*/
public boolean isInstalledInModule(final String moduleName) {
Pom pom = getProjectOperations().getPomFromModuleName(moduleName);
if (pom == null) {
return false;
}
// Check if spring-boot-starter-data-jpa has been included
Set<Dependency> dependencies = pom.getDependencies();
Dependency starter =
new Dependency("org.springframework.boot", "spring-boot-starter-data-jpa", "");
boolean hasStarter = dependencies.contains(starter);
// Check existing application profiles
boolean existsSpringConfigProfileInModule = false;
List<String> applicationProfiles =
getApplicationConfigService().getApplicationProfiles(moduleName);
for (String profile : applicationProfiles) {
if (getApplicationConfigService().existsSpringConfigFile(moduleName, profile)) {
existsSpringConfigProfileInModule = true;
break;
}
}
return existsSpringConfigProfileInModule && hasStarter;
}
public String getName() {
return FeatureNames.JPA;
}
}