package ca.uhn.fhir.jpa.entity; /* * #%L * HAPI FHIR JPA Server * %% * Copyright (C) 2014 - 2017 University Health Network * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import static org.apache.commons.lang3.StringUtils.defaultString; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Index; import javax.persistence.OneToMany; import javax.persistence.SequenceGenerator; import javax.persistence.Table; import javax.persistence.Transient; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.lucene.analysis.core.LowerCaseFilterFactory; import org.apache.lucene.analysis.core.StopFilterFactory; import org.apache.lucene.analysis.miscellaneous.WordDelimiterFilterFactory; import org.apache.lucene.analysis.ngram.EdgeNGramFilterFactory; import org.apache.lucene.analysis.ngram.NGramFilterFactory; import org.apache.lucene.analysis.pattern.PatternTokenizerFactory; import org.apache.lucene.analysis.phonetic.PhoneticFilterFactory; import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory; import org.apache.lucene.analysis.standard.StandardFilterFactory; import org.apache.lucene.analysis.standard.StandardTokenizerFactory; import org.hibernate.search.annotations.Analyze; import org.hibernate.search.annotations.Analyzer; import org.hibernate.search.annotations.AnalyzerDef; import org.hibernate.search.annotations.AnalyzerDefs; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Fields; import org.hibernate.search.annotations.Indexed; import org.hibernate.search.annotations.IndexedEmbedded; import org.hibernate.search.annotations.Parameter; import org.hibernate.search.annotations.Store; import org.hibernate.search.annotations.TokenFilterDef; import org.hibernate.search.annotations.TokenizerDef; import ca.uhn.fhir.jpa.search.IndexNonDeletedInterceptor; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; //@formatter:off @Indexed(interceptor=IndexNonDeletedInterceptor.class) @Entity @Table(name = "HFJ_RESOURCE", uniqueConstraints = {}, indexes= { @Index(name = "IDX_RES_DATE", columnList="RES_UPDATED"), @Index(name = "IDX_RES_LANG", columnList="RES_TYPE,RES_LANGUAGE"), @Index(name = "IDX_RES_PROFILE", columnList="RES_PROFILE"), @Index(name = "IDX_RES_TYPE", columnList="RES_TYPE"), @Index(name = "IDX_INDEXSTATUS", columnList="SP_INDEX_STATUS") }) @AnalyzerDefs({ @AnalyzerDef(name = "autocompleteEdgeAnalyzer", tokenizer = @TokenizerDef(factory = PatternTokenizerFactory.class, params= { @Parameter(name="pattern", value="(.*)"), @Parameter(name="group", value="1") }), filters = { @TokenFilterDef(factory = LowerCaseFilterFactory.class), @TokenFilterDef(factory = StopFilterFactory.class), @TokenFilterDef(factory = EdgeNGramFilterFactory.class, params = { @Parameter(name = "minGramSize", value = "3"), @Parameter(name = "maxGramSize", value = "50") }), }), @AnalyzerDef(name = "autocompletePhoneticAnalyzer", tokenizer = @TokenizerDef(factory=StandardTokenizerFactory.class), filters = { @TokenFilterDef(factory=StandardFilterFactory.class), @TokenFilterDef(factory=StopFilterFactory.class), @TokenFilterDef(factory=PhoneticFilterFactory.class, params = { @Parameter(name="encoder", value="DoubleMetaphone") }), @TokenFilterDef(factory=SnowballPorterFilterFactory.class, params = { @Parameter(name="language", value="English") }) }), @AnalyzerDef(name = "autocompleteNGramAnalyzer", tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), filters = { @TokenFilterDef(factory = WordDelimiterFilterFactory.class), @TokenFilterDef(factory = LowerCaseFilterFactory.class), @TokenFilterDef(factory = NGramFilterFactory.class, params = { @Parameter(name = "minGramSize", value = "3"), @Parameter(name = "maxGramSize", value = "20") }), }), @AnalyzerDef(name = "standardAnalyzer", tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), filters = { @TokenFilterDef(factory = LowerCaseFilterFactory.class), }), @AnalyzerDef(name = "exactAnalyzer", tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), filters = { }) } ) //@formatter:on public class ResourceTable extends BaseHasResource implements Serializable { private static final int MAX_LANGUAGE_LENGTH = 20; private static final int MAX_PROFILE_LENGTH = 200; static final int RESTYPE_LEN = 30; private static final long serialVersionUID = 1L; /** * Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB */ //@formatter:off @Transient() @Fields({ @Field(name = "myContentText", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")), @Field(name = "myContentTextEdgeNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteEdgeAnalyzer")), @Field(name = "myContentTextNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteNGramAnalyzer")), @Field(name = "myContentTextPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer")) }) //@formatter:on private String myContentText; @Column(name = "HASH_SHA256", length=64, nullable=true) private String myHashSha256; @Column(name = "SP_HAS_LINKS") private boolean myHasLinks; @Id @SequenceGenerator(name = "SEQ_RESOURCE_ID", sequenceName = "SEQ_RESOURCE_ID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RESOURCE_ID") @Column(name = "RES_ID") private Long myId; @OneToMany(mappedBy = "myTargetResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) private Collection<ResourceLink> myIncomingResourceLinks; @Column(name = "SP_INDEX_STATUS", nullable = true) private Long myIndexStatus; @Column(name = "RES_LANGUAGE", length = MAX_LANGUAGE_LENGTH, nullable = true) private String myLanguage; /** * Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB */ @Transient() @Fields({ @Field(name = "myNarrativeText", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")), @Field(name = "myNarrativeTextEdgeNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteEdgeAnalyzer")), @Field(name = "myNarrativeTextNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteNGramAnalyzer")), @Field(name = "myNarrativeTextPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer")) }) private String myNarrativeText; @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) private Collection<ResourceIndexedSearchParamCoords> myParamsCoords; @Column(name = "SP_COORDS_PRESENT") private boolean myParamsCoordsPopulated; @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) private Collection<ResourceIndexedSearchParamDate> myParamsDate; @Column(name = "SP_DATE_PRESENT") private boolean myParamsDatePopulated; @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) private Collection<ResourceIndexedSearchParamNumber> myParamsNumber; @Column(name = "SP_NUMBER_PRESENT") private boolean myParamsNumberPopulated; @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) private Collection<ResourceIndexedSearchParamQuantity> myParamsQuantity; @Column(name = "SP_QUANTITY_PRESENT") private boolean myParamsQuantityPopulated; @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) private Collection<ResourceIndexedSearchParamString> myParamsString; @Column(name = "SP_STRING_PRESENT") private boolean myParamsStringPopulated; @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) private Collection<ResourceIndexedSearchParamToken> myParamsToken; @Column(name = "SP_TOKEN_PRESENT") private boolean myParamsTokenPopulated; @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) private Collection<ResourceIndexedSearchParamUri> myParamsUri; @Column(name = "SP_URI_PRESENT") private boolean myParamsUriPopulated; @Column(name = "RES_PROFILE", length = MAX_PROFILE_LENGTH, nullable = true) private String myProfile; @OneToMany(mappedBy = "mySourceResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) @IndexedEmbedded() private Collection<ResourceLink> myResourceLinks; @Column(name = "RES_TYPE", length = RESTYPE_LEN) @Field private String myResourceType; @OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) private Collection<SearchParamPresent> mySearchParamPresents; @OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) private Set<ResourceTag> myTags; @Column(name = "RES_VER") private long myVersion; @Override public ResourceTag addTag(TagDefinition theTag) { ResourceTag tag = new ResourceTag(this, theTag); getTags().add(tag); return tag; } public String getHashSha256() { return myHashSha256; } @Override public Long getId() { return myId; } @Override public IdDt getIdDt() { if (getForcedId() == null) { Long id = myId; return new IdDt(myResourceType + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + myVersion); } else { return new IdDt(getForcedId().getResourceType() + '/' + getForcedId().getForcedId() + '/' + Constants.PARAM_HISTORY + '/' + myVersion); } } public Long getIndexStatus() { return myIndexStatus; } public String getLanguage() { return myLanguage; } public Collection<ResourceIndexedSearchParamCoords> getParamsCoords() { if (myParamsCoords == null) { myParamsCoords = new ArrayList<ResourceIndexedSearchParamCoords>(); } return myParamsCoords; } public Collection<ResourceIndexedSearchParamDate> getParamsDate() { if (myParamsDate == null) { myParamsDate = new ArrayList<ResourceIndexedSearchParamDate>(); } return myParamsDate; } public Collection<ResourceIndexedSearchParamNumber> getParamsNumber() { if (myParamsNumber == null) { myParamsNumber = new ArrayList<ResourceIndexedSearchParamNumber>(); } return myParamsNumber; } public Collection<ResourceIndexedSearchParamQuantity> getParamsQuantity() { if (myParamsQuantity == null) { myParamsQuantity = new ArrayList<ResourceIndexedSearchParamQuantity>(); } return myParamsQuantity; } public Collection<ResourceIndexedSearchParamString> getParamsString() { if (myParamsString == null) { myParamsString = new ArrayList<ResourceIndexedSearchParamString>(); } return myParamsString; } public Collection<ResourceIndexedSearchParamToken> getParamsToken() { if (myParamsToken == null) { myParamsToken = new ArrayList<ResourceIndexedSearchParamToken>(); } return myParamsToken; } public Collection<ResourceIndexedSearchParamUri> getParamsUri() { if (myParamsUri == null) { myParamsUri = new ArrayList<ResourceIndexedSearchParamUri>(); } return myParamsUri; } public String getProfile() { return myProfile; } public Collection<ResourceLink> getResourceLinks() { if (myResourceLinks == null) { myResourceLinks = new ArrayList<ResourceLink>(); } return myResourceLinks; } @Override public String getResourceType() { return myResourceType; } @Override public Collection<ResourceTag> getTags() { if (myTags == null) { myTags = new HashSet<ResourceTag>(); } return myTags; } @Override public long getVersion() { return myVersion; } public boolean hasTag(System theSystem, String theTerm) { for (ResourceTag next : getTags()) { if (next.getTag().getSystem().equals(theSystem) && next.getTag().getCode().equals(theTerm)) { return true; } } return false; } public boolean isHasLinks() { return myHasLinks; } public boolean isParamsCoordsPopulated() { return myParamsCoordsPopulated; } public boolean isParamsDatePopulated() { return myParamsDatePopulated; } public boolean isParamsNumberPopulated() { return myParamsNumberPopulated; } public boolean isParamsQuantityPopulated() { return myParamsQuantityPopulated; } public boolean isParamsStringPopulated() { return myParamsStringPopulated; } public boolean isParamsTokenPopulated() { return myParamsTokenPopulated; } public boolean isParamsUriPopulated() { return myParamsUriPopulated; } public void setContentTextParsedIntoWords(String theContentText) { myContentText = theContentText; } public void setHashSha256(String theHashSha256) { myHashSha256 = theHashSha256; } public void setHasLinks(boolean theHasLinks) { myHasLinks = theHasLinks; } public void setId(Long theId) { myId = theId; } public void setIndexStatus(Long theIndexStatus) { myIndexStatus = theIndexStatus; } public void setLanguage(String theLanguage) { if (defaultString(theLanguage).length() > MAX_LANGUAGE_LENGTH) { throw new UnprocessableEntityException("Language exceeds maximum length of " + MAX_LANGUAGE_LENGTH + " chars: " + theLanguage); } myLanguage = theLanguage; } public void setNarrativeTextParsedIntoWords(String theNarrativeText) { myNarrativeText = theNarrativeText; } public void setParamsCoords(Collection<ResourceIndexedSearchParamCoords> theParamsCoords) { if (!isParamsTokenPopulated() && theParamsCoords.isEmpty()) { return; } getParamsCoords().clear(); getParamsCoords().addAll(theParamsCoords); } public void setParamsCoordsPopulated(boolean theParamsCoordsPopulated) { myParamsCoordsPopulated = theParamsCoordsPopulated; } public void setParamsDate(Collection<ResourceIndexedSearchParamDate> theParamsDate) { if (!isParamsDatePopulated() && theParamsDate.isEmpty()) { return; } getParamsDate().clear(); getParamsDate().addAll(theParamsDate); } public void setParamsDatePopulated(boolean theParamsDatePopulated) { myParamsDatePopulated = theParamsDatePopulated; } public void setParamsNumber(Collection<ResourceIndexedSearchParamNumber> theNumberParams) { if (!isParamsNumberPopulated() && theNumberParams.isEmpty()) { return; } getParamsNumber().clear(); getParamsNumber().addAll(theNumberParams); } public void setParamsNumberPopulated(boolean theParamsNumberPopulated) { myParamsNumberPopulated = theParamsNumberPopulated; } public void setParamsQuantity(Collection<ResourceIndexedSearchParamQuantity> theQuantityParams) { if (!isParamsQuantityPopulated() && theQuantityParams.isEmpty()) { return; } getParamsQuantity().clear(); getParamsQuantity().addAll(theQuantityParams); } public void setParamsQuantityPopulated(boolean theParamsQuantityPopulated) { myParamsQuantityPopulated = theParamsQuantityPopulated; } public void setParamsString(Collection<ResourceIndexedSearchParamString> theParamsString) { if (!isParamsStringPopulated() && theParamsString.isEmpty()) { return; } getParamsString().clear(); getParamsString().addAll(theParamsString); } public void setParamsStringPopulated(boolean theParamsStringPopulated) { myParamsStringPopulated = theParamsStringPopulated; } public void setParamsToken(Collection<ResourceIndexedSearchParamToken> theParamsToken) { if (!isParamsTokenPopulated() && theParamsToken.isEmpty()) { return; } getParamsToken().clear(); getParamsToken().addAll(theParamsToken); } public void setParamsTokenPopulated(boolean theParamsTokenPopulated) { myParamsTokenPopulated = theParamsTokenPopulated; } public void setParamsUri(Collection<ResourceIndexedSearchParamUri> theParamsUri) { if (!isParamsTokenPopulated() && theParamsUri.isEmpty()) { return; } getParamsUri().clear(); getParamsUri().addAll(theParamsUri); } public void setParamsUriPopulated(boolean theParamsUriPopulated) { myParamsUriPopulated = theParamsUriPopulated; } public void setProfile(String theProfile) { if (defaultString(theProfile).length() > MAX_PROFILE_LENGTH) { throw new UnprocessableEntityException("Profile name exceeds maximum length of " + MAX_PROFILE_LENGTH + " chars: " + theProfile); } myProfile = theProfile; } public void setResourceLinks(Collection<ResourceLink> theLinks) { if (!isHasLinks() && theLinks.isEmpty()) { return; } getResourceLinks().clear(); getResourceLinks().addAll(theLinks); } public void setResourceType(String theResourceType) { myResourceType = theResourceType; } public void setVersion(long theVersion) { myVersion = theVersion; } public ResourceHistoryTable toHistory(ResourceHistoryTable theResourceHistoryTable) { ResourceHistoryTable retVal = theResourceHistoryTable != null ? theResourceHistoryTable : new ResourceHistoryTable(); retVal.setResourceId(myId); retVal.setResourceType(myResourceType); retVal.setVersion(myVersion); retVal.setTitle(getTitle()); retVal.setPublished(getPublished()); retVal.setUpdated(getUpdated()); retVal.setEncoding(getEncoding()); retVal.setFhirVersion(getFhirVersion()); retVal.setResource(getResource()); retVal.setDeleted(getDeleted()); retVal.setForcedId(getForcedId()); retVal.getTags().clear(); retVal.setHasTags(isHasTags()); if (isHasTags()) { for (ResourceTag next : getTags()) { retVal.addTag(next); } } return retVal; } @Override public String toString() { ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); b.append("resourceType", myResourceType); b.append("pid", myId); return b.build(); } }