Skip to content

Commit 7f4671e

Browse files
authored
Merge pull request #299 from netgrif/NAE-2090
[NAE-2090] Publish module services to actions
2 parents 2ef1e0a + f6922d4 commit 7f4671e

6 files changed

Lines changed: 133 additions & 0 deletions

File tree

application-engine/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,5 @@ log/
143143
/src/main/resources/certificates/private.der
144144
/src/main/resources/certificates/public.crt
145145
/modules/
146+
!src/main/java/**/modules
147+
!src/main/groovy/**/modules
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.netgrif.application.engine.integration.modules
2+
3+
/**
4+
* A class where the module services are injected. The plugins are injected into metaclasses
5+
*/
6+
class ModuleHolder {
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.netgrif.application.engine.integration.modules
2+
3+
/**
4+
* A class where the module services are injected for a named module. The plugins are injected into metaclasses
5+
*/
6+
class ModuleServiceHolder {
7+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.netgrif.application.engine.integration.modules
2+
3+
import com.netgrif.application.engine.configuration.ApplicationContextProvider
4+
import com.netgrif.application.engine.startup.ApplicationEngineFinishRunner
5+
import com.netgrif.application.engine.startup.annotation.RunnerOrder
6+
import org.slf4j.Logger
7+
import org.slf4j.LoggerFactory
8+
import org.springframework.boot.ApplicationArguments
9+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
10+
import org.springframework.stereotype.Component
11+
12+
/**
13+
* Component responsible for injecting module services into the application runtime.
14+
* This class implements {@link ApplicationEngineFinishRunner} to perform the injection
15+
* after the application finishes its startup routine. The services are grouped by modules
16+
* and made available for runtime action delegation.
17+
*
18+
* The execution of this class is controlled by properties defined in the application configuration.
19+
* By default, it is enabled and executed in the specified order.
20+
*/
21+
22+
@Component
23+
@RunnerOrder(209)
24+
@ConditionalOnProperty(value = "nae.modules.services.enabled", havingValue = "true", matchIfMissing = true)
25+
class ModuleServiceInjector implements ApplicationEngineFinishRunner {
26+
27+
private static final Logger log = LoggerFactory.getLogger(ModuleServiceInjector.class)
28+
private final String DEFAULT_KEY = "default"
29+
30+
/**
31+
* Executes the injection of services annotated with {@code @ModuleService}.
32+
* This method collects all beans marked with the {@code @ModuleService} annotation,
33+
* groups them by their associated module, and injects them into the respective meta-classes
34+
* (e.g., {@code ModuleHolder} and {@code ModuleServiceHolder}).
35+
*
36+
* @param args application arguments passed during startup
37+
* @throws Exception if any error occurs during the injection process
38+
*/
39+
40+
@Override
41+
void run(ApplicationArguments args) throws Exception {
42+
def injectableBeans = ApplicationContextProvider.getAppContext().getBeansWithAnnotation(ModuleService.class)
43+
if (!injectableBeans.isEmpty()) {
44+
MetaClass holderMetaClass = ModuleHolder.metaClass
45+
def groupedServices = groupServicesByModule(injectableBeans)
46+
groupedServices.each { entry ->
47+
if (entry.key == DEFAULT_KEY) {
48+
entry.value.each { serviceEntry ->
49+
log.info("Injecting module service {} into action delegate making it available under module.{}", serviceEntry.key, serviceEntry.key)
50+
holderMetaClass[serviceEntry.key] = serviceEntry.value
51+
}
52+
} else {
53+
MetaClass moduleServiceHolderMetaClass = ModuleServiceHolder.metaClass
54+
entry.value.each { serviceEntry ->
55+
log.info("Injecting module service {} into module holder {} into action delegate making it available under module.{}.{}", serviceEntry.key, entry.key, entry.key, serviceEntry.key)
56+
moduleServiceHolderMetaClass[serviceEntry.key] = serviceEntry.value
57+
}
58+
holderMetaClass[entry.key] = new ModuleServiceHolder()
59+
}
60+
}
61+
}
62+
}
63+
64+
/**
65+
* Groups services annotated with {@code @ModuleService} by their associated module.
66+
* Each service is categorized under a module key specified by the annotation's value.
67+
* Services without a specified module are grouped under the "default" key.
68+
*
69+
* @param services a map of service names to service instances retrieved from the application context
70+
* @return a map where each key represents a module, and the value is another map containing
71+
* services (name and instance) associated with that module
72+
* @throws IllegalStateException if any service lacks the {@code @ModuleService} annotation
73+
*/
74+
75+
private Map<String, Map<String, Object>> groupServicesByModule(Map<String, Object> services) {
76+
Map<String, Map<String, Object>> grouped = [(DEFAULT_KEY): [:]]
77+
services.each { entry ->
78+
ModuleService[] annotations = entry.value.getClass().getAnnotationsByType(ModuleService.class)
79+
if (annotations.length == 0) throw new IllegalStateException("Module Service bean must have @ModuleService annotations")
80+
ModuleService annotation = annotations[0]
81+
if (annotation.value().isBlank()) {
82+
grouped[(DEFAULT_KEY)].put(entry.key, entry.value)
83+
} else {
84+
String moduleName = annotation.value()
85+
if (!grouped.containsKey(moduleName)) grouped.put(moduleName, [:])
86+
grouped[moduleName].put(entry.key, entry.value)
87+
}
88+
}
89+
return grouped
90+
}
91+
92+
}

application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package com.netgrif.application.engine.petrinet.domain.dataset.logic.action
22

3+
import com.netgrif.application.engine.auth.service.GroupService
4+
import com.netgrif.application.engine.auth.service.UserService
5+
import com.netgrif.application.engine.integration.modules.ModuleHolder
6+
import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService
37
import com.netgrif.application.engine.AsyncRunner
48
import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService
59
import com.netgrif.application.engine.adapter.spring.workflow.domain.QCase
@@ -202,6 +206,8 @@ class ActionDelegate {
202206

203207
FrontendActionOutcome Frontend
204208

209+
ModuleHolder Module
210+
205211
/**
206212
* Reference of case and task in which current action is taking place.
207213
*/
@@ -223,6 +229,7 @@ class ActionDelegate {
223229
this.initTransitionsMap(action.transitionIds)
224230
this.outcomes = new ArrayList<>()
225231
this.Frontend = new FrontendActionOutcome(this.useCase, this.task, this.outcomes)
232+
this.Module = new ModuleHolder()
226233
}
227234

228235
def initFieldsMap(Map<String, String> fieldIds) {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.netgrif.application.engine.integration.modules;
2+
3+
import org.springframework.core.annotation.AliasFor;
4+
5+
import java.lang.annotation.*;
6+
7+
@Documented
8+
@Target({ElementType.TYPE})
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface ModuleService {
11+
12+
@AliasFor("module")
13+
String value() default "";
14+
15+
@AliasFor("value")
16+
String module() default "";
17+
18+
}

0 commit comments

Comments
 (0)