001/*-
002 * #%L
003 * HAPI FHIR JPA Model
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.model.search;
021
022import jakarta.annotation.Nonnull;
023import org.hibernate.search.engine.backend.document.DocumentElement;
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027import java.util.Arrays;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031
032/**
033 * Provide a lookup of created Hibernate Search DocumentElement entries.
034 *
035 * The Hibernate Search DocumentElement api only supports create - it does not support fetching an existing element.
036 * This class demand-creates object elements for a given path.
037 */
038public class HSearchElementCache {
039        private static final Logger ourLog = LoggerFactory.getLogger(HSearchElementCache.class);
040        private final DocumentElement myRoot;
041        private final Map<String, DocumentElement> myCache = new HashMap<>();
042
043        /**
044         * Create the helper rooted on the given DocumentElement
045         * @param theRoot the document root
046         */
047        public HSearchElementCache(DocumentElement theRoot) {
048                this.myRoot = theRoot;
049        }
050
051        /**
052         * Fetch or create an Object DocumentElement with thePath from the root element.
053         *
054         * @param thePath the property names of the object path.  E.g. "sp","code","token"
055         * @return the existing or created element
056         */
057        public DocumentElement getObjectElement(@Nonnull String... thePath) {
058                return getObjectElement(Arrays.asList(thePath));
059        }
060
061        /**
062         * Fetch or create an Object DocumentElement with thePath from the root element.
063         *
064         * @param thePath the property names of the object path.  E.g. "sp","code","token"
065         * @return the existing or created element
066         */
067        public DocumentElement getObjectElement(@Nonnull List<String> thePath) {
068                if (thePath.size() == 0) {
069                        return myRoot;
070                }
071                String key = String.join(".", thePath);
072                // re-implement computeIfAbsent since we're recursive, and it isn't rentrant.
073                DocumentElement result = myCache.get(key);
074                if (result == null) {
075                        DocumentElement parent = getObjectElement(thePath.subList(0, thePath.size() - 1));
076                        String lastSegment = thePath.get(thePath.size() - 1);
077                        assert (lastSegment.indexOf('.') == -1);
078                        result = parent.addObject(lastSegment);
079                        myCache.put(key, result);
080                }
081                ourLog.trace("getNode {}: {}", key, result);
082                return result;
083        }
084}