1
0
mirror of https://github.com/pcvolkmer/onkostar-plugin-dnpm.git synced 2025-07-04 18:12:55 +00:00

refactor: use package name following Java guidelines

This commit is contained in:
2024-09-21 22:10:24 +02:00
parent 93215825f5
commit cc27edc544
93 changed files with 199 additions and 200 deletions

View File

@ -0,0 +1,54 @@
package dev.dnpm;
import java.text.SimpleDateFormat;
import java.util.Date;
public class VerweisVon {
private int procedure_id;
private int data_form_id;
private String data_catalogue;
private String data_catalogue_entry;
private String formname;
private Date datum;
public VerweisVon() {
}
@SuppressWarnings("unused")
public int getProcedure_id() { return this.procedure_id; }
public int getData_form_id() { return this.data_form_id; }
public String getData_catalogue_name() { return this.data_catalogue; }
public String getData_catalogue_entry_name() { return this.data_catalogue_entry; }
public String getFormname() { return this.formname; }
public Date getDate() { return this.datum; }
public String getTable() {
return "dk_" + this.data_catalogue.toLowerCase().replaceAll("[^a-zA-Z0-9]", "_");
}
public String getField() {
return this.data_catalogue_entry.toLowerCase();
}
public String getSQL() {
return "SELECT " + this.getField() + " AS value FROM " + this.getTable() + " WHERE id = " + this.getProcedure_id();
}
private String getDatumAsString() {
SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy");
String Datum = null;
if (this.getDate() != null) {
Datum = DATE_FORMAT.format(this.getDate());
}
return Datum;
}
public String getVerbundenesFormular() {
String FName = "Formular " + this.getFormname();
if (this.getDatumAsString() != null ) { FName += " vom " + this.getDatumAsString(); }
return FName;
}
@SuppressWarnings("unused")
public void setProcedure_id(int procedure_id) {this.procedure_id = procedure_id; }
public void setData_form_id(int data_form_id) {this.data_form_id = data_form_id; }
public void setData_catalogue_name(String data_catalogue_name) {this.data_catalogue = data_catalogue_name; }
public void setData_catalogue_entry_name(String data_catalogue_entry) {this.data_catalogue_entry = data_catalogue_entry; }
public void setDate(Date datum) { this.datum = datum; }
public void setFormname(String formname) { this.formname = formname; }
}

View File

@ -0,0 +1,12 @@
package dev.dnpm.analyzer;
import de.itc.onkostar.api.analysis.OnkostarPluginType;
public abstract class Analyzer implements IPluginPart {
@Override
public final OnkostarPluginType getType() {
return OnkostarPluginType.ANALYZER;
}
}

View File

@ -0,0 +1,145 @@
package dev.dnpm.analyzer;
import java.util.Map;
import java.util.Optional;
/**
* Klasse mit Hilfsfunktionen für Analyzer
*
* @since 0.1.0
*/
public class AnalyzerUtils {
private AnalyzerUtils() {}
/**
* Prüft, ob in InputMap einen Eintrag mit key <code>key</code> und Typ <code>type</code>
* gefunden wurde.
*
* @param input InputMap
* @param key Key des Werts
* @param type Typ des Werts
* @return <code>true</code>>, wenn ein Wert von dem Typ gefunden wurde
*/
public static boolean requiredValuePresent(final Map<String, Object> input, final String key, final Class<?> type) {
var value = input.get(key);
if (null == value) {
return false;
}
return type.isInstance(value);
}
/**
* Übergibt ein Optional mit Wert, wenn in InputMap ein Eintrag mit key <code>key</code> und Typ <code>type</code>
* gefunden wurde. Anderenfalls ein leeres Optional
*
* <p><b>Beispiel</b>
* <pre>
* var id = AnalyzerUtils.getRequiredValue(input, "id", Integer.class);
* if (id.isEmpty()) {
* logger.error("Keine ID angegeben!");
* return false;
* }
*
* var idNummer = id.get();
* ...
* </pre>
*
* @param input InputMap
* @param key Key des Werts
* @param type Typ des Werts
* @return Optional mit entsprechendem Wert oder leeres Optional
*/
public static <T> Optional<T> getRequiredValue(final Map<String, Object> input, final String key, final Class<T> type) {
if (! requiredValuePresent(input, key, type)) {
return Optional.empty();
}
@SuppressWarnings("unchecked")
var result = Optional.of((T)input.get(key));
return result;
}
/**
* Prüft, ob ein Wert in der InputMap als Zeichenkette dem angegebenen RegExp entspricht
*
* @param input InputMap
* @param key Key des Werts
* @param regexp Der zu prüfende reguläre Ausdruck
* @return <code>true</code>>, wenn ein Wert gefunden wurde, der dem RegExp entspricht
*/
public static boolean requiredValueMatches(final Map<String, Object> input, final String key, final String regexp) {
var value = input.get(key);
if (null == value) {
return false;
}
return value.toString().matches(regexp);
}
/**
* Übergibt ein Optional mit dem Wert als Zeichenkette, wenn er dem angegebenen RegExp entspricht.
* Hierzu wird die Methode <code>toString()</code> auf den Wert angewendet.
*
* @param input InputMap
* @param key Key des Werts
* @param regexp Der zu prüfende reguläre Ausdruck
* @return Optional mit entsprechendem Wert als Zeichenkette oder leeres Optional
*/
public static Optional<String> getRequiredValueMatching(final Map<String, Object> input, final String key, final String regexp) {
if (! requiredValueMatches(input, key, regexp)) {
return Optional.empty();
}
return Optional.of(input.get(key).toString());
}
/**
* Prüft, ob ein Wert in der InputMap eine ID ist und damit eine Zahl größer Null ist.
*
* @param input InputMap
* @param key Key des Werts
* @return <code>true</code>>, wenn ein Wert gefunden wurde, der dem RegExp entspricht
*/
public static boolean requiredValueIsId(final Map<String, Object> input, final String key) {
return requiredValuePresent(input, key, Integer.class) && Integer.parseInt(input.get(key).toString()) > 0;
}
/**
* Übergibt ein Optional, wenn der Wert eine ID ist und damit eine Zahl größer Null ist.
* <p><b>Beispiel</b>
* <pre>
* var id = AnalyzerUtils.getRequiredId(input, "id");
* if (id.isEmpty()) {
* logger.error("Keine gültige ID angegeben!");
* return false;
* }
*
* // Ist hier immer größer als Null
* var idNummer = id.get();
* ...
* </pre>
*
* @param input InputMap
* @param key Key des Werts
* @return Optional mit entsprechendem Wert oder leeres Optional
*/
public static Optional<Integer> getRequiredId(final Map<String, Object> input, final String key) {
if (! requiredValuePresent(input, key, Integer.class)) {
return Optional.empty();
}
var id = (int)input.get(key);
if (id > 0) {
return Optional.of(id);
}
return Optional.empty();
}
}

View File

@ -0,0 +1,25 @@
package dev.dnpm.analyzer;
import de.itc.onkostar.api.Disease;
import de.itc.onkostar.api.Procedure;
import de.itc.onkostar.api.analysis.OnkostarPluginType;
public abstract class BackendService implements IPluginPart {
@Override
public final OnkostarPluginType getType() {
return OnkostarPluginType.BACKEND_SERVICE;
}
/**
* Ein Backend-Service verwendet die Methode nicht, daher wird hier eine final Stub-Implementierung
* verwendet, die ein Überschreiben verhindert.
* @param procedure
* @param disease
*/
@Override
public final void analyze(Procedure procedure, Disease disease) {
// No op
}
}

View File

@ -0,0 +1,63 @@
package dev.dnpm.analyzer;
import dev.dnpm.services.consent.ConsentManagerServiceFactory;
import de.itc.onkostar.api.Disease;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Procedure;
import de.itc.onkostar.api.analysis.AnalyzerRequirement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConsentManager extends Analyzer {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final IOnkostarApi onkostarApi;
private final ConsentManagerServiceFactory consentManagerServiceFactory;
public ConsentManager(
final IOnkostarApi onkostarApi,
final ConsentManagerServiceFactory consentManagerServiceFactory
) {
this.onkostarApi = onkostarApi;
this.consentManagerServiceFactory = consentManagerServiceFactory;
}
@Override
public String getDescription() {
return "Aktualisiert Consent Daten in verknüpften Formularen";
}
@Override
public AnalyzerRequirement getRequirement() {
return AnalyzerRequirement.PROCEDURE;
}
@Override
public boolean isRelevantForAnalyzer(Procedure prozedur, Disease erkrankung) {
return prozedur.getFormName().equals(onkostarApi.getGlobalSetting("consentform"));
}
@Override
public boolean isRelevantForDeletedProcedure() {
// TODO is relevant for deleted procedure = true
return false;
}
@Override
public boolean isSynchronous() {
return true;
}
@Override
public void analyze(Procedure prozedur, Disease erkrankung) {
var consentManagerService = consentManagerServiceFactory.currentUsableInstance();
if (! consentManagerService.canApply(prozedur)) {
logger.error("Fehler im ConsentManagement: Kann Prozedur mit Formularnamen '{}' nicht anwenden", prozedur.getFormName());
return;
}
consentManagerService.applyConsent(prozedur);
}
}

View File

@ -0,0 +1,260 @@
package dev.dnpm.analyzer;
import dev.dnpm.dto.EcogStatusWithDate;
import dev.dnpm.VerweisVon;
import dev.dnpm.security.DelegatingDataBasedPermissionEvaluator;
import dev.dnpm.security.IllegalSecuredObjectAccessException;
import dev.dnpm.security.PermissionType;
import dev.dnpm.services.systemtherapie.SystemtherapieService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.itc.onkostar.api.Disease;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Procedure;
import de.itc.onkostar.api.analysis.AnalyzerRequirement;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.transform.Transformers;
import org.hibernate.type.StandardBasicTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DNPMHelper extends BackendService {
private static final Logger logger = LoggerFactory.getLogger(DNPMHelper.class);
private final IOnkostarApi onkostarApi;
private final SystemtherapieService systemtherapieService;
private final DelegatingDataBasedPermissionEvaluator delegatingDataBasedPermissionEvaluator;
public DNPMHelper(
final IOnkostarApi onkostarApi,
final SystemtherapieService systemtherapieService,
final DelegatingDataBasedPermissionEvaluator permissionEvaluator
) {
this.onkostarApi = onkostarApi;
this.systemtherapieService = systemtherapieService;
this.delegatingDataBasedPermissionEvaluator = permissionEvaluator;
}
@Override
public String getDescription() {
return "Methoden für DNPM-Formulare";
}
@Override
public boolean isRelevantForDeletedProcedure() {
return false;
}
@Override
public boolean isSynchronous() {
return true;
}
@Override
public AnalyzerRequirement getRequirement() {
return AnalyzerRequirement.PROCEDURE;
}
@Override
public boolean isRelevantForAnalyzer(Procedure entry, Disease currentDisease) {
// Plugin enthält nur Methoden für Formulare und soll nicht ausgeführt werden
return false;
}
@SuppressWarnings("unchecked")
public List<Map<String, String>> getVerweise(final Map<String, Object> input) {
var procedureId = AnalyzerUtils.getRequiredId(input, "ProcedureId");
var patientId = AnalyzerUtils.getRequiredId(input, "PatientId");
if (procedureId.isEmpty() || patientId.isEmpty()) {
return null;
}
var verbundeneFormulare = new ArrayList<Map<String, String>>();
try {
SessionFactory sessionFactory = onkostarApi.getSessionFactory();
Session session = sessionFactory.getCurrentSession();
String sql = "SELECT prozedur.id AS procedure_id, prozedur.data_form_id, data_catalogue.name AS data_catalogue, data_catalogue_entry.name AS data_catalogue_entry, data_form.description AS formname, prozedur.beginndatum AS datum " +
"FROM prozedur " +
"LEFT JOIN data_form_data_catalogue ON data_form_data_catalogue.data_form_id = prozedur.data_form_id " +
"LEFT JOIN data_catalogue_entry ON data_catalogue_entry.data_catalogue_id = data_form_data_catalogue.data_catalogue_id " +
"LEFT JOIN data_catalogue ON data_catalogue.id = data_catalogue_entry.data_catalogue_id " +
"LEFT JOIN data_form ON data_form.id = prozedur.data_form_id " +
"WHERE patient_id = " + patientId.get() + " " +
"AND geloescht = 0 " +
"AND data_catalogue_entry.type = 'formReference' " +
"GROUP BY prozedur.id, prozedur.data_form_id, data_catalogue.name, data_catalogue_entry.name";
SQLQuery query = session.createSQLQuery(sql)
.addScalar("procedure_id", StandardBasicTypes.INTEGER)
.addScalar("data_form_id", StandardBasicTypes.INTEGER)
.addScalar("data_catalogue", StandardBasicTypes.STRING)
.addScalar("data_catalogue_entry", StandardBasicTypes.STRING)
.addScalar("formname", StandardBasicTypes.STRING)
.addScalar("datum", StandardBasicTypes.DATE);
query.setResultTransformer(Transformers.aliasToBean(VerweisVon.class));
List<VerweisVon> result = query.list();
try {
int value = 0;
for (VerweisVon verweisVon : result) {
sql = verweisVon.getSQL();
query = session.createSQLQuery(sql)
.addScalar("value", StandardBasicTypes.INTEGER);
if (query.uniqueResult() != null) {
value = (Integer) query.uniqueResult();
}
if (value == procedureId.get()) {
verbundeneFormulare.add(Map.of("formular", verweisVon.getVerbundenesFormular()));
value = 0;
}
}
} catch (Exception e) {
logger.warn("Fehler beim Hinzufügen eines Formularverweises", e);
}
} catch (Exception e) {
logger.error("Fehler beim Ermitteln der Formularverweise", e);
return null;
}
return verbundeneFormulare;
}
public List<Map<String, String>> getSystemischeTherapienFromDiagnose(final Map<String, Object> input) {
var diagnoseId = AnalyzerUtils.getRequiredId(input, "DiagnoseId");
if (diagnoseId.isEmpty()) {
logger.error("Kein Parameter 'DiagnoseId' angegeben, gebe 'null' zurück");
return null;
}
return systemtherapieService.getSystemischeTherapienFromDiagnose(diagnoseId.get());
}
public String getProzedurenFromDiagnose(final Map<String, Object> input) {
// Prozedur, Feldname, Wert
var dataForm = AnalyzerUtils.getRequiredValue(input, "dataForm", String.class);
var diagnoseId = AnalyzerUtils.getRequiredId(input, "DiagnoseId");
var patientId = AnalyzerUtils.getRequiredId(input, "PatientId");
if (dataForm.isEmpty() || diagnoseId.isEmpty() || patientId.isEmpty()) {
return "";
}
var formulare = new ArrayList<Map<String, Object>>();
List<Procedure> prozeduren = onkostarApi.getProceduresByPatientId(patientId.get());
for (Procedure Prozedur : prozeduren) {
// Formular gehört zur aktuellen Diagnose und hat den angegebenen Namen
if (Prozedur.getDiseaseIds().contains(diagnoseId.get()) && Prozedur.getFormName().contains(dataForm.get())) {
// alle Werte auslesen
// System.out.println(WerteListe.getKey() + ": " + WerteListe.getValue());
formulare.add(Map.of(
"Formular", Prozedur.getFormName(),
"Felder", new HashMap<>(Prozedur.getAllValues())
));
}
}
try {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(formulare);
} catch (JsonProcessingException e) {
logger.error("Kann Formulare nicht in JSON mappen", e);
}
return "";
}
public Object getEmpfehlung(final Map<String, Object> input) {
var procedureID = AnalyzerUtils.getRequiredId(input, "ProcedureID");
if (procedureID.isEmpty()) {
logger.error("Kein Parameter 'ProcedureID' angegeben, gebe 'null' zurück");
return null;
}
try {
SessionFactory sessionFactory = onkostarApi.getSessionFactory();
Session session = sessionFactory.getCurrentSession();
var sql = "SELECT prozedur.id, genname, geneid, geneidlink, empfehlung, beginndatum FROM prozedur "
+ "LEFT JOIN dk_mtb_einzelempfehlung em ON em.id = prozedur.id "
+ "JOIN data_form df ON prozedur.data_form_id = df.id AND df.name = 'MR.MTB_Einzelempfehlung' "
+ "WHERE prozedur.hauptprozedur_id = " + procedureID.get() + " AND prozedur.geloescht = 0 "
+ "ORDER BY beginndatum";
SQLQuery query = session.createSQLQuery(sql)
.addScalar("id", StandardBasicTypes.STRING)
.addScalar("genname", StandardBasicTypes.STRING)
.addScalar("geneid", StandardBasicTypes.STRING)
.addScalar("geneidlink", StandardBasicTypes.STRING)
.addScalar("empfehlung", StandardBasicTypes.STRING)
.addScalar("beginndatum", StandardBasicTypes.STRING);
@SuppressWarnings("unchecked")
List<String[]> rows = query.list();
return rows;
} catch (Exception e) {
logger.error("Fehler bei Abfrage von Empfehlungen", e);
return null;
}
}
public Object updateEmpfehlungPrio(final Map<String, Object> input) {
// Auslesen und Prüfen der Parameter aus 'input'
var rid = AnalyzerUtils.getRequiredId(input, "rid");
if (rid.isEmpty()) {
logger.error("Kein Parameter 'rid' angegeben, gebe 'false' zurück");
return false;
}
var strDate = AnalyzerUtils.getRequiredValueMatching(input, "bd", "[\\d]{4}-[\\d]{2}-[\\d]{2}");
if (strDate.isEmpty()) {
logger.error("Kein oder ungültiger Parameter 'bd' angegeben, gebe 'false' zurück");
return false;
}
//String strD = strDate.toString();
//String CompareDate = strD.substring(1, 11);
//DateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd");
try {
String sql = "UPDATE prozedur SET beginndatum = '" + strDate.get() + "' WHERE id = '" + rid.get() + "' ";
SQLQuery result = onkostarApi.getSessionFactory().getCurrentSession().createSQLQuery(sql);
result.executeUpdate();
return true;
} catch (Exception e) {
return "Achtung: Ein Fehler ist aufgetreten, Änderung konnte nicht gespeichert werden!";
//return null;
}
}
public List<EcogStatusWithDate> getEcogStatus(final Map<String, Object> input) {
var pid = AnalyzerUtils.getRequiredId(input, "PatientId");
if (pid.isEmpty()) {
logger.error("Kein Parameter 'PatientId' angegeben, gebe leere Liste zurück");
return List.of();
}
var patient = onkostarApi.getPatient(pid.get());
if (null == patient) {
logger.error("Patient nicht gefunden, gebe leere Liste zurück");
return List.of();
}
if (delegatingDataBasedPermissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), patient, PermissionType.READ)) {
return systemtherapieService.ecogStatus(patient);
}
throw new IllegalSecuredObjectAccessException("Kein Zugriff auf diesen Patienten");
}
}

