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