1 package com.acumenvelocity.ath.solr;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.List;
6
7 import org.apache.solr.client.solrj.SolrClient;
8 import org.apache.solr.common.SolrInputDocument;
9
10 import com.acumenvelocity.ath.common.Log;
11 import com.acumenvelocity.ath.common.OkapiUtil;
12 import com.acumenvelocity.ath.steps.BaseMergerStep;
13
14 import net.sf.okapi.common.Event;
15 import net.sf.okapi.common.LocaleId;
16 import net.sf.okapi.common.pipeline.BasePipelineStep;
17 import net.sf.okapi.common.resource.ISegments;
18 import net.sf.okapi.common.resource.ITextUnit;
19 import net.sf.okapi.common.resource.Segment;
20 import net.sf.okapi.common.resource.TextContainer;
21
22
23
24
25
26 public abstract class SolrIndexWriterStep extends BasePipelineStep {
27
28 private SolrClient solrClient;
29 private String coreName;
30 private boolean autoCommit;
31 private int tuBatchSize;
32 private List<ITextUnit> tuBatch;
33 private boolean requireTarget = true;
34
35 protected abstract SolrInputDocument getSolrDocument(SolrClient solrClient, String coreName,
36 String sourceWithCodes);
37
38
39
40
41
42
43
44
45
46
47
48
49
50 protected abstract void populateSolrDocument(SolrInputDocument doc, ITextUnit tu,
51 Segment sseg, Segment tseg, LocaleId srcLoc, LocaleId trgLoc) throws Exception;
52
53
54
55
56
57
58
59
60 public SolrIndexWriterStep(SolrClient solrClient, int tuBatchSize, String coreName,
61 boolean autoCommit, boolean requireTarget) {
62
63 this.solrClient = solrClient;
64 this.tuBatch = new ArrayList<>();
65 this.tuBatchSize = tuBatchSize;
66 this.coreName = coreName;
67 this.autoCommit = autoCommit;
68 this.requireTarget = requireTarget;
69 }
70
71 @Override
72 public String getName() {
73 return "Solr Indexing Processor";
74 }
75
76 @Override
77 public String getDescription() {
78 return "Handles batch indexing of text units into a Solr collection.";
79 }
80
81 @Override
82 protected Event handleStartBatch(Event event) {
83 tuBatch.clear();
84 return event;
85 }
86
87 @Override
88 protected Event handleTextUnit(Event event) {
89 ITextUnit tu = event.getTextUnit();
90
91 if (tu != null) {
92 tuBatch.add(tu);
93
94 if (tuBatch.size() >= tuBatchSize) {
95 processTuBatch();
96 }
97 }
98 return event;
99 }
100
101 @Override
102 protected Event handleEndBatch(Event event) {
103 if (!tuBatch.isEmpty()) {
104 processTuBatch();
105 }
106
107 return event;
108 }
109
110
111
112
113 private void processTuBatch() {
114 if (tuBatch.isEmpty()) {
115 Log.warn(SolrIndexWriterStep.class, "Attempted to index an empty batch");
116 return;
117 }
118
119 if (solrClient == null) {
120 throw new IllegalStateException("Index client must be configured prior to use.");
121 }
122
123 try {
124 Collection<SolrInputDocument> indexDocs = generateIndexDocuments(tuBatch);
125
126 if (indexDocs.size() < tuBatch.size()) {
127 Log.warn(SolrIndexWriterStep.class, "Generated {} index documents from {} text units",
128 indexDocs.size(), tuBatch.size());
129 }
130
131 Log.debug(SolrIndexWriterStep.class, "Indexing {} documents into Solr", indexDocs.size());
132
133 if (!indexDocs.isEmpty()) {
134 solrClient.add(coreName, indexDocs);
135
136 if (autoCommit) {
137 solrClient.commit(coreName);
138 }
139 }
140
141 Log.debug(SolrIndexWriterStep.class, "Completed indexing of {} documents", indexDocs.size());
142
143 } catch (Exception e) {
144 throw new RuntimeException("Indexing operation failed", e);
145
146 } finally {
147 tuBatch.clear();
148 }
149 }
150
151
152
153
154
155
156
157 private Collection<SolrInputDocument> generateIndexDocuments(List<ITextUnit> tus) {
158 Collection<SolrInputDocument> docs = new ArrayList<>();
159
160 for (ITextUnit tu : tus) {
161 if (tu == null) {
162 continue;
163 }
164
165
166 if (!BaseMergerStep.checkTu(tu)) {
167 continue;
168 }
169
170 Collection<LocaleId> trgLocs = tu.getTargetLocales();
171
172 if (trgLocs.isEmpty() && requireTarget) {
173 Log.debug(SolrIndexWriterStep.class,
174 "Omitting text unit without output languages since target content is required");
175 continue;
176 }
177
178 if (trgLocs.isEmpty()) {
179 processTuSegments(tu, null, docs);
180
181 } else {
182 for (LocaleId lang : trgLocs) {
183 TextContainer target = tu.getTarget(lang);
184
185 if (requireTarget && (target == null || !target.hasText(false))) {
186 Log.debug(SolrIndexWriterStep.class,
187 "Omitting text unit for language {} due to missing content", lang);
188
189 continue;
190 }
191
192 processTuSegments(tu, lang, docs);
193 }
194 }
195 }
196
197 return docs;
198 }
199
200
201
202
203
204
205
206
207 private void processTuSegments(ITextUnit tu, LocaleId trgLoc,
208 Collection<SolrInputDocument> docs) {
209
210 TextContainer source = tu.getSource();
211 TextContainer target = OkapiUtil.safeGetTarget(tu, trgLoc);
212
213 ISegments sourceSegments = source.getSegments();
214
215 for (Segment sseg : sourceSegments) {
216 Segment tseg = null;
217
218 if (target != null) {
219 tseg = target.getSegments().get(sseg.getId());
220
221 if (requireTarget && tseg == null) {
222 Log.debug(SolrIndexWriterStep.class, "Omitting segment {} due to missing output segment",
223 sseg.getId());
224
225 continue;
226 }
227 }
228
229 if (!requireTarget && tseg == null) {
230 Log.debug(SolrIndexWriterStep.class, "Proceeding with segment {} without output segment",
231 sseg.getId());
232 }
233
234
235 SolrInputDocument doc = getSolrDocument(solrClient, coreName, sseg.toString());
236
237 try {
238 LocaleId effectiveOutputLang = (trgLoc != null) ? trgLoc : getTargetLocale();
239 populateSolrDocument(doc, tu, sseg, tseg, getSourceLocale(), effectiveOutputLang);
240
241 } catch (Exception e) {
242 Log.warn(SolrIndexWriterStep.class, "Unable to populate index document for segment {}",
243 sseg.getId(), e);
244
245 continue;
246 }
247
248 docs.add(doc);
249 }
250 }
251
252 public SolrClient getSolrClient() {
253 return solrClient;
254 }
255
256 public String getCoreName() {
257 return coreName;
258 }
259
260 public boolean isAutoCommit() {
261 return autoCommit;
262 }
263
264 public int getTuBatchSize() {
265 return tuBatchSize;
266 }
267
268 public List<ITextUnit> getTuBatch() {
269 return tuBatch;
270 }
271
272 public boolean isRequireTarget() {
273 return requireTarget;
274 }
275
276 }