View File

@ -0,0 +1,134 @@
package dev.dnpm.analyzer;
import dev.dnpm.dto.Studie;
import dev.dnpm.dto.Variant;
import dev.dnpm.security.PermissionType;
import dev.dnpm.security.PersonPoolBasedPermissionEvaluator;
import dev.dnpm.services.StudienService;
import dev.dnpm.services.molekulargenetik.MolekulargenetikFormService;
import de.itc.onkostar.api.Disease;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Procedure;
import de.itc.onkostar.api.analysis.AnalyzerRequirement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* Diese Klasse implementiert ein Plugin, welches Funktionen für DNPM UF Einzelempfehlung bereitstellt.
*
* @since 0.2.0
*/
@Component
public class EinzelempfehlungAnalyzer extends BackendService {
private final static Logger logger = LoggerFactory.getLogger(EinzelempfehlungAnalyzer.class);
private final IOnkostarApi onkostarApi;
private final MolekulargenetikFormService molekulargenetikFormService;
private final StudienService studienService;
private final PersonPoolBasedPermissionEvaluator permissionEvaluator;
public EinzelempfehlungAnalyzer(
final IOnkostarApi onkostarApi,
final StudienService studienService,
final MolekulargenetikFormService molekulargenetikFormService,
final PersonPoolBasedPermissionEvaluator permissionEvaluator
) {
this.onkostarApi = onkostarApi;
this.studienService = studienService;
this.molekulargenetikFormService = molekulargenetikFormService;
this.permissionEvaluator = permissionEvaluator;
}
@Override
public String getDescription() {
return "Stellt Funktionen zur Nutzung im Therapieplan-Unterformular für Einzelempfehlungen bereit";
}
/**
* @deprecated
*/
@Override
public boolean isRelevantForDeletedProcedure() {
return false;
}
@Override
public boolean isRelevantForAnalyzer(Procedure procedure, Disease disease) {
return false;
}
@Override
public boolean isSynchronous() {
return false;
}
@Override
public AnalyzerRequirement getRequirement() {
return AnalyzerRequirement.PROCEDURE;
}
public List<Variant> getVariants(Map<String, Object> input) {
var procedureId = AnalyzerUtils.getRequiredId(input, "id");
if (procedureId.isEmpty()) {
return List.of();
}
var procedure = onkostarApi.getProcedure(procedureId.get());
if (null == procedure) {
return List.of();
}
if (permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), procedure, PermissionType.READ)) {
return molekulargenetikFormService.getVariants(procedure);
} else {
logger.error("Security: No permission to access procedure '{}'", procedure.getId());
return List.of();
}
}
/**
* Übergibt alle Studien, deren (Kurz-)Beschreibung oder NCT-Nummer den übergebenen Eingabewert <code>q</code> enthält
*
* <p>Wurde der Eingabewert nicht angegeben oder ist leer, werden alle Studien übergeben.
*
* <p>Beispiel zur Nutzung in einem Formularscript
* <pre>
* executePluginMethod(
* 'TherapieplanAnalyzer',
* 'getStudien',
* { q: 'NCT-12', inactive: true },
* (response) => console.log(response),
* false
* );
* </pre>
*
* @param input Map mit Eingabewerten
* @return Liste mit Studien
*/
public List<Studie> getStudien(Map<String, Object> input) {
var query = AnalyzerUtils.getRequiredValue(input, "q", String.class);
var inactive = AnalyzerUtils.getRequiredValue(input, "inactive", Boolean.class).orElse(false);
if (query.isEmpty() || query.get().isBlank()) {
if (inactive) {
return studienService.findAll();
}
return studienService.findActive();
}
if (inactive) {
return studienService.findByQuery(query.get());
}
return studienService.findActiveByQuery(query.get());
}
}

View File

@ -0,0 +1,103 @@
package dev.dnpm.analyzer;
import de.itc.onkostar.api.Disease;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Item;
import de.itc.onkostar.api.Procedure;
import de.itc.onkostar.api.analysis.AnalyseTriggerEvent;
import de.itc.onkostar.api.analysis.AnalyzerRequirement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* Diese Klasse implementiert ein Plugin, welches Aktionen nach Bearbeitung eines FollowUps durchführt.
*
* @since 0.0.2
*/
@Component
public class FollowUpAnalyzer extends Analyzer {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final IOnkostarApi onkostarApi;
public FollowUpAnalyzer(IOnkostarApi onkostarApi) {
this.onkostarApi = onkostarApi;
}
@Override
public String getDescription() {
return "Aktualisiert verknüpfte Formulare nach Änderungen im FollowUp-Formular";
}
/**
* @deprecated
*/
@Override
public boolean isRelevantForDeletedProcedure() {
return false;
}
@Override
public boolean isRelevantForAnalyzer(Procedure procedure, Disease disease) {
return null != procedure && procedure.getFormName().equals("DNPM FollowUp");
}
@Override
public boolean isSynchronous() {
return false;
}
@Override
public AnalyzerRequirement getRequirement() {
return AnalyzerRequirement.PROCEDURE;
}
@Override
public Set<AnalyseTriggerEvent> getTriggerEvents() {
return Set.of(
AnalyseTriggerEvent.EDIT_SAVE,
AnalyseTriggerEvent.EDIT_LOCK,
AnalyseTriggerEvent.REORG
);
}
@Override
public void analyze(Procedure procedure, Disease disease) {
backlinkToEinzelempfehlung(procedure);
}
/**
* Verlinke aktuelles FollowUp in angegebener Einzelempfehlung
*
* @param procedure Das FollowUp
*/
private void backlinkToEinzelempfehlung(Procedure procedure) {
if (null == procedure.getValue("LinkTherapieempfehlung")) {
return;
}
var referencedProcedureId = procedure.getValue("LinkTherapieempfehlung");
if (null == referencedProcedureId || referencedProcedureId.getInt() == 0) {
// Alles gut, es ist keine Einzelempfehlung angegeben
return;
}
var referencedProcedure = onkostarApi.getProcedure(referencedProcedureId.getInt());
if (null == referencedProcedure) {
logger.error("Referenzierte Einzelempfehlung wurde nicht gefunden: {}", referencedProcedureId);
return;
}
referencedProcedure.setValue("refdnpmfollowup", new Item("ref_dnpm_followup", procedure.getId()));
try {
onkostarApi.saveProcedure(referencedProcedure);
} catch (Exception e) {
logger.error("FollowUp konnte nicht mit Einzelempfehlung verknüpft werden", e);
}
}
}

View File

@ -0,0 +1,19 @@
package dev.dnpm.analyzer;
import de.itc.onkostar.api.analysis.IProcedureAnalyzer;
public interface IPluginPart extends IProcedureAnalyzer {
default String getVersion() {
return "0.4.0";
}
default String getName() {
return "DNPM Plugin";
}
default String getDescription() {
return String.format("Plugin-Bestandteil '%s'", this.getClass().getSimpleName());
}
}

View File

@ -0,0 +1,97 @@
package dev.dnpm.analyzer;
import de.itc.onkostar.api.Disease;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Procedure;
import de.itc.onkostar.api.analysis.AnalyzerRequirement;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.type.StandardBasicTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
public class Merkmalskatalog extends BackendService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final IOnkostarApi onkostarApi;
public Merkmalskatalog(final IOnkostarApi onkostarApi) {
this.onkostarApi = onkostarApi;
}
@Override
public String getDescription() {
return "Methoden für Merkmalskataloge";
}
@Override
public boolean isRelevantForDeletedProcedure() {
return false;
}
@Override
public boolean isSynchronous() {
return true;
}
@Override
public AnalyzerRequirement getRequirement() {
return AnalyzerRequirement.PROCEDURE;
}
@Override
public boolean isRelevantForAnalyzer(Procedure procedure, Disease currentDisease) {
return false;
}
public List<String[]> getMerkmalskatalog(final Map<String, Object> input) {
var merkmalskatalog = AnalyzerUtils.getRequiredValue(input, "Merkmalskatalog", String.class);
var spalten = AnalyzerUtils.getRequiredValue(input, "Spalten", String.class);
if (merkmalskatalog.isEmpty()) {
logger.error("Kein Merkmalskatalog angegeben!");
return null;
}
if (spalten.isEmpty()) {
logger.error("Keine Spalten angegeben!");
return null;
}
String[] spaltenArray = spalten.get().split("\\s*,\\s*");
try {
SQLQuery query = getSqlQuery(merkmalskatalog.get());
for (String s : spaltenArray) {
query.addScalar(s, StandardBasicTypes.STRING);
}
@SuppressWarnings("unchecked")
List<String[]> rows = query.list();
return rows;
} catch (Exception e) {
logger.error("Fehler bei der Ausführung von getMerkmalskatalog()", e);
return null;
}
}
private SQLQuery getSqlQuery(String merkmalskatalog) {
SessionFactory sessionFactory = onkostarApi.getSessionFactory();
Session session = sessionFactory.getCurrentSession();
String sql = "SELECT p.id, p.code, p.shortdesc, p.description, p.note, p.synonyms "
+ "FROM property_catalogue "
+ "LEFT JOIN property_catalogue_version ON property_catalogue_version.datacatalog_id = property_catalogue.id "
+ "LEFT JOIN property_catalogue_version_entry p ON p.property_version_id = property_catalogue_version.id "
+ "WHERE name = '" + merkmalskatalog + "' AND aktiv = 1 "
+ "ORDER BY position ASC";
return session.createSQLQuery(sql);
}
}

View File

