View Javadoc
1   package ca.uhn.fhir.jpa.entity;
2   
3   /*
4    * #%L
5    * HAPI FHIR JPA Server
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import ca.uhn.fhir.jpa.dao.DaoConfig;
24  import ca.uhn.fhir.model.api.IQueryParameterType;
25  import ca.uhn.fhir.rest.param.StringParam;
26  import org.apache.commons.lang3.StringUtils;
27  import org.apache.commons.lang3.builder.EqualsBuilder;
28  import org.apache.commons.lang3.builder.HashCodeBuilder;
29  import org.apache.commons.lang3.builder.ToStringBuilder;
30  import org.apache.commons.lang3.builder.ToStringStyle;
31  import org.hibernate.search.annotations.*;
32  
33  import javax.persistence.*;
34  import javax.persistence.Index;
35  
36  import static org.apache.commons.lang3.StringUtils.left;
37  
38  //@formatter:off
39  @Embeddable
40  @Entity
41  @Table(name = "HFJ_SPIDX_STRING", indexes = {
42  	/*
43  	 * Note: We previously had indexes with the following names,
44  	 * do not reuse these names:
45  	 * IDX_SP_STRING
46  	 */
47  	@Index(name = "IDX_SP_STRING_HASH_NRM", columnList = "HASH_NORM_PREFIX,SP_VALUE_NORMALIZED"),
48  	@Index(name = "IDX_SP_STRING_HASH_EXCT", columnList = "HASH_EXACT"),
49  
50  	@Index(name = "IDX_SP_STRING_UPDATED", columnList = "SP_UPDATED"),
51  	@Index(name = "IDX_SP_STRING_RESID", columnList = "RES_ID")
52  })
53  @Indexed()
54  //@AnalyzerDefs({
55  //	@AnalyzerDef(name = "autocompleteEdgeAnalyzer",
56  //		tokenizer = @TokenizerDef(factory = PatternTokenizerFactory.class, params= {
57  //			@Parameter(name="pattern", value="(.*)"),
58  //			@Parameter(name="group", value="1")
59  //		}),
60  //		filters = {
61  //			@TokenFilterDef(factory = LowerCaseFilterFactory.class),
62  //			@TokenFilterDef(factory = StopFilterFactory.class),
63  //			@TokenFilterDef(factory = EdgeNGramFilterFactory.class, params = {
64  //				@Parameter(name = "minGramSize", value = "3"),
65  //				@Parameter(name = "maxGramSize", value = "50") 
66  //			}), 
67  //		}),
68  //	@AnalyzerDef(name = "autocompletePhoneticAnalyzer",
69  //		tokenizer = @TokenizerDef(factory=StandardTokenizerFactory.class),
70  //		filters = {
71  //			@TokenFilterDef(factory=StandardFilterFactory.class),
72  //			@TokenFilterDef(factory=StopFilterFactory.class),
73  //			@TokenFilterDef(factory=PhoneticFilterFactory.class, params = {
74  //				@Parameter(name="encoder", value="DoubleMetaphone")
75  //			}),
76  //			@TokenFilterDef(factory=SnowballPorterFilterFactory.class, params = {
77  //				@Parameter(name="language", value="English") 
78  //			})
79  //		}),
80  //	@AnalyzerDef(name = "autocompleteNGramAnalyzer",
81  //		tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
82  //		filters = {
83  //			@TokenFilterDef(factory = WordDelimiterFilterFactory.class),
84  //			@TokenFilterDef(factory = LowerCaseFilterFactory.class),
85  //			@TokenFilterDef(factory = NGramFilterFactory.class, params = {
86  //				@Parameter(name = "minGramSize", value = "3"),
87  //				@Parameter(name = "maxGramSize", value = "20") 
88  //			}),
89  //		}),
90  //	@AnalyzerDef(name = "standardAnalyzer",
91  //		tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
92  //		filters = {
93  //			@TokenFilterDef(factory = LowerCaseFilterFactory.class),
94  //		}) // Def
95  //	}
96  //)
97  //@formatter:on
98  public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchParam {
99  
100 	/*
101 	 * Note that MYSQL chokes on unique indexes for lengths > 255 so be careful here
102 	 */
103 	public static final int MAX_LENGTH = 200;
104 	public static final int HASH_PREFIX_LENGTH = 1;
105 	private static final long serialVersionUID = 1L;
106 	@Id
107 	@SequenceGenerator(name = "SEQ_SPIDX_STRING", sequenceName = "SEQ_SPIDX_STRING")
108 	@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_STRING")
109 	@Column(name = "SP_ID")
110 	private Long myId;
111 
112 	@ManyToOne(optional = false)
113 	@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", insertable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_SPIDXSTR_RESOURCE"))
114 	@ContainedIn
115 	private ResourceTable myResourceTable;
116 
117 	@Column(name = "SP_VALUE_EXACT", length = MAX_LENGTH, nullable = true)
118 	@Fields({
119 		@Field(name = "myValueText", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")),
120 		@Field(name = "myValueTextEdgeNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteEdgeAnalyzer")),
121 		@Field(name = "myValueTextNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteNGramAnalyzer")),
122 		@Field(name = "myValueTextPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer"))
123 	})
124 	private String myValueExact;
125 
126 	@Column(name = "SP_VALUE_NORMALIZED", length = MAX_LENGTH, nullable = true)
127 	private String myValueNormalized;
128 	/**
129 	 * @since 3.4.0 - At some point this should be made not-null
130 	 */
131 	@Column(name = "HASH_NORM_PREFIX", nullable = true)
132 	private Long myHashNormalizedPrefix;
133 	/**
134 	 * @since 3.4.0 - At some point this should be made not-null
135 	 */
136 	@Column(name = "HASH_EXACT", nullable = true)
137 	private Long myHashExact;
138 	@Transient
139 	private transient DaoConfig myDaoConfig;
140 
141 	public ResourceIndexedSearchParamString() {
142 		super();
143 	}
144 
145 
146 	public ResourceIndexedSearchParamString(DaoConfig theDaoConfig, String theName, String theValueNormalized, String theValueExact) {
147 		setDaoConfig(theDaoConfig);
148 		setParamName(theName);
149 		setValueNormalized(theValueNormalized);
150 		setValueExact(theValueExact);
151 	}
152 
153 	@PrePersist
154 	public void calculateHashes() {
155 		if (myHashNormalizedPrefix == null && myDaoConfig != null) {
156 			String resourceType = getResourceType();
157 			String paramName = getParamName();
158 			String valueNormalized = getValueNormalized();
159 			String valueExact = getValueExact();
160 			setHashNormalizedPrefix(calculateHashNormalized(myDaoConfig, resourceType, paramName, valueNormalized));
161 			setHashExact(calculateHashExact(resourceType, paramName, valueExact));
162 		}
163 	}
164 
165 	@Override
166 	protected void clearHashes() {
167 		myHashNormalizedPrefix = null;
168 		myHashExact = null;
169 	}
170 
171 	@Override
172 	public boolean equals(Object theObj) {
173 		if (this == theObj) {
174 			return true;
175 		}
176 		if (theObj == null) {
177 			return false;
178 		}
179 		if (!(theObj instanceof ResourceIndexedSearchParamString)) {
180 			return false;
181 		}
182 		ResourceIndexedSearchParamString obj = (ResourceIndexedSearchParamString) theObj;
183 		EqualsBuilder b = new EqualsBuilder();
184 		b.append(getParamName(), obj.getParamName());
185 		b.append(getResource(), obj.getResource());
186 		b.append(getValueExact(), obj.getValueExact());
187 		b.append(getHashExact(), obj.getHashExact());
188 		b.append(getHashNormalizedPrefix(), obj.getHashNormalizedPrefix());
189 		return b.isEquals();
190 	}
191 
192 	public Long getHashExact() {
193 		calculateHashes();
194 		return myHashExact;
195 	}
196 
197 	public void setHashExact(Long theHashExact) {
198 		myHashExact = theHashExact;
199 	}
200 
201 	public Long getHashNormalizedPrefix() {
202 		calculateHashes();
203 		return myHashNormalizedPrefix;
204 	}
205 
206 	public void setHashNormalizedPrefix(Long theHashNormalizedPrefix) {
207 		myHashNormalizedPrefix = theHashNormalizedPrefix;
208 	}
209 
210 	@Override
211 	protected Long getId() {
212 		return myId;
213 	}
214 
215 	public String getValueExact() {
216 		return myValueExact;
217 	}
218 
219 	public void setValueExact(String theValueExact) {
220 		if (StringUtils.defaultString(theValueExact).length() > MAX_LENGTH) {
221 			throw new IllegalArgumentException("Value is too long: " + theValueExact.length());
222 		}
223 		myValueExact = theValueExact;
224 	}
225 
226 	public String getValueNormalized() {
227 		return myValueNormalized;
228 	}
229 
230 	public void setValueNormalized(String theValueNormalized) {
231 		if (StringUtils.defaultString(theValueNormalized).length() > MAX_LENGTH) {
232 			throw new IllegalArgumentException("Value is too long: " + theValueNormalized.length());
233 		}
234 		myValueNormalized = theValueNormalized;
235 	}
236 
237 	@Override
238 	public int hashCode() {
239 		HashCodeBuilder b = new HashCodeBuilder();
240 		b.append(getParamName());
241 		b.append(getResource());
242 		b.append(getValueExact());
243 		return b.toHashCode();
244 	}
245 
246 	public BaseResourceIndexedSearchParam setDaoConfig(DaoConfig theDaoConfig) {
247 		myDaoConfig = theDaoConfig;
248 		return this;
249 	}
250 
251 	@Override
252 	public IQueryParameterType toQueryParameterType() {
253 		return new StringParam(getValueExact());
254 	}
255 
256 	@Override
257 	public String toString() {
258 		ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
259 		b.append("paramName", getParamName());
260 		b.append("resourceId", getResourcePid());
261 		b.append("value", getValueNormalized());
262 		return b.build();
263 	}
264 
265 	public static long calculateHashExact(String theResourceType, String theParamName, String theValueExact) {
266 		return hash(theResourceType, theParamName, theValueExact);
267 	}
268 
269 	public static long calculateHashNormalized(DaoConfig theDaoConfig, String theResourceType, String theParamName, String theValueNormalized) {
270 		/*
271 		 * If we're not allowing contained searches, we'll add the first
272 		 * bit of the normalized value to the hash. This helps to
273 		 * make the hash even more unique, which will be good for
274 		 * performance.
275 		 */
276 		int hashPrefixLength = HASH_PREFIX_LENGTH;
277 		if (theDaoConfig.isAllowContainsSearches()) {
278 			hashPrefixLength = 0;
279 		}
280 
281 		return hash(theResourceType, theParamName, left(theValueNormalized, hashPrefixLength));
282 	}
283 
284 }