001/*
002 * #%L
003 * HAPI FHIR JPA Server
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.util.jsonpatch;
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;
035import org.intellij.lang.annotations.Language;
036
037import java.io.IOException;
038
039import static org.apache.commons.lang3.StringUtils.defaultString;
040
041public class JsonPatchUtils {
042
043        public static <T extends IBaseResource> T apply(
044                        FhirContext theCtx, T theResourceToUpdate, @Language("JSON") String thePatchBody) {
045                // Parse the patch
046                ObjectMapper mapper = new ObjectMapper();
047                mapper.configure(JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION, false);
048
049                JsonFactory factory = mapper.getFactory();
050
051                final JsonPatch patch;
052                try {
053                        com.fasterxml.jackson.core.JsonParser parser = factory.createParser(thePatchBody);
054                        JsonNode jsonPatchNode = mapper.readTree(parser);
055                        patch = JsonPatch.fromJson(jsonPatchNode);
056
057                        JsonNode originalJsonDocument =
058                                        mapper.readTree(theCtx.newJsonParser().encodeResourceToString(theResourceToUpdate));
059                        JsonNode after = patch.apply(originalJsonDocument);
060
061                        @SuppressWarnings("unchecked")
062                        Class<T> clazz = (Class<T>) theResourceToUpdate.getClass();
063
064                        String postPatchedContent = mapper.writeValueAsString(after);
065
066                        IParser fhirJsonParser = theCtx.newJsonParser();
067                        fhirJsonParser.setParserErrorHandler(new StrictErrorHandler());
068
069                        T retVal;
070                        try {
071                                retVal = fhirJsonParser.parseResource(clazz, postPatchedContent);
072                        } catch (DataFormatException e) {
073                                String resourceId = theResourceToUpdate
074                                                .getIdElement()
075                                                .toUnqualifiedVersionless()
076                                                .getValue();
077                                String resourceType = theCtx.getResourceType(theResourceToUpdate);
078                                resourceId = defaultString(resourceId, resourceType);
079                                String msg = theCtx.getLocalizer()
080                                                .getMessage(JsonPatchUtils.class, "failedToApplyPatch", resourceId, e.getMessage());
081                                throw new InvalidRequestException(Msg.code(818) + msg);
082                        }
083                        return retVal;
084
085                } catch (IOException | JsonPatchException theE) {
086                        throw new InvalidRequestException(Msg.code(819) + theE.getMessage());
087                }
088        }
089}