@ -0,0 +1,168 @@
package dev.dnpm.analyzer;
import dev.dnpm.dto.EcogStatusWithDate;
import dev.dnpm.services.strahlentherapie.StrahlentherapieService;
import dev.dnpm.services.systemtherapie.SystemtherapieService;
import de.itc.onkostar.api.Disease;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Item;
import de.itc.onkostar.api.Procedure;
import de.itc.onkostar.api.analysis.AnalyseTriggerEvent;
import de.itc.onkostar.api.analysis.AnalyzerRequirement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Diese Klasse implementiert ein Plugin, welches Aktionen nach Bearbeitung eines Formulars zur Systemtherapie durchführt.
*
* @since 0.6.0
*/
@Component
public class TherapieMitEcogAnalyzer extends Analyzer {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final IOnkostarApi onkostarApi;
private final StrahlentherapieService strahlentherapieService;
private final SystemtherapieService systemtherapieService;
public TherapieMitEcogAnalyzer(
final IOnkostarApi onkostarApi,
final StrahlentherapieService strahlentherapieService,
final SystemtherapieService systemtherapieService
) {
this.onkostarApi = onkostarApi;
this.strahlentherapieService = strahlentherapieService;
this.systemtherapieService = systemtherapieService;
}
@Override
public String getDescription() {
return "Aktualisiert verknüpfte Formulare nach Änderungen in Formularen vom Typ Strahlen-/Systemtherapie mit ECOG-Status";
}
/**
* @deprecated
*/
@Override
public boolean isRelevantForDeletedProcedure() {
return true;
}
@Override
public boolean isRelevantForAnalyzer(Procedure procedure, Disease disease) {
return null != procedure && null != disease && (
procedure.getFormName().equals("OS.Strahlentherapie")
|| procedure.getFormName().equals("OS.Strahlentherapie.VarianteUKW")
|| procedure.getFormName().equals("OS.Systemische Therapie")
|| procedure.getFormName().equals("OS.Systemische Therapie.VarianteUKW")
);
}
@Override
public boolean isSynchronous() {
return false;
}
@Override
public AnalyzerRequirement getRequirement() {
return AnalyzerRequirement.PROCEDURE;
}
@Override
public Set<AnalyseTriggerEvent> getTriggerEvents() {
return Set.of(
AnalyseTriggerEvent.EDIT_SAVE,
AnalyseTriggerEvent.EDIT_LOCK,
AnalyseTriggerEvent.REORG
);
}
@Override
public void analyze(Procedure procedure, Disease disease) {
var date = procedure.getStartDate();
var status = procedure.getValue("ECOGvorTherapie");
if (null == date || null == status) {
// Ignore
return;
}
var ecog = strahlentherapieService.ecogStatus(procedure.getPatient())
.stream()
.filter(ecogStatusWithDate -> ecogStatusWithDate.getDate().after(disease.getDiagnosisDate()))
.collect(Collectors.toList());
ecog.addAll(systemtherapieService.ecogStatus(procedure.getPatient())
.stream()
.filter(ecogStatusWithDate -> ecogStatusWithDate.getDate().after(disease.getDiagnosisDate()))
.collect(Collectors.toList()));
if (ecog.isEmpty()) {
// Nothing to do
return;
}
procedure.getPatient().getDiseases().stream()
.flatMap(d -> onkostarApi.getProceduresForDiseaseByForm(d.getId(), "DNPM Klinik/Anamnese").stream())
.forEach(p -> {
var ufEcog = p.getValue("ECOGVerlauf");
if (null != ufEcog && ufEcog.getValue() instanceof List) {
updateExistingEcogVerlauf(p, ecog, ufEcog);
} else {
newEcogverlauf(p, ecog);
}
});
}
private void updateExistingEcogVerlauf(Procedure p, List<EcogStatusWithDate> ecogFromCompleted, Item ufEcog) {
var shouldSave = false;
var existingDates = ufEcog.<List<Map<String, String>>>getValue().stream()
.map(v -> v.get("Datum"))
.collect(Collectors.toList());
for (var ecog : ecogFromCompleted) {
var formattedDate = new SimpleDateFormat("yyyy-MM-dd").format(ecog.getDate());
if (!existingDates.contains(formattedDate)) {
var newSubProcedure = new Procedure(onkostarApi);
newSubProcedure.setStartDate(ecog.getDate());
newSubProcedure.setValue("Datum", new Item("Datum", ecog.getDate()));
newSubProcedure.setValue("ECOG", new Item("ECOG", ecog.getStatus()));
p.addSubProcedure("ECOGVerlauf", newSubProcedure);
shouldSave = true;
}
}
if (shouldSave) {
try {
onkostarApi.saveProcedure(p, true);
} catch (Exception e) {
logger.error("Cannot update ECOG for procedure '{}'", p.getId());
}
}
}
private void newEcogverlauf(Procedure p, List<EcogStatusWithDate> ecogFromCompleted) {
p.setValue("ECOGVerlauf", new Item("ECOGVerlauf", List.of()));
for (var ecog : ecogFromCompleted) {
var newSubProcedure = new Procedure(onkostarApi);
newSubProcedure.setStartDate(ecog.getDate());
newSubProcedure.setValue("Datum", new Item("Datum", ecog.getDate()));
newSubProcedure.setValue("ECOG", new Item("ECOG", ecog.getStatus()));
p.addSubProcedure("ECOGVerlauf", newSubProcedure);
}
try {
onkostarApi.saveProcedure(p, true);
} catch (Exception e) {
logger.error("Create update ECOG for procedure '{}'", p.getId());
}
}
}

View File

@ -0,0 +1,127 @@
package dev.dnpm.analyzer;
import dev.dnpm.security.DelegatingDataBasedPermissionEvaluator;
import dev.dnpm.security.PermissionType;
import dev.dnpm.services.mtb.MtbService;
import dev.dnpm.services.therapieplan.TherapieplanServiceFactory;
import de.itc.onkostar.api.Disease;
import de.itc.onkostar.api.Procedure;
import de.itc.onkostar.api.analysis.AnalyseTriggerEvent;
import de.itc.onkostar.api.analysis.AnalyzerRequirement;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
/**
* Diese Klasse implementiert ein Plugin, welches Aktionen nach Bearbeitung eines Therapieplans durchführt.
*
* @since 0.0.2
*/
@Component
public class TherapieplanAnalyzer extends Analyzer {
private final TherapieplanServiceFactory therapieplanServiceFactory;
private final MtbService mtbService;
private final DelegatingDataBasedPermissionEvaluator permissionEvaluator;
public TherapieplanAnalyzer(
final TherapieplanServiceFactory therapieplanServiceFactory,
final MtbService mtbService,
final DelegatingDataBasedPermissionEvaluator permissionEvaluator
) {
this.therapieplanServiceFactory = therapieplanServiceFactory;
this.mtbService = mtbService;
this.permissionEvaluator = permissionEvaluator;
}
@Override
public String getDescription() {
return "Aktualisiert Unterformulare nach Änderungen im Therapieplan-Formular";
}
/**
* @deprecated
*/
@Override
public boolean isRelevantForDeletedProcedure() {
return false;
}
@Override
public boolean isRelevantForAnalyzer(Procedure procedure, Disease disease) {
return null != procedure && procedure.getFormName().equals("DNPM Therapieplan");
}
@Override
public boolean isSynchronous() {
return false;
}
@Override
public AnalyzerRequirement getRequirement() {
return AnalyzerRequirement.PROCEDURE;
}
@Override
public Set<AnalyseTriggerEvent> getTriggerEvents() {
return Set.of(
AnalyseTriggerEvent.EDIT_SAVE,
AnalyseTriggerEvent.EDIT_LOCK,
AnalyseTriggerEvent.REORG
);
}
@Override
public void analyze(Procedure procedure, Disease disease) {
therapieplanServiceFactory.currentUsableInstance().updateRequiredMtbEntries(procedure);
}
/**
* Übergibt den Text der referenzierten MTBs für den Protokollauszug
*
* <p>Wurde der Eingabewert <code>id</code> nicht übergeben, wird ein leerer String zurück gegeben.
*
* <p>Beispiel zur Nutzung in einem Formularscript
* <pre>
* executePluginMethod(
* 'TherapieplanAnalyzer',
* 'getProtokollauszug',
* { id: 12345 },
* (response) => console.log(response),
* false
* );
* </pre>
*
* @param input Map mit Eingabewerten
* @return Zeichenkette mit Protokollauszug
*/
public String getProtokollauszug(Map<String, Object> input) {
var procedureId = AnalyzerUtils.getRequiredId(input, "id");
if (procedureId.isEmpty()) {
return "";
}
if (
permissionEvaluator.hasPermission(
SecurityContextHolder.getContext().getAuthentication(),
procedureId.get(),
Procedure.class.getSimpleName(),
PermissionType.READ
)
) {
return mtbService.getProtocol(
therapieplanServiceFactory
.currentUsableInstance()
.findReferencedMtbs(procedureId.get())
);
}
return "";
}
}

View File

@ -0,0 +1,88 @@
package dev.dnpm.config;
import dev.dnpm.database.SettingsRepository;
import dev.dnpm.services.*;
import dev.dnpm.services.consent.ConsentManagerServiceFactory;
import dev.dnpm.services.molekulargenetik.MolekulargenetikFormService;
import dev.dnpm.services.molekulargenetik.OsMolekulargenetikFormService;
import dev.dnpm.services.mtb.DefaultMtbService;
import dev.dnpm.services.mtb.MtbService;
import dev.dnpm.services.strahlentherapie.DefaultStrahlentherapieService;
import dev.dnpm.services.strahlentherapie.StrahlentherapieService;
import dev.dnpm.services.systemtherapie.DefaultSystemtherapieService;
import dev.dnpm.services.systemtherapie.SystemtherapieService;
import dev.dnpm.services.therapieplan.TherapieplanServiceFactory;
import de.itc.onkostar.api.IOnkostarApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import javax.sql.DataSource;
/**
* Dynamische Konfiguration des Plugins basierend auf Onkostar-Einstellungen
*
* @since 0.0.2
*/
@Configuration
@ComponentScan(basePackages = { "dev.dnpm.analyzer", "dev.dnpm.security" })
@EnableJpaRepositories(basePackages = "dev.dnpm.database")
public class PluginConfiguration {
@Bean
public FormService formService(final DataSource dataSource) {
return new DefaultFormService(dataSource);
}
@Bean
public StudienService studienService(final DataSource dataSource) {
return new DefaultStudienService(dataSource);
}
@Bean
public SettingsService settingsService(final SettingsRepository settingsRepository) {
return new SettingsService(settingsRepository);
}
@Bean
public MtbService mtbService(final IOnkostarApi onkostarApi) {
return new DefaultMtbService(onkostarApi);
}
@Bean
public SystemtherapieService systemtherapieService(
final IOnkostarApi onkostarApi,
final SettingsService settingsService
) {
return new DefaultSystemtherapieService(onkostarApi, settingsService);
}
@Bean
public StrahlentherapieService strahlentherapieService(
final IOnkostarApi onkostarApi,
final SettingsService settingsService
) {
return new DefaultStrahlentherapieService(onkostarApi, settingsService);
}
@Bean
public ConsentManagerServiceFactory consentManagerServiceFactory(final IOnkostarApi onkostarApi) {
return new ConsentManagerServiceFactory(onkostarApi);
}
@Bean
public TherapieplanServiceFactory therapieplanServiceFactory(
final IOnkostarApi onkostarApi,
final SettingsService settingsService,
final FormService formService
) {
return new TherapieplanServiceFactory(onkostarApi, settingsService, formService);
}
@Bean
public MolekulargenetikFormService molekulargenetikFormService() {
return new OsMolekulargenetikFormService();
}
}

View File

@ -0,0 +1,22 @@
package dev.dnpm.database;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;
import java.io.Serializable;
import java.util.List;
/**
* Basis-Repository for ReadOnly Spring-Data-JPA Repositories
* <p>Entity-Klassen müssen in Package <code>de.itc.db.dnpm</code> liegen
* @param <T> Typ des Entities
* @param <ID> Typ der ID
*/
@NoRepositoryBean
public interface ReadOnlyRepository<T, ID extends Serializable> extends Repository<T, ID> {
T findById(ID id);
List<T> findAll();
}

View File

@ -0,0 +1,14 @@
package dev.dnpm.database;
import de.itc.db.dnpm.Setting;
import org.springframework.stereotype.Repository;
/**
* Spring Data JPA Repository zum Lesen von Einstellungen
*/
@Repository("dnpmSettingRepository")
public interface SettingsRepository extends ReadOnlyRepository<Setting, Long> {
Setting findByName(String name);
}

View File

@ -0,0 +1,44 @@
package dev.dnpm.dto;
import org.springframework.util.Assert;
import java.util.Date;
/**
* Datenklasse zum Abbilden des ECOG-Status und Datum
*/
public class EcogStatusWithDate {
private final Date date;
private final String status;
public EcogStatusWithDate(Date date, String status) {
Assert.notNull(date, "Date cannot be null");
Assert.hasText(status, "Status cannot be empty String");
Assert.isTrue(isValidEcogCode(status), "Not a valid ADT.LeistungszustandECOG code");
this.date = date;
this.status = status;
}
private boolean isValidEcogCode(String status) {
switch (status) {
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "U":
return true;
default:
return false;
}
}
public Date getDate() {
return date;
}
public String getStatus() {
return status;
}
}

View File

@ -0,0 +1,74 @@
package dev.dnpm.dto;
public class Studie {
private final String kategorieName;
private final String code;
private final String studiennummer;
private final String shortDesc;
private final String description;
private final int version;
private final boolean active;
public Studie(final String kategorieName, final int version, final String code, final String studiennummer, final String shortDesc, final String description, final boolean active) {
this.kategorieName = kategorieName;
this.version = version;
this.code = code;
this.studiennummer = studiennummer;
this.shortDesc = shortDesc;
this.description = description;
this.active = active;
}
public String getKategorieName() {
return kategorieName;
}
public int getVersion() {
return version;
}
public String getCode() {
return code;
}
public String getStudiennummer() {
return studiennummer;
}
public String getShortDesc() {
return shortDesc;
}
public String getDescription() {
return description;
}
public boolean isActive() {
return active;
}
public Type getType() {
if (this.hasNctNumber()) {
return Type.NCT;
} else if (this.hasEudraCtNumber()) {
return Type.EUDRA_CT;
} else {
return Type.UNKNOWN;
}
}
private boolean hasNctNumber() {
return null != studiennummer && studiennummer.toLowerCase().startsWith("nct");
}
private boolean hasEudraCtNumber() {
return null != studiennummer && studiennummer.matches("\\d{4}-\\d{6}-\\d{2}");
}
public enum Type {
NCT,
EUDRA_CT,
UNKNOWN
}
}

