001/*-
002 * #%L
003 * HAPI FHIR JPA Model
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.model.dao;
021
022import ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator;
023import ca.uhn.fhir.jpa.model.entity.IdAndPartitionId;
024import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
025import ca.uhn.fhir.rest.api.server.storage.BaseResourcePersistentId;
026import ca.uhn.hapi.fhir.sql.hibernatesvc.PartitionedIdProperty;
027import jakarta.annotation.Nonnull;
028import jakarta.persistence.Column;
029import jakarta.persistence.Embeddable;
030import jakarta.persistence.GeneratedValue;
031import jakarta.persistence.GenerationType;
032import org.apache.commons.collections4.ComparatorUtils;
033import org.hibernate.annotations.GenericGenerator;
034import org.hibernate.search.engine.backend.types.Projectable;
035import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
036
037import java.util.ArrayList;
038import java.util.Arrays;
039import java.util.Collection;
040import java.util.Comparator;
041import java.util.HashSet;
042import java.util.List;
043import java.util.Objects;
044import java.util.Set;
045
046import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
047
048/**
049 * JPA implementation of IResourcePersistentId.  JPA uses a Long as the primary key.  This class should be used in any
050 * context where the pid is known to be a Long.
051 */
052@Embeddable
053public class JpaPid extends BaseResourcePersistentId<Long> implements Comparable<JpaPid> {
054
055        @GenericGenerator(name = "SEQ_RESOURCE_ID", type = HapiSequenceStyleGenerator.class)
056        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RESOURCE_ID")
057        @Column(name = "RES_ID", nullable = false)
058        @GenericField(projectable = Projectable.YES)
059        private Long myId;
060
061        @PartitionedIdProperty
062        @Column(name = PartitionablePartitionId.PARTITION_ID, nullable = false)
063        private Integer myPartitionIdValue;
064
065        private static final Comparator<JpaPid> COMPARATOR;
066
067        static {
068                Comparator<JpaPid> partitionComparator =
069                                Comparator.comparing(t -> defaultIfNull(t.myPartitionIdValue, Integer.MIN_VALUE));
070                Comparator<JpaPid> idComparator = Comparator.comparing(t -> t.myId);
071                COMPARATOR = ComparatorUtils.chainedComparator(List.of(partitionComparator, idComparator));
072        }
073
074        /**
075         * Constructor - Do not call this directly, only used for
076         * JPA instantiation
077         */
078        public JpaPid() {
079                super(null);
080        }
081
082        private JpaPid(Long theId) {
083                super(null);
084                myId = theId;
085        }
086
087        public JpaPid(Integer thePartitionIdValue, Long theId) {
088                super(null);
089                myId = theId;
090                myPartitionIdValue = thePartitionIdValue;
091        }
092
093        private JpaPid(Long theId, Long theVersion) {
094                super(theVersion, null);
095                myId = theId;
096        }
097
098        public JpaPid(Integer thePartitionId, Long theId, Long theVersion) {
099                super(theVersion, null);
100                myPartitionIdValue = thePartitionId;
101                myId = theId;
102        }
103
104        private JpaPid(Long theId, String theResourceType) {
105                super(theResourceType);
106                myId = theId;
107        }
108
109        private JpaPid(Long theId, Long theVersion, String theResourceType) {
110                super(theVersion, theResourceType);
111                myId = theId;
112        }
113
114        public PartitionablePartitionId getPartitionablePartitionId() {
115                return new PartitionablePartitionId(myPartitionIdValue, null);
116        }
117
118        public JpaPid setPartitionablePartitionId(PartitionablePartitionId thePartitionablePartitionId) {
119                myPartitionIdValue = thePartitionablePartitionId != null ? thePartitionablePartitionId.getPartitionId() : null;
120                return this;
121        }
122
123        public JpaPid setPartitionIdIfNotAlreadySet(Integer thePartitionId) {
124                if (myPartitionIdValue == null && thePartitionId != null) {
125                        myPartitionIdValue = thePartitionId;
126                }
127                return this;
128        }
129
130        @Override
131        public Integer getPartitionId() {
132                return myPartitionIdValue;
133        }
134
135        public void setPartitionId(Integer thePartitionId) {
136                myPartitionIdValue = thePartitionId;
137        }
138
139        /**
140         * Note that equals and hashCode for this object only consider the ID and Partition ID because
141         * this class gets used as cache keys
142         */
143        @Override
144        public boolean equals(Object theO) {
145                if (this == theO) {
146                        return true;
147                }
148                if (!(theO instanceof JpaPid)) {
149                        return false;
150                }
151                JpaPid jpaPid = (JpaPid) theO;
152                return Objects.equals(myId, jpaPid.myId);
153        }
154
155        /**
156         * Note that equals and hashCode for this object only consider the ID and Partition ID because
157         * this class gets used as cache keys
158         */
159        @Override
160        public int hashCode() {
161                return Objects.hash(myId);
162        }
163
164        @Override
165        public Long getId() {
166                return myId;
167        }
168
169        public void setId(Long theId) {
170                myId = theId;
171        }
172
173        @Override
174        public String toString() {
175                String retVal = myPartitionIdValue != null ? myPartitionIdValue + "/" + myId.toString() : myId.toString();
176                return retVal;
177        }
178
179        @Override
180        public int compareTo(@Nonnull JpaPid theOther) {
181                return COMPARATOR.compare(this, theOther);
182        }
183
184        public JpaPidFk toFk() {
185                return JpaPidFk.fromPid(this);
186        }
187
188        public static List<Long> toLongList(JpaPid[] thePids) {
189                return toLongList(Arrays.asList(thePids));
190        }
191
192        public static List<Long> toLongList(Collection<JpaPid> thePids) {
193                List<Long> retVal = new ArrayList<>(thePids.size());
194                for (JpaPid next : thePids) {
195                        retVal.add(next.getId());
196                }
197                return retVal;
198        }
199
200        public static Set<Long> toLongSet(Collection<JpaPid> thePids) {
201                Set<Long> retVal = new HashSet<>(thePids.size());
202                for (JpaPid next : thePids) {
203                        retVal.add(next.getId());
204                }
205                return retVal;
206        }
207
208        public static List<JpaPid> fromLongList(Collection<Long> theResultList) {
209                List<JpaPid> retVal = new ArrayList<>(theResultList.size());
210                for (Long next : theResultList) {
211                        retVal.add(fromId(next));
212                }
213                return retVal;
214        }
215
216        public static JpaPid fromId(Long theId) {
217                return new JpaPid(theId);
218        }
219
220        public static JpaPid fromId(Long theResourceId, Integer thePartitionId) {
221                return new JpaPid(thePartitionId, theResourceId);
222        }
223
224        public static JpaPid fromId(Long theResourceId, PartitionablePartitionId thePartitionId) {
225                return new JpaPid(thePartitionId != null ? thePartitionId.getPartitionId() : null, theResourceId);
226        }
227
228        public static JpaPid fromIdAndVersion(Long theId, Long theVersion) {
229                return new JpaPid(theId, theVersion);
230        }
231
232        public static JpaPid fromIdAndResourceType(Long theId, String theResourceType) {
233                return new JpaPid(theId, theResourceType);
234        }
235
236        public static JpaPid fromIdAndVersionAndResourceType(Long theId, Long theVersion, String theResourceType) {
237                return new JpaPid(theId, theVersion, theResourceType);
238        }
239
240        public static JpaPid fromId(IdAndPartitionId theId) {
241                JpaPid retVal = new JpaPid(theId.getId());
242                retVal.setPartitionIdIfNotAlreadySet(theId.getPartitionIdValue());
243                return retVal;
244        }
245}