Skip to content

Commit

Permalink
Feature/add plugin system (ehrbase#772)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
stefanspiska authored Mar 23, 2022
1 parent ce77006 commit 31a08e1
Show file tree
Hide file tree
Showing 23 changed files with 859 additions and 111 deletions.
8 changes: 5 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,8 @@ commands:
java -jar "application/target/application-${EHRbase_VERSION}.jar" \
--system.allow-template-overwrite=<< parameters.allow-template-overwrite >> \
--server.nodename=<< parameters.nodename >> \
--plugin-manager.enable=true \
--plugin-manager.plugin-dir='tests/test_plugin' \
--cache.enabled=<< parameters.cache-enabled >> > log & app_pid=$!
timeout=60
while [ ! -f ./log ];
Expand Down Expand Up @@ -1426,7 +1428,7 @@ commands:
cd ~/projects
EHRbase_VERSION=$(mvn -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive exec:exec)
echo ${EHRbase_VERSION}
java -jar application/target/application-${EHRbase_VERSION}.jar --cache.enabled=true > log &
java -jar application/target/application-${EHRbase_VERSION}.jar --cache.enabled=true --plugin-manager.enable=true --plugin-manager.plugin-dir='tests/test_plugin' > log &
grep -m 1 "Started EhrBase in" <(tail -f log)
cd ~/projects/openEHR_SDK
jps
Expand All @@ -1448,7 +1450,7 @@ commands:
echo ${EHRbase_VERSION}
cd ~/projects # NOTE: This is where the target folder w/ artifacts were persisted to in previous step.
java -javaagent:/home/circleci/jacoco/lib/jacocoagent.jar=output=tcpserver,address=127.0.0.1 \
-jar application/target/application-${EHRbase_VERSION}.jar --cache.enabled=true > log &
-jar application/target/application-${EHRbase_VERSION}.jar --cache.enabled=true --plugin-manager.enable=true --plugin-manager.plugin-dir='tests/test_plugin' > log &
grep -m 1 "Started EhrBase in" <(tail -f log)
cd ~/projects/openEHR_SDK
jps
Expand Down Expand Up @@ -1624,7 +1626,7 @@ commands:
ls -la
EHRbase_VERSION=$(mvn -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive exec:exec)
echo ${EHRbase_VERSION}
java -jar application/target/application-${EHRbase_VERSION}.jar --cache.enabled=true > log &
java -jar application/target/application-${EHRbase_VERSION}.jar --cache.enabled=true --plugin-manager.enable=true --plugin-manager.plugin-dir='tests/test_plugin' > log &
grep -m 1 "Started EhrBase in" <(tail -f log)
jps
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,4 @@ vulnerability_analysis.json
# Docker
.pgdata
application/.pgdata
/plugin_dir/
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@

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

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

## [unreleased]

### Added

- Add Plugins system ([#772](https://github.com/ehrbase/ehrbase/pull/772)).

### Changed

### Fixed

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

## [0.19.0]
## [0.19.0]

### Added

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import java.util.Optional;
import java.util.UUID;

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

Composition buildComposition(String content, CompositionFormat format, String templateId);




}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2022. vitasystems GmbH and Hannover Medical School.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.ehrbase.application.config.plugin;


import org.pf4j.spring.ExtensionsInjector;
import org.pf4j.spring.SpringPluginManager;
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;

/**
* @author Stefan Spiska
*/
public class EhrBasePluginManager extends SpringPluginManager {

public EhrBasePluginManager(PluginManagerProperties properties) {
super(properties.getPluginDir());
}

private boolean init = false;

@Override
public void init() {
// Plugins will be initialised in initPlugins
}

public void initPlugins() {


if (!init) {

startPlugins();

AbstractAutowireCapableBeanFactory beanFactory =
(AbstractAutowireCapableBeanFactory)
getApplicationContext().getAutowireCapableBeanFactory();
ExtensionsInjector extensionsInjector = new ExtensionsInjector(this, beanFactory);
extensionsInjector.injectExtensions();
init = true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2022. vitasystems GmbH and Hannover Medical School.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.ehrbase.application.config.plugin;

import static org.ehrbase.plugin.PluginHelper.PLUGIN_MANAGER_PREFIX;

import java.util.HashMap;
import java.util.Map;
import org.ehrbase.api.exception.InternalServerException;
import org.ehrbase.plugin.EhrBasePlugin;
import org.pf4j.PluginWrapper;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.util.UriComponentsBuilder;

/**
* @author Stefan Spiska
*/
@Configuration
@EnableConfigurationProperties(PluginManagerProperties.class)
@ConditionalOnProperty(prefix = PLUGIN_MANAGER_PREFIX, name = "enable", havingValue = "true")
public class PluginConfig {

@Bean
public EhrBasePluginManager pluginManager(Environment environment) {

return new EhrBasePluginManager(getPluginManagerProperties(environment));
}
// since this is used in a BeanFactoryPostProcessor the PluginManagerProperties must be bound
// manually.
private PluginManagerProperties getPluginManagerProperties(Environment environment) {
return Binder.get(environment).bind(PLUGIN_MANAGER_PREFIX, PluginManagerProperties.class).get();
}

/** Register the {@link DispatcherServlet} for all {@link EhrBasePlugin} */
@Bean
public BeanFactoryPostProcessor beanFactoryPostProcessor(
EhrBasePluginManager pluginManager, Environment environment) {

PluginManagerProperties pluginManagerProperties = getPluginManagerProperties(environment);

Map<String, String> registeredUrl = new HashMap<>();

return beanFactory -> {
pluginManager.loadPlugins();

pluginManager.getPlugins().stream()
.map(PluginWrapper::getPlugin)
.filter(p -> EhrBasePlugin.class.isAssignableFrom(p.getClass()))
.map(EhrBasePlugin.class::cast)
.forEach(p -> register(beanFactory, pluginManagerProperties, registeredUrl, p));
};
}

/**
* Register the {@link DispatcherServlet} for a {@link EhrBasePlugin}
*
* @param beanFactory
* @param pluginManagerProperties
* @param registeredUrl
* @param p
*/
private void register(
ConfigurableListableBeanFactory beanFactory,
PluginManagerProperties pluginManagerProperties,
Map<String, String> registeredUrl,
EhrBasePlugin p) {

String pluginId = p.getWrapper().getPluginId();

final String uri =
UriComponentsBuilder.newInstance()
.path(pluginManagerProperties.getPluginContextPath())
.path(p.getContextPath())
.path("/*")
.build()
.getPath();

// check for duplicate plugin uri
registeredUrl.entrySet().stream()
.filter(e -> e.getValue().equals(uri))
.findAny()
.ifPresent(
e -> {
throw new InternalServerException(
String.format(
"uri %s for plugin %s already registered by plugin %s",
uri, pluginId, e.getKey()));
});

registeredUrl.put(pluginId, uri);

ServletRegistrationBean<DispatcherServlet> bean =
new ServletRegistrationBean<>(p.getDispatcherServlet(), uri);

bean.setLoadOnStartup(1);
bean.setOrder(1);
bean.setName(pluginId);
beanFactory.initializeBean(bean, pluginId);
beanFactory.autowireBean(bean);
beanFactory.registerSingleton(pluginId, bean);
}

/**
* Create a Listener for the {@link ServletWebServerInitializedEvent } to initialise the {@link
* org.pf4j.ExtensionPoint} after all {@link DispatcherServlet} have been initialised.
*
* @param pluginManager
* @return
*/
@Bean
ApplicationListener<ServletWebServerInitializedEvent>
servletWebServerInitializedEventApplicationListener(EhrBasePluginManager pluginManager) {

return event -> pluginManager.initPlugins();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2022. vitasystems GmbH and Hannover Medical School.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.ehrbase.application.config.plugin;

import static org.ehrbase.plugin.PluginHelper.PLUGIN_MANAGER_PREFIX;

import java.nio.file.Path;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* @author Stefan Spiska
* <p>{@link ConfigurationProperties} for {@link EhrBasePluginManager}.
*/
@ConfigurationProperties(prefix = PLUGIN_MANAGER_PREFIX)
public class PluginManagerProperties {

private Path pluginDir;
private boolean enable;
private String pluginContextPath;

public void setPluginDir(Path pluginDir) {
this.pluginDir = pluginDir;
}

public boolean isEnable() {
return enable;
}

public void setEnable(boolean enable) {
this.enable = enable;
}

public Path getPluginDir() {
return pluginDir;
}

public void setPluginDir(String pluginDir) {
this.pluginDir = Path.of(pluginDir);
}

public String getPluginContextPath() {
return pluginContextPath;
}

public void setPluginContextPath(String pluginContextPath) {
this.pluginContextPath = pluginContextPath;
}
}
8 changes: 7 additions & 1 deletion application/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,10 @@ client:

# JavaMelody
javamelody:
enabled: false
enabled: false

# plugin configuration
plugin-manager:
plugin-dir: ./plugin_dir
enable: false
plugin-context-path: /plugin
Loading

0 comments on commit 31a08e1

Please sign in to comment.