View File

@ -0,0 +1,110 @@
package dev.dnpm.dto;
import de.itc.onkostar.api.Procedure;
import java.util.Optional;
/**
* Ein Auszug der Variante aus dem NGS-Bericht zur Übertragung an das Frontend zur Auswahl der stützenden molekularen Alteration
*
* @since 0.2.0
*/
public class Variant {
private final Integer id;
private final String ergebnis;
private final String gen;
private final String exon;
private final String pathogenitaetsklasse;
private Variant(
final int id,
final String ergebnis,
final String gen,
final String exon,
final String pathogenitaetsklasse
) {
this.id = id;
this.ergebnis = ergebnis;
this.gen = gen;
this.exon = exon;
this.pathogenitaetsklasse = pathogenitaetsklasse;
}
public Integer getId() {
return id;
}
public String getErgebnis() {
return ergebnis;
}
public String getGen() {
return gen;
}
public String getExon() {
return exon;
}
public String getPathogenitaetsklasse() {
return pathogenitaetsklasse;
}
/**
* Erstellt ein Optional einer Variante aus einer Prozedur
* @param procedure Die zu verwendende Prozedur
* @return Das Optional, wenn die Prozedur verwendet werden kann, ansonsten ein leeres Optional
*/
public static Optional<Variant> fromProcedure(Procedure procedure) {
if (!"OS.Molekulargenetische Untersuchung".equals(procedure.getFormName())) {
return Optional.empty();
}
var ergebnis = procedure.getValue("Ergebnis");
var gene = procedure.getValue("Untersucht");
var exon = procedure.getValue("ExonInt");
var pathogenitaetsklasse = procedure.getValue("Pathogenitaetsklasse");
if (null == gene) {
return Optional.empty();
}
if (ergebnis.getString().equals("P")) {
return Optional.of(
new Variant(
procedure.getId(),
"Einfache Variante (Mutation)",
gene.getString().isBlank() ? "-" : gene.getString(),
null == exon || exon.getString().isBlank() ? "-" : exon.getString(),
null == pathogenitaetsklasse || pathogenitaetsklasse.getString().isBlank() ? "-" : pathogenitaetsklasse.getString()
)
);
} else if (ergebnis.getString().equals("CNV")) {
return Optional.of(
new Variant(
procedure.getId(),
"Copy Number Variation (CNV)",
gene.getString().isBlank() ? "-" : gene.getString(),
null == exon || exon.getString().isBlank() ? "-" : exon.getString(),
null == pathogenitaetsklasse || pathogenitaetsklasse.getString().isBlank() ? "-" : pathogenitaetsklasse.getString()
)
);
} else if (ergebnis.getString().equals("F")) {
return Optional.of(
new Variant(
procedure.getId(),
"Fusion (Translokation Inversion Insertion)",
gene.getString().isBlank() ? "-" : gene.getString(),
null == exon || exon.getString().isBlank() ? "-" : exon.getString(),
null == pathogenitaetsklasse || pathogenitaetsklasse.getString().isBlank() ? "-" : pathogenitaetsklasse.getString()
)
);
} else {
return Optional.empty();
}
}
}

View File

@ -0,0 +1,9 @@
package dev.dnpm.exceptions;
public class FormException extends RuntimeException {
public FormException(String message) {
super(message);
}
}

View File

@ -0,0 +1,23 @@
package dev.dnpm.security;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Patient;
import de.itc.onkostar.api.Procedure;
import org.springframework.security.access.PermissionEvaluator;
public abstract class AbstractDelegatedPermissionEvaluator implements PermissionEvaluator {
protected static final String PATIENT = Patient.class.getSimpleName();
protected static final String PROCEDURE = Procedure.class.getSimpleName();
protected final IOnkostarApi onkostarApi;
protected final SecurityService securityService;
protected AbstractDelegatedPermissionEvaluator(final IOnkostarApi onkostarApi, final SecurityService securityService) {
this.onkostarApi = onkostarApi;
this.securityService = securityService;
}
}

View File

@ -0,0 +1,56 @@
package dev.dnpm.security;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.List;
/**
* PermissionEvaluator zur Gesamtprüfung der Zugriffsberechtigung.
* Die konkrete Berechtigungsprüfung wird an die nachgelagerten PermissionEvaluatoren delegiert,
* welche jeweils einzeln dem Zugriff zustimmen müssen.
*/
@Component
public class DelegatingDataBasedPermissionEvaluator implements PermissionEvaluator {
private final List<AbstractDelegatedPermissionEvaluator> permissionEvaluators;
public DelegatingDataBasedPermissionEvaluator(final List<AbstractDelegatedPermissionEvaluator> permissionEvaluators) {
this.permissionEvaluators = permissionEvaluators;
}
/**
* Auswertung der Zugriffsberechtigung für authentifizierten Benutzer auf Zielobjekt mit angeforderter Berechtigung.
* Hierbei wird die Berechtigungsprüfung an alle nachgelagerten PermissionEvaluatoren delegiert.
* Alle müssen dem Zugriff zustimmen.
*
* @param authentication Das Authentication Objekt
* @param targetObject Das Zielobjekt
* @param permissionType Die angeforderte Berechtigung
* @return Gibt <code>true</code> zurück, wenn der Benutzer die Berechtigung hat
*/
@Override
public boolean hasPermission(Authentication authentication, Object targetObject, Object permissionType) {
return permissionEvaluators.stream()
.allMatch(permissionEvaluator -> permissionEvaluator.hasPermission(authentication, targetObject, permissionType));
}
/**
* Auswertung anhand der ID und des Namens des Zielobjekts.
* Hierbei wird die Berechtigungsprüfung an alle nachgelagerten PermissionEvaluatoren delegiert.
* Alle müssen dem Zugriff zustimmen.
*
* @param authentication Authentication-Object
* @param targetId ID des Objekts
* @param targetType Name der Zielobjektklasse
* @param permissionType Die angeforderte Berechtigung
* @return Gibt <code>true</code> zurück, wenn der Benutzer die Berechtigung hat
*/
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permissionType) {
return permissionEvaluators.stream()
.allMatch(permissionEvaluator -> permissionEvaluator.hasPermission(authentication, targetId, targetType, permissionType));
}
}

View File

@ -0,0 +1,59 @@
package dev.dnpm.security;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Procedure;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* Permission-Evaluator zur Auswertung der Berechtigung auf Objekte aufgrund der Formularberechtigung
*/
@Component
public class FormBasedPermissionEvaluator extends AbstractDelegatedPermissionEvaluator {
public FormBasedPermissionEvaluator(final IOnkostarApi onkostarApi, final SecurityService securityService) {
super(onkostarApi, securityService);
}
/**
* Auswertung der Zugriffsberechtigung für authentifizierten Benutzer auf Zielobjekt mit angeforderter Berechtigung.
* Zugriff auf Objekte vom Typ "Patient" wird immer gewährt.
*
* @param authentication Das Authentication Objekt
* @param targetObject Das Zielobjekt
* @param permissionType Die angeforderte Berechtigung
* @return Gibt <code>true</code> zurück, wenn der Benutzer die Berechtigung hat
*/
@Override
public boolean hasPermission(Authentication authentication, Object targetObject, Object permissionType) {
if (permissionType instanceof PermissionType && targetObject instanceof Procedure) {
return this.securityService.getFormNamesForPermission(authentication, (PermissionType)permissionType)
.contains(((Procedure)targetObject).getFormName());
}
return true;
}
/**
* Auswertung anhand der ID und des Namens des Zielobjekts.
* Zugriff auf Objekte vom Typ "Patient" wird immer gewährt.
*
* @param authentication Authentication-Object
* @param targetId ID des Objekts
* @param targetType Name der Zielobjektklasse
* @param permissionType Die angeforderte Berechtigung
* @return Gibt <code>true</code> zurück, wenn der Benutzer die Berechtigung hat
*/
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permissionType) {
if (permissionType instanceof PermissionType && targetId instanceof Integer && PROCEDURE.equals(targetType)) {
var procedure = this.onkostarApi.getProcedure((int)targetId);
if (null != procedure) {
return this.securityService.getFormNamesForPermission(authentication, (PermissionType) permissionType).contains(procedure.getFormName());
}
}
return true;
}
}

View File

@ -0,0 +1,51 @@
package dev.dnpm.security;
import de.itc.onkostar.api.Procedure;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Arrays;
// TODO Disabled for now - check bytecode reported incompatibility for older OS installations
//@Component
@Aspect
public class FormBasedSecurityAspects {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final FormBasedPermissionEvaluator permissionEvaluator;
public FormBasedSecurityAspects(
final FormBasedPermissionEvaluator permissionEvaluator
) {
this.permissionEvaluator = permissionEvaluator;
}
@AfterReturning(value = "@annotation(dev.dnpm.security.FormSecuredResult)", returning = "procedure")
public void afterProcedureFormBased(Procedure procedure) {
if (
null != procedure
&& ! permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), procedure, PermissionType.READ_WRITE)
) {
logger.warn("Rückgabe von Prozedur blockiert: {}", procedure.getId());
throw new IllegalSecuredObjectAccessException();
}
}
@Before(value = "@annotation(dev.dnpm.security.FormSecured)")
public void beforeProcedureFormBased(JoinPoint jp) {
Arrays.stream(jp.getArgs())
.filter(arg -> arg instanceof Procedure)
.forEach(procedure -> {
if (! permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), procedure, PermissionType.READ_WRITE)) {
logger.warn("Zugriff auf Prozedur blockiert: {}", ((Procedure)procedure).getId());
throw new IllegalSecuredObjectAccessException();
}
});
}
}

View File

@ -0,0 +1,14 @@
package dev.dnpm.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FormSecured {
PermissionType value() default PermissionType.READ_WRITE;
}

View File

@ -0,0 +1,14 @@
package dev.dnpm.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FormSecuredResult {
PermissionType value() default PermissionType.READ_WRITE;
}

View File

@ -0,0 +1,13 @@
package dev.dnpm.security;
public class IllegalSecuredObjectAccessException extends RuntimeException {
public IllegalSecuredObjectAccessException() {
super();
}
public IllegalSecuredObjectAccessException(String message) {
super(message);
}
}

View File

@ -0,0 +1,6 @@
package dev.dnpm.security;
public enum PermissionType {
READ,
READ_WRITE
}

View File

@ -0,0 +1,81 @@
package dev.dnpm.security;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Patient;
import de.itc.onkostar.api.Procedure;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* Permission-Evaluator zur Auswertung der Berechtigung auf Objekte aufgrund der Personenstammberechtigung
*/
@Component
public class PersonPoolBasedPermissionEvaluator extends AbstractDelegatedPermissionEvaluator {
public PersonPoolBasedPermissionEvaluator(final IOnkostarApi onkostarApi, final SecurityService securityService) {
super(onkostarApi, securityService);
}
/**
* Auswertung der Zugriffsberechtigung für authentifizierten Benutzer auf Zielobjekt mit angeforderter Berechtigung.
* @param authentication Das Authentication Objekt
* @param targetObject Das Zielobjekt
* @param permissionType Die angeforderte Berechtigung
* @return Gibt <code>true</code> zurück, wenn der Benutzer die Berechtigung hat
*/
@Override
public boolean hasPermission(Authentication authentication, Object targetObject, Object permissionType) {
if (permissionType instanceof PermissionType) {
if (targetObject instanceof Patient) {
return this.securityService.getPersonPoolIdsForPermission(authentication, (PermissionType)permissionType)
.contains(((Patient)targetObject).getPersonPoolCode());
} else if (targetObject instanceof Procedure) {
return this.securityService.getPersonPoolIdsForPermission(authentication, (PermissionType)permissionType)
.contains(((Procedure)targetObject).getPatient().getPersonPoolCode());
}
}
return false;
}
/**
* Auswertung anhand der ID und des Namens des Zielobjekts.
* @param authentication Authentication-Object
* @param targetId ID des Objekts
* @param targetType Name der Zielobjektklasse
* @param permissionType Die angeforderte Berechtigung
* @return Gibt <code>true</code> zurück, wenn der Benutzer die Berechtigung hat
*/
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permissionType) {
if (targetId instanceof Integer && permissionType instanceof PermissionType) {
var personPoolCode = getPersonPoolCode((int)targetId, targetType);
if (null != personPoolCode) {
return this.securityService.getPersonPoolIdsForPermission(authentication, (PermissionType) permissionType).contains(personPoolCode);
}
}
return false;
}
private String getPersonPoolCode(int id, String type) {
Patient patient = null;
if (PATIENT.equals(type)) {
patient = onkostarApi.getPatient(id);
} else if (PROCEDURE.equals(type)) {
var procedure = onkostarApi.getProcedure(id);
if (null != procedure) {
patient = procedure.getPatient();
}
}
if (null != patient) {
return patient.getPersonPoolCode();
}
return null;
}
}

View File

@ -0,0 +1,74 @@
package dev.dnpm.security;
import de.itc.onkostar.api.Patient;
import de.itc.onkostar.api.Procedure;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class PersonPoolBasedSecurityAspects {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final PersonPoolBasedPermissionEvaluator permissionEvaluator;
public PersonPoolBasedSecurityAspects(PersonPoolBasedPermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
}
@AfterReturning(value = "@annotation(dev.dnpm.security.PersonPoolSecuredResult) ", returning = "patient")
public void afterPatient(Patient patient) {
if (
null != patient
&& ! permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), patient, PermissionType.READ_WRITE)
) {
logger.warn("Rückgabe von Patient blockiert: {}", patient.getId());
throw new IllegalSecuredObjectAccessException();
}
}
@AfterReturning(value = "@annotation(dev.dnpm.security.PersonPoolSecuredResult)", returning = "procedure")
public void afterProcedure(Procedure procedure) {
if (
null != procedure
&& ! permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), procedure, PermissionType.READ_WRITE)
) {
logger.warn("Rückgabe von Prozedur blockiert: {}", procedure.getId());
throw new IllegalSecuredObjectAccessException();
}
}
@Before(value = "@annotation(dev.dnpm.security.PersonPoolSecured)")
public void beforePatient(JoinPoint jp) {
Arrays.stream(jp.getArgs())
.filter(arg -> arg instanceof Patient)
.forEach(patient -> {
if (! permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), patient, PermissionType.READ_WRITE)) {
logger.warn("Zugriff auf Patient blockiert: {}", ((Patient)patient).getId());
throw new IllegalSecuredObjectAccessException();
}
});
}
@Before(value = "@annotation(dev.dnpm.security.PersonPoolSecured)")
public void beforeProcedure(JoinPoint jp) {
Arrays.stream(jp.getArgs())
.filter(arg -> arg instanceof Procedure)
.forEach(procedure -> {
if (! permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), procedure, PermissionType.READ_WRITE)) {
logger.warn("Zugriff auf Prozedur blockiert: {}", ((Procedure)procedure).getId());
throw new IllegalSecuredObjectAccessException();
}
});
}
}

