populate NamedDomainObjectContainer from file - gradle

Suppose I am currently authoring a Gradle plugin, and I have this extension:
abstract class MyExtension {
abstract val inputFiles: ConfigurableFileCollection
val names: Provider<List<String>> by lazy {
// Read input files to get a list of names
inputFiles.elements.map { ... }
}
abstract val instances: NamedDomainObjectContainer<MyType>
}
What I want to do in my plugin is to make sure that every name provided by names is registered in instances if not already registered. That is, something like this:
names.get().forEach {
if (!instances.names.contains(it)) {
instances.register(it) {
// Additional configuration goes here
}
}
}
I have heard that project.afterEvaluate() is an option, however I have also heard that it is a bad option that shouldn't be used. What would be a way to have this functionality?

You can use another DomainObjectContainer, which has various functionality to allow it to stay up-to-date with other objects being configured.
For instance you could have the extension class:
abstract class MyExtension {
abstract val inputFiles: ConfigurableFileCollection
abstract val names: DomainObjectSet<String>
abstract val instances: NamedDomainObjectContainer<MyType>
}
Then have in your plugin code:
val ext = project.extensions.create("MyExtension", MyExtension::class.java)
ext.names.all { eachName ->
ext.instances.register(eachName) {
// Additional config
}
}
ext.names.addAllLater(ext.inputFiles.elements.map { ... })
The lambda passed to all will be executed for each new name added to names, including all the files, so all the collections will stay current with each other.

Related

Clean Architecture: Cannot map PagingSource<Int, Entities> to PagingSource<RepositoryModel>

My requirement is to display the notes in pages using clean architecture along with offline suppport.
I am using the Paging library for pagination. And below is the clean architectural diagram for getting notes.
Note: Please open the above image in new tab and zoom to view it clear.
I have four layers UI, UseCase, Repository, and Datasource. I am planning to abstract the internal implementation of the data source. For that, I need to map NotesEntities to another model before crossing the boundary.
class TimelineDao{
#Transaction
#Query("SELECT * FROM NotesEntities ORDER BY timeStamp DESC")
abstract fun getPagingSourceForNotes(): PagingSource<Int, NotesEntities>
}
Current Implementation:
internal class NotesLocalDataSourceImpl #Inject constructor(
private val notesDao: NotesDao
) : NotesLocalDataSource {
override suspend fun insertNotes(notes: NotesEntities) {
notesDao.insert(NotesEntities)
}
override fun getNotesPagingSource(): PagingSource<Int, NotesEntities> {
return notesDao.getPagingSourceForNotes()
}
}
Expected Implementation:
internal class NotesLocalDataSourceImpl #Inject constructor(
private val notesDao: NotesDao
) : NotesLocalDataSource {
override suspend fun insertNotes(notes: NotesRepositoryModel) {
notesDao.insert(NotesRepositoryModel.toEntity())
}
override fun getNotesPagingSource(): PagingSource<Int, NotesRepositoryModel> {
return notesDao.getPagingSourceForNotes().map{ it.toNotesRepositoryModel() }
}
}
I am having an issue mapping the PagingSource<Int, NotesEntities> to PagingSource<Int, NotesRespositoryModel>. As for as I have researched, there is no way to map
PagingSource<Int, NotesEntities> to PagingSource<Int, NotesRespositoryModel>
Kindly let me know if there is a clean way/ workaround way to map the paging source objects. If anyone is sure if there is no way as of now. Please leave a comment as well.
Please Note: I am aware that paging allows transformation for PagingData. Below is code snippet that gets notes in pages. It maps NotesEntities to NotesDomainModel. But then I want to use NotesRespositoryModel instead of NotesEntities in the NotesRespositoryImpl, abstracting the NotesEntities within NotesLocalDataSourceImpl layer.
override fun getPaginatedNotes(): Flow<PagingData<NotesDomainModel>> {
return Pager<Int, NotesEntities>(
config = PagingConfig(pageSize = 10),
remoteMediator = NotesRemoteMediator(localDataSource,remoteDataSource),
pagingSourceFactory = localDataSource.getNotesPagingSource()
).flow.map{ it.toDomainModel() }
}
The solution I have thought of:
Instead of using the PagingSource in Dao directly, I thought of creating a custom PagingSource, that calls the Dao and maps the NoteEntities to LocalRepositoryModel.
But then I need to understand that any updates to the DB will not be reflected in the PagingSource. I need to handle it internally.
Kindly let me know your thoughts on this.
What about creating an implementation of PagingSource that forwards all of the calls to the original PagingSource and performs the mapping, something like this:
class MappingPagingSource<Key: Any, Value: Any, MappedValue: Any>(
private val originalSource: PagingSource<Key, Value>,
private val mapper: (Value) -> MappedValue,
) : PagingSource<Key, MappedValue>() {
override fun getRefreshKey(state: PagingState<Key, MappedValue>): Key? {
return originalSource.getRefreshKey(
PagingState(
pages = emptyList(),
leadingPlaceholderCount = 0,
anchorPosition = state.anchorPosition,
config = state.config,
)
)
}
override suspend fun load(params: LoadParams<Key>): LoadResult<Key, MappedValue> {
val originalResult = originalSource.load(params)
return when (originalResult) {
is LoadResult.Error -> LoadResult.Error(originalResult.throwable)
is LoadResult.Invalid -> LoadResult.Invalid()
is LoadResult.Page -> LoadResult.Page(
data = originalResult.data.map(mapper),
prevKey = originalResult.prevKey,
nextKey = originalResult.nextKey,
)
}
}
override val jumpingSupported: Boolean
get() = originalSource.jumpingSupported
}
Usage would be like this then:
override fun getNotesPagingSource(): PagingSource<Int, NotesRepositoryModel> {
return MappingPagingSource(
originalSource = notesDao.getPagingSourceForNotes(),
mapper = { it.toNotesRepositoryModel() },
)
}
Regarding the empty pages in PagingState - mapping all loaded pages back to original value would be too expensive and room's paging implementation is only using anchorPosition and config.initialLoadSize anyway - see here and here.

