Skip to content

Commit

Permalink
Release v0.1.2 | Hotfix fix generated package paths when the config i…
Browse files Browse the repository at this point in the history
…s not provided (#17)
  • Loading branch information
Dhi13man committed Oct 6, 2023
1 parent 2b661b8 commit 704df75
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 80 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.MD
@@ -1,5 +1,17 @@
# Releases

## [0.1.2] - 6th October 2023

- HOTFIX: Fix `@EnableJpaRepositories.basePackages` being empty for secondary data sources
when `@EnableMultiDataSourceConfig.generatedRepositoryPackagePrefix` is not set. Instead, it will
be set to the package of the `@EnableMultiDataSourceConfig` annotated class followed by
`.generated.repositories` and then the snake-cased datasource name.
- Default `@EnableMultiDataSourceConfig.generatedConfigPackage` changed to the package of the
`@EnableMultiDataSourceConfig` annotated class followed by `.generated.config` instead of
`@EnableMultiDataSourceConfig.generatedRepositoryPackagePrefix` followed by `.config`.
- No repositories will be scanned for data sources which do not have a `@TargetDataSource`.
- Refactor internal logic for more readability.

## [0.1.1] - 26th September 2023

### [PR#13](https://github.com/Dhi13man/spring-multi-data-source/pull/13)
Expand Down
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -80,12 +80,12 @@ for configuring multi-data source configurations for a service. Let's break down
- `generatedConfigPackage`: The package where the generated data source configs will
be placed. The generated config class with relevant beans will follow a specific naming
format. If this is not specified, the generated config will be placed in the same package as
the class where this annotation is applied, followed by `.config`.
the class where this annotation is applied, followed by `.generated.config`.
- `generatedRepositoryPackagePrefix`: The prefix of the package where the generated copies
of the repositories will be placed. The generated repositories will follow a specific
naming format. If this is not specified, the generated repositories will be placed in the
same package as the class where this annotation is applied, followed by `.repositories` and
then `.<data_source_name>`.
same package as the class where this annotation is applied, followed by
`.generated.repositories` and then `.<data_source_name>`.
- `dataSourceConfigs`: An array of `@DataSourceConfig` annotations. Each annotation represents
a data source and its configuration.

Expand Down
Expand Up @@ -46,7 +46,7 @@
* The package where the generated data source configs will be placed.
* <p>
* If this is not provided, the generated config will be placed in the same package as the class
* annotated with @EnableMultiDataSourceConfig followed by .config
* annotated with @EnableMultiDataSourceConfig followed by .generated.config
* <p>
* The generated Config class with all the relevant beans will be placed in this package with the
* following format:
Expand All @@ -61,7 +61,7 @@
* The prefix of the package where the generated copies of the repositories will be placed.
* <p>
* If this is not provided, the generated repositories will be placed in the same package as the
* class annotated with @EnableMultiDataSourceConfig followed by .repositories and then
* class annotated with @EnableMultiDataSourceConfig followed by .generated.repositories and then
* .{snake_case_data_source_name}
* <p>
* The generated repositories will be placed in packages with the following format:
Expand Down
Expand Up @@ -73,30 +73,32 @@ public MultiDataSourceConfigGenerator(
* manager factory and transaction manager and provide the proper constants for the bean names, to
* conveniently auto-wire them where needed.
*
* @param dataSourceConfig the {@link DataSourceConfig} for which the
* configuration class is being generated
* @param dataSourceName the name of the data source for which the configuration
* class is being generated
* @param dataSourceConfigClassName the name of the data source configuration class being
* generated
* @param dataSourcePropertiesPath the path of where the properties of the data source are
* located in application.properties
* @param dataSourceRepositoryPackages the packages where the repositories associated with the
* data source are located
* @param dataSourceEntityPackages the exact packages where the entities associated with
* the data source are located
* @param generatedRepositoryPackagePrefix the prefix of the package where the generated copies of
* the repositories will be placed
* @param dataSourceConfig the {@link DataSourceConfig} for which the configuration
* class is being generated
* @param dataSourceName the name of the data source for which the configuration
* class is being generated
* @param dataSourceConfigClassName the name of the data source configuration class being
* generated
* @param dataSourcePropertiesPath the path of where the properties of the data source are
* located in application.properties
* @param repositoryPackagesToInclude the packages where the repositories associated with the data
* source are located (to be included in the
* {@link EnableJpaRepositories} annotation)
* @param repositoryPackagesToExclude the packages where the repositories associated with other
* data sources are located (to be excluded in the
* {@link EnableJpaRepositories} annotation)
* @param dataSourceEntityPackages the exact packages where the entities associated with the
* data source are located
* @return the {@link TypeSpec} for a data source Spring Configuration class
*/
public TypeSpec generateMultiDataSourceConfigTypeElement(
DataSourceConfig dataSourceConfig,
String dataSourceName,
String dataSourceConfigClassName,
String dataSourcePropertiesPath,
String[] dataSourceRepositoryPackages,
String[] dataSourceEntityPackages,
String generatedRepositoryPackagePrefix
String[] repositoryPackagesToInclude,
String[] repositoryPackagesToExclude,
String[] dataSourceEntityPackages
) {
// Constants exposing important bean names
final FieldSpec dataSourcePropertiesBeanNameField = multiDataSourceGeneratorUtils.createConstantStringFieldSpec(
Expand Down Expand Up @@ -126,7 +128,7 @@ public TypeSpec generateMultiDataSourceConfigTypeElement(
.addMember(
"basePackages",
"$L",
stringArrayToGeneratedStringArray(dataSourceRepositoryPackages)
stringArrayToGeneratedStringArray(repositoryPackagesToInclude)
)
.addMember(
"entityManagerFactoryRef",
Expand All @@ -140,25 +142,25 @@ public TypeSpec generateMultiDataSourceConfigTypeElement(
dataSourceConfigClassName,
transactionManagerBeanNameField
);
final boolean isMasterConfig = dataSourceConfig.isPrimary();
// Exclude the other data source repositories from the primary data source config
if (isMasterConfig) {
// Exclude the other data source repositories from the scan
if (repositoryPackagesToExclude.length > 0) {
enableJpaRepositoriesAnnotationBuilder.addMember(
"excludeFilters",
"$L",
AnnotationSpec.builder(ComponentScan.Filter.class)
.addMember("type", "$T.REGEX", FilterType.class)
.addMember(
"pattern",
"{\"$L\"}",
generatedRepositoryPackagePrefix
"$L",
stringArrayToGeneratedStringArray(repositoryPackagesToExclude)
)
.build()
);
}

// Create the config class bean creation methods while adding the primary annotation to the
// DataSourceProperties bean
final boolean isMasterConfig = dataSourceConfig.isPrimary();
final MethodSpec dataSourcePropertiesMethod = addPrimaryAnnotationIfPrimaryConfigAndBuild(
createDataSourcePropertiesBeanMethod(
dataSourcePropertiesPath,
Expand Down
Expand Up @@ -56,9 +56,9 @@ public class MultiDataSourceAnnotationProcessor extends AbstractProcessor {

private static final String JPA_REPOSITORY_INTERFACE_NAME = "JpaRepository";

private static final String CONFIG_PACKAGE_SUFFIX = ".config";
private static final String CONFIG_PACKAGE_SUFFIX = ".generated.config";

private static final String REPOSITORIES_PACKAGE_SUFFIX = ".repositories";
private static final String REPOSITORIES_PACKAGE_SUFFIX = ".generated.repositories";

private Filer filer;

Expand All @@ -74,7 +74,6 @@ public class MultiDataSourceAnnotationProcessor extends AbstractProcessor {

private MultiDataSourceRepositoryGenerator repositoryGenerator;


/**
* Constructor for the annotation processor to be run during compile time.
*/
Expand Down Expand Up @@ -180,8 +179,18 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
createDataSourceToConfigMap(dataSourceConfigs);

// Create primary data source configuration class
final PackageElement elementPackage = elementUtils.getPackageOf(annotatedElement);
createDataSourceConfigurationClass(primaryDataSourceConfig, annotation, elementPackage);
final PackageElement annotatedElementPackage = elementUtils.getPackageOf(annotatedElement);
final String nonEmptyGeneratedRepositoryPackagePrefix =
getGeneratedRepositoryPackagePrefix(annotation, annotatedElementPackage);
final String nonEmptyGeneratedConfigPackage =
getGeneratedConfigPackage(annotation, annotatedElementPackage);
createDataSourceConfigurationClass(
primaryDataSourceConfig,
annotation,
nonEmptyGeneratedConfigPackage,
annotation.repositoryPackages(), // For primary data source, scan all the packages provided
new String[]{nonEmptyGeneratedRepositoryPackagePrefix} // Generated repos excluded from scan
);
secondaryDataSourceConfigMap.remove(primaryDataSourceConfig.dataSourceName());

// Get secondary data source to target repository method elements map
Expand All @@ -191,7 +200,15 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
secondaryDataSourceConfigMap.keySet().stream()
.filter(dataSource -> !dataSourceToTargetRepositoryMethodMap.containsKey(dataSource))
.map(secondaryDataSourceConfigMap::get)
.forEach(config -> createDataSourceConfigurationClass(config, annotation, elementPackage));
.forEach(
config -> createDataSourceConfigurationClass(
config,
annotation,
nonEmptyGeneratedConfigPackage,
annotation.repositoryPackages(), // All repos scanned as no @TargetDataSource
annotation.repositoryPackages() // All repos excluded as no @TargetDataSource
)
);
if (dataSourceToTargetRepositoryMethodMap.isEmpty()) {
messager.printMessage(
Kind.NOTE,
Expand All @@ -215,22 +232,19 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
final String[] dataSourceEntityPackages = repositoryToMethodMap.keySet().stream()
.map(this::getJpaRepositoryEntityPackage)
.toArray(String[]::new);
final String repositoryDataSourceSubPackage = generateNonPrimaryDataSourceRepositoryPackage(
nonEmptyGeneratedRepositoryPackagePrefix,
dataSourceName
);
createDataSourceConfigurationClass(
secondaryDataSourceConfigMap.get(dataSourceName),
annotation,
elementPackage,
nonEmptyGeneratedConfigPackage,
new String[]{repositoryDataSourceSubPackage}, // Only scan the generated package
new String[]{}, // No repos excluded from scan as all repos are in the generated package
dataSourceEntityPackages
);

// Get the data source name and other relevant details for this data source
final String repositoryPackage = annotation.generatedRepositoryPackagePrefix();
final String generatedRepositoryPackagePrefix = StringUtils.hasText(repositoryPackage)
? repositoryPackage : elementPackage + REPOSITORIES_PACKAGE_SUFFIX;
final String repositoryDataSourceSubPackage = generateNonPrimaryDataSourceRepositoryPackage(
generatedRepositoryPackagePrefix,
dataSourceName
);

// Copy all the repositories to the relevant sub-package with only the annotated methods
for (final var typeToExecutableEntry : repositoryToMethodMap.entrySet()) {
final TypeElement typeElement = typeToExecutableEntry.getKey();
Expand Down Expand Up @@ -340,54 +354,86 @@ private Map<String, DataSourceConfig> createDataSourceToConfigMap(
return secondaryDataSourceConfigMap;
}

/**
* Get the generated repository package prefix from the annotation or the element package.
* <p>
* If the annotation has a value for the generated repository package prefix, use that. Otherwise,
* use the package of the element on which the annotation is declared.
*
* @param annotation the {@link EnableMultiDataSourceConfig} annotation
* @param elementPackage the package of the element on which the annotation is declared
* @return the generated repository package prefix
*/
private String getGeneratedRepositoryPackagePrefix(
EnableMultiDataSourceConfig annotation,
PackageElement elementPackage
) {
final String repositoryPackage = annotation.generatedRepositoryPackagePrefix();
return StringUtils.hasText(repositoryPackage) ? repositoryPackage
: elementPackage + REPOSITORIES_PACKAGE_SUFFIX;
}

/**
* Get the generated config package from the annotation or the element package.
* <p>
* If the annotation has a value for the generated config package, use that. Otherwise, use the
* package of the element on which the annotation is declared.
*
* @param annotation the {@link EnableMultiDataSourceConfig} annotation
* @param elementPackage the package of the element on which the annotation is declared
* @return the generated config package
*/
private String getGeneratedConfigPackage(
EnableMultiDataSourceConfig annotation,
PackageElement elementPackage
) {
final String generatedConfigPackage = annotation.generatedConfigPackage();
return StringUtils.hasText(generatedConfigPackage) ? generatedConfigPackage
: elementPackage + CONFIG_PACKAGE_SUFFIX;
}

/**
* Creates a data source config class for the {@link DataSourceConfig} provided.
*
* @param dataSourceConfig the {@link DataSourceConfig} for which the config class is to be
* generated
* @param annotation the {@link EnableMultiDataSourceConfig} annotation from which the
* global level config is to be read
* @param elementPackage the package of the element on which the annotation is declared (used
* for defaulting the package of the generated config class)
* @param extraEntityPackages extra entity packages to be scanned for entities, specifically for
* this data source. This will be used in addition to the entity
* packages provided in the global annotation
* @param dataSourceConfig the {@link DataSourceConfig} for which the config
* class is to be generated
* @param annotation the {@link EnableMultiDataSourceConfig} annotation
* from which the global level config is to be read
* @param generatedConfigPackage the package where the generated data source
* configuration will be placed
* @param repositoryPackagesToIncludeInScan the repository packages to be scanned for
* repositories, specifically for this data source
* @param repositoryPackagesToExcludeFromScan the repository packages to be excluded from scanning
* for repositories, specifically for this data
* source.
* @param extraEntityPackagesToScan extra entity packages to be scanned for entities,
* specifically for this data source. This will be used
* in addition to the entity packages provided in the
* global annotation
* @throws IllegalArgumentException if no entity packages or repository packages are provided in
* the annotation
*/
private void createDataSourceConfigurationClass(
DataSourceConfig dataSourceConfig,
EnableMultiDataSourceConfig annotation,
PackageElement elementPackage,
String... extraEntityPackages
String generatedConfigPackage,
String[] repositoryPackagesToIncludeInScan,
String[] repositoryPackagesToExcludeFromScan,
String... extraEntityPackagesToScan
) {
final String dataSourceName = dataSourceConfig.dataSourceName();
final String dataSourceConfigClassName = getDataSourceConfigClassName(dataSourceName);
final String dataSourceConfigPropertiesPath = annotation.datasourcePropertiesPrefix()
+ "." + commonStringUtils.toKebabCase(dataSourceName);
// For primary data source, use the provided repository packages but for secondary data sources,
// generate a new package name
final String[] repositoryPackages = dataSourceConfig.isPrimary()
? annotation.repositoryPackages()
: new String[]{
generateNonPrimaryDataSourceRepositoryPackage(
annotation.generatedRepositoryPackagePrefix(),
dataSourceName
)
};
final Set<String> entityPackages = new HashSet<>(Set.of(annotation.exactEntityPackages()));
entityPackages.addAll(List.of(extraEntityPackages));
final String configPackage = annotation.generatedConfigPackage();
final String repositoryPackage = annotation.generatedRepositoryPackagePrefix();
final String generatedRepositoryPackagePrefix = StringUtils.hasText(repositoryPackage)
? repositoryPackage : elementPackage + REPOSITORIES_PACKAGE_SUFFIX;
entityPackages.addAll(List.of(extraEntityPackagesToScan));

// Validate the provided data source values
if (entityPackages.isEmpty()) {
messager.printMessage(Kind.ERROR, NO_ENTITY_PACKAGES_PROVIDED_IN_CONFIG);
throw new IllegalArgumentException(NO_ENTITY_PACKAGES_PROVIDED_IN_CONFIG);
}
if (repositoryPackages.length == 0) {
if (repositoryPackagesToIncludeInScan.length == 0) {
messager.printMessage(Kind.ERROR, NO_REPOSITORY_PACKAGES_PROVIDED_IN_CONFIG);
throw new IllegalArgumentException(NO_REPOSITORY_PACKAGES_PROVIDED_IN_CONFIG);
}
Expand All @@ -398,14 +444,12 @@ private void createDataSourceConfigurationClass(
dataSourceName,
dataSourceConfigClassName,
dataSourceConfigPropertiesPath,
repositoryPackages,
entityPackages.toArray(String[]::new),
generatedRepositoryPackagePrefix
repositoryPackagesToIncludeInScan,
repositoryPackagesToExcludeFromScan,
entityPackages.toArray(String[]::new)
);

// Write the data source config class to the relevant package
final String generatedConfigPackage = StringUtils.hasText(configPackage)
? configPackage : elementPackage + CONFIG_PACKAGE_SUFFIX;
writeTypeSpecToPackage(generatedConfigPackage, configurationTypeSpec);
}

Expand Down

0 comments on commit 704df75

Please sign in to comment.