View File

@ -0,0 +1,14 @@
package dev.dnpm.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PersonPoolSecured {
PermissionType value() default PermissionType.READ_WRITE;
}

View File

@ -0,0 +1,14 @@
package dev.dnpm.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PersonPoolSecuredResult {
PermissionType value() default PermissionType.READ_WRITE;
}

View File

@ -0,0 +1,60 @@
package dev.dnpm.security;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.util.List;
/**
* Service mit Methoden zum Feststellen von sicherheitsrelevanten Informationen eines Benutzers
*/
@Service
public class SecurityService {
private final JdbcTemplate jdbcTemplate;
public SecurityService(final DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
List<String> getPersonPoolIdsForPermission(Authentication authentication, PermissionType permissionType) {
var sql = "SELECT p.kennung FROM personenstamm_zugriff " +
" JOIN usergroup u ON personenstamm_zugriff.benutzergruppe_id = u.id " +
" JOIN akteur_usergroup au ON u.id = au.usergroup_id " +
" JOIN akteur a ON au.akteur_id = a.id " +
" JOIN personenstamm p on personenstamm_zugriff.personenstamm_id = p.id " +
" WHERE a.login = ? AND a.aktiv AND a.anmelden_moeglich ";
if (PermissionType.READ_WRITE == permissionType) {
sql += " AND personenstamm_zugriff.bearbeiten ";
}
var userDetails = (UserDetails)authentication.getPrincipal();
return jdbcTemplate
.query(sql, new Object[]{userDetails.getUsername()}, (rs, rowNum) -> rs.getString("kennung"));
}
List<String> getFormNamesForPermission(Authentication authentication, PermissionType permissionType) {
var sql = "SELECT df.name FROM formular_usergroup_zugriff " +
" JOIN data_form df ON formular_usergroup_zugriff.formular_id = df.id " +
" JOIN usergroup u ON formular_usergroup_zugriff.usergroup_id = u.id " +
" JOIN akteur_usergroup au ON u.id = au.usergroup_id " +
" JOIN akteur a on au.akteur_id = a.id " +
" WHERE a.login = ? AND a.aktiv AND a.anmelden_moeglich ";
if (PermissionType.READ_WRITE == permissionType) {
sql += " AND formular_usergroup_zugriff.bearbeiten ";
}
var userDetails = (UserDetails)authentication.getPrincipal();
return jdbcTemplate
.query(sql, new Object[]{userDetails.getUsername()}, (rs, rowNum) -> rs.getString("name"));
}
}

View File

@ -0,0 +1,37 @@
package dev.dnpm.services;
import dev.dnpm.exceptions.FormException;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.util.List;
/**
* Standardimplementierung zum Ermitteln von Unter- und Hauptformularen
*
* @since 0.0.2
*/
public class DefaultFormService implements FormService {
private final JdbcTemplate jdbcTemplate;
public DefaultFormService(final DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public int getMainFormProcedureId(int procedureId) throws FormException {
var sql = "SELECT hauptprozedur_id FROM prozedur WHERE id = ?";
try {
return jdbcTemplate.queryForObject(sql, (resultSet, i) -> resultSet.getInt("hauptprozedur_id"), procedureId);
} catch (Exception e) {
throw new FormException(String.format("No main form found for subform with ID '%d'", procedureId));
}
}
@Override
public List<Integer> getSubFormProcedureIds(int procedureId) {
var sql = "SELECT id FROM prozedur WHERE hauptprozedur_id = ?";
return jdbcTemplate.queryForList(sql, Integer.class, procedureId);
}
}

View File

@ -0,0 +1,78 @@
package dev.dnpm.services;
import dev.dnpm.dto.Studie;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.util.List;
import java.util.stream.Collectors;
/**
* Standardimplementierung zum Ermitteln von Studien
*
* @since 0.0.2
*/
public class DefaultStudienService implements StudienService {
private final JdbcTemplate jdbcTemplate;
public DefaultStudienService(final DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public List<Studie> findAll() {
var sql = "SELECT pcc.name, pcv.version_number, TRIM(studie.studien_nummer) AS studien_nummer, studie.startdatum, studie.endedatum, pcve.code, pcve.shortdesc, pcve.description, studie.aktiv FROM studie "
+ "LEFT JOIN category_entry ce ON ce.version_entry_id = studie.property_version_entry "
+ "LEFT JOIN property_catalogue_category pcc ON pcc.id = ce.category_id "
+ "JOIN property_catalogue_version_entry pcve ON pcve.id = studie.property_version_entry "
+ "JOIN property_catalogue_version pcv ON pcv.id = pcve.property_version_id "
+ "JOIN property_catalogue pc ON pc.id = pcv.datacatalog_id "
+ "WHERE pc.name = 'OS.Studien'"
+ "ORDER BY TRIM(studie.studien_nummer)";
return this.jdbcTemplate.query(sql, (resultSet, i) -> new Studie(
resultSet.getString("name"),
resultSet.getInt("version_number"),
resultSet.getString("code"),
resultSet.getString("studien_nummer"),
resultSet.getString("shortdesc"),
resultSet.getString("description"),
resultSet.getBoolean("aktiv")
));
}
@Override
public List<Studie> findByQuery(String query) {
var sql = "SELECT pcc.name, pcv.version_number, TRIM(studie.studien_nummer) AS studien_nummer, studie.startdatum, studie.endedatum, pcve.code, pcve.shortdesc, pcve.description, studie.aktiv FROM studie "
+ "LEFT JOIN category_entry ce ON ce.version_entry_id = studie.property_version_entry "
+ "LEFT JOIN property_catalogue_category pcc ON pcc.id = ce.category_id "
+ "JOIN property_catalogue_version_entry pcve ON pcve.id = studie.property_version_entry "
+ "JOIN property_catalogue_version pcv ON pcv.id = pcve.property_version_id "
+ "JOIN property_catalogue pc ON pc.id = pcv.datacatalog_id "
+ "WHERE pc.name = 'OS.Studien' AND (pcve.shortdesc LIKE ? OR pcve.description LIKE ? OR studie.studien_nummer LIKE ?)"
+ "ORDER BY TRIM(studie.studien_nummer)";
var like = String.format("%%%s%%", query);
return this.jdbcTemplate.query(sql, new Object[]{like, like, like}, (resultSet, i) -> new Studie(
resultSet.getString("name"),
resultSet.getInt("version_number"),
resultSet.getString("code"),
resultSet.getString("studien_nummer"),
resultSet.getString("shortdesc"),
resultSet.getString("description"),
resultSet.getBoolean("aktiv")
));
}
@Override
public List<Studie> findActive() {
return findAll().stream().filter(Studie::isActive).collect(Collectors.toList());
}
@Override
public List<Studie> findActiveByQuery(String query) {
return findByQuery(query).stream().filter(Studie::isActive).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,51 @@
package dev.dnpm.services;
import dev.dnpm.exceptions.FormException;
import de.itc.onkostar.api.Procedure;
import de.itc.onkostar.api.constants.JaNeinUnbekannt;
import java.util.List;
public interface FormService {
/**
* Diese Methode übergibt die Prozedur-ID des zugehörigen Hauptformulars zu einem Unterformular
* Siehe auch: <a href="https://github.com/CCC-MF/onkostar-plugin-forminfo/blob/master/src/main/java/de/ukw/ccc/onkostar/forminfo/services/FormInfoService.java">FormInfoService.java</a>
*
* @param procedureId Die Prozedur-ID des Unterformulars
* @return Die Prozedur-ID des zugehörigen Hauptformulars
* @throws FormException Wird geworfen, wenn ein Fehler auftrat
*/
int getMainFormProcedureId(int procedureId) throws FormException;
/**
* Diese Methode übergibt die Prozedur-IDs von Unterformularen zu einem Formular
* Siehe auch: <a href="https://github.com/CCC-MF/onkostar-plugin-forminfo/blob/master/src/main/java/de/ukw/ccc/onkostar/forminfo/services/FormInfoService.java">FormInfoService.java</a>
*
* @param procedureId Die Prozedur-ID des Formulars
* @return Eine Liste mit Prozedur-IDs der Unterformulare
*/
List<Integer> getSubFormProcedureIds(int procedureId);
/**
* Prüft, ob ein Formularfeld in der Prozedur einen Wert hat oder null ist
* @param procedure Die zu prüfende Prozedur
* @param fieldName Der Formularfeldname
* @return Gibt <code>true</code> zurück, wenn das Feld einen Wert hat
*/
static boolean hasValue(final Procedure procedure, final String fieldName) {
return null != procedure.getValue(fieldName);
}
/**
* Prüft, ob ein Formularfeld mit Ja/Nein/Unbekannt den Wert Ja hat
* @param procedure Die zu prüfende Prozedur
* @param fieldName Der Formularfeldname
* @return Gibt <code>true</code> zurück, wenn das Feld den Wert "Ja" hat
*/
static boolean isYes(final Procedure procedure, final String fieldName) {
return hasValue(procedure, fieldName)
&& procedure.getValue(fieldName).getString().equals(JaNeinUnbekannt.JA.getCode());
}
}

View File

@ -0,0 +1,47 @@
package dev.dnpm.services;
import dev.dnpm.database.SettingsRepository;
import java.util.Optional;
/**
* Implementiert den Dienst zur Ermittlung von Systemeinstellungen
*/
public class SettingsService {
private final SettingsRepository settingsRepository;
public SettingsService(final SettingsRepository settingsRepository) {
this.settingsRepository = settingsRepository;
}
/**
* Übergibt ein <code>Optional</code> für die Einstellung mit angegebenen Namen
* @param name Name der Einstellung
* @return Optional mit Wert der Einstellung oder ein leeres Optional, wenn Einstellung nicht gefunden
*/
public Optional<String> getSetting(String name) {
var sid = settingsRepository.findByName(name);
if (null == sid) {
return Optional.empty();
}
return Optional.of(sid.getValue());
}
/**
* Übergibt die SID als <code>Optional</code>
* @return Optional mit Wert der SID
*/
public Optional<String> getSID() {
return getSetting("SID");
}
/**
* Übergibt die Einstellung für <code>mehrere_mtb_in_mtbepisode</code>
* @return Übergibt <code>true</code>, wenn <code>mehrere_mtb_in_mtbepisode</code> auf "Ja" gesetzt ist.
*/
public boolean multipleMtbsInMtbEpisode() {
var setting = getSetting("mehrere_mtb_in_mtbepisode");
return setting.isPresent() && setting.get().equals("true");
}
}

View File

@ -0,0 +1,39 @@
package dev.dnpm.services;
import dev.dnpm.dto.Studie;
import java.util.List;
public interface StudienService {
/**
* Übergibt eine Liste mit allen Studien
*
* @return Liste mit allen Studien
*/
List<Studie> findAll();
/**
* Übergibt eine Liste mit Studien, deren (Kurz-)Beschreibung oder Studiennummer den übergebenen Wert enthalten
*
* @param query Wert der enthalten sein muss
* @return Gefilterte Liste mit Studien
*/
List<Studie> findByQuery(String query);
/**
* Übergibt eine Liste mit aktiven Studien
*
* @return Liste mit aktiven Studien
*/
List<Studie> findActive();
/**
* Übergibt eine Liste mit aktiven Studien, deren (Kurz-)Beschreibung oder Studiennummer den übergebenen Wert enthalten
*
* @param query Wert der enthalten sein muss
* @return Gefilterte Liste mit aktiven Studien
*/
List<Studie> findActiveByQuery(String query);
}

View File

@ -0,0 +1,30 @@
package dev.dnpm.services;
import dev.dnpm.dto.EcogStatusWithDate;
import de.itc.onkostar.api.Patient;
import java.util.List;
import java.util.Optional;
/**
* Schnittstelle zum Ermitteln von ECOG-Statusinformationen
*
* @since 0.6.0
*/
public interface TherapieMitEcogService {
/**
* Ermittelt den letzten bekannten ECOG-Status aus allen Therapieformularen des Patienten
* @param patient Der zu verwendende Patient
* @return Der ECOG-Status als String oder leeres Optional
*/
Optional<String> latestEcogStatus(Patient patient);
/**
* Ermittelt jeden bekannten ECOG-Status aus allen Therapieformularen des Patienten
* @param patient Der zu verwendende Patient
* @return Eine Liste mit Datum und ECOG-Status als String
*/
List<EcogStatusWithDate> ecogStatus(Patient patient);
}

View File

@ -0,0 +1,27 @@
package dev.dnpm.services.consent;
import de.itc.onkostar.api.Procedure;
/**
* Schnittstelle für die Anwendung von Consent-Änderungen
*
* @since 0.2.0
*/
public interface ConsentManagerService {
/**
* Wende Consent an, wenn dieses Consent-Formular gespeichert wird
* @param procedure Prozedur des Consent-Formulars
*/
void applyConsent(Procedure procedure);
/**
* Optionale Prüfung, ob die angegebene Prozedur angewendet werden kann.
* @param procedure Anzuwendende Prozedur
* @return Gibt <code>true</code> zurück, wenn die Prozedur angewendet werden kann.
*/
default boolean canApply(Procedure procedure) {
return null != procedure;
}
}

View File

@ -0,0 +1,28 @@
package dev.dnpm.services.consent;
import de.itc.onkostar.api.IOnkostarApi;
public class ConsentManagerServiceFactory {
private final IOnkostarApi onkostarApi;
public ConsentManagerServiceFactory(
final IOnkostarApi onkostarApi
) {
this.onkostarApi = onkostarApi;
}
public ConsentManagerService currentUsableInstance() {
var consentFormName = onkostarApi.getGlobalSetting("consentform");
switch (consentFormName) {
case "Excel-Formular":
return new UkwConsentManagerService(this.onkostarApi);
case "MR.Consent":
return new MrConsentManagerService(this.onkostarApi);
default:
return procedure -> {};
}
}
}

View File

@ -0,0 +1,126 @@
package dev.dnpm.services.consent;
import dev.dnpm.VerweisVon;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Item;
import de.itc.onkostar.api.Procedure;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.transform.Transformers;
import org.hibernate.type.StandardBasicTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
/**
* Detailimplementierung für das Formular `MR.Consent`
*
* @since 0.2.0
*/
public class MrConsentManagerService implements ConsentManagerService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final IOnkostarApi onkostarApi;
public MrConsentManagerService(final IOnkostarApi onkostarApi) {
this.onkostarApi = onkostarApi;
}
@Override
public boolean canApply(Procedure procedure) {
return null != procedure && procedure.getFormName().equals("MR.Consent");
}
/**
* Wende Consent an, wenn dieses Consent-Formular gespeichert wird
*
* @param procedure Prozedur des Consent-Formulars
*/
@Override
public void applyConsent(Procedure procedure) {
int value = 0;
try {
SessionFactory sessionFactory = onkostarApi.getSessionFactory();
Session session = sessionFactory.getCurrentSession();
// geänderte Werte checken
String sql1 = "select id, max(timestamp) AS datum from aenderungsprotokoll where entity_id = '" + procedure.getId() + "'";
SQLQuery query1 = session.createSQLQuery(sql1)
.addScalar("id", StandardBasicTypes.INTEGER)
.addScalar("datum", StandardBasicTypes.TIMESTAMP);
logger.info("Wert-Check: {}", query1.uniqueResult());
String sql = "SELECT prozedur.id AS procedure_id, prozedur.data_form_id, data_catalogue.name AS data_catalogue, data_catalogue_entry.name AS data_catalogue_entry, data_form.description AS formname, prozedur.beginndatum AS datum " +
"FROM prozedur " +
"LEFT JOIN data_form_data_catalogue ON data_form_data_catalogue.data_form_id = prozedur.data_form_id " +
"LEFT JOIN data_catalogue_entry ON data_catalogue_entry.data_catalogue_id = data_form_data_catalogue.data_catalogue_id " +
"LEFT JOIN data_catalogue ON data_catalogue.id = data_catalogue_entry.data_catalogue_id " +
"LEFT JOIN data_form ON data_form.id = prozedur.data_form_id " +
"WHERE patient_id = " + procedure.getPatientId() + " " +
"AND geloescht = 0 " +
"AND data_catalogue_entry.type = 'formReference' " +
"GROUP BY prozedur.id, prozedur.data_form_id, data_catalogue.name, data_catalogue_entry.name";
SQLQuery query = session.createSQLQuery(sql)
.addScalar("procedure_id", StandardBasicTypes.INTEGER)
.addScalar("data_form_id", StandardBasicTypes.INTEGER)
.addScalar("data_catalogue", StandardBasicTypes.STRING)
.addScalar("data_catalogue_entry", StandardBasicTypes.STRING)
.addScalar("formname", StandardBasicTypes.STRING)
.addScalar("datum", StandardBasicTypes.DATE);
query.setResultTransformer(Transformers.aliasToBean(VerweisVon.class));
@SuppressWarnings("unchecked")
List<VerweisVon> result = query.list();
for (VerweisVon verweisVon : result) {
sql = verweisVon.getSQL();
query = session.createSQLQuery(sql)
.addScalar("value", StandardBasicTypes.INTEGER);
if (query.uniqueResult() != null) {
value = (Integer) query.uniqueResult();
}
if (value == procedure.getId()) {
saveReferencedProcedure(procedure, verweisVon);
value = 0;
}
}
} catch (RuntimeException e) {
logger.error("Sonstiger Fehler bei der Ausführung von analyze()", e);
}
}
private void saveReferencedProcedure(Procedure prozedur, VerweisVon verweisVon) {
Procedure andereprozedur = onkostarApi.getProcedure(verweisVon.getProcedure_id());
try {
Map<String, Item> felder = prozedur.getAllValues();
for (Map.Entry<String, Item> feld : felder.entrySet()) {
if (feld.getKey().startsWith("Consent")) {
if (feld.getKey().equals("ConsentStatusEinwilligungDNPM")) {
switch (feld.getValue().getValue().toString()) {
case "z":
andereprozedur.setValue(feld.getKey(), new Item(feld.getKey(), "active"));
break;
case "a":
case "w":
andereprozedur.setValue(feld.getKey(), new Item(feld.getKey(), "rejected"));
break;
default:
break;
}
} else {
andereprozedur.setValue(feld.getKey(), prozedur.getValue(feld.getKey()));
}
}
}
onkostarApi.saveProcedure(andereprozedur);
} catch (Exception e) {
logger.error("Kann Prozedur nicht speichern", e);
}
}
}

View File

@ -0,0 +1,72 @@
package dev.dnpm.services.consent;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Item;
import de.itc.onkostar.api.Procedure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Comparator;
/**
* Detailimplementierung für das Formular `Excel-Formular`
*
* @since 0.2.0
*/
public class UkwConsentManagerService implements ConsentManagerService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final IOnkostarApi onkostarApi;
public UkwConsentManagerService(final IOnkostarApi onkostarApi) {
this.onkostarApi = onkostarApi;
}
@Override
public boolean canApply(Procedure procedure) {
return null != procedure && procedure.getFormName().equals("Excel-Formular");
}
/**
* Wende Consent an, wenn dieses Consent-Formular gespeichert wird
*
* @param procedure Prozedur des Consent-Formulars
*/
@Override
public void applyConsent(Procedure procedure) {
var refdnpmklinikanamnese = procedure.getValue("refdnpmklinikanamnese").getInt();
var dnpmKlinikAnamnese = this.onkostarApi.getProcedure(refdnpmklinikanamnese);
if (null == dnpmKlinikAnamnese) {
return;
}
var consents = procedure.getSubProceduresMap().get("ufdnpmconsent");
if (null == consents) {
return;
}
consents.stream()
.max(Comparator.comparing(Procedure::getStartDate))
.ifPresent(lastConsent -> {
var date = lastConsent.getStartDate();
var status = lastConsent.getValue("status");
if (null == date || null == status || status.getString().isBlank()) {
logger.warn("Kein DNPM-Einwilligungstatus angegeben");
return;
}
dnpmKlinikAnamnese.setValue("ConsentStatusEinwilligungDNPM", new Item("Einwilligung", status.getString()));
dnpmKlinikAnamnese.setValue("ConsentDatumEinwilligungDNPM", new Item("DatumEinwilligung", date));
try {
onkostarApi.saveProcedure(dnpmKlinikAnamnese, false);
} catch (Exception e) {
logger.error("Kann DNPM-Einwilligungstatus nicht aktualisieren", e);
}
});
}
}

View File

@ -0,0 +1,20 @@
package dev.dnpm.services.molekulargenetik;
import dev.dnpm.dto.Variant;
import de.itc.onkostar.api.Procedure;
import java.util.List;
/**
* Schnittstellenbeschreibung für Methoden zum Formular "OS.Molekulargenetik"
*/
public interface MolekulargenetikFormService {
/**
* Ermittelt alle (unterstützten) Varianten zur Prozedur eines Formulars "OS.Molekulargenetik"
* @param procedure Die Prozedur zum Formular "OS.Molekulargenetik"
* @return Die unterstützten Varianten oder eine leere Liste, wenn keine Varianten gefunden wurden.
*/
List<Variant> getVariants(Procedure procedure);
}

View File

@ -0,0 +1,45 @@
package dev.dnpm.services.molekulargenetik;
import dev.dnpm.dto.Variant;
import de.itc.onkostar.api.Procedure;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class OsMolekulargenetikFormService implements MolekulargenetikFormService {
/**
* Ermittelt alle (unterstützten) Varianten zur Prozedur eines Formulars "OS.Molekulargenetik" (oder Variante)
* Unterstützte Varianten sind:
* <uL>
* <li>Einfache Variante
* <li>CNV
* <li>Fusion
* @param procedure Die Prozedur zum Formular "OS.Molekulargenetik" (oder Variante)
* @return Die unterstützten Varianten oder eine leere Liste, wenn keine Varianten gefunden wurden.
*/
@Override
public List<Variant> getVariants(Procedure procedure) {
if (! procedureWithUsableFormVariant(procedure)) {
return List.of();
}
var subforms = procedure.getSubProceduresMap().get("MolekulargenetischeUntersuchung");
if (null == subforms) {
return List.of();
}
return subforms.stream()
.map(Variant::fromProcedure)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
private boolean procedureWithUsableFormVariant(Procedure procedure) {
return "OS.Molekulargenetik".equals(procedure.getFormName())
|| "UKER.Molekulargenetik".equals(procedure.getFormName());
}
}

View File

@ -0,0 +1,70 @@
package dev.dnpm.services.mtb;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Procedure;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Standardimplementierung des MtbService
*
* @since 0.0.2
*/
public class DefaultMtbService implements MtbService {
private final IOnkostarApi onkostarApi;
public DefaultMtbService(final IOnkostarApi onkostarApi) {
this.onkostarApi = onkostarApi;
}
/**
* Zusammenfassung der Prozeduren.
* Dabei werden alle Prozeduren sortiert, mit ermitteltem Mapper in {@link Optional} eines {@link String}s
* gewandelt und, wenn dies erfolgreich war, die Zeichenkette extrahiert.
* Im Anschluss wird die Abfolge der Zeichenketten mit den einzelnen Prozedur-Zusammenfassungen in eine
* einzige Zusammenfassung zusammengefügt.
* @param procedures Prozeduren, die zusammen gefasst werden sollen
* @return Text mit Zusammenfassung aller übergebenen Prozeduren
*/
@Override
public String getProtocol(List<Procedure> procedures) {
return this.sortedDistinctProcedureProtocolList(procedures.stream())
.collect(Collectors.joining("\n\n"));
}
private Stream<String> sortedDistinctProcedureProtocolList(Stream<Procedure> procedures) {
return procedures
.sorted(Comparator.comparing(Procedure::getStartDate))
.map(this::selectAndApplyMapper)
.filter(Optional::isPresent)
.map(Optional::get)
.distinct();
}
/**
* Übergibt anzuwendenden Mapper für eine Prozedur.
* Wurde keine Implementierung festgelegt, wird ein Mapper zurückgegeben, der eine
* Prozedur in ein leeres {@link Optional} zurück gibt, übergeben.
* @param procedure Prozedur, für die ein Mapper ermittelt werden soll
* @return Mapper für diese Prozedur
*/
@Override
public ProcedureToProtocolMapper procedureToProtocolMapper(Procedure procedure) {
switch (procedure.getFormName()) {
case "OS.Tumorkonferenz":
return new OsTumorkonferenzToProtocolMapper();
case "OS.Tumorkonferenz.VarianteUKW":
return new OsTumorkonferenzVarianteUkwToProtocolMapper();
case "MR.MTB_Anmeldung":
return new MrMtbAnmeldungToProtocolMapper(this.onkostarApi);
default:
return p -> Optional.empty();
}
}
}

View File

@ -0,0 +1,63 @@
package dev.dnpm.services.mtb;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Procedure;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Optional;
public class MrMtbAnmeldungToProtocolMapper implements ProcedureToProtocolMapper {
private final IOnkostarApi onkostarApi;
public MrMtbAnmeldungToProtocolMapper(final IOnkostarApi onkostarApi) {
this.onkostarApi = onkostarApi;
}
/**
* Wandelt eine Prozedur mit Formularnamen "MR.MTB_Anmeldung" in ein {@link Optional} mit einer
* Zeichenkette oder im Fehlerfall in ein leeres Optional um.
*
* @param procedure Die Prozedur, für die eine Zusammenfassung ermittelt werden soll.
* @return Das {@link Optional} mit, im Erfolgsfall, der Zusammenfassung für die Prozedur.
*/
@Override
public Optional<String> apply(Procedure procedure) {
if ((!procedure.getFormName().equals("MR.MTB_Anmeldung"))) {
throw new AssertionError("Procedure is not of form type 'MR.MTB_Anmeldung'");
}
var resultParts = new ArrayList<String>();
var fragestellung = procedure.getValue("Fragestellung");
if (null != fragestellung && !fragestellung.getString().isBlank()) {
resultParts.add(String.format("Fragestellung:%n%s", fragestellung.getString()));
}
var refEmpfehlung = procedure.getValue("Empfehlung");
if (null != refEmpfehlung && refEmpfehlung.getInt() > 0) {
var empfehlungsProzedur = onkostarApi.getProcedure(refEmpfehlung.getInt());
var refEinzelempfehlungen = onkostarApi.getSubprocedures(empfehlungsProzedur.getId());
if (null != refEinzelempfehlungen) {
refEinzelempfehlungen.stream()
.sorted(Comparator.comparingInt(proc -> proc.getValue("Prioritaet").getInt()))
.forEach(proc -> {
if (proc.getFormName().equals("MR.MTB_Einzelempfehlung")) {
var empfehlung = proc.getValue("Empfehlung");
if (null != empfehlung && !empfehlung.getString().isBlank()) {
resultParts.add(String.format("Empfehlung:%n%s", empfehlung.getString()));
}
}
});
}
}
if (resultParts.isEmpty()) {
return Optional.empty();
}
return Optional.of(String.join("\n\n", resultParts));
}
}

View File

@ -0,0 +1,31 @@
package dev.dnpm.services.mtb;
import de.itc.onkostar.api.Procedure;
import java.util.List;
import java.util.Optional;
public interface MtbService {
/**
* Zusammenfassung der Prozeduren
* @param procedures Prozeduren, die zusammen gefasst werden sollen
* @return Text mit Zusammenfassung der Prozeduren
*/
String getProtocol(List<Procedure> procedures);
/**
* Übergibt anzuwendenden Mapper für eine Prozedur
* @param procedure Prozedur, für die ein Mapper ermittelt werden soll
* @return Mapper für diese Prozedur
*/
ProcedureToProtocolMapper procedureToProtocolMapper(Procedure procedure);
/**
* Select mapper using method {@link #procedureToProtocolMapper(Procedure)} and apply procedure
* @param procedure The Procedure to select mapper for and apply
* @return {@link Optional} with protocol or empty {@link Optional}
*/
default Optional<String> selectAndApplyMapper(Procedure procedure) {
return this.procedureToProtocolMapper(procedure).apply(procedure);
}
}

View File

@ -0,0 +1,43 @@
package dev.dnpm.services.mtb;
import de.itc.onkostar.api.Procedure;
import java.util.Optional;
/**
* Mapper zum Ermitteln des Protokollauszugs für Formular "OS.Tumorkonferenz"
*
* @since 0.0.2
*/
public class OsTumorkonferenzToProtocolMapper implements ProcedureToProtocolMapper {
/**
* Wandelt eine Prozedur mit Formularnamen "OS.Tumorkonferenz" in ein {@link Optional} mit einer
* Zeichenkette oder im Fehlerfall in ein leeres Optional um.
* @param procedure Die Prozedur, für die eine Zusammenfassung ermittelt werden soll.
* @return Das {@link Optional} mit, im Erfolgsfall, der Zusammenfassung für die Prozedur.
*/
@Override
public Optional<String> apply(Procedure procedure) {
if ((!procedure.getFormName().equals("OS.Tumorkonferenz"))) {
throw new AssertionError("Procedure is not of form type 'OS.Tumorkonferenz'");
}
var fragestellung = procedure.getValue("Fragestellung");
var empfehlung = procedure.getValue("Empfehlung");
if (
null != fragestellung && !fragestellung.getString().isBlank()
&& null != empfehlung && !empfehlung.getString().isBlank()
) {
return Optional.of(String.format("Fragestellung:%n%s%n%nEmpfehlung:%n%s", fragestellung.getString(), empfehlung.getString()));
} else if (null != fragestellung && !fragestellung.getString().isBlank()) {
return Optional.of(fragestellung.getString());
} else if (null != empfehlung && !empfehlung.getString().isBlank()) {
return Optional.of(empfehlung.getString());
}
return Optional.empty();
}
}

View File

@ -0,0 +1,42 @@
package dev.dnpm.services.mtb;
import de.itc.onkostar.api.Procedure;
import java.util.Optional;
/**
* Mapper zum Ermitteln des Protokollauszugs für Formular "OS.Tumorkonferenz.VarianteUKW"
*
* @since 0.0.2
*/
public class OsTumorkonferenzVarianteUkwToProtocolMapper implements ProcedureToProtocolMapper {
/**
* Wandelt eine Prozedur mit Formularnamen "OS.Tumorkonferenz.VarianteUKW" in ein {@link Optional} mit einer
* Zeichenkette oder im Fehlerfall in ein leeres Optional um.
* @param procedure Die Prozedur, für die eine Zusammenfassung ermittelt werden soll.
* @return Das {@link Optional} mit, im Erfolgsfall, der Zusammenfassung für die Prozedur.
*/
@Override
public Optional<String> apply(Procedure procedure) {
if ((!procedure.getFormName().equals("OS.Tumorkonferenz.VarianteUKW"))) {
throw new AssertionError("Procedure is not of form type 'OS.Tumorkonferenz.VarianteUKW'");
}
var fragestellung = procedure.getValue("Fragestellung");
var empfehlung = procedure.getValue("Empfehlung");
if (
null != fragestellung && !fragestellung.getString().isBlank()
&& null != empfehlung && !empfehlung.getString().isBlank()
) {
return Optional.of(String.format("Fragestellung:%n%s%n%nEmpfehlung:%n%s", fragestellung.getString().trim(), empfehlung.getString().trim()));
} else if (null != fragestellung && !fragestellung.getString().isBlank()) {
return Optional.of(fragestellung.getString().trim());
} else if (null != empfehlung && !empfehlung.getString().isBlank()) {
return Optional.of(empfehlung.getString().trim());
}
return Optional.empty();
}
}

View File

@ -0,0 +1,9 @@
package dev.dnpm.services.mtb;
import de.itc.onkostar.api.Procedure;
import java.util.Optional;
import java.util.function.Function;
@FunctionalInterface
public interface ProcedureToProtocolMapper extends Function<Procedure, Optional<String>> {}

View File

@ -0,0 +1,74 @@
package dev.dnpm.services.strahlentherapie;
import dev.dnpm.dto.EcogStatusWithDate;
import dev.dnpm.services.SettingsService;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Patient;
import de.itc.onkostar.api.Procedure;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Standardimplementierung des StrahlentherapieServices
*
* @since 0.6.0
*/
public class DefaultStrahlentherapieService implements StrahlentherapieService {
private static final String ECOG_FIELD = "ECOGvorTherapie";
private final IOnkostarApi onkostarApi;
private final SettingsService settingsService;
public DefaultStrahlentherapieService(final IOnkostarApi onkostarApi, final SettingsService settingsService) {
this.onkostarApi = onkostarApi;
this.settingsService = settingsService;
}
/**
* Ermittelt den letzten bekannten ECOG-Status aus allen Systemtherapieformularen des Patienten
*
* @param patient Der zu verwendende Patient
* @return Der ECOG-Status als String oder leeres Optional
*/
@Override
public Optional<String> latestEcogStatus(Patient patient) {
return ecogStatus(patient).stream()
.max(Comparator.comparing(EcogStatusWithDate::getDate))
.map(EcogStatusWithDate::getStatus);
}
/**
* Ermittelt jeden bekannten ECOG-Status aus allen Systemtherapieformularen des Patienten
*
* @param patient Der zu verwendende Patient
* @return Eine Liste mit Datum und ECOG-Status als String
*/
@Override
public List<EcogStatusWithDate> ecogStatus(Patient patient) {
return patient.getDiseases().stream()
.flatMap(disease -> onkostarApi.getProceduresForDiseaseByForm(disease.getId(), getFormName()).stream())
.filter(procedure -> null != procedure.getStartDate())
.sorted(Comparator.comparing(Procedure::getStartDate))
.map(procedure -> {
try {
return new EcogStatusWithDate(procedure.getStartDate(), procedure.getValue(ECOG_FIELD).getString());
} catch (IllegalArgumentException e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private String getFormName() {
return settingsService
.getSetting("strahlentherapieform")
.orElse("OS.Strahlentherapie");
}
}

View File

@ -0,0 +1,10 @@
package dev.dnpm.services.strahlentherapie;
import dev.dnpm.services.TherapieMitEcogService;
/**
* Service für Systemtherapieformulare
*
* @since 0.6.0
*/
public interface StrahlentherapieService extends TherapieMitEcogService {}

View File

@ -0,0 +1,98 @@
package dev.dnpm.services.systemtherapie;
import dev.dnpm.dto.EcogStatusWithDate;
import dev.dnpm.services.SettingsService;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Patient;
import de.itc.onkostar.api.Procedure;
import java.util.*;
import java.util.stream.Collectors;
/**
* Standardimplementierung des Systemtherapieservices
*
* @since 0.2.0
*/
public class DefaultSystemtherapieService implements SystemtherapieService {
private static final String ECOG_FIELD = "ECOGvorTherapie";
private final IOnkostarApi onkostarApi;
private final SettingsService settingsService;
public DefaultSystemtherapieService(final IOnkostarApi onkostarApi, final SettingsService settingsService) {
this.onkostarApi = onkostarApi;
this.settingsService = settingsService;
}
/**
* Ermittelt eine Zusammenfassung der systemischen Therapien für eine Erkrankung
*
* @param diseaseId Die ID der Erkrankung
* @return Zusammenfassung der systemischen Therapien
*/
@Override
public List<Map<String, String>> getSystemischeTherapienFromDiagnose(int diseaseId) {
List<Map<String, String>> result = new ArrayList<>();
for (Procedure prozedur : onkostarApi.getProceduresForDiseaseByForm(diseaseId, getFormName())) {
prozedurToProzedurwerteMapper(prozedur).apply(prozedur).ifPresent(result::add);
}
return result;
}
/**
* Übergibt aktuell immer den Mapper für das Formular "OS.Systemische Therapie",
* da beide bekannte Varianten damit gemappt werden können.
*
* @param procedure Die Prozedur für die ein Mapper erstellt werden soll
* @return Der Mapper für die Prozedur
*/
@Override
public ProzedurToProzedurwerteMapper prozedurToProzedurwerteMapper(Procedure procedure) {
return new OsSystemischeTherapieToProzedurwerteMapper();
}
/**
* Ermittelt den letzten bekannten ECOG-Status aus allen Systemtherapieformularen des Patienten
*
* @param patient Der zu verwendende Patient
* @return Der ECOG-Status als String oder leeres Optional
*/
@Override
public Optional<String> latestEcogStatus(Patient patient) {
return ecogStatus(patient).stream()
.max(Comparator.comparing(EcogStatusWithDate::getDate))
.map(EcogStatusWithDate::getStatus);
}
/**
* Ermittelt jeden bekannten ECOG-Status aus allen Systemtherapieformularen des Patienten
*
* @param patient Der zu verwendende Patient
* @return Eine Liste mit Datum und ECOG-Status als String
*/
@Override
public List<EcogStatusWithDate> ecogStatus(Patient patient) {
return patient.getDiseases().stream()
.flatMap(disease -> onkostarApi.getProceduresForDiseaseByForm(disease.getId(), getFormName()).stream())
.filter(procedure -> null != procedure.getStartDate())
.sorted(Comparator.comparing(Procedure::getStartDate))
.map(procedure -> {
try {
return new EcogStatusWithDate(procedure.getStartDate(), procedure.getValue(ECOG_FIELD).getString());
} catch (IllegalArgumentException e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private String getFormName() {
return settingsService
.getSetting("systemtherapieform")
.orElse("OS.Systemische Therapie");
}
}

View File

@ -0,0 +1,90 @@
package dev.dnpm.services.systemtherapie;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.itc.onkostar.api.Item;
import de.itc.onkostar.api.Procedure;
import de.ukw.ccc.onkostar.atccodes.AtcCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* Implementierung zum Mappen des Formulars "OS.Systemische Therapie" auf die Prozedurwerte
*
* @since 0.2.0
*/
public class OsSystemischeTherapieToProzedurwerteMapper implements ProzedurToProzedurwerteMapper {
private static final Logger logger = LoggerFactory.getLogger(OsSystemischeTherapieToProzedurwerteMapper.class);
@Override
public Optional<Map<String, String>> apply(Procedure procedure) {
try {
return Optional.of(getProzedurwerte(procedure));
} catch (Exception e) {
logger.error("Fehler beim Mappen der Prozedur auf Prozedurwerte", e);
return Optional.empty();
}
}
private static Map<String, String> getProzedurwerte(Procedure prozedur) {
List<String> wirkstoffListe = new ArrayList<>();
// SubstanzenCodesListe enthält die Liste der SubstanzenCodes
List<Map<String, String>> substanzenCodesListe = new ArrayList<>();
// alle Werte der Prozedur auslesen
Map<String, Item> alleWerte = prozedur.getAllValues();
// Prozedurwerte enthält nur die interessanten Werte
Map<String, String> prozedurwerte = new HashMap<>();
// alle Werte durchgehen und die interessanten übernehmen
if (alleWerte.containsKey("Beendigung")) {
prozedurwerte.put("Beendigung", alleWerte.get("Beendigung").getValue());
}
if (alleWerte.containsKey("Ergebnis")) {
prozedurwerte.put("Ergebnis", alleWerte.get("Ergebnis").getValue());
}
if (alleWerte.containsKey("Beginn")) {
prozedurwerte.put("Beginn", alleWerte.get("Beginn").getString());
}
if (alleWerte.containsKey("Ende")) {
prozedurwerte.put("Ende", alleWerte.get("Ende").getString());
}
if (alleWerte.containsKey("SubstanzenList")) {
List<Map<String, String>> substanzList = alleWerte.get("SubstanzenList").getValue();
for (var substanz : substanzList) {
var substanzCodes = getSubstanzCode(substanz);
substanzenCodesListe.add(substanzCodes);
wirkstoffListe.add(substanzCodes.get("substance"));
}
}
prozedurwerte.put("Wirkstoffe", String.join(", ", wirkstoffListe));
try {
ObjectMapper mapper = new ObjectMapper();
prozedurwerte.put("WirkstoffCodes", mapper.writeValueAsString(substanzenCodesListe));
} catch (JsonProcessingException e) {
logger.error("Kann 'WirkstoffCodes' nicht in JSON-String mappen", e);
}
return prozedurwerte;
}
private static Map<String, String> getSubstanzCode(Map<String, String> substanz) {
Map<String, String> substanzCode = new HashMap<>();
if (substanz.containsKey("Substanz")) {
if (AtcCode.isAtcCode(substanz.get("Substanz"))) {
substanzCode.put("system", "ATC");
} else {
substanzCode.put("system", "other");
}
substanzCode.put("code", substanz.get("Substanz"));
}
if (substanz.containsKey("Substanz_shortDescription")) {
substanzCode.put("substance", substanz.get("Substanz_shortDescription"));
}
return substanzCode;
}
}

View File

@ -0,0 +1,14 @@
package dev.dnpm.services.systemtherapie;
import de.itc.onkostar.api.Procedure;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
/**
* Mapper um ein Systemtherapieformular in eine Optional-Map mit Prozedurwerten umzuwandeln
*
* @since 0.2.0
*/
public interface ProzedurToProzedurwerteMapper extends Function<Procedure, Optional<Map<String, String>>> {}

View File

@ -0,0 +1,29 @@
package dev.dnpm.services.systemtherapie;
import dev.dnpm.services.TherapieMitEcogService;
import de.itc.onkostar.api.Procedure;
import java.util.List;
import java.util.Map;
/**
* Service für Systemtherapieformulare
*
* @since 0.2.0
*/
public interface SystemtherapieService extends TherapieMitEcogService {
/**
* Ermittelt eine Zusammenfassung der systemischen Therapien für eine Erkrankung
* @param diseaseId Die ID der Erkrankung
* @return Die Zusammenfassung der systemischen Therapien
*/
List<Map<String, String>> getSystemischeTherapienFromDiagnose(int diseaseId);
/**
* Erstellt den Mapper for die Prozedur
* @param procedure Die Prozedur für die ein Mapper erstellt werden soll
* @return Der erstellte ProzedurToProzedurwerteMapper
*/
ProzedurToProzedurwerteMapper prozedurToProzedurwerteMapper(Procedure procedure);
}

View File

@ -0,0 +1,42 @@
package dev.dnpm.services.therapieplan;
import dev.dnpm.services.FormService;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Procedure;
import java.util.List;
import java.util.stream.Collectors;
public abstract class AbstractTherapieplanService implements TherapieplanService {
protected final IOnkostarApi onkostarApi;
protected final FormService formService;
protected AbstractTherapieplanService(final IOnkostarApi onkostarApi, final FormService formService) {
this.onkostarApi = onkostarApi;
this.formService = formService;
}
@Override
public List<Procedure> findReferencedFollowUpsForSubform(Procedure procedure) {
if (null == procedure || !"DNPM UF Einzelempfehlung".equals(procedure.getFormName())) {
return List.of();
}
return procedure.getDiseaseIds().stream()
.flatMap(diseaseId -> onkostarApi.getProceduresForDiseaseByForm(diseaseId, "DNPM FollowUp").stream())
.filter(p -> p.getValue("LinkTherapieempfehlung").getInt() == procedure.getId())
.collect(Collectors.toList());
}
@Override
public List<Procedure> findReferencedFollowUpsForSubform(int procedureId) {
var procedure = this.onkostarApi.getProcedure(procedureId);
if (null == procedure || !"DNPM UF Einzelempfehlung".equals(procedure.getFormName())) {
return List.of();
}
return findReferencedFollowUpsForSubform(procedure);
}
}

View File

@ -0,0 +1,196 @@
package dev.dnpm.services.therapieplan;
import dev.dnpm.services.FormService;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Item;
import de.itc.onkostar.api.Procedure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import static dev.dnpm.services.FormService.hasValue;
import static dev.dnpm.services.FormService.isYes;
public class DefaultTherapieplanService extends AbstractTherapieplanService {
public static final String FORMFIELD_REFERSTEMTB = "referstemtb";
public static final String FORMFIELD_HUMANGENBERATUNG = "humangenberatung";
public static final String FORMFIELD_REEVALUATION = "reevaluation";
public static final String FORMFIELD_DATUM = "datum";
public static final String FORMFIELD_REFTKHUMANGENBER = "reftkhumangenber";
public static final String FORMFIELD_DATUMTKHUMANGENBER = "datumtkhumangenber";
public static final String FORMFIELD_REFTKREEVALUATION = "reftkreevaluation";
public static final String FORMFIELD_DATUMTKREEVALUATION = "datumtkreevaluation";
public static final String FORMFIELD_MTB = "mtb";
public static final String FORMFIELD_UFEEDATUM = "ufeedatum";
public static final String FORMFIELD_REFTUMORKONFERENZ = "reftumorkonferenz";
public static final String FORMFIELD_UFRBDATUM = "ufrbdatum";
public static final String DATAFIELD_REF_TK_HUMANGENBER = "ref_tk_humangenber";
public static final String DATAFIELD_DATUM_TK_HUMANGENBER = "datum_tk_humangenber";
public static final String DATAFIELD_DATUM = "datum";
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public DefaultTherapieplanService(final IOnkostarApi onkostarApi, final FormService formService) {
super(onkostarApi, formService);
}
/**
* Verlinke MTB und Übernahme Datum aus Hauptformular in weiteren Bereichen
* "Humangenetische Beratung" und "Reevaluation" und Unterformularen, wenn erforderlich.
*
* @param procedure Die Prozedur mit Hauptformular
*/
@Override
public void updateRequiredMtbEntries(Procedure procedure) {
this.updateMtbInSections(procedure);
this.updateMtbInSubforms(procedure);
}
/**
* Finde verlinkte MTBs in Hauptformular und Unterformularen
*
* @param procedure Die Prozedur mit Hauptformular
* @return Liste mit verlinkten MTBs
*/
@Override
public List<Procedure> findReferencedMtbs(Procedure procedure) {
if (!hasValue(procedure, FORMFIELD_REFERSTEMTB)) {
return List.of();
}
var mtbProcedure = this.onkostarApi.getProcedure(procedure.getValue(FORMFIELD_REFERSTEMTB).getInt());
if (null == mtbProcedure) {
return List.of();
}
return List.of(mtbProcedure);
}
/**
* Finde verlinkte MTBs in Hauptformular und Unterformularen
*
* @param procedureId ID der Prozedur mit Hauptformular
* @return Liste mit verlinkten MTBs
*/
@Override
public List<Procedure> findReferencedMtbs(int procedureId) {
var procedure = this.onkostarApi.getProcedure(procedureId);
if (null == procedure) {
return List.of();
}
return findReferencedMtbs(procedure);
}
private void updateMtbInSections(Procedure procedure) {
if (!isYes(procedure, FORMFIELD_HUMANGENBERATUNG) && !isYes(procedure, FORMFIELD_REEVALUATION)) {
return;
}
var mtbReference = procedure.getValue(FORMFIELD_REFERSTEMTB).getInt();
var mtbDate = procedure.getValue(FORMFIELD_DATUM).getDate();
var noUpdateRequired = true;
if (
isYes(procedure, FORMFIELD_HUMANGENBERATUNG) && (
!hasValue(procedure, FORMFIELD_REFTKHUMANGENBER)
|| mtbReference != procedure.getValue(FORMFIELD_REFTKHUMANGENBER).getInt()
)
) {
procedure.setValue(FORMFIELD_REFTKHUMANGENBER, new Item(DATAFIELD_REF_TK_HUMANGENBER, mtbReference));
noUpdateRequired = false;
}
if (
isYes(procedure, FORMFIELD_HUMANGENBERATUNG) && (
!hasValue(procedure, FORMFIELD_DATUMTKHUMANGENBER)
|| !mtbDate.equals(procedure.getValue(FORMFIELD_DATUMTKHUMANGENBER).getDate())
)
) {
procedure.setValue(FORMFIELD_DATUMTKHUMANGENBER, new Item(DATAFIELD_DATUM_TK_HUMANGENBER, mtbDate));
noUpdateRequired = false;
}
if (
isYes(procedure, FORMFIELD_REEVALUATION) && (
!hasValue(procedure, FORMFIELD_REFTKREEVALUATION)
|| mtbReference != procedure.getValue(FORMFIELD_REFTKREEVALUATION).getInt()
)
) {
procedure.setValue(FORMFIELD_REFTKREEVALUATION, new Item("ref_tk_reevaluation", mtbReference));
noUpdateRequired = false;
}
if (
isYes(procedure, FORMFIELD_REEVALUATION) && (
!hasValue(procedure, FORMFIELD_DATUMTKREEVALUATION)
|| !mtbDate.equals(procedure.getValue(FORMFIELD_DATUMTKREEVALUATION).getDate())
)
) {
procedure.setValue(FORMFIELD_DATUMTKREEVALUATION, new Item("datum_tk_reevaluation", mtbDate));
noUpdateRequired = false;
}
if (noUpdateRequired) {
return;
}
try {
onkostarApi.saveProcedure(procedure, false);
} catch (Exception e) {
logger.error("Formular 'DNPM Therapieplan' konnte nicht aktualisiert werden", e);
}
}
private void updateMtbInSubforms(Procedure procedure) {
if (
!hasValue(procedure, FORMFIELD_REFERSTEMTB) || !hasValue(procedure, FORMFIELD_DATUM)
) {
return;
}
var mtbReference = procedure.getValue(FORMFIELD_REFERSTEMTB).getInt();
var mtbDate = procedure.getValue(FORMFIELD_DATUM).getDate();
formService.getSubFormProcedureIds(procedure.getId()).stream()
.map(onkostarApi::getProcedure)
.filter(Objects::nonNull)
.forEach(subform -> {
if (isUsableEinzelempfehlung(subform, mtbReference, mtbDate)) {
subform.setValue(FORMFIELD_MTB, new Item("ref_tumorkonferenz", mtbReference));
subform.setValue(FORMFIELD_UFEEDATUM, new Item(DATAFIELD_DATUM, mtbDate));
try {
onkostarApi.saveProcedure(subform, false);
} catch (Exception e) {
logger.error("Formular 'DNPM UF Einzelempfehlung' konnte nicht aktualisiert werden", e);
}
}
if (isUsableRebiopsie(subform, mtbReference, mtbDate)) {
subform.setValue(FORMFIELD_REFTUMORKONFERENZ, new Item("ref_tumorkonferenz", mtbReference));
subform.setValue(FORMFIELD_UFRBDATUM, new Item(DATAFIELD_DATUM, mtbDate));
try {
onkostarApi.saveProcedure(subform, false);
} catch (Exception e) {
logger.error("Formular 'DNPM UF Rebiopsie' konnte nicht aktualisiert werden", e);
}
}
});
}
private static boolean isUsableRebiopsie(Procedure subform, int mtbReference, Date mtbDate) {
return subform.getFormName().equals("DNPM UF Rebiopsie") && mtbReference != subform.getValue(FORMFIELD_REFTUMORKONFERENZ).getInt() && !mtbDate.equals(subform.getValue(FORMFIELD_UFRBDATUM).getDate());
}
private static boolean isUsableEinzelempfehlung(Procedure subform, int mtbReference, Date mtbDate) {
return subform.getFormName().equals("DNPM UF Einzelempfehlung") && mtbReference != subform.getValue(FORMFIELD_MTB).getInt() && !mtbDate.equals(subform.getValue(FORMFIELD_UFEEDATUM).getDate());
}
}

View File

@ -0,0 +1,71 @@
package dev.dnpm.services.therapieplan;
import dev.dnpm.services.FormService;
import de.itc.onkostar.api.IOnkostarApi;
import de.itc.onkostar.api.Procedure;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static dev.dnpm.services.FormService.hasValue;
import static dev.dnpm.services.FormService.isYes;
public class MultipleMtbTherapieplanService extends AbstractTherapieplanService {
public MultipleMtbTherapieplanService(final IOnkostarApi onkostarApi, final FormService formService) {
super(onkostarApi, formService);
}
@Override
public void updateRequiredMtbEntries(Procedure procedure) {
// No action required
}
@Override
public List<Procedure> findReferencedMtbs(Procedure procedure) {
var procedureIds = new ArrayList<Integer>();
var mtbReference = procedure.getValue("referstemtb").getInt();
procedureIds.add(mtbReference);
if (isYes(procedure, "humangenberatung") && hasValue(procedure, "reftkhumangenber")) {
procedureIds.add(procedure.getValue("reftkhumangenber").getInt());
}
if (isYes(procedure, "reevaluation") && hasValue(procedure, "reftkreevaluation")) {
procedureIds.add(procedure.getValue("reftkreevaluation").getInt());
}
formService.getSubFormProcedureIds(procedure.getId()).stream()
.map(onkostarApi::getProcedure)
.filter(Objects::nonNull)
.forEach(subform -> {
if (subform.getFormName().equals("DNPM UF Einzelempfehlung")) {
procedureIds.add(subform.getValue("mtb").getInt());
}
if (subform.getFormName().equals("DNPM UF Rebiopsie")) {
procedureIds.add(subform.getValue("reftumorkonferenz").getInt());
}
});
return procedureIds.stream()
.distinct()
.map(onkostarApi::getProcedure)
.filter(Objects::nonNull)
.sorted(Comparator.comparing(Procedure::getStartDate))
.collect(Collectors.toList());
}
@Override
public List<Procedure> findReferencedMtbs(int procedureId) {
var procedure = this.onkostarApi.getProcedure(procedureId);
if (null == procedure) {
return List.of();
}
return findReferencedMtbs(procedure);
}
}

View File

@ -0,0 +1,49 @@
package dev.dnpm.services.therapieplan;
import de.itc.onkostar.api.Procedure;
import java.util.List;
public interface TherapieplanService {
/**
* Verlinke MTB und Übernahme Datum aus Hauptformular in weiteren Bereichen
* "Humangenetische Beratung" und "Reevaluation" und Unterformularen, wenn erforderlich.
*
* @param procedure Die Prozedur mit Hauptformular
*/
void updateRequiredMtbEntries(Procedure procedure);
/**
* Finde verlinkte MTBs in Hauptformular und Unterformularen
*
* @param procedure Die Prozedur mit Hauptformular
* @return Liste mit verlinkten MTBs
*/
List<Procedure> findReferencedMtbs(Procedure procedure);
/**
* Finde verlinkte MTBs in Hauptformular und Unterformularen
*
* @param procedureId ID der Prozedur mit Hauptformular
* @return Liste mit verlinkten MTBs
*/
List<Procedure> findReferencedMtbs(int procedureId);
/**
* Finde verlinkte FollowUps für DNPM UF Einzelempfehlung
*
* @param procedure Die DNPM UF Einzelempfehlung Prozedur
* @return Liste mit verlinkten FollowUps
*/
List<Procedure> findReferencedFollowUpsForSubform(Procedure procedure);
/**
* Finde verlinkte FollowUps für DNPM UF Einzelempfehlung
*
* @param procedureId ID der Prozedur
* @return Liste mit verlinkten FollowUps
*/
List<Procedure> findReferencedFollowUpsForSubform(int procedureId);
}

View File

@ -0,0 +1,33 @@
package dev.dnpm.services.therapieplan;
import dev.dnpm.services.FormService;
import dev.dnpm.services.SettingsService;
import de.itc.onkostar.api.IOnkostarApi;
public class TherapieplanServiceFactory {
private final IOnkostarApi onkostarApi;
private final SettingsService settingsService;
private final FormService formService;
public TherapieplanServiceFactory(
final IOnkostarApi onkostarApi,
final SettingsService settingsService,
final FormService formService
) {
this.onkostarApi = onkostarApi;
this.settingsService = settingsService;
this.formService = formService;
}
public TherapieplanService currentUsableInstance() {
if (settingsService.multipleMtbsInMtbEpisode()) {
return new MultipleMtbTherapieplanService(onkostarApi, formService);
}
return new DefaultTherapieplanService(onkostarApi, formService);
}
}