Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
adafc4f
maven javadoc plugin version upgrade
hhund Oct 9, 2025
a3b89b0
new option in dsf-maven-plugin to add email to generated certificates
hhund Oct 9, 2025
9781ae0
improved task authorization rule
hhund Oct 12, 2025
60bac4a
log message improvements
hhund Oct 14, 2025
b80a5e1
field injection fix for classes implementing UserTaskListener
hhund Oct 14, 2025
92f6af3
code refactored and log message improvements
hhund Oct 14, 2025
5f74771
formatting and constraint for Task.restriction.recipient.identifier.type
hhund Oct 14, 2025
38299a3
practitioner-role ValueSet preferred binding, formatting, version 2.0.0
hhund Oct 14, 2025
20ce72a
new questionnaire authorization extension, version 2.0.0
hhund Oct 14, 2025
e415301
new auth/filter logic for authorization extension and practitioner users
hhund Oct 14, 2025
30955cc
QuestionnaireResponseHandler now updates resources completed -> amended
hhund Oct 14, 2025
abcf010
additional info box properties, mods for new ammended status
hhund Oct 14, 2025
f2fcce7
code and documentation for questionnaire authorization extension
hhund Oct 14, 2025
517af42
new Questionnaire / QuestionnaireResponse API v2 integration test
hhund Oct 15, 2025
b5a238f
added profile declaration to QuestionnaireResponse, refactored code
hhund Oct 15, 2025
d54fadd
not null and not empty checks
hhund Oct 15, 2025
c74872d
changed test execution to enable throwing error boundary events
hhund Oct 15, 2025
f369646
configured two additional practitioner users and client connections
hhund Oct 15, 2025
af65540
additional Questionnaire/QuestionnaireResponse tests
hhund Oct 15, 2025
ceac1a7
added methods to set list of string
hhund Oct 15, 2025
f3bf02f
added filter for null and blank values
hhund Oct 15, 2025
fc20dad
additional Questionnaire/QuestionnaireResponse integration test
hhund Oct 15, 2025
97bf109
Merge remote-tracking branch 'origin/develop_2' into
hhund Oct 15, 2025
833d495
logging fix: removed not needed argument
hhund Oct 16, 2025
3d18c5b
Task.requester search parameter fix
hhund Oct 16, 2025
d6ea621
new QuestionnaireResponse.author search parameter
hhund Oct 16, 2025
5a8616c
added QuestionnaireResponse.author column to the search results ui
hhund Oct 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import org.camunda.bpm.engine.delegate.TaskListener;
import org.camunda.bpm.engine.delegate.VariableScope;
import org.camunda.bpm.engine.impl.bpmn.parser.FieldDeclaration;
import org.camunda.bpm.engine.impl.util.ClassDelegateUtil;
import org.camunda.bpm.engine.variable.value.PrimitiveValue;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.ActivityDefinition;
Expand Down Expand Up @@ -454,7 +453,7 @@ public TaskListener getTaskListener(String className, List<FieldDeclaration> fie
VariableScope variableScope)
{
UserTaskListener target = get(UserTaskListener.class, className);
ClassDelegateUtil.applyFieldDeclaration(fieldDeclarations, target);
injectFields(target, fieldDeclarations, variableScope);

return new UserTaskListenerDelegate(getProcessPluginApi(), variablesFactory, target);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;

import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
Expand Down Expand Up @@ -102,4 +107,44 @@ public String getLocalVersionlessAbsoluteUrl(QuestionnaireResponse questionnaire
return questionnaireResponse.getIdElement().toVersionless()
.withServerBase(serverBaseUrl, ResourceType.QuestionnaireResponse.name()).getValue();
}

@Override
public Extension createQuestionnaireAuthorizationExtension(Set<Identifier> practitioners,
Set<Coding> practitionerRoles)
{
Extension e = new Extension(EXTENSION_QUESTIONNAIRE_AUTHORIZATION);

if (practitioners != null)
practitioners.stream().map(this::createQuestionnaireAuthorizationPractitionerSubExtension)
.forEach(e::addExtension);
if (practitionerRoles != null)
practitionerRoles.stream().map(this::createQuestionnaireAuthorizationPractitionerRoleSubExtension)
.forEach(e::addExtension);

return e;
}

@Override
public Extension createQuestionnaireAuthorizationPractitionerSubExtension(Identifier practitioner)
{
Objects.requireNonNull(practitioner, "practitioner");
if (!practitioner.hasSystem())
throw new IllegalArgumentException("practitioner.system missing");
if (!practitioner.hasValue())
throw new IllegalArgumentException("practitioner.value missing");

return new Extension(EXTENSION_QUESTIONNAIRE_AUTHORIZATION_PRACTITIONER).setValue(practitioner);
}

@Override
public Extension createQuestionnaireAuthorizationPractitionerRoleSubExtension(Coding practitionerRole)
{
Objects.requireNonNull(practitionerRole, "practitionerRole");
if (!practitionerRole.hasSystem())
throw new IllegalArgumentException("practitionerRole.system missing");
if (!practitionerRole.hasCode())
throw new IllegalArgumentException("practitionerRole.code missing");

return new Extension(EXTENSION_QUESTIONNAIRE_AUTHORIZATION_PRACTITIONER_ROLE).setValue(practitionerRole);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.stream.Stream;

import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.variable.Variables.SerializationDataFormats;
import org.camunda.bpm.engine.variable.value.TypedValue;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
import org.hl7.fhir.r4.model.Resource;
Expand Down Expand Up @@ -96,7 +97,7 @@ private JsonHolder toJsonHolder(Object json)
}

@SuppressWarnings("unchecked")
private <T> T fromJsonJolder(JsonHolder holder)
private <T> T fromJsonHolder(JsonHolder holder)
{
try
{
Expand Down Expand Up @@ -372,6 +373,13 @@ public void setString(String variableName, String value)
setVariable(variableName, org.camunda.bpm.engine.variable.Variables.stringValue(value));
}

@Override
public void setStringList(String variableName, List<String> value)
{
setVariable(variableName, org.camunda.bpm.engine.variable.Variables.objectValue(value)
.serializationDataFormat(SerializationDataFormats.JAVA).create());
}

@Override
public void setByteArray(String variableName, byte[] value)
{
Expand Down Expand Up @@ -444,7 +452,7 @@ public <T> T getVariable(String variableName)
Object variable = execution.getVariable(variableName);

if (variable instanceof JsonHolder jsonVariable)
return (T) fromJsonJolder(jsonVariable);
return (T) fromJsonHolder(jsonVariable);
else
return (T) variable;
}
Expand All @@ -461,6 +469,13 @@ public void setStringLocal(String variableName, String value)
setVariableLocal(variableName, org.camunda.bpm.engine.variable.Variables.stringValue(value));
}

@Override
public void setStringListLocal(String variableName, List<String> value)
{
setVariable(variableName, org.camunda.bpm.engine.variable.Variables.objectValue(value)
.serializationDataFormat(SerializationDataFormats.JAVA).create());
}

@Override
public void setByteArrayLocal(String variableName, byte[] value)
{
Expand Down Expand Up @@ -533,7 +548,7 @@ public <T> T getVariableLocal(String variableName)
Object variable = execution.getVariable(variableName);

if (variable instanceof JsonHolder jsonHolder)
return (T) fromJsonJolder(jsonHolder);
return (T) fromJsonHolder(jsonHolder);
else
return (T) variable;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package dev.dsf.bpe.v2.activity;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
import org.hl7.fhir.r4.model.Reference;
Expand All @@ -17,7 +23,9 @@

import dev.dsf.bpe.v2.ProcessPluginApi;
import dev.dsf.bpe.v2.activity.values.CreateQuestionnaireResponseValues;
import dev.dsf.bpe.v2.constants.CodeSystems;
import dev.dsf.bpe.v2.constants.CodeSystems.BpmnUserTask;
import dev.dsf.bpe.v2.constants.NamingSystems;
import dev.dsf.bpe.v2.variables.Variables;

/**
Expand All @@ -30,11 +38,112 @@
* To modify the behavior of the listener, for example to set default values in the created 'in-progress'
* {@link QuestionnaireResponse}, extend this class, register it as a prototype {@link Bean} and specify the class name
* as a task listener with event type 'create' in the BPMN.
* <p>
* This listener will add a questionnaire authorization extension to the {@link QuestionnaireResponse} if practitioner
* roles or practitioner identifiers are set. A single role or identifier can be configured via a BPMN field injections
* using fields <code>practitionerRole</code> and <code>practitioner</code> with a String constant or expression. To
* configure multiple roles or identifiers use fields <code>practitionerRoles</code> and <code>practitioners</code> with
* an expression to access a process variable of type <code>List&lt;String&gt;</code>. Note: To use field injects the
* fully qualified name of this class needs to be set as the task listener JavaClass.
* <p>
* This class (as is) does not need to be registered as a prototype {@link Bean}.
*/
public class DefaultUserTaskListener implements UserTaskListener
{
private static final Logger logger = LoggerFactory.getLogger(DefaultUserTaskListener.class);

protected static final String PROFILE_QUESTIONNAIRE_RESPONSE = "http://dsf.dev/fhir/StructureDefinition/questionnaire-response";

private static final record KeyAndValue(String key, String value)
{
static Function<String, KeyAndValue> fromString(String defaultSystem)
{
return keyAndValue ->
{
Objects.requireNonNull(keyAndValue, "keyAndValue");

String[] split = keyAndValue.split("\\|");

if (split.length == 1)
return new KeyAndValue(defaultSystem, split[0]);
if (split.length == 2)
return new KeyAndValue(split[0], split[1]);
else
throw new IllegalArgumentException("Invalid format: must be a simple 'value' for default key "
+ defaultSystem + " or 'key|value'");
};
}

Identifier toIdentifier()
{
return new Identifier().setSystem(key).setValue(value);
}

Coding toCoding()
{
return new Coding().setSystem(key).setCode(value);
}
}

private final Set<KeyAndValue> practitionerRoles = new HashSet<>();
private final Set<KeyAndValue> practitioners = new HashSet<>();

/**
* @param practitionerRole
* does nothing if <code>null</code> or blank
* @deprecated only for field injection
*/
@Deprecated
public void setPractitionerRole(String practitionerRole)
{
if (practitionerRole != null && !practitionerRole.isBlank())
setPractitionerRoles(List.of(practitionerRole));
}

/**
* @param practitioner
* does nothing if <code>null</code> or blank
* @deprecated only for field injection
*/
@Deprecated
public void setPractitioner(String practitioner)
{
if (practitioner != null && !practitioner.isBlank())
setPractitioners(List.of(practitioner));
}

/**
* @param practitionerRoles
* does nothing if <code>null</code>, ignores <code>null</code> and blank values
* @deprecated only for field injection
*/
@Deprecated
public void setPractitionerRoles(List<String> practitionerRoles)
{
if (practitionerRoles != null)
{
practitionerRoles.stream().filter(Objects::nonNull).filter(p -> !p.isBlank())
.map(KeyAndValue.fromString(CodeSystems.PractitionerRole.SYSTEM))
.forEach(this.practitionerRoles::add);
}
}

/**
* @param practitioners
* does nothing if <code>null</code>, ignores <code>null</code> and blank values
* @deprecated only for field injection
*/
@Deprecated
public void setPractitioners(List<String> practitioners)
{
if (practitioners != null)
{
practitioners.stream().filter(Objects::nonNull).filter(p -> !p.isBlank())
.map(KeyAndValue.fromString(NamingSystems.PractitionerIdentifier.SID))
.forEach(this.practitioners::add);
}
}

@Override
public void notify(ProcessPluginApi api, Variables variables,
CreateQuestionnaireResponseValues createQuestionnaireResponseValues) throws Exception
Expand Down Expand Up @@ -89,6 +198,7 @@ private QuestionnaireResponse createDefaultQuestionnaireResponse(ProcessPluginAp
String questionnaireUrlWithVersion, String businessKey, String userTaskId)
{
QuestionnaireResponse questionnaireResponse = new QuestionnaireResponse();
questionnaireResponse.getMeta().addProfile(PROFILE_QUESTIONNAIRE_RESPONSE);
questionnaireResponse.setQuestionnaire(questionnaireUrlWithVersion);
questionnaireResponse.setStatus(QuestionnaireResponse.QuestionnaireResponseStatus.INPROGRESS);

Expand All @@ -104,6 +214,13 @@ private QuestionnaireResponse createDefaultQuestionnaireResponse(ProcessPluginAp
BpmnUserTask.Codes.USER_TASK_ID, "The user-task-id of the process execution",
new StringType(userTaskId));

Set<Identifier> practitioners = getPractitioners();
Set<Coding> practitionerRoles = getPractitionerRoles();

if (!practitioners.isEmpty() || !practitionerRoles.isEmpty())
questionnaireResponse.addExtension(api.getQuestionnaireResponseHelper()
.createQuestionnaireAuthorizationExtension(practitioners, practitionerRoles));

return questionnaireResponse;
}

Expand Down Expand Up @@ -186,4 +303,24 @@ protected void afterQuestionnaireResponseCreate(ProcessPluginApi api, Variables
{
// Nothing to do in default behavior
}

/**
* <i>Override this method if you do not want to configure practitioner roles via field-injection in BPMN</i>
*
* @return practitioner-role entries used in creating the questionnaire authorization extension
*/
protected Set<Coding> getPractitionerRoles()
{
return practitionerRoles.stream().map(KeyAndValue::toCoding).collect(Collectors.toUnmodifiableSet());
}

/**
* <i>Override this method if you do not want to configure practitioner identifiers via field-injection in BPMN</i>
*
* @return practitioner entries used in creating the questionnaire authorization extension
*/
protected Set<Identifier> getPractitioners()
{
return practitioners.stream().map(KeyAndValue::toIdentifier).collect(Collectors.toUnmodifiableSet());
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
package dev.dsf.bpe.v2.error;

import java.util.Objects;

public class ErrorBoundaryEvent extends RuntimeException
{
private static final long serialVersionUID = 3161271266680097207L;

private final String errorCode;
private final String errorMessage;

/**
* @param errorCode
* not <code>null</code>, not empty
* @param errorMessage
* not <code>null</code>, not empty
*/
public ErrorBoundaryEvent(String errorCode, String errorMessage)
{
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.errorCode = Objects.requireNonNull(errorCode, "errorCode");
this.errorMessage = Objects.requireNonNull(errorMessage, "errorMessage");

if (errorCode.isEmpty())
throw new IllegalArgumentException("errorCode empty");
if (errorMessage.isEmpty())
throw new IllegalArgumentException("errorMessage empty");
}

public String getErrorCode()
Expand Down
Loading