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