@@ -51,49 +51,34 @@ protected Parameters doInvoke(FHIROperationContext operationContext, Class<? ext
5151 try {
5252 Parameter resourceParameter = getParameter (parameters , "resource" );
5353 String resourceTypeName = resourceType != null ? resourceType .getSimpleName () : null ;
54- Resource resource ;
55- // If $validate is on resource-instance level, fetch the resource from database.
56- if (operationContext != null && FHIROperationContext .Type .INSTANCE .equals (operationContext .getType ()) && resourceParameter == null ) {
57- resource = resourceHelper .doRead (resourceTypeName , logicalId ).getResource ();
58- if (resource == null ) {
59- throw buildExceptionWithIssue (resourceTypeName + " with id '" + logicalId + "' was not found" , IssueType .NOT_FOUND );
60- }
61- } else if (resourceParameter == null ) {
62- throw buildExceptionWithIssue ("Input parameter 'resource' is required for the $validate operation" , IssueType .INVALID );
63- } else {
64- resource = resourceParameter .getResource ();
65- }
54+ Parameter modeParameter = getParameter (parameters , "mode" );
55+ ModeType modeType = getModeType (modeParameter );
56+ // Validate the resource parameter
57+ Resource resource = validateResource (operationContext , logicalId , resourceHelper , resourceParameter , resourceTypeName , modeType );
6658
6759 List <OperationOutcome .Issue > issues ;
6860 Parameter profileParameter = getParameter (parameters , "profile" );
69- Parameter modeParameter = getParameter (parameters , "mode" );
7061
71- // Only valid " mode" codes are accepted by the $validate operation .
72- validateModeParameter (modeParameter );
62+ // Validate mode parameter .
63+ validateModeParameter (modeType , resourceHelper , resourceTypeName , operationContext , logicalId );
7364
7465 if (profileParameter != null && profileParameter .getValue () != null ) {
7566 // If the 'profile' parameter is specified, always validate the resource against that profile.
7667 Uri profileUri = profileParameter .getValue ().as (Uri .class );
7768 String profile = profileUri == null ? null : profileUri .getValue ();
7869 issues = FHIRValidator .validator ().validate (resource , profile );
79- } else if (modeParameter != null && modeParameter .getValue () != null
80- && ("create" .equals (modeParameter .getValue ().as (Code .class ).getValue ())
81- || "update" .equals (modeParameter .getValue ().as (Code .class ).getValue ()))) {
70+ } else if (modeType == ModeType .CREATE || modeType == ModeType .UPDATE ) {
8271 // If the 'mode' parameter is specified and its value is 'create' or 'update', validate the resource
8372 // against the FHIR server config's profile properties and the resource's asserted profiles.
8473 issues = resourceHelper .validateResource (resource );
85- } else if (modeParameter != null
86- && modeParameter .getValue () != null
87- && ModeType .DELETE .value ().equals (modeParameter .getValue ().as (Code .class ).getValue ())
88- && FHIROperationContext .Type .INSTANCE .equals (operationContext .getType ())) {
74+ } else if (modeType == ModeType .DELETE && operationContext .getType () == FHIROperationContext .Type .INSTANCE ) {
8975 // If the 'mode' parameter is specified and its value is 'delete' and delete is invoked at the resource-instance level,
9076 // validate if the persistence layer implementation supports the "delete" operation
9177 issues = validateDeleteResource (resourceTypeName , logicalId , operationContext );
9278 } else {
9379 // Standard validation against the resource's asserted profiles.
9480 issues = FHIRValidator .validator ().validate (resource );
95- }
96-
81+ }
9782 return FHIROperationUtil .getOutputParameters (buildResourceValidOperationOutcome (issues ));
9883 } catch (FHIROperationException e ) {
9984 throw e ;
@@ -131,21 +116,90 @@ private OperationOutcome buildResourceValidOperationOutcome(List<Issue> issues)
131116
132117
133118 /**
134- * Check if a resource validation mode code is valid.
119+ * This method does the following validations if modeType(mode parameter) is not null. If the mode parameter is null then the below validations are skipped.
120+ * 1. Validate an interaction for a specified resource type.
121+ * 2. Validate if persistence layer implementation supports update/create mode if mode = create.
122+ * 3. Validate if persistence layer implementation supports update/create mode if mode = update but the resource doesnot exist yet in the DB.
123+ * 4. Validate if modes update and delete are only be used when the operation is invoked at the resource instance level
135124 *
136125 * @param modeParameter resource validation mode code to be validated
137- * @throws FHIROperationException when the resource validation mode code is invalid
126+ * @param resourceHelper Resource operation provider for loading related Library resources
127+ * @param resourceType a valid resource type string
128+ * @param operationContext the FHIROperationContext associated with the request
129+ * @throws Exception
130+ */
131+ private void validateModeParameter (ModeType modetype , FHIRResourceHelpers resourceHelper , String resourceType , FHIROperationContext operationContext , String logicalId ) throws Exception {
132+ if (modetype == null ) {
133+ return ;
134+ }
135+ // Validate an interaction for a specified resource type.
136+ if ((modetype == ModeType .CREATE
137+ || modetype == ModeType .UPDATE
138+ || modetype == ModeType .DELETE )
139+ && resourceType != null ) {
140+ FHIRResourceHelpers .Interaction interaction = FHIRResourceHelpers .Interaction .from (modetype .value ());
141+ resourceHelper .validateInteraction (interaction , resourceType );
142+ }
143+ // Validate if persistence layer implementation supports update/create mode if mode = create.
144+ if (operationContext != null && operationContext .getType () == FHIROperationContext .Type .INSTANCE
145+ && modetype == ModeType .CREATE ) {
146+ validateUpdateCreateEnabled (modetype , resourceType , operationContext );
147+ }
148+ // Validate if persistence layer implementation supports update/create mode if mode = update but the resource does not exist yet in the DB.
149+ if (operationContext != null && operationContext .getType () == FHIROperationContext .Type .INSTANCE
150+ && modetype == ModeType .UPDATE ) {
151+ Resource existingResource = resourceHelper .doRead (resourceType , logicalId ).getResource ();
152+ if (existingResource == null ) {
153+ validateUpdateCreateEnabled (modetype , resourceType , operationContext );
154+ }
155+ }
156+ // Validate if modes update and delete are only be used when the operation is invoked at the resource instance level
157+ if (operationContext != null && operationContext .getType () == FHIROperationContext .Type .RESOURCE_TYPE &&
158+ (modetype == ModeType .UPDATE || modetype == ModeType .DELETE )) {
159+
160+ throw buildExceptionWithIssue (IssueSeverity .ERROR , IssueType .NOT_SUPPORTED , "Modes update and delete can only be used when the operation is invoked at the resource instance level." , null );
161+ }
162+ }
163+
164+ /**
165+ * This method when invoked checks if the persistence layer implementation supports update/create.
166+ * If the persistence layer implementation does not support update/create a FHIROperationException with issue is thrown.
167+ * @param modetype
168+ * @param resourceType
169+ * @param operationContext
170+ * @throws FHIROperationException
138171 */
139- private void validateModeParameter (Parameter modeParameter ) throws FHIROperationException {
172+ private void validateUpdateCreateEnabled (ModeType modetype , String resourceType , FHIROperationContext operationContext ) throws FHIROperationException {
173+ FHIRPersistence persistence =
174+ (FHIRPersistence ) operationContext .getProperty (FHIROperationContext .PROPNAME_PERSISTENCE_IMPL );
175+ if (!persistence .isUpdateCreateEnabled ()) {
176+ throw buildExceptionWithIssue (IssueSeverity .ERROR , IssueType .NOT_SUPPORTED , "Resource " + modetype .value () + ", of type '"
177+ + resourceType + "', is not supported." , null );
178+ }
179+ }
180+
181+ /**
182+ * If the mode parameter is not null, this method validates if a resource validation mode code is valid and
183+ * returns valid resource validation mode code from ModeType enum.
184+ * If mode parameter is null this method returns null.
185+ * If mode parameter is not valid this method throws FHIROperationException with issues.
186+ * @param modeParameter resource validation mode code
187+ * @return The corresponding ModeType or null if a null value was passed
188+ * @throws FHIROperationException
189+ */
190+ private ModeType getModeType (Parameter modeParameter ) throws FHIROperationException {
140191 if (modeParameter != null && modeParameter .getValue () != null ) {
192+ ModeType modetype ;
141193 try {
142- ModeType .from (modeParameter .getValue ().as (Code .class ).getValue ());
194+ modetype = ModeType .from (modeParameter .getValue ().as (Code .class ).getValue ());
143195 } catch (IllegalArgumentException e ) {
144196 String msg = "'" + modeParameter .getValue ().as (Code .class ).getValue () + "' is not a valid resource validation mode" ;
145- throw buildExceptionWithIssue (msg , IssueType .VALUE );
197+ throw buildExceptionWithIssue (IssueSeverity . ERROR , IssueType .NOT_SUPPORTED , msg , null );
146198 }
199+ return modetype ;
200+
147201 }
148-
202+ return null ;
149203 }
150204
151205
@@ -163,9 +217,72 @@ public List<Issue> validateDeleteResource(String type, String id, FHIROperationC
163217 FHIRPersistence persistence =
164218 (FHIRPersistence ) operationContext .getProperty (FHIROperationContext .PROPNAME_PERSISTENCE_IMPL );
165219 if (!persistence .isDeleteSupported ()) {
166- warnings .add (FHIRUtil .buildOperationOutcomeIssue (IssueSeverity .WARNING , IssueType .NOT_SUPPORTED , "Resource deletion of type '"
167- + type + "' with id '" + id + "' is not supported." ));
220+ warnings .add (FHIRUtil .buildOperationOutcomeIssue (IssueSeverity .ERROR , IssueType .NOT_SUPPORTED , "Resource deletion, of type '"
221+ + type + "', with id '" + id + "', is not supported." ));
168222 }
169223 return warnings ;
170224 }
225+
226+ /**
227+ * Method to build the OperationOutcome error response
228+ * @param severity the issue severity
229+ * @param issueType describes the type of issue
230+ * @param msg describes the error message
231+ * @param cause the throwable that causes this OperationOutcome/Exception
232+ * @return FHIROperationException with issues
233+ * @throws FHIROperationException
234+ */
235+ protected FHIROperationException buildExceptionWithIssue (IssueSeverity severity , IssueType issueType , String msg , Throwable cause ) throws FHIROperationException {
236+ OperationOutcome .Issue ooi = FHIRUtil .buildOperationOutcomeIssue (severity , issueType , msg );
237+ return new FHIROperationException (msg , cause ).withIssue (ooi );
238+ }
239+
240+ /**
241+ * Method to validate the resource parameter. The below validations are performed by this method.
242+ * 1. if $validate is invoked at instance level and mode = create, check if the resource already exists.
243+ * 2. resource parameter must be present unless the mode is "delete" (or "profile" per https://jira.hl7.org/browse/FHIR-37998)
244+ * 3. if mode=profile AND no resource parameter value is provided then the resource at this id is read and validated against the nominated profile
245+ * @param operationContext - the FHIROperationContext associated with the request
246+ * @param logicalId - the logical id of the FHIR resource
247+ * @param resourceHelper - Resource operation provider for loading related Library resources
248+ * @param resourceParameter - the input resource parameter
249+ * @param resourceType - a valid resource type string
250+ * @param modeType - a valid resource validation mode code
251+ * @return Resource - a FHIR Resource object
252+ * @throws Exception
253+ * @throws FHIROperationException
254+ */
255+ private Resource validateResource (FHIROperationContext operationContext , String logicalId , FHIRResourceHelpers resourceHelper , Parameter resourceParameter ,
256+ String resourceType , ModeType modeType ) throws Exception , FHIROperationException {
257+ Resource resource = null ;
258+ // if $validate is invoked at instance level and mode = create, check if the resource already exists.
259+ if (operationContext != null && operationContext .getType () == FHIROperationContext .Type .INSTANCE
260+ && modeType == ModeType .CREATE ) {
261+ Resource existingResource = resourceHelper .doRead (resourceType , logicalId ).getResource ();
262+ if (existingResource != null ) {
263+ throw buildExceptionWithIssue (IssueSeverity .ERROR , IssueType .NOT_SUPPORTED , resourceType + " with id '" + logicalId + "' already exists" , null );
264+ }
265+ }
266+ // resource parameter must be present unless the mode is "delete" (or "profile" per https://jira.hl7.org/browse/FHIR-37998)
267+ if (resourceParameter == null
268+ && (modeType == null
269+ || modeType == ModeType .CREATE
270+ || modeType == ModeType .UPDATE )) {
271+ throw buildExceptionWithIssue (IssueSeverity .ERROR , IssueType .INVALID , "Input parameter 'resource' must be present unless the mode is 'delete' or 'profile'" , null );
272+ }
273+ // if mode=profile AND no resource parameter value is provided then the resource at this id is read and validated against the nominated profile
274+ if (resourceParameter == null && operationContext != null && operationContext .getType () == FHIROperationContext .Type .INSTANCE
275+ && modeType == ModeType .PROFILE ) {
276+ resource = resourceHelper .doRead (resourceType , logicalId ).getResource ();
277+ if (resource == null ) {
278+ throw buildExceptionWithIssue (IssueSeverity .ERROR , IssueType .INVALID , resourceType + " with id '" + logicalId + "' does not exist" , null );
279+ }
280+ }
281+
282+ if (resourceParameter != null ) {
283+ resource = resourceParameter .getResource ();
284+ }
285+ return resource ;
286+ }
287+
171288}
0 commit comments