/
BridgeHelper.java
262 lines (237 loc) · 9.63 KB
/
BridgeHelper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/*
* Copyright 2008 Glencoe Software, Inc. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.services.fulltext;
import java.io.File;
import java.io.Reader;
import java.io.StringReader;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import ome.conditions.ApiUsageException;
import ome.io.nio.OriginalFilesService;
import ome.model.IObject;
import ome.model.core.OriginalFile;
import ome.services.messages.ReindexMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.search.bridge.FieldBridge;
import org.hibernate.search.bridge.LuceneOptions;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
/**
* Base class for building custom {@link FieldBridge} implementations.
*
* @author Josh Moore, josh at glencoesoftware.com
* @since 3.0-Beta3
*/
public abstract class BridgeHelper implements FieldBridge,
ApplicationEventPublisherAware {
/**
* Name of the {@link Field} which contains the union of all fields. This is
* also the default search field, so users need not append the value to
* search the full index. A field name need only be added to a search to
* eliminate other fields.
*/
//TODO add to constants
public final static String COMBINED = "combined_fields";
/**
* Simpler wrapper to handle superclass proxy objects (e.g. Annotation)
* which do * not behave properly with instanceof checks.
*
* @see <a href="http://trac.openmicroscopy.org/ome/ticket/5076">ticket:5076</a>
*/
@SuppressWarnings("unchecked")
public static <T> T getProxiedObject(T proxy) {
if (proxy instanceof HibernateProxy) {
return (T) ((HibernateProxy) proxy).getHibernateLazyInitializer()
.getImplementation();
}
return proxy;
}
protected final Logger log = LoggerFactory.getLogger(getClass());
protected ApplicationEventPublisher publisher;
public final Logger logger() {
return log;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
/**
* Method to be implemented by all {@link FieldBridge bridges}. The "value"
* argument is an active Hibernate object, and so the full graph can be
* walked.
*/
public abstract void set(final String name, final Object value,
final Document document, final LuceneOptions opts);
/**
* Helper method which takes the parameters from the
* {@link #set(String, Object, Document, LuceneOptions)}
* method (possibly modified) as well as the parsed {@link String} value
* which should be added to the index, and adds two fields. One with the
* given field name and another to the {@link #COMBINED} field which is the
* default search provided to users. In addition to storing the value as is,
* another {@link Field} will be added for both the named and
* {@link #COMBINED} cases using a {@link StringReader} to allow Lucene to
* tokenize the {@link String}.
*
* @param d
* Document as passed to the set method. Do not modify.
* @param field
* Field name which probably <em/>should</em> be modified. If
* this value is null, then the "value" will only be added to the
* {@link #COMBINED} field.
* @param value
* Value which has been parsed out for this field. Should
* <em/>not</em> be null. If you need to store a null value in
* the index, use a null token like "null".
* @param opts
* LuceneOptions, passed in from the runtime. If overriding on
* the interface values is required, see {@link SimpleLuceneOptions}
*/
protected void add(Document d, String field, String value, LuceneOptions opts) {
if (value == null) {
throw new RuntimeException(
"Value for indexing cannot be null. Use a null token instead.");
}
Float boost = opts.getBoost();
Store store = opts.getStore();
Index index = opts.getIndex();
// If the field == null, then we ignore it, to allow easy addition
// of Fields as COMBINED
if (field != null) {
final Field named_field = new Field(field, value, store, index);
if (boost != null) {
named_field.setBoost(boost);
}
d.add(named_field);
final Field named_parsed_field = new Field(field, new StringReader(
value));
d.add(named_parsed_field);
}
// Never storing in combined fields, since it's duplicated
final Field combined_field = new Field(COMBINED, value, Store.NO, index);
if (boost != null) {
combined_field.setBoost(boost);
}
d.add(combined_field);
final Field combined_parsed_field = new Field(COMBINED,
new StringReader(value));
d.add(combined_parsed_field);
}
/**
* Second helper method used when parsing files. The {@link OriginalFile}
* will be passed to {@link #parse(OriginalFile, OriginalFilesService, Map)}
* to generate {@link Reader} instances, which will be read until they
* signal an end, however it is not the responsibility of this instance to
* close the Readers since this happens asynchronously.
*
* The contents of the file will be parsed both to {@link #COMBINED} and
* "file.contents".
*
* @param d
* {@link Document} as passed to set. Do not modify.
* @param name String to be used as the name of the field. If null, then
* the contents will only be added to the {@link #COMBINED}
* {@link Field}.
* @param file
* Non-null, possibly unloaded {@link OriginalFile} which is used
* to look up the file on disk.
* @param files
* {@link OriginalFilesService} which knows how to find where this
* {@link OriginalFile} is stored on disk.
* @param parsers
* {@link Map} of {@link FileParser} instances to be used based
* on the {@link OriginalFile#getMimetype() Format} of
* the {@link OriginalFile}
* @param opts
* The search option.
*/
protected void addContents(final Document d, final String name,
final OriginalFile file, final OriginalFilesService files,
final Map<String, FileParser> parsers, final LuceneOptions opts) {
if (file == null) {
throw new RuntimeException(
"File cannot be null. Either do not attempt to add "
+ "anything for this field, or use a null token like "
+ "\"null\" instead.");
}
Field f;
Float boost = opts.getBoost();
if (name != null) {
for (Reader parsed : parse(file, files, parsers)) {
f = new Field(name, parsed);
if (boost != null) {
f.setBoost(boost);
}
d.add(f);
}
}
for (Reader parsed : parse(file, files, parsers)) {
f = new Field(COMBINED, parsed);
if (boost != null) {
f.setBoost(boost);
}
d.add(f);
}
}
/**
* Publishes a {@link ReindexMessage} which will get processed
* asynchronously.
*/
protected <T extends IObject> void reindex(T object) {
reindexAll(Collections.singletonList(object));
}
/**
* Publishes a {@link ReindexMessage} which will get processed
* asynchronously.
*/
protected <T extends IObject> void reindexAll(List<T> list) {
if (publisher == null) {
throw new ApiUsageException(
"Bridge is not configured for sending messages.");
}
for (T object : list) {
if (object == null || object.getId() == null) {
throw new ApiUsageException("Object cannot be null");
}
}
final ReindexMessage<T> rm = new ReindexMessage<T>(this, list);
publisher.publishEvent(rm);
}
/**
* Attempts to parse the given {@link OriginalFile}. If any of the
* necessary components is null, then it will return an empty, but not null
* {@link Iterable}. Also looks for the catch all parser under "*"
*
* @param file
* Can be null.
* @return will not be null.
*/
protected Iterable<Reader> parse(final OriginalFile file,
final OriginalFilesService files,
final Map<String, FileParser> parsers) {
if (files != null && parsers != null) {
if (file != null && file.getMimetype() != null) {
String path = files.getFilesPath(file.getId());
String format = file.getMimetype();
FileParser parser = parsers.get(format);
if (parser != null) {
return parser.parse(new File(path));
} else {
parser = parsers.get("*");
if (parser != null) {
return parser.parse(new File(path));
}
}
}
}
return FileParser.EMPTY;
}
}