001/*-
002 * #%L
003 * HAPI FHIR JPA Server
004 * %%
005 * Copyright (C) 2014 - 2025 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.util;
021
022import ca.uhn.fhir.jpa.model.dao.JpaPid;
023import ca.uhn.fhir.jpa.model.util.JpaConstants;
024
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.List;
028
029/*
030        This class encapsulate the implementation providing a workaround to a known issue involving Hibernate. If queries are used with "in" clauses with large and varying
031        numbers of parameters, this can overwhelm Hibernate's QueryPlanCache and deplete heap space. See the following link for more info:
032        https://stackoverflow.com/questions/31557076/spring-hibernate-query-plan-cache-memory-usage.
033
034        Normalizing the number of parameters in the "in" clause stabilizes the size of the QueryPlanCache, so long as the number of
035        arguments never exceeds the maximum specified below.
036*/
037public class InClauseNormalizer {
038
039        public static List<JpaPid> normalizeIdListForInClause(List<JpaPid> theResourceIds) {
040
041                List<JpaPid> retVal = theResourceIds;
042
043                int listSize = theResourceIds.size();
044
045                if (listSize > 1 && listSize < 10) {
046                        retVal = padIdListWithPlaceholders(theResourceIds, 10);
047                } else if (listSize > 10 && listSize < 50) {
048                        retVal = padIdListWithPlaceholders(theResourceIds, 50);
049                } else if (listSize > 50 && listSize < 100) {
050                        retVal = padIdListWithPlaceholders(theResourceIds, 100);
051                } else if (listSize > 100 && listSize < 200) {
052                        retVal = padIdListWithPlaceholders(theResourceIds, 200);
053                } else if (listSize > 200 && listSize < 500) {
054                        retVal = padIdListWithPlaceholders(theResourceIds, 500);
055                } else if (listSize > 500 && listSize < 800) {
056                        retVal = padIdListWithPlaceholders(theResourceIds, 800);
057                }
058
059                return retVal;
060        }
061
062        private static List<JpaPid> padIdListWithPlaceholders(List<JpaPid> theIdList, int preferredListSize) {
063                List<JpaPid> retVal = theIdList;
064
065                if (isUnmodifiableList(theIdList)) {
066                        retVal = new ArrayList<>(preferredListSize);
067                        retVal.addAll(theIdList);
068                }
069
070                while (retVal.size() < preferredListSize) {
071                        retVal.add(JpaConstants.NO_MORE);
072                }
073
074                return retVal;
075        }
076
077        private static boolean isUnmodifiableList(List<JpaPid> theList) {
078                try {
079                        theList.addAll(Collections.emptyList());
080                } catch (Exception e) {
081                        return true;
082                }
083                return false;
084        }
085
086        private InClauseNormalizer() {}
087}