Skip to content

Commit 31a08e1

Browse files
authored
Feature/add plugin system (ehrbase#772)
* add plugin system see CDR-276 * switch to return uuid instead of full composition logic see CDR-276 * retro error in aspect see CDR-276 * cleanup code see CDR-276 * cleanup code and add javadoc see CDR-276 * add javadoc more javadoc see CDR-276 * run test with test_plugins see CDR-276 * fix ci see CDR-276 * run test with test_plugins see CDR-276 * fix ci see CDR-276 * add test plugins with error handling see CDR-276 * edit changelog see CDR-276 * check for duplicate plugin uri see CDR-276 * fix review see CDR-276 * fix review see CDR-276
1 parent ce77006 commit 31a08e1

File tree

23 files changed

+859
-111
lines changed

23 files changed

+859
-111
lines changed

.circleci/config.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,8 @@ commands:
12531253
java -jar "application/target/application-${EHRbase_VERSION}.jar" \
12541254
--system.allow-template-overwrite=<< parameters.allow-template-overwrite >> \
12551255
--server.nodename=<< parameters.nodename >> \
1256+
--plugin-manager.enable=true \
1257+
--plugin-manager.plugin-dir='tests/test_plugin' \
12561258
--cache.enabled=<< parameters.cache-enabled >> > log & app_pid=$!
12571259
timeout=60
12581260
while [ ! -f ./log ];
@@ -1426,7 +1428,7 @@ commands:
14261428
cd ~/projects
14271429
EHRbase_VERSION=$(mvn -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive exec:exec)
14281430
echo ${EHRbase_VERSION}
1429-
java -jar application/target/application-${EHRbase_VERSION}.jar --cache.enabled=true > log &
1431+
java -jar application/target/application-${EHRbase_VERSION}.jar --cache.enabled=true --plugin-manager.enable=true --plugin-manager.plugin-dir='tests/test_plugin' > log &
14301432
grep -m 1 "Started EhrBase in" <(tail -f log)
14311433
cd ~/projects/openEHR_SDK
14321434
jps
@@ -1448,7 +1450,7 @@ commands:
14481450
echo ${EHRbase_VERSION}
14491451
cd ~/projects # NOTE: This is where the target folder w/ artifacts were persisted to in previous step.
14501452
java -javaagent:/home/circleci/jacoco/lib/jacocoagent.jar=output=tcpserver,address=127.0.0.1 \
1451-
-jar application/target/application-${EHRbase_VERSION}.jar --cache.enabled=true > log &
1453+
-jar application/target/application-${EHRbase_VERSION}.jar --cache.enabled=true --plugin-manager.enable=true --plugin-manager.plugin-dir='tests/test_plugin' > log &
14521454
grep -m 1 "Started EhrBase in" <(tail -f log)
14531455
cd ~/projects/openEHR_SDK
14541456
jps
@@ -1624,7 +1626,7 @@ commands:
16241626
ls -la
16251627
EHRbase_VERSION=$(mvn -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive exec:exec)
16261628
echo ${EHRbase_VERSION}
1627-
java -jar application/target/application-${EHRbase_VERSION}.jar --cache.enabled=true > log &
1629+
java -jar application/target/application-${EHRbase_VERSION}.jar --cache.enabled=true --plugin-manager.enable=true --plugin-manager.plugin-dir='tests/test_plugin' > log &
16281630
grep -m 1 "Started EhrBase in" <(tail -f log)
16291631
jps
16301632

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,4 @@ vulnerability_analysis.json
217217
# Docker
218218
.pgdata
219219
application/.pgdata
220+
/plugin_dir/

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,23 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project
6-
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
6+
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [unreleased]
99

1010
### Added
1111

12+
- Add Plugins system ([#772](https://github.com/ehrbase/ehrbase/pull/772)).
13+
1214
### Changed
1315

1416
### Fixed
1517

1618
- Remove unused Operational Template cache ([#759](https://github.com/ehrbase/ehrbase/pull/759)).
1719
- Allow update/adding/removal of feeder_audit/links on Composition ([#773](https://github.com/ehrbase/ehrbase/pull/773))
1820

19-
## [0.19.0]
21+
## [0.19.0]
2022

2123
### Added
2224

api/src/main/java/org/ehrbase/api/service/CompositionService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import java.util.Optional;
3333
import java.util.UUID;
3434

35-
public interface CompositionService extends BaseService, VersionedObjectService<Composition, CompositionDto> {
35+
public interface CompositionService extends BaseService, VersionedObjectService<Composition, UUID> {
3636
/**
3737
* @param compositionId The {@link UUID} of the composition to be returned.
3838
* @param version The version to returned. If null return the latest
@@ -136,4 +136,8 @@ public interface CompositionService extends BaseService, VersionedObjectService<
136136
Optional<OriginalVersion<Composition>> getOriginalVersionComposition(UUID versionedObjectUid, int version);
137137

138138
Composition buildComposition(String content, CompositionFormat format, String templateId);
139+
140+
141+
142+
139143
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2022. vitasystems GmbH and Hannover Medical School.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.ehrbase.application.config.plugin;
18+
19+
20+
import org.pf4j.spring.ExtensionsInjector;
21+
import org.pf4j.spring.SpringPluginManager;
22+
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
23+
24+
/**
25+
* @author Stefan Spiska
26+
*/
27+
public class EhrBasePluginManager extends SpringPluginManager {
28+
29+
public EhrBasePluginManager(PluginManagerProperties properties) {
30+
super(properties.getPluginDir());
31+
}
32+
33+
private boolean init = false;
34+
35+
@Override
36+
public void init() {
37+
// Plugins will be initialised in initPlugins
38+
}
39+
40+
public void initPlugins() {
41+
42+
43+
if (!init) {
44+
45+
startPlugins();
46+
47+
AbstractAutowireCapableBeanFactory beanFactory =
48+
(AbstractAutowireCapableBeanFactory)
49+
getApplicationContext().getAutowireCapableBeanFactory();
50+
ExtensionsInjector extensionsInjector = new ExtensionsInjector(this, beanFactory);
51+
extensionsInjector.injectExtensions();
52+
init = true;
53+
}
54+
}
55+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright (c) 2022. vitasystems GmbH and Hannover Medical School.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.ehrbase.application.config.plugin;
18+
19+
import static org.ehrbase.plugin.PluginHelper.PLUGIN_MANAGER_PREFIX;
20+
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
import org.ehrbase.api.exception.InternalServerException;
24+
import org.ehrbase.plugin.EhrBasePlugin;
25+
import org.pf4j.PluginWrapper;
26+
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
27+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
29+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
30+
import org.springframework.boot.context.properties.bind.Binder;
31+
import org.springframework.boot.web.servlet.ServletRegistrationBean;
32+
import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent;
33+
import org.springframework.context.ApplicationListener;
34+
import org.springframework.context.annotation.Bean;
35+
import org.springframework.context.annotation.Configuration;
36+
import org.springframework.core.env.Environment;
37+
import org.springframework.web.servlet.DispatcherServlet;
38+
import org.springframework.web.util.UriComponentsBuilder;
39+
40+
/**
41+
* @author Stefan Spiska
42+
*/
43+
@Configuration
44+
@EnableConfigurationProperties(PluginManagerProperties.class)
45+
@ConditionalOnProperty(prefix = PLUGIN_MANAGER_PREFIX, name = "enable", havingValue = "true")
46+
public class PluginConfig {
47+
48+
@Bean
49+
public EhrBasePluginManager pluginManager(Environment environment) {
50+
51+
return new EhrBasePluginManager(getPluginManagerProperties(environment));
52+
}
53+
// since this is used in a BeanFactoryPostProcessor the PluginManagerProperties must be bound
54+
// manually.
55+
private PluginManagerProperties getPluginManagerProperties(Environment environment) {
56+
return Binder.get(environment).bind(PLUGIN_MANAGER_PREFIX, PluginManagerProperties.class).get();
57+
}
58+
59+
/** Register the {@link DispatcherServlet} for all {@link EhrBasePlugin} */
60+
@Bean
61+
public BeanFactoryPostProcessor beanFactoryPostProcessor(
62+
EhrBasePluginManager pluginManager, Environment environment) {
63+
64+
PluginManagerProperties pluginManagerProperties = getPluginManagerProperties(environment);
65+
66+
Map<String, String> registeredUrl = new HashMap<>();
67+
68+
return beanFactory -> {
69+
pluginManager.loadPlugins();
70+
71+
pluginManager.getPlugins().stream()
72+
.map(PluginWrapper::getPlugin)
73+
.filter(p -> EhrBasePlugin.class.isAssignableFrom(p.getClass()))
74+
.map(EhrBasePlugin.class::cast)
75+
.forEach(p -> register(beanFactory, pluginManagerProperties, registeredUrl, p));
76+
};
77+
}
78+
79+
/**
80+
* Register the {@link DispatcherServlet} for a {@link EhrBasePlugin}
81+
*
82+
* @param beanFactory
83+
* @param pluginManagerProperties
84+
* @param registeredUrl
85+
* @param p
86+
*/
87+
private void register(
88+
ConfigurableListableBeanFactory beanFactory,
89+
PluginManagerProperties pluginManagerProperties,
90+
Map<String, String> registeredUrl,
91+
EhrBasePlugin p) {
92+
93+
String pluginId = p.getWrapper().getPluginId();
94+
95+
final String uri =
96+
UriComponentsBuilder.newInstance()
97+
.path(pluginManagerProperties.getPluginContextPath())
98+
.path(p.getContextPath())
99+
.path("/*")
100+
.build()
101+
.getPath();
102+
103+
// check for duplicate plugin uri
104+
registeredUrl.entrySet().stream()
105+
.filter(e -> e.getValue().equals(uri))
106+
.findAny()
107+
.ifPresent(
108+
e -> {
109+
throw new InternalServerException(
110+
String.format(
111+
"uri %s for plugin %s already registered by plugin %s",
112+
uri, pluginId, e.getKey()));
113+
});
114+
115+
registeredUrl.put(pluginId, uri);
116+
117+
ServletRegistrationBean<DispatcherServlet> bean =
118+
new ServletRegistrationBean<>(p.getDispatcherServlet(), uri);
119+
120+
bean.setLoadOnStartup(1);
121+
bean.setOrder(1);
122+
bean.setName(pluginId);
123+
beanFactory.initializeBean(bean, pluginId);
124+
beanFactory.autowireBean(bean);
125+
beanFactory.registerSingleton(pluginId, bean);
126+
}
127+
128+
/**
129+
* Create a Listener for the {@link ServletWebServerInitializedEvent } to initialise the {@link
130+
* org.pf4j.ExtensionPoint} after all {@link DispatcherServlet} have been initialised.
131+
*
132+
* @param pluginManager
133+
* @return
134+
*/
135+
@Bean
136+
ApplicationListener<ServletWebServerInitializedEvent>
137+
servletWebServerInitializedEventApplicationListener(EhrBasePluginManager pluginManager) {
138+
139+
return event -> pluginManager.initPlugins();
140+
}
141+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2022. vitasystems GmbH and Hannover Medical School.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.ehrbase.application.config.plugin;
18+
19+
import static org.ehrbase.plugin.PluginHelper.PLUGIN_MANAGER_PREFIX;
20+
21+
import java.nio.file.Path;
22+
import org.springframework.boot.context.properties.ConfigurationProperties;
23+
24+
/**
25+
* @author Stefan Spiska
26+
* <p>{@link ConfigurationProperties} for {@link EhrBasePluginManager}.
27+
*/
28+
@ConfigurationProperties(prefix = PLUGIN_MANAGER_PREFIX)
29+
public class PluginManagerProperties {
30+
31+
private Path pluginDir;
32+
private boolean enable;
33+
private String pluginContextPath;
34+
35+
public void setPluginDir(Path pluginDir) {
36+
this.pluginDir = pluginDir;
37+
}
38+
39+
public boolean isEnable() {
40+
return enable;
41+
}
42+
43+
public void setEnable(boolean enable) {
44+
this.enable = enable;
45+
}
46+
47+
public Path getPluginDir() {
48+
return pluginDir;
49+
}
50+
51+
public void setPluginDir(String pluginDir) {
52+
this.pluginDir = Path.of(pluginDir);
53+
}
54+
55+
public String getPluginContextPath() {
56+
return pluginContextPath;
57+
}
58+
59+
public void setPluginContextPath(String pluginContextPath) {
60+
this.pluginContextPath = pluginContextPath;
61+
}
62+
}

application/src/main/resources/application.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,10 @@ client:
206206

207207
# JavaMelody
208208
javamelody:
209-
enabled: false
209+
enabled: false
210+
211+
# plugin configuration
212+
plugin-manager:
213+
plugin-dir: ./plugin_dir
214+
enable: false
215+
plugin-context-path: /plugin

0 commit comments

Comments
 (0)