Unable to iterate over stream inside use{} block

I just want to print name of every user stored in database.
I am using this repository:
#Repository
interface User: JpaSpecificationExecutor<User>, PagingAndSortingRepository<User, Long> {
#Query("from User")
fun findAllUsers(): Stream<User>
}
inside this Service:
#Service
class MyService(val user: User) {
private val log = LoggerFactory.getLogger(javaClass)
#Transactional(readOnly = true)
fun printNames() {
log.info("here")
user.findAllUsers().use { users ->
users.map { it.firstName }
}
}
}
and it prints only here in console, but no name.
It seems like map() automatically closed the stream but I don`t know why and how to workaround it. When I put inside of use{} block only log.info(users.count()) it prints number of users stored in database. So there is a user I can print.
My question is, how can I print all names from the given stream?
Kotlin's use function is just a short-hand way of executing some code (your closure function) and then call close on the receiver (in this case the Stream) once done.
What you called users is actually the Stream<User>returned by your repository, so basically your code is just calling users.map {...}. Now, the map operator is an intermediate operator, and since Java streams are lazy, they won't actually do anything until you call a terminal operator (such as .collect or .forEach).
Assuming you want to print the user, try with:
user.findAllUsers().use {
it.forEach { println(it.firstName) }
}
Full working example (without Spring data):
import java.util.stream.Stream
// simulate a repository
fun findAllUsers() = Stream.of("First", "Second", "Third")
fun printNames() {
findAllUsers().use {
it.forEach(::println)
}
}
fun main() {
printNames()
}
Prints:
First
Second
Third

Laravel tag all class implemeting an interface

I'm using Laravel 8 and i want to get all the classes that implements an Interface X.
I did it with symfony4 few month ago with DI :
services.yml
_instanceof:
App\Calculator\Budget\BudgetCalculatorInterface:
tags: ['app.budget_calculator']
App\Handler\CalculatorBudgetHandler:
arguments: [!tagged app.budget_calculator]
and then in my class CalculatorBudgetHandler.php
private $calculatorList = [];
public function __construct(iterable $calculatorList)
{
$this->calculatorList = $calculatorList;
}
public function __construct(iterable $calculatorList)
{
$this->calculatorList = $calculatorList;
}
public function calculate(array $data): float
{
foreach ($this->calculatorList as $calculator) {
if ($calculator->supports($data)) {
return $calculator->calculate($data);
}
}
}
but i dot not understand how to do it with Laravel. I think i have to pass all my classes in a bind or tag :
$this->app->tag([CpuReport::class, MemoryReport::class], 'reports');
thats mean if i got a new class implementing X, i have to add it in the bind/tag ?
I want to do it automatically .
thx !
I needed this too. Looked for a longer time and I basically found a solution. The bad thing about this is that in PHP classes aren't actually declared when you did not use them. So you'll have to either scan the entire project for classes and test each class to find classes implementing your interface or (better) you use the composer autoload class maps. There you could probably limit the searching scope for classes to a sub namespace.
A small but cool package working this way is this one: https://gitlab.com/hpierce1102/ClassFinder - basically it uses composer PSR4 classmaps and is in general fine performance wise.
Here is the solution to which I came:
// Add to service provider
private function tagByInterface(string $interfaceName, string $tagName, string $rootNamespace)
{
foreach (ClassFinder::getClassesInNamespace($rootNamespace, ClassFinder::RECURSIVE_MODE) as $className) {
$class = new \ReflectionClass($className);
if ($class->isAbstract() || $class->isInterface()) {
continue;
}
if ($class->implementsInterface($interfaceName)) {
$this->app->tag($className, $tagName);
}
}
}
Which can then be used like this in the register():
$this->tagByInterface(SomeInterface::class, 'some-tag', 'App\Domain\Something');
$this->app->when(SomeClass::class)->needs('$myServices')->giveTagged('some-tag');
As the classes are loaded using reflection, this operation still might take time if your root namespace is not properly set or too wide. Reflection is quick (as far as I know quicker than loading the information from cache), but you should still think of using a deferred provider for the task so that the search for implementing classes only triggers when it's actually needed.
Update some months later
This solution works, but might be a huge drain on performance if the project gets big. I'm now caching the tagged classes. Something like this:
use HaydenPierce\ClassFinder\ClassFinder as HPClassFinder;
use Illuminate\Contracts\Cache\Repository;
class InheritanceClassFinder
{
public function __construct(private ?Repository $cache = null)
{
}
public function findClassesImplementingOrExtending(string $interfaceOrClass, string $rootNamespace): array
{
if ($this->cache) {
return $this->cache->rememberForever(
'inheriting-classes-'.$interfaceOrClass,
fn () => $this->findClassesInheriting($interfaceOrClass, $rootNamespace));
}
return $this->findClassesInheriting($interfaceOrClass, $rootNamespace);
}
private function findClassesInheriting(string $interfaceOrClass, string $rootNamespace): array
{
$classes = [];
foreach (HPClassFinder::getClassesInNamespace($rootNamespace, HPClassFinder::RECURSIVE_MODE) as $className) {
if (!is_subclass_of($className, $interfaceOrClass)
|| ($class = new \ReflectionClass($className))->isAbstract() || $class->isInterface()) {
continue;
}
$classes[] = $className;
}
return $classes;
}
}
This means as long as the cache is injected, stuff will be loaded once and then taken from cache. I inject the cache only in production, so locally its a bit slower but always up to date. In production I throw away the cache with every deployment, so I get a fresh load once after every deployment.

How do I access nested configuration values from my custom gradle extension?

I'm writing a custom gradle plugin that needs to accept an arbitrary number of nested parameters from the buildscript. Something like:
myPlugin{
configObjects = [
{
name="objectA",
value=5,
},
{
name="objectB",
value=9,
}
]
}
...where the number of items in configObjects, and the the values inside them is defined in whatever buildscript is importing the plugin.
So in my plugin code, I create an extension...
val config = extensions.create("myPlugin", myPluginTaskConfiguration::class.java, project)
tasks {
register<myPluginTask>("myPlugin") {
configObjects= config.configObjects
}
}
and a class defining the structure of the data received through the extension:
open class myPluginTaskConfiguration(project: Project) {
#Input
#Option(option="configObjects", description = "list of configObjects")
var configObjects:List<ConfigObject>?=null
}
Gradle allows me to specify the outer type, but apparently not the inner members. Running my plugin task I get the following error:
class build_f42r2ugava4a351q5usw8u65g$_run_closure1$_closure5 cannot be cast to class com.myplugin.ConfigObject (build_f42r2ugava4a351q5usw8u65g$_run_closure1$_closure5 is in unnamed module of loader org.gradle.groovy.scripts.internal.DefaultScriptCompilationHandler$ScriptClassLoader #224ed88; com.myplugin.ConfigObject is in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader #72fe231e)
It's not clear to me what the type of the objects in the configObjects block is (well, apparently they're of type build_f42r2ugava4a351q5usw8u65g$_run_closure1$_closure5, but I don't think that's something I can use at author-time)
How can I take the list of items from my groovy buildscript, and convert them into typed objects in my plugin (preferably in a way that allows the IDE to provide suggestions/hints to users editing the buildscript)?
#Input and #Option are for tasks. From the looks of it, you are using them for extensions.
There is no need to need to pass in a project instance in the constructor of a Task. All Tasks have a reference to the Project they belong to https://docs.gradle.org/current/javadoc/org/gradle/api/Task.html#getProject--
With that said, full working example in Kotlin would be:
open class MyPluginTaskConfiguration #Inject constructor(objects: ObjectFactory) {
val configObjects: ListProperty<Map<*, *>> = objects.listProperty()
}
open class MyPluginTask : DefaultTask() {
#Input
#Option(option="configObjects", description = "list of configObjects")
val configObjects: ListProperty<Map<*, *>> = project.objects.listProperty()
#TaskAction
fun printMessage() {
configObjects.get().forEach {
println("$it")
}
}
}
val config = extensions.create("myPlugin", MyPluginTaskConfiguration::class.java)
configure<MyPluginTaskConfiguration> {
configObjects.set(listOf(
mapOf<String, Any>(
"name" to "objectA",
"value" to 5
),
mapOf<String, Any>(
"name" to "objectB",
"value" to 9
)
))
}
tasks.register("myPlugin", MyPluginTask::class) {
configObjects.set(config.configObjects)
}
Executing the above produces:
./gradlew myPlugin
> Task :myPlugin
{name=objectA, value=5}
{name=objectB, value=9}
Refer to below doc for more details:
https://docs.gradle.org/current/userguide/lazy_configuration.html

How to Access Mono<T> While Handling Exception with onErrorMap()?

In data class I defined the 'name' must be unique across whole mongo collection:
#Document
data class Inn(#Indexed(unique = true) val name: String,
val description: String) {
#Id
var id: String = UUID.randomUUID().toString()
var intro: String = ""
}
So in service I have to capture the unexpected exception if someone pass the same name again.
#Service
class InnService(val repository: InnRepository) {
fun create(inn: Mono<Inn>): Mono<Inn> =
repository
.create(inn)
.onErrorMap(
DuplicateKeyException::class.java,
{ err -> InnAlreadyExistedException("The inn already existed", err) }
)
}
This is OK, but what if I want to add more info to the exceptional message like "The inn named '$it.name' already existed", what should I do for transforming exception with enriched message.
Clearly, assign Mono<Inn> to a local variable at the beginning is not a good idea...
Similar situation in handler, I'd like to give client more info which derived from the customized exception, but no proper way can be found.
#Component
class InnHandler(val innService: InnService) {
fun create(req: ServerRequest): Mono<ServerResponse> {
return innService
.create(req.bodyToMono<Inn>())
.flatMap {
created(URI.create("/api/inns/${it.id}"))
.contentType(MediaType.APPLICATION_JSON_UTF8).body(it.toMono())
}
.onErrorReturn(
InnAlreadyExistedException::class.java,
badRequest().body(mapOf("code" to "SF400", "message" to t.message).toMono()).block()
)
}
}
In reactor, you aren't going to have the value you want handed to you in onErrorMap as an argument, you just get the Throwable. However, in Kotlin you can reach outside the scope of the error handler and just refer to inn directly. You don't need to change much:
fun create(inn: Mono<Inn>): Mono<Inn> =
repository
.create(inn)
.onErrorMap(
DuplicateKeyException::class.java,
{ InnAlreadyExistedException("The inn ${inn.name} already existed", it) }
)
}

Resources