001/*
002 * #%L
003 * HAPI FHIR Storage api
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.patch;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.parser.DataFormatException;
025import ca.uhn.fhir.parser.IParser;
026import ca.uhn.fhir.parser.StrictErrorHandler;
027import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
028import com.fasterxml.jackson.core.JsonFactory;
029import com.fasterxml.jackson.core.JsonParser;
030import com.fasterxml.jackson.databind.JsonNode;
031import com.fasterxml.jackson.databind.ObjectMapper;
032import com.github.fge.jsonpatch.JsonPatch;
033import com.github.fge.jsonpatch.JsonPatchException;
034import org.hl7.fhir.instance.model.api.IBaseResource;
035
036import java.io.IOException;
037
038import static org.apache.commons.lang3.StringUtils.defaultString;
039
040public class JsonPatchUtils {
041
042        public static <T extends IBaseResource> T apply(FhirContext theCtx, T theResourceToUpdate, String thePatchBody) {
043                // Parse the patch
044                ObjectMapper mapper = new ObjectMapper();
045                mapper.configure(JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION, false);
046
047                JsonFactory factory = mapper.getFactory();
048
049                final JsonPatch patch;
050                try {
051                        JsonParser parser = factory.createParser(thePatchBody);
052                        JsonNode jsonPatchNode = mapper.readTree(parser);
053                        patch = JsonPatch.fromJson(jsonPatchNode);
054
055                        JsonNode originalJsonDocument =
056                                        mapper.readTree(theCtx.newJsonParser().encodeResourceToString(theResourceToUpdate));
057                        JsonNode after = patch.apply(originalJsonDocument);
058
059                        @SuppressWarnings("unchecked")
060                        Class<T> clazz = (Class<T>) theResourceToUpdate.getClass();
061
062                        String postPatchedContent = mapper.writeValueAsString(after);
063
064                        IParser fhirJsonParser = theCtx.newJsonParser();
065                        fhirJsonParser.setParserErrorHandler(new StrictErrorHandler());
066
067                        T retVal;
068                        try {
069                                retVal = fhirJsonParser.parseResource(clazz, postPatchedContent);
070                        } catch (DataFormatException e) {
071                                String resourceId = theResourceToUpdate
072                                                .getIdElement()
073                                                .toUnqualifiedVersionless()
074                                                .getValue();
075                                String resourceType =
076                                                theCtx.getResourceDefinition(theResourceToUpdate).getName();
077                                resourceId = defaultString(resourceId, resourceType);
078                                String msg = theCtx.getLocalizer()
079                                                .getMessage(JsonPatchUtils.class, "failedToApplyPatch", resourceId, e.getMessage());
080                                throw new InvalidRequestException(Msg.code(1271) + msg);
081                        }
082                        return retVal;
083
084                } catch (IOException | JsonPatchException theE) {
085                        throw new InvalidRequestException(Msg.code(1272) + theE.getMessage());
086                }
087        }
088}