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  
48  	// This one us used only for sorting
49  	@Index(name = "IDX_SP_STRING_HASH_IDENT", columnList = "HASH_IDENTITY"),
50  
51  	@Index(name = "IDX_SP_STRING_HASH_NRM", columnList = "HASH_NORM_PREFIX,SP_VALUE_NORMALIZED"),
52  	@Index(name = "IDX_SP_STRING_HASH_EXCT", columnList = "HASH_EXACT"),
53  
54  	@Index(name = "IDX_SP_STRING_UPDATED", columnList = "SP_UPDATED"),
55  	@Index(name = "IDX_SP_STRING_RESID", columnList = "RES_ID")
56  })
57  @Indexed()
58  //@AnalyzerDefs({
59  //	@AnalyzerDef(name = "autocompleteEdgeAnalyzer",
60  //		tokenizer = @TokenizerDef(factory = PatternTokenizerFactory.class, params= {
61  //			@Parameter(name="pattern", value="(.*)"),
62  //			@Parameter(name="group", value="1")
63  //		}),
64  //		filters = {
65  //			@TokenFilterDef(factory = LowerCaseFilterFactory.class),
66  //			@TokenFilterDef(factory = StopFilterFactory.class),
67  //			@TokenFilterDef(factory = EdgeNGramFilterFactory.class, params = {
68  //				@Parameter(name = "minGramSize", value = "3"),
69  //				@Parameter(name = "maxGramSize", value = "50") 
70  //			}), 
71  //		}),
72  //	@AnalyzerDef(name = "autocompletePhoneticAnalyzer",
73  //		tokenizer = @TokenizerDef(factory=StandardTokenizerFactory.class),
74  //		filters = {
75  //			@TokenFilterDef(factory=StandardFilterFactory.class),
76  //			@TokenFilterDef(factory=StopFilterFactory.class),
77  //			@TokenFilterDef(factory=PhoneticFilterFactory.class, params = {
78  //				@Parameter(name="encoder", value="DoubleMetaphone")
79  //			}),
80  //			@TokenFilterDef(factory=SnowballPorterFilterFactory.class, params = {
81  //				@Parameter(name="language", value="English") 
82  //			})
83  //		}),
84  //	@AnalyzerDef(name = "autocompleteNGramAnalyzer",
85  //		tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
86  //		filters = {
87  //			@TokenFilterDef(factory = WordDelimiterFilterFactory.class),
88  //			@TokenFilterDef(factory = LowerCaseFilterFactory.class),
89  //			@TokenFilterDef(factory = NGramFilterFactory.class, params = {
90  //				@Parameter(name = "minGramSize", value = "3"),
91  //				@Parameter(name = "maxGramSize", value = "20") 
92  //			}),
93  //		}),
94  //	@AnalyzerDef(name = "standardAnalyzer",
95  //		tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
96  //		filters = {
97  //			@TokenFilterDef(factory = LowerCaseFilterFactory.class),
98  //		}) // Def
99  //	}
100 //)
101 //@formatter:on
102 public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchParam {
103 
104 	/*
105 	 * Note that MYSQL chokes on unique indexes for lengths > 255 so be careful here
106 	 */
107 	public static final int MAX_LENGTH = 200;
108 	public static final int HASH_PREFIX_LENGTH = 1;
109 	private static final long serialVersionUID = 1L;
110 	@Id
111 	@SequenceGenerator(name = "SEQ_SPIDX_STRING", sequenceName = "SEQ_SPIDX_STRING")
112 	@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_STRING")
113 	@Column(name = "SP_ID")
114 	private Long myId;
115 
116 	@ManyToOne(optional = false)
117 	@JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", insertable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_SPIDXSTR_RESOURCE"))
118 	@ContainedIn
119 	private ResourceTable myResourceTable;
120 
121 	@Column(name = "SP_VALUE_EXACT", length = MAX_LENGTH, nullable = true)
122 	@Fields({
123 		@Field(name = "myValueText", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")),
124 		@Field(name = "myValueTextEdgeNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteEdgeAnalyzer")),
125 		@Field(name = "myValueTextNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteNGramAnalyzer")),
126 		@Field(name = "myValueTextPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer"))
127 	})
128 	private String myValueExact;
129 
130 	@Column(name = "SP_VALUE_NORMALIZED", length = MAX_LENGTH, nullable = true)
131 	private String myValueNormalized;
132 	/**
133 	 * @since 3.4.0 - At some point this should be made not-null
134 	 */
135 	@Column(name = "HASH_NORM_PREFIX", nullable = true)
136 	private Long myHashNormalizedPrefix;
137 	/**
138 	 * @since 3.6.0 - At some point this should be made not-null
139 	 */
140 	@Column(name = "HASH_IDENTITY", nullable = true)
141 	private Long myHashIdentity;
142 	/**
143 	 * @since 3.4.0 - At some point this should be made not-null
144 	 */
145 	@Column(name = "HASH_EXACT", nullable = true)
146 	private Long myHashExact;
147 	@Transient
148 	private transient DaoConfig myDaoConfig;
149 	public ResourceIndexedSearchParamString() {
150 		super();
151 	}
152 
153 	public ResourceIndexedSearchParamString(DaoConfig theDaoConfig, String theName, String theValueNormalized, String theValueExact) {
154 		setDaoConfig(theDaoConfig);
155 		setParamName(theName);
156 		setValueNormalized(theValueNormalized);
157 		setValueExact(theValueExact);
158 	}
159 
160 	public void setHashIdentity(Long theHashIdentity) {
161 		myHashIdentity = theHashIdentity;
162 	}
163 
164 	@Override
165 	@PrePersist
166 	public void calculateHashes() {
167 		if (myHashNormalizedPrefix == null && myDaoConfig != null) {
168 			String resourceType = getResourceType();
169 			String paramName = getParamName();
170 			String valueNormalized = getValueNormalized();
171 			String valueExact = getValueExact();
172 			setHashNormalizedPrefix(calculateHashNormalized(myDaoConfig, resourceType, paramName, valueNormalized));
173 			setHashExact(calculateHashExact(resourceType, paramName, valueExact));
174 			setHashIdentity(calculateHashIdentity(resourceType, paramName));
175 		}
176 	}
177 
178 	@Override
179 	protected void clearHashes() {
180 		myHashNormalizedPrefix = null;
181 		myHashExact = null;
182 	}
183 
184 	@Override
185 	public boolean equals(Object theObj) {
186 		if (this == theObj) {
187 			return true;
188 		}
189 		if (theObj == null) {
190 			return false;
191 		}
192 		if (!(theObj instanceof ResourceIndexedSearchParamString)) {
193 			return false;
194 		}
195 		ResourceIndexedSearchParamString obj = (ResourceIndexedSearchParamString) theObj;
196 		EqualsBuilder b = new EqualsBuilder();
197 		b.append(getParamName(), obj.getParamName());
198 		b.append(getResource(), obj.getResource());
199 		b.append(getValueExact(), obj.getValueExact());
200 		b.append(getHashExact(), obj.getHashExact());
201 		b.append(getHashNormalizedPrefix(), obj.getHashNormalizedPrefix());
202 		return b.isEquals();
203 	}
204 
205 	public Long getHashExact() {
206 		calculateHashes();
207 		return myHashExact;
208 	}
209 
210 	public void setHashExact(Long theHashExact) {
211 		myHashExact = theHashExact;
212 	}
213 
214 	public Long getHashNormalizedPrefix() {
215 		calculateHashes();
216 		return myHashNormalizedPrefix;
217 	}
218 
219 	public void setHashNormalizedPrefix(Long theHashNormalizedPrefix) {
220 		myHashNormalizedPrefix = theHashNormalizedPrefix;
221 	}
222 
223 	@Override
224 	protected Long getId() {
225 		return myId;
226 	}
227 
228 	public String getValueExact() {
229 		return myValueExact;
230 	}
231 
232 	public void setValueExact(String theValueExact) {
233 		if (StringUtils.defaultString(theValueExact).length() > MAX_LENGTH) {
234 			throw new IllegalArgumentException("Value is too long: " + theValueExact.length());
235 		}
236 		myValueExact = theValueExact;
237 	}
238 
239 	public String getValueNormalized() {
240 		return myValueNormalized;
241 	}
242 
243 	public void setValueNormalized(String theValueNormalized) {
244 		if (StringUtils.defaultString(theValueNormalized).length() > MAX_LENGTH) {
245 			throw new IllegalArgumentException("Value is too long: " + theValueNormalized.length());
246 		}
247 		myValueNormalized = theValueNormalized;
248 	}
249 
250 	@Override
251 	public int hashCode() {
252 		HashCodeBuilder b = new HashCodeBuilder();
253 		b.append(getParamName());
254 		b.append(getResource());
255 		b.append(getValueExact());
256 		return b.toHashCode();
257 	}
258 
259 	public BaseResourceIndexedSearchParam setDaoConfig(DaoConfig theDaoConfig) {
260 		myDaoConfig = theDaoConfig;
261 		return this;
262 	}
263 
264 	@Override
265 	public IQueryParameterType toQueryParameterType() {
266 		return new StringParam(getValueExact());
267 	}
268 
269 	@Override
270 	public String toString() {
271 		ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
272 		b.append("paramName", getParamName());
273 		b.append("resourceId", getResourcePid());
274 		b.append("value", getValueNormalized());
275 		return b.build();
276 	}
277 
278 	public static long calculateHashExact(String theResourceType, String theParamName, String theValueExact) {
279 		return hash(theResourceType, theParamName, theValueExact);
280 	}
281 
282 	public static long calculateHashNormalized(DaoConfig theDaoConfig, String theResourceType, String theParamName, String theValueNormalized) {
283 		/*
284 		 * If we're not allowing contained searches, we'll add the first
285 		 * bit of the normalized value to the hash. This helps to
286 		 * make the hash even more unique, which will be good for
287 		 * performance.
288 		 */
289 		int hashPrefixLength = HASH_PREFIX_LENGTH;
290 		if (theDaoConfig.isAllowContainsSearches()) {
291 			hashPrefixLength = 0;
292 		}
293 
294 		return hash(theResourceType, theParamName, left(theValueNormalized, hashPrefixLength));
295 	}
296 
297 }