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}