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 ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.jpa.model.entity.StorageSettings; 024import ca.uhn.fhir.model.dstu2.composite.CodingDt; 025import com.google.common.collect.HashMultimap; 026import com.google.common.collect.Multimaps; 027import com.google.common.collect.SetMultimap; 028import org.hibernate.search.engine.backend.document.DocumentElement; 029import org.hl7.fhir.instance.model.api.IBaseCoding; 030import org.hl7.fhir.instance.model.api.IBaseResource; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034import java.math.BigDecimal; 035import java.util.Date; 036import java.util.Objects; 037import java.util.function.BiConsumer; 038 039/** 040 * Collects our HSearch extended indexing data. 041 * 042 */ 043public class ExtendedHSearchIndexData { 044 private static final Logger ourLog = LoggerFactory.getLogger(ExtendedHSearchIndexData.class); 045 046 final FhirContext myFhirContext; 047 final StorageSettings myStorageSettings; 048 049 final SetMultimap<String, String> mySearchParamStrings = HashMultimap.create(); 050 final SetMultimap<String, IBaseCoding> mySearchParamTokens = HashMultimap.create(); 051 final SetMultimap<String, BigDecimal> mySearchParamNumbers = HashMultimap.create(); 052 final SetMultimap<String, String> mySearchParamLinks = HashMultimap.create(); 053 final SetMultimap<String, String> mySearchParamUri = HashMultimap.create(); 054 final SetMultimap<String, DateSearchIndexData> mySearchParamDates = HashMultimap.create(); 055 final SetMultimap<String, QuantitySearchIndexData> mySearchParamQuantities = HashMultimap.create(); 056 final SetMultimap<String, CompositeSearchIndexData> mySearchParamComposites = HashMultimap.create(); 057 private String myForcedId; 058 private String myResourceJSON; 059 private IBaseResource myResource; 060 061 public ExtendedHSearchIndexData( 062 FhirContext theFhirContext, StorageSettings theStorageSettings, IBaseResource theResource) { 063 this.myFhirContext = theFhirContext; 064 this.myStorageSettings = theStorageSettings; 065 myResource = theResource; 066 } 067 068 private <V> BiConsumer<String, V> ifNotContained(BiConsumer<String, V> theIndexWriter) { 069 return (s, v) -> { 070 // Ignore contained resources for now. 071 if (!s.contains(".")) { 072 theIndexWriter.accept(s, v); 073 } 074 }; 075 } 076 077 /** 078 * Write the index document. 079 * 080 * Called by Hibernate Search after the ResourceTable entity has been flushed/committed. 081 * Keep this in sync with the schema defined in {@link SearchParamTextPropertyBinder} 082 * 083 * @param theDocument the Hibernate Search document for ResourceTable 084 */ 085 public void writeIndexElements(DocumentElement theDocument) { 086 HSearchIndexWriter indexWriter = HSearchIndexWriter.forRoot(myStorageSettings, theDocument); 087 088 ourLog.debug("Writing JPA index to Hibernate Search"); 089 090 // todo can this be moved back to ResourceTable as a computed field to merge with myId? 091 theDocument.addValue("myForcedId", myForcedId); 092 093 if (myResourceJSON != null) { 094 theDocument.addValue("myRawResource", myResourceJSON); 095 } 096 097 mySearchParamStrings.forEach(ifNotContained(indexWriter::writeStringIndex)); 098 mySearchParamTokens.forEach(ifNotContained(indexWriter::writeTokenIndex)); 099 mySearchParamLinks.forEach(ifNotContained(indexWriter::writeReferenceIndex)); 100 // we want to receive the whole entry collection for each invocation 101 mySearchParamQuantities.forEach(ifNotContained(indexWriter::writeQuantityIndex)); 102 Multimaps.asMap(mySearchParamNumbers).forEach(ifNotContained(indexWriter::writeNumberIndex)); 103 mySearchParamDates.forEach(ifNotContained(indexWriter::writeDateIndex)); 104 Multimaps.asMap(mySearchParamUri).forEach(ifNotContained(indexWriter::writeUriIndex)); 105 Multimaps.asMap(mySearchParamComposites).forEach(indexWriter::writeCompositeIndex); 106 } 107 108 public void addStringIndexData(String theSpName, String theText) { 109 mySearchParamStrings.put(theSpName, theText); 110 } 111 112 /** 113 * Add if not already present. 114 */ 115 public void addTokenIndexDataIfNotPresent(String theSpName, String theSystem, String theValue) { 116 boolean isPresent = mySearchParamTokens.get(theSpName).stream() 117 .anyMatch(c -> Objects.equals(c.getSystem(), theSystem) && Objects.equals(c.getCode(), theValue)); 118 if (!isPresent) { 119 addTokenIndexData(theSpName, new CodingDt(theSystem, theValue)); 120 } 121 } 122 123 public void addTokenIndexData(String theSpName, IBaseCoding theNextValue) { 124 mySearchParamTokens.put(theSpName, theNextValue); 125 } 126 127 public void addUriIndexData(String theSpName, String theValue) { 128 mySearchParamUri.put(theSpName, theValue); 129 } 130 131 public void addResourceLinkIndexData(String theSpName, String theTargetResourceId) { 132 mySearchParamLinks.put(theSpName, theTargetResourceId); 133 } 134 135 public void addDateIndexData( 136 String theSpName, 137 Date theLowerBound, 138 int theLowerBoundOrdinal, 139 Date theUpperBound, 140 int theUpperBoundOrdinal) { 141 addDateIndexData( 142 theSpName, 143 new DateSearchIndexData(theLowerBound, theLowerBoundOrdinal, theUpperBound, theUpperBoundOrdinal)); 144 } 145 146 public void addDateIndexData(String theSpName, DateSearchIndexData value) { 147 mySearchParamDates.put(theSpName, value); 148 } 149 150 public SetMultimap<String, DateSearchIndexData> getDateIndexData() { 151 return mySearchParamDates; 152 } 153 154 public void addNumberIndexDataIfNotPresent(String theParamName, BigDecimal theValue) { 155 mySearchParamNumbers.put(theParamName, theValue); 156 } 157 158 public void addQuantityIndexData(String theSpName, QuantitySearchIndexData value) { 159 mySearchParamQuantities.put(theSpName, value); 160 } 161 162 public SetMultimap<String, QuantitySearchIndexData> getQuantityIndexData() { 163 return mySearchParamQuantities; 164 } 165 166 public void setForcedId(String theForcedId) { 167 myForcedId = theForcedId; 168 } 169 170 public String getForcedId() { 171 return myForcedId; 172 } 173 174 public void setRawResourceData(String theResourceJSON) { 175 myResourceJSON = theResourceJSON; 176 } 177 178 public SetMultimap<String, CompositeSearchIndexData> getSearchParamComposites() { 179 return mySearchParamComposites; 180 } 181 182 public void addCompositeIndexData(String theSearchParamName, CompositeSearchIndexData theBuildCompositeIndexData) { 183 mySearchParamComposites.put(theSearchParamName, theBuildCompositeIndexData); 184 } 185}