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