001/*
002 * #%L
003 * HAPI FHIR JPA Model
004 * %%
005 * Copyright (C) 2014 - 2023 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.entity;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
024import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
025import ca.uhn.fhir.jpa.model.dao.JpaPid;
026import ca.uhn.fhir.jpa.model.search.ExtendedHSearchIndexData;
027import ca.uhn.fhir.jpa.model.search.ResourceTableRoutingBinder;
028import ca.uhn.fhir.jpa.model.search.SearchParamTextPropertyBinder;
029import ca.uhn.fhir.model.primitive.IdDt;
030import ca.uhn.fhir.rest.api.Constants;
031import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
032import org.apache.commons.lang3.builder.ToStringBuilder;
033import org.apache.commons.lang3.builder.ToStringStyle;
034import org.hibernate.annotations.GenericGenerator;
035import org.hibernate.Session;
036import org.hibernate.annotations.GenerationTime;
037import org.hibernate.annotations.GeneratorType;
038import org.hibernate.annotations.OptimisticLock;
039import org.hibernate.search.engine.backend.types.Projectable;
040import org.hibernate.search.engine.backend.types.Searchable;
041import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.PropertyBinderRef;
042import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.RoutingBinderRef;
043import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
044import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
045import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
046import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexingDependency;
047import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectPath;
048import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyBinding;
049import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyValue;
050import org.hibernate.tuple.ValueGenerator;
051import org.hl7.fhir.instance.model.api.IIdType;
052
053import javax.persistence.CascadeType;
054import javax.persistence.Column;
055import javax.persistence.Entity;
056import javax.persistence.FetchType;
057import javax.persistence.GeneratedValue;
058import javax.persistence.GenerationType;
059import javax.persistence.Id;
060import javax.persistence.Index;
061import javax.persistence.NamedEntityGraph;
062import javax.persistence.OneToMany;
063import javax.persistence.OneToOne;
064import javax.persistence.PrePersist;
065import javax.persistence.PreUpdate;
066import org.hibernate.annotations.GenericGenerator;
067import javax.persistence.Table;
068import javax.persistence.Transient;
069import javax.persistence.Version;
070import java.io.Serializable;
071import java.util.ArrayList;
072import java.util.Collection;
073import java.util.HashSet;
074import java.util.Objects;
075import java.util.Set;
076import java.util.stream.Collectors;
077
078@Indexed(routingBinder= @RoutingBinderRef(type = ResourceTableRoutingBinder.class))
079@Entity
080@Table(name = ResourceTable.HFJ_RESOURCE, uniqueConstraints = {}, indexes = {
081        // Do not reuse previously used index name: IDX_INDEXSTATUS, IDX_RES_TYPE
082        @Index(name = "IDX_RES_DATE", columnList = BaseHasResource.RES_UPDATED),
083        @Index(name = "IDX_RES_TYPE_DEL_UPDATED", columnList = "RES_TYPE,RES_DELETED_AT,RES_UPDATED,PARTITION_ID,RES_ID"),
084})
085@NamedEntityGraph(name = "Resource.noJoins")
086public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource<JpaPid> {
087        public static final int RESTYPE_LEN = 40;
088        private static final int MAX_LANGUAGE_LENGTH = 20;
089        private static final long serialVersionUID = 1L;
090        public static final String HFJ_RESOURCE = "HFJ_RESOURCE";
091        public static final String RES_TYPE = "RES_TYPE";
092
093        /**
094         * Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB
095         * Note the extra config needed in HS6 for indexing transient props:
096         * https://docs.jboss.org/hibernate/search/6.0/migration/html_single/#indexed-transient-requires-configuration
097         *
098         * Note that we depend on `myVersion` updated for this field to be indexed.
099         */
100        @Transient
101        @FullTextField(name = "myContentText", searchable = Searchable.YES, projectable = Projectable.YES, analyzer = "standardAnalyzer")
102        @FullTextField(name = "myContentTextEdgeNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer =  "autocompleteEdgeAnalyzer")
103        @FullTextField(name = "myContentTextNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer =  "autocompleteNGramAnalyzer")
104        @FullTextField(name = "myContentTextPhonetic", searchable= Searchable.YES, projectable= Projectable.NO, analyzer =  "autocompletePhoneticAnalyzer")
105        @OptimisticLock(excluded = true)
106        @IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "myVersion")))
107        private String myContentText;
108
109        @Column(name = "HASH_SHA256", length = 64, nullable = true)
110        @OptimisticLock(excluded = true)
111        private String myHashSha256;
112
113        @Column(name = "SP_HAS_LINKS")
114        @OptimisticLock(excluded = true)
115        private boolean myHasLinks;
116
117        @Id
118        @GenericGenerator(name = "SEQ_RESOURCE_ID", strategy = "ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator")
119        @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RESOURCE_ID")
120        @Column(name = "RES_ID")
121        @GenericField(projectable = Projectable.YES)
122        private Long myId;
123
124        @Column(name = "SP_INDEX_STATUS", nullable = true)
125        @OptimisticLock(excluded = true)
126        private Long myIndexStatus;
127
128        // TODO: Removed in 5.5.0. Drop in a future release.
129        @Column(name = "RES_LANGUAGE", length = MAX_LANGUAGE_LENGTH, nullable = true)
130        @OptimisticLock(excluded = true)
131        private String myLanguage;
132
133        /**
134         * Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB
135         */
136        @Transient()
137        @FullTextField(name = "myNarrativeText", searchable = Searchable.YES, projectable = Projectable.YES, analyzer = "standardAnalyzer")
138        @FullTextField(name = "myNarrativeTextEdgeNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer =  "autocompleteEdgeAnalyzer")
139        @FullTextField(name = "myNarrativeTextNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer =  "autocompleteNGramAnalyzer")
140        @FullTextField(name = "myNarrativeTextPhonetic", searchable= Searchable.YES, projectable= Projectable.NO, analyzer =  "autocompletePhoneticAnalyzer")
141        @OptimisticLock(excluded = true)
142        @IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "myVersion")))
143        private String myNarrativeText;
144
145        @Transient
146        @IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "myVersion")))
147        @PropertyBinding(binder = @PropertyBinderRef(type = SearchParamTextPropertyBinder.class))
148        private ExtendedHSearchIndexData myLuceneIndexData;
149
150        @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
151        @OptimisticLock(excluded = true)
152        private Collection<ResourceIndexedSearchParamCoords> myParamsCoords;
153
154        @Column(name = "SP_COORDS_PRESENT")
155        @OptimisticLock(excluded = true)
156        private boolean myParamsCoordsPopulated;
157
158        @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
159        @OptimisticLock(excluded = true)
160        private Collection<ResourceIndexedSearchParamDate> myParamsDate;
161
162        @Column(name = "SP_DATE_PRESENT")
163        @OptimisticLock(excluded = true)
164        private boolean myParamsDatePopulated;
165
166        @OptimisticLock(excluded = true)
167        @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
168        private Collection<ResourceIndexedSearchParamNumber> myParamsNumber;
169
170        @Column(name = "SP_NUMBER_PRESENT")
171        @OptimisticLock(excluded = true)
172        private boolean myParamsNumberPopulated;
173
174        @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
175        @OptimisticLock(excluded = true)
176        private Collection<ResourceIndexedSearchParamQuantity> myParamsQuantity;
177
178        @Column(name = "SP_QUANTITY_PRESENT")
179        @OptimisticLock(excluded = true)
180        private boolean myParamsQuantityPopulated;
181        
182        /**
183         * Added to support UCUM conversion
184         * since 5.3.0
185         */
186        @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
187        @OptimisticLock(excluded = true)
188        private Collection<ResourceIndexedSearchParamQuantityNormalized> myParamsQuantityNormalized;
189        
190        /**
191         * Added to support UCUM conversion, 
192         * NOTE : use Boolean class instead of boolean primitive, in order to set the existing rows to null
193         * since 5.3.0
194         */
195        @Column(name = "SP_QUANTITY_NRML_PRESENT")
196        @OptimisticLock(excluded = true)
197        private Boolean myParamsQuantityNormalizedPopulated = Boolean.FALSE;
198
199        @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
200        @OptimisticLock(excluded = true)
201        private Collection<ResourceIndexedSearchParamString> myParamsString;
202
203        @Column(name = "SP_STRING_PRESENT")
204        @OptimisticLock(excluded = true)
205        private boolean myParamsStringPopulated;
206
207        @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
208        @OptimisticLock(excluded = true)
209        private Collection<ResourceIndexedSearchParamToken> myParamsToken;
210
211        @Column(name = "SP_TOKEN_PRESENT")
212        @OptimisticLock(excluded = true)
213        private boolean myParamsTokenPopulated;
214
215        @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
216        @OptimisticLock(excluded = true)
217        private Collection<ResourceIndexedSearchParamUri> myParamsUri;
218
219        @Column(name = "SP_URI_PRESENT")
220        @OptimisticLock(excluded = true)
221        private boolean myParamsUriPopulated;
222
223        // Added in 3.0.0 - Should make this a primitive Boolean at some point
224        @OptimisticLock(excluded = true)
225        @Column(name = "SP_CMPSTR_UNIQ_PRESENT")
226        private Boolean myParamsComboStringUniquePresent = false;
227
228        @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
229        @OptimisticLock(excluded = true)
230        private Collection<ResourceIndexedComboStringUnique> myParamsComboStringUnique;
231
232        // Added in 5.5.0 - Should make this a primitive Boolean at some point
233        @OptimisticLock(excluded = true)
234        @Column(name = "SP_CMPTOKS_PRESENT")
235        private Boolean myParamsComboTokensNonUniquePresent = false;
236
237        @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
238        @OptimisticLock(excluded = true)
239        private Collection<ResourceIndexedComboTokenNonUnique> myParamsComboTokensNonUnique;
240
241        @OneToMany(mappedBy = "mySourceResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
242        @OptimisticLock(excluded = true)
243        private Collection<ResourceLink> myResourceLinks;
244
245        /**
246         * This is a clone of {@link #myResourceLinks} but without the hibernate annotations.
247         * Before we persist we copy the contents of {@link #myResourceLinks} into this field. We
248         * have this separate because that way we can only populate this field if
249         * {@link #myHasLinks} is true, meaning that there are actually resource links present
250         * right now. This avoids Hibernate Search triggering a select on the resource link
251         * table.
252         * <p>
253         * This field is used by FulltextSearchSvcImpl
254         * <p>
255         * You can test that any changes don't cause extra queries by running
256         * FhirResourceDaoR4QueryCountTest
257         */
258        @FullTextField
259        @Transient
260        @IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "myResourceLinks")))
261        private String myResourceLinksField;
262
263        @OneToMany(mappedBy = "myTargetResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
264        @OptimisticLock(excluded = true)
265        private Collection<ResourceLink> myResourceLinksAsTarget;
266
267        @Column(name = RES_TYPE, length = RESTYPE_LEN, nullable = false)
268        @FullTextField
269        @OptimisticLock(excluded = true)
270        private String myResourceType;
271
272        @OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
273        @OptimisticLock(excluded = true)
274        private Collection<SearchParamPresentEntity> mySearchParamPresents;
275
276        @OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
277        @OptimisticLock(excluded = true)
278        private Set<ResourceTag> myTags;
279
280        @Transient
281        private transient boolean myUnchangedInCurrentOperation;
282
283
284        /**
285         * The id of the Resource.
286         * Will contain either the client-assigned id, or the sequence value.
287         * Will be null during insert time until the first read.
288         *
289         */
290        @Column(name= "FHIR_ID",
291                // [A-Za-z0-9\-\.]{1,64} - https://www.hl7.org/fhir/datatypes.html#id
292                length = 64,
293                // we never update this after insert, and the Generator will otherwise "dirty" the object.
294                updatable = false)
295        // inject the pk for server-assigned sequence ids.
296        @GeneratorType(when = GenerationTime.INSERT, type = FhirIdGenerator.class)
297        // Make sure the generator doesn't bump the history version.
298        @OptimisticLock(excluded = true)
299        private String myFhirId;
300
301        /**
302         * Is there a corresponding row in {@link ResourceSearchUrlEntity} for
303         * this row.
304         * TODO: Added in 6.6.0 - Should make this a primitive boolean at some point
305         */
306        @OptimisticLock(excluded = true)
307        @Column(name = "SEARCH_URL_PRESENT", nullable = true)
308        private Boolean mySearchUrlPresent = false;
309
310        /**
311         * Populate myFhirId with server-assigned sequence id when no client-id provided.
312         * We eat this complexity during insert to simplify query time with a uniform column.
313         * Server-assigned sequence ids aren't available until just before insertion.
314         * Hibernate calls insert Generators after the pk has been assigned, so we can use myId safely here.
315         */
316        public static final class FhirIdGenerator implements ValueGenerator<String> {
317                @Override
318                public String generateValue(Session session, Object owner) {
319                        ResourceTable that = (ResourceTable) owner;
320                        return that.myFhirId != null ? that.myFhirId : that.myId.toString();
321                }
322        }
323
324        @Version
325        @Column(name = "RES_VER")
326        private long myVersion;
327
328        @OneToMany(mappedBy = "myResourceTable", fetch = FetchType.LAZY)
329        private Collection<ResourceHistoryProvenanceEntity> myProvenance;
330
331        @Transient
332        private transient ResourceHistoryTable myCurrentVersionEntity;
333
334        @OneToOne(optional = true, fetch = FetchType.EAGER, cascade = {}, orphanRemoval = false, mappedBy = "myResource")
335        @OptimisticLock(excluded = true)
336        private ForcedId myForcedId;
337
338        @Transient
339        private volatile String myCreatedByMatchUrl;
340
341        /**
342         * Constructor
343         */
344        public ResourceTable() {
345                super();
346        }
347
348        @Override
349        public ResourceTag addTag(TagDefinition theTag) {
350                for (ResourceTag next : getTags()) {
351                        if (next.getTag().equals(theTag)) {
352                                return next;
353                        }
354                }
355                ResourceTag tag = new ResourceTag(this, theTag, getPartitionId());
356                getTags().add(tag);
357                return tag;
358        }
359
360
361        public String getHashSha256() {
362                return myHashSha256;
363        }
364
365        public void setHashSha256(String theHashSha256) {
366                myHashSha256 = theHashSha256;
367        }
368
369        @Override
370        public Long getId() {
371                return myId;
372        }
373
374        public void setId(Long theId) {
375                myId = theId;
376        }
377
378        public Long getIndexStatus() {
379                return myIndexStatus;
380        }
381
382        public void setIndexStatus(Long theIndexStatus) {
383                myIndexStatus = theIndexStatus;
384        }
385
386        public Collection<ResourceIndexedComboStringUnique> getParamsComboStringUnique() {
387                if (myParamsComboStringUnique == null) {
388                        myParamsComboStringUnique = new ArrayList<>();
389                }
390                return myParamsComboStringUnique;
391        }
392
393        public Collection<ResourceIndexedComboTokenNonUnique> getmyParamsComboTokensNonUnique() {
394                if (myParamsComboTokensNonUnique == null) {
395                        myParamsComboTokensNonUnique = new ArrayList<>();
396                }
397                return myParamsComboTokensNonUnique;
398        }
399
400        public Collection<ResourceIndexedSearchParamCoords> getParamsCoords() {
401                if (myParamsCoords == null) {
402                        myParamsCoords = new ArrayList<>();
403                }
404                return myParamsCoords;
405        }
406
407        public void setParamsCoords(Collection<ResourceIndexedSearchParamCoords> theParamsCoords) {
408                if (!isParamsTokenPopulated() && theParamsCoords.isEmpty()) {
409                        return;
410                }
411                getParamsCoords().clear();
412                getParamsCoords().addAll(theParamsCoords);
413        }
414
415        public Collection<ResourceIndexedSearchParamDate> getParamsDate() {
416                if (myParamsDate == null) {
417                        myParamsDate = new ArrayList<>();
418                }
419                return myParamsDate;
420        }
421
422        public void setParamsDate(Collection<ResourceIndexedSearchParamDate> theParamsDate) {
423                if (!isParamsDatePopulated() && theParamsDate.isEmpty()) {
424                        return;
425                }
426                getParamsDate().clear();
427                getParamsDate().addAll(theParamsDate);
428        }
429
430        public Collection<ResourceIndexedSearchParamNumber> getParamsNumber() {
431                if (myParamsNumber == null) {
432                        myParamsNumber = new ArrayList<>();
433                }
434                return myParamsNumber;
435        }
436
437        public void setParamsNumber(Collection<ResourceIndexedSearchParamNumber> theNumberParams) {
438                if (!isParamsNumberPopulated() && theNumberParams.isEmpty()) {
439                        return;
440                }
441                getParamsNumber().clear();
442                getParamsNumber().addAll(theNumberParams);
443        }
444
445        public Collection<ResourceIndexedSearchParamQuantity> getParamsQuantity() {
446                if (myParamsQuantity == null) {
447                        myParamsQuantity = new ArrayList<>();
448                }
449                return myParamsQuantity;
450        }
451
452        public void setParamsQuantity(Collection<ResourceIndexedSearchParamQuantity> theQuantityParams) {
453                if (!isParamsQuantityPopulated() && theQuantityParams.isEmpty()) {
454                        return;
455                }
456                getParamsQuantity().clear();
457                getParamsQuantity().addAll(theQuantityParams);
458        }
459
460        public Collection<ResourceIndexedSearchParamQuantityNormalized> getParamsQuantityNormalized() {
461                if (myParamsQuantityNormalized == null) {
462                        myParamsQuantityNormalized = new ArrayList<>();
463                }
464                return myParamsQuantityNormalized;
465        }
466
467        public void setParamsQuantityNormalized(Collection<ResourceIndexedSearchParamQuantityNormalized> theQuantityNormalizedParams) {
468                if (!isParamsQuantityNormalizedPopulated() && theQuantityNormalizedParams.isEmpty()) {
469                        return;
470                }
471                getParamsQuantityNormalized().clear();
472                getParamsQuantityNormalized().addAll(theQuantityNormalizedParams);
473        }
474
475        public Collection<ResourceIndexedSearchParamString> getParamsString() {
476                if (myParamsString == null) {
477                        myParamsString = new ArrayList<>();
478                }
479                return myParamsString;
480        }
481
482        public void setParamsString(Collection<ResourceIndexedSearchParamString> theParamsString) {
483                if (!isParamsStringPopulated() && theParamsString.isEmpty()) {
484                        return;
485                }
486                getParamsString().clear();
487                getParamsString().addAll(theParamsString);
488        }
489
490        public Collection<ResourceIndexedSearchParamToken> getParamsToken() {
491                if (myParamsToken == null) {
492                        myParamsToken = new ArrayList<>();
493                }
494                return myParamsToken;
495        }
496
497        public void setParamsToken(Collection<ResourceIndexedSearchParamToken> theParamsToken) {
498                if (!isParamsTokenPopulated() && theParamsToken.isEmpty()) {
499                        return;
500                }
501                getParamsToken().clear();
502                getParamsToken().addAll(theParamsToken);
503        }
504
505        public Collection<ResourceIndexedSearchParamUri> getParamsUri() {
506                if (myParamsUri == null) {
507                        myParamsUri = new ArrayList<>();
508                }
509                return myParamsUri;
510        }
511
512        public void setParamsUri(Collection<ResourceIndexedSearchParamUri> theParamsUri) {
513                if (!isParamsTokenPopulated() && theParamsUri.isEmpty()) {
514                        return;
515                }
516                getParamsUri().clear();
517                getParamsUri().addAll(theParamsUri);
518        }
519
520        @Override
521        public Long getResourceId() {
522                return getId();
523        }
524
525        public Collection<ResourceLink> getResourceLinks() {
526                if (myResourceLinks == null) {
527                        myResourceLinks = new ArrayList<>();
528                }
529                return myResourceLinks;
530        }
531
532        public void setResourceLinks(Collection<ResourceLink> theLinks) {
533                if (!isHasLinks() && theLinks.isEmpty()) {
534                        return;
535                }
536                getResourceLinks().clear();
537                getResourceLinks().addAll(theLinks);
538        }
539
540        @Override
541        public String getResourceType() {
542                return myResourceType;
543        }
544
545        public ResourceTable setResourceType(String theResourceType) {
546                myResourceType = theResourceType;
547                return this;
548        }
549
550        @Override
551        public Collection<ResourceTag> getTags() {
552                if (myTags == null) {
553                        myTags = new HashSet<>();
554                }
555                return myTags;
556        }
557
558        @Override
559        public long getVersion() {
560                return myVersion;
561        }
562
563        @Override
564        public boolean isDeleted() {
565                return getDeleted() != null;
566        }
567
568        @Override
569        public void setNotDeleted() {
570                setDeleted(null);
571        }
572
573        public void setVersion(long theVersion) {
574                myVersion = theVersion;
575        }
576
577        public boolean isHasLinks() {
578                return myHasLinks;
579        }
580
581        public void setHasLinks(boolean theHasLinks) {
582                myHasLinks = theHasLinks;
583        }
584
585        public boolean isParamsComboStringUniquePresent() {
586                if (myParamsComboStringUniquePresent == null) {
587                        return false;
588                }
589                return myParamsComboStringUniquePresent;
590        }
591
592        public void setParamsComboStringUniquePresent(boolean theParamsComboStringUniquePresent) {
593                myParamsComboStringUniquePresent = theParamsComboStringUniquePresent;
594        }
595
596        public boolean isParamsComboTokensNonUniquePresent() {
597                if (myParamsComboTokensNonUniquePresent == null) {
598                        return false;
599                }
600                return myParamsComboTokensNonUniquePresent;
601        }
602
603        public void setParamsComboTokensNonUniquePresent(boolean theParamsComboTokensNonUniquePresent) {
604                myParamsComboStringUniquePresent = theParamsComboTokensNonUniquePresent;
605        }
606
607        public boolean isParamsCoordsPopulated() {
608                return myParamsCoordsPopulated;
609        }
610
611        public void setParamsCoordsPopulated(boolean theParamsCoordsPopulated) {
612                myParamsCoordsPopulated = theParamsCoordsPopulated;
613        }
614
615        public boolean isParamsDatePopulated() {
616                return myParamsDatePopulated;
617        }
618
619        public void setParamsDatePopulated(boolean theParamsDatePopulated) {
620                myParamsDatePopulated = theParamsDatePopulated;
621        }
622
623        public boolean isParamsNumberPopulated() {
624                return myParamsNumberPopulated;
625        }
626
627        public void setParamsNumberPopulated(boolean theParamsNumberPopulated) {
628                myParamsNumberPopulated = theParamsNumberPopulated;
629        }
630
631        public boolean isParamsQuantityPopulated() {
632                return myParamsQuantityPopulated;
633        }
634
635        public void setParamsQuantityPopulated(boolean theParamsQuantityPopulated) {
636                myParamsQuantityPopulated = theParamsQuantityPopulated;
637        }
638        
639        public Boolean isParamsQuantityNormalizedPopulated() {
640                if (myParamsQuantityNormalizedPopulated == null)
641                        return Boolean.FALSE;
642                else
643                        return myParamsQuantityNormalizedPopulated;
644        }
645
646        public void setParamsQuantityNormalizedPopulated(Boolean theParamsQuantityNormalizedPopulated) {
647                if (theParamsQuantityNormalizedPopulated == null)
648                        myParamsQuantityNormalizedPopulated = Boolean.FALSE;
649                else
650                        myParamsQuantityNormalizedPopulated = theParamsQuantityNormalizedPopulated;
651        }
652
653        public boolean isParamsStringPopulated() {
654                return myParamsStringPopulated;
655        }
656
657        public void setParamsStringPopulated(boolean theParamsStringPopulated) {
658                myParamsStringPopulated = theParamsStringPopulated;
659        }
660
661        public boolean isParamsTokenPopulated() {
662                return myParamsTokenPopulated;
663        }
664
665        public void setParamsTokenPopulated(boolean theParamsTokenPopulated) {
666                myParamsTokenPopulated = theParamsTokenPopulated;
667        }
668
669        public boolean isParamsUriPopulated() {
670                return myParamsUriPopulated;
671        }
672
673        public void setParamsUriPopulated(boolean theParamsUriPopulated) {
674                myParamsUriPopulated = theParamsUriPopulated;
675        }
676
677        /**
678         * Transient (not saved in DB) flag indicating that this resource was found to be unchanged by the current operation
679         * and was not re-saved in the database
680         */
681        public boolean isUnchangedInCurrentOperation() {
682                return myUnchangedInCurrentOperation;
683        }
684
685        /**
686         * Transient (not saved in DB) flag indicating that this resource was found to be unchanged by the current operation
687         * and was not re-saved in the database
688         */
689        public void setUnchangedInCurrentOperation(boolean theUnchangedInCurrentOperation) {
690
691                myUnchangedInCurrentOperation = theUnchangedInCurrentOperation;
692        }
693
694        public void setContentText(String theContentText) {
695                myContentText = theContentText;
696        }
697
698        public String getContentText() {
699                return myContentText;
700        }
701
702        public void setNarrativeText(String theNarrativeText) {
703                myNarrativeText = theNarrativeText;
704        }
705
706        public boolean isSearchUrlPresent() {
707                return Boolean.TRUE.equals(mySearchUrlPresent);
708        }
709
710        public void setSearchUrlPresent(boolean theSearchUrlPresent) {
711                mySearchUrlPresent = theSearchUrlPresent;
712        }
713
714        public ResourceHistoryTable toHistory(boolean theCreateVersionTags) {
715                ResourceHistoryTable retVal = new ResourceHistoryTable();
716
717                retVal.setResourceId(myId);
718                retVal.setResourceType(myResourceType);
719                retVal.setVersion(myVersion);
720                retVal.setTransientForcedId(getTransientForcedId());
721
722                retVal.setPublished(getPublishedDate());
723                retVal.setUpdated(getUpdatedDate());
724                retVal.setFhirVersion(getFhirVersion());
725                retVal.setDeleted(getDeleted());
726                retVal.setResourceTable(this);
727                retVal.setForcedId(getForcedId());
728                retVal.setPartitionId(getPartitionId());
729
730                retVal.getTags().clear();
731
732                retVal.setHasTags(isHasTags());
733                if (isHasTags() && theCreateVersionTags) {
734                        for (ResourceTag next : getTags()) {
735                                retVal.addTag(next);
736                        }
737                }
738
739                return retVal;
740        }
741
742        @Override
743        public String toString() {
744                ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
745                b.append("pid", myId);
746                b.append("resourceType", myResourceType);
747                b.append("version", myVersion);
748                if (getPartitionId() != null) {
749                        b.append("partitionId", getPartitionId().getPartitionId());
750                }
751                b.append("lastUpdated", getUpdated().getValueAsString());
752                if (getDeleted() != null) {
753                        b.append("deleted");
754                }
755                return b.build();
756        }
757
758        @PrePersist
759        @PreUpdate
760        public void preSave() {
761                if (myHasLinks && myResourceLinks != null) {
762                        myResourceLinksField = getResourceLinks()
763                                .stream()
764                                .map(ResourceLink::getTargetResourcePid)
765                                .filter(Objects::nonNull)
766                                .map(Object::toString)
767                                .collect(Collectors.joining(" "));
768                } else {
769                        myResourceLinksField = null;
770                }
771        }
772
773        /**
774         * This is a convenience to avoid loading the version a second time within a single transaction. It is
775         * not persisted.
776         */
777        public void setCurrentVersionEntity(ResourceHistoryTable theCurrentVersionEntity) {
778                myCurrentVersionEntity = theCurrentVersionEntity;
779        }
780
781        /**
782         * This is a convenience to avoid loading the version a second time within a single transaction. It is
783         * not persisted.
784         */
785        public ResourceHistoryTable getCurrentVersionEntity() {
786                return myCurrentVersionEntity;
787        }
788
789        @Override
790        public JpaPid getPersistentId() {
791                return JpaPid.fromId(getId());
792        }
793
794        @Override
795        public ForcedId getForcedId() {
796                return myForcedId;
797        }
798
799        @Override
800        public void setForcedId(ForcedId theForcedId) {
801                myForcedId = theForcedId;
802        }
803
804
805
806        @Override
807        public IdDt getIdDt() {
808                IdDt retVal = new IdDt();
809                populateId(retVal);
810                return retVal;
811        }
812
813
814        public IIdType getIdType(FhirContext theContext) {
815                IIdType retVal = theContext.getVersion().newIdType();
816                populateId(retVal);
817                return retVal;
818        }
819
820        private void populateId(IIdType retVal) {
821                if (getTransientForcedId() != null) {
822                        // Avoid a join query if possible
823                        retVal.setValue(getResourceType() + '/' + getTransientForcedId() + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
824                } else if (getForcedId() == null) {
825                        Long id = this.getResourceId();
826                        retVal.setValue(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
827                } else {
828                        String forcedId = getForcedId().getForcedId();
829                        retVal.setValue(getResourceType() + '/' + forcedId + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
830                }
831        }
832
833        public void setCreatedByMatchUrl(String theCreatedByMatchUrl) {
834                myCreatedByMatchUrl = theCreatedByMatchUrl;
835        }
836
837        public String getCreatedByMatchUrl() {
838                return myCreatedByMatchUrl;
839        }
840
841        public void setLuceneIndexData(ExtendedHSearchIndexData theLuceneIndexData) {
842                myLuceneIndexData = theLuceneIndexData;
843        }
844
845        public Collection<SearchParamPresentEntity> getSearchParamPresents() {
846                if (mySearchParamPresents == null) {
847                        mySearchParamPresents = new ArrayList<>();
848                }
849                return mySearchParamPresents;
850        }
851
852        /**
853         * Get the FHIR resource id.
854         *
855         * @return the resource id, or null if the resource doesn't have a client-assigned id,
856         * and hasn't been saved to the db to get a server-assigned id yet.
857         */
858        public String getFhirId() {
859                return myFhirId;
860        }
861
862        public void setFhirId(String theFhirId) {
863                myFhirId = theFhirId;
864        }
865}