001// --------------------------------------------------------------------------------
002// Copyright 2002-2024 Echo Three, LLC
003//
004// Licensed under the Apache License, Version 2.0 (the "License");
005// you may not use this file except in compliance with the License.
006// You may obtain a copy of the License at
007//
008//     http://www.apache.org/licenses/LICENSE-2.0
009//
010// Unless required by applicable law or agreed to in writing, software
011// distributed under the License is distributed on an "AS IS" BASIS,
012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013// See the License for the specific language governing permissions and
014// limitations under the License.
015// --------------------------------------------------------------------------------
016
017package com.echothree.model.control.index.server.indexer;
018
019import com.echothree.model.control.core.common.EntityAttributeTypes;
020import com.echothree.model.control.core.server.control.CoreControl;
021import com.echothree.model.control.index.common.IndexConstants;
022import com.echothree.model.control.index.common.IndexFields;
023import com.echothree.model.control.index.common.IndexSubfields;
024import com.echothree.model.control.index.common.exception.IndexIOErrorException;
025import com.echothree.model.control.index.server.analysis.BasicAnalyzer;
026import com.echothree.model.control.index.server.control.IndexControl;
027import com.echothree.model.control.tag.server.control.TagControl;
028import com.echothree.model.control.workflow.server.control.WorkflowControl;
029import com.echothree.model.data.core.server.entity.EntityAliasType;
030import com.echothree.model.data.core.server.entity.EntityAttribute;
031import com.echothree.model.data.core.server.entity.EntityAttributeDetail;
032import com.echothree.model.data.core.server.entity.EntityClobAttribute;
033import com.echothree.model.data.core.server.entity.EntityDateAttribute;
034import com.echothree.model.data.core.server.entity.EntityGeoPointAttribute;
035import com.echothree.model.data.core.server.entity.EntityInstance;
036import com.echothree.model.data.core.server.entity.EntityIntegerAttribute;
037import com.echothree.model.data.core.server.entity.EntityListItemAttribute;
038import com.echothree.model.data.core.server.entity.EntityLongAttribute;
039import com.echothree.model.data.core.server.entity.EntityMultipleListItemAttribute;
040import com.echothree.model.data.core.server.entity.EntityNameAttribute;
041import com.echothree.model.data.core.server.entity.EntityStringAttribute;
042import com.echothree.model.data.core.server.entity.EntityTime;
043import com.echothree.model.data.core.server.entity.EntityTimeAttribute;
044import com.echothree.model.data.core.server.entity.EntityType;
045import com.echothree.model.data.index.server.entity.Index;
046import com.echothree.model.data.index.server.entity.IndexDetail;
047import com.echothree.model.data.index.server.entity.IndexStatus;
048import com.echothree.model.data.party.server.entity.Language;
049import com.echothree.model.data.tag.server.entity.EntityTag;
050import com.echothree.model.data.tag.server.entity.TagScope;
051import com.echothree.util.common.message.ExecutionErrors;
052import com.echothree.util.common.persistence.BasePK;
053import com.echothree.util.server.control.BaseLogic;
054import com.echothree.util.server.message.ExecutionErrorAccumulator;
055import com.echothree.util.server.persistence.BaseEntity;
056import com.echothree.util.server.persistence.Session;
057import java.io.Closeable;
058import java.io.File;
059import java.io.IOException;
060import java.nio.file.Paths;
061import java.util.List;
062import org.apache.commons.logging.Log;
063import org.apache.commons.logging.LogFactory;
064import org.apache.lucene.analysis.Analyzer;
065import org.apache.lucene.document.Document;
066import org.apache.lucene.document.Field;
067import org.apache.lucene.document.IntPoint;
068import org.apache.lucene.document.LongPoint;
069import org.apache.lucene.index.IndexWriter;
070import org.apache.lucene.index.IndexWriterConfig;
071import org.apache.lucene.index.SerialMergeScheduler;
072import org.apache.lucene.index.Term;
073import org.apache.lucene.store.Directory;
074import org.apache.lucene.store.FSDirectory;
075
076public abstract class BaseIndexer<BE extends BaseEntity>
077        extends BaseLogic
078        implements Closeable {
079    
080    protected CoreControl coreControl = Session.getModelController(CoreControl.class);
081    protected IndexControl indexControl = Session.getModelController(IndexControl.class);
082    protected TagControl tagControl = Session.getModelController(TagControl.class);
083    protected WorkflowControl workflowControl = Session.getModelController(WorkflowControl.class);
084    protected Log log = LogFactory.getLog(this.getClass());
085
086    protected ExecutionErrorAccumulator eea;
087    protected Index index;
088    
089    protected Language language;
090    protected EntityType entityType;
091    protected IndexStatus indexStatus;
092    protected List<EntityAliasType> entityAliasTypes;
093    protected List<EntityAttribute> entityAttributes;
094    protected List<TagScope> tagScopes;
095    
096    protected IndexWriter indexWriter;
097    
098    protected BaseIndexer(final ExecutionErrorAccumulator eea, final Index index) {
099        this.eea = eea;
100        this.index = index;
101    }
102    
103    public Index getIndex() {
104        return index;
105    }
106    
107    public void open() {
108        if(indexWriter == null) {
109            IndexDetail indexDetail = index.getLastDetail();
110
111            this.language = indexDetail.getLanguage();
112            this.entityType = indexDetail.getIndexType().getLastDetail().getEntityType();
113            this.indexStatus = indexControl.getIndexStatusForUpdate(index);
114            this.entityAliasTypes = coreControl.getEntityAliasTypesByEntityType(entityType);
115            this.entityAttributes = coreControl.getEntityAttributesByEntityType(entityType);
116            this.tagScopes = tagControl.getTagScopesByEntityType(entityType);
117
118            openIndexWriter(getAnalyzer());
119        }
120    }
121    
122    public EntityType getEntityType() {
123        return entityType;
124    }
125    
126    @Override
127    public void close() {
128        if(indexWriter != null) {
129            closeIndexWriter();
130        }
131    }
132    
133    /** Index an EntityInstance in all of its Workflows. */
134    private void indexWorkflowEntityStatuses(final Document document, final EntityInstance entityInstance) {
135        workflowControl.getWorkflowsByEntityType(entityInstance.getEntityType()).stream().forEach((workflow) -> {
136            var workflowEntityStatuses = workflowControl.getWorkflowEntityStatusesByEntityInstance(workflow, entityInstance);
137
138            if (!workflowEntityStatuses.isEmpty()) {
139                var workflowStepNamesBuilder = new StringBuilder();
140
141                workflowEntityStatuses.forEach((workflowEntityStatus) -> {
142                    if(workflowStepNamesBuilder.length() != 0) {
143                        workflowStepNamesBuilder.append(' ');
144                    }
145
146                    workflowStepNamesBuilder.append(workflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName());
147                });
148
149                document.add(new Field(workflow.getLastDetail().getWorkflowName(), workflowStepNamesBuilder.toString(), FieldTypes.NOT_STORED_TOKENIZED));
150            }
151        });
152    }
153
154    private void indexEntityTimes(final Document document, final EntityInstance entityInstance) {
155        EntityTime entityTime = coreControl.getEntityTime(entityInstance);
156
157        if(entityTime != null) {
158            Long createdTime = entityTime.getCreatedTime();
159            Long modifiedTime = entityTime.getModifiedTime();
160            Long deletedTime = entityTime.getDeletedTime();
161
162            if(createdTime != null) {
163                document.add(new LongPoint(IndexFields.createdTime.name(), createdTime));
164            }
165
166            if(modifiedTime != null) {
167                document.add(new LongPoint(IndexFields.modifiedTime.name(), modifiedTime));
168            }
169
170            if(deletedTime != null) {
171                document.add(new LongPoint(IndexFields.deletedTime.name(), deletedTime));
172            }
173        }
174    }
175
176    private void indexEntityAliases(final Document document, final EntityInstance entityInstance) {
177        var entityAliases = coreControl.getEntityAliasesByEntityInstance(entityInstance);
178
179        for(var entityAlias : entityAliases) {
180            var fieldName = entityAlias.getEntityAliasType().getLastDetail().getEntityAliasTypeName();
181            var alias = entityAlias.getAlias();
182
183            document.add(new Field(fieldName, alias, FieldTypes.NOT_STORED_NOT_TOKENIZED));
184        }
185
186    }
187
188    private void indexEntityAttributes(final Document document, final EntityInstance entityInstance) {
189        entityAttributes.forEach((entityAttribute) -> {
190            EntityAttributeDetail entityAttributeDetail = entityAttribute.getLastDetail();
191            String fieldName = entityAttributeDetail.getEntityAttributeName();
192            String entityAttributeTypeName = entityAttributeDetail.getEntityAttributeType().getEntityAttributeTypeName();
193
194            if (entityAttributeTypeName.equals(EntityAttributeTypes.BOOLEAN.name())) {
195                var entityBooleanAttribute = coreControl.getEntityBooleanAttribute(entityAttribute, entityInstance);
196
197                if(entityBooleanAttribute != null) {
198                    var booleanAttribute = entityBooleanAttribute.getBooleanAttribute();
199                    if(IndexerDebugFlags.LogBaseIndexing) {
200                        log.info("--- fieldName =\"" + fieldName + ", \"booleanAttribute = \"" + booleanAttribute + "\"");
201                    }
202                    document.add(new Field(fieldName, booleanAttribute.toString(), FieldTypes.NOT_STORED_NOT_TOKENIZED));
203                }
204            } else if (entityAttributeTypeName.equals(EntityAttributeTypes.NAME.name())) {
205                EntityNameAttribute entityNameAttribute = coreControl.getEntityNameAttribute(entityAttribute, entityInstance);
206
207                if(entityNameAttribute != null) {
208                    String nameAttribute = entityNameAttribute.getNameAttribute();
209                    if(IndexerDebugFlags.LogBaseIndexing) {
210                        log.info("--- fieldName =\"" + fieldName + ", \"nameAttribute = \"" + nameAttribute + "\"");
211                    }
212                    document.add(new Field(fieldName, nameAttribute, FieldTypes.NOT_STORED_NOT_TOKENIZED));
213                }
214            } else if (entityAttributeTypeName.equals(EntityAttributeTypes.INTEGER.name())) {
215                EntityIntegerAttribute entityIntegerAttribute = coreControl.getEntityIntegerAttribute(entityAttribute, entityInstance);
216                
217                if(entityIntegerAttribute != null) {
218                    Integer integerAttribute = entityIntegerAttribute.getIntegerAttribute();
219                    if(IndexerDebugFlags.LogBaseIndexing) {
220                        log.info("--- fieldName =\"" + fieldName + ",\" integerAttribute = \"" + integerAttribute + "\"");
221                    }
222                    document.add(new IntPoint(fieldName, integerAttribute));
223                }
224            } else if (entityAttributeTypeName.equals(EntityAttributeTypes.LONG.name())) {
225                EntityLongAttribute entityLongAttribute = coreControl.getEntityLongAttribute(entityAttribute, entityInstance);
226                
227                if(entityLongAttribute != null) {
228                    Long longAttribute = entityLongAttribute.getLongAttribute();
229                    if(IndexerDebugFlags.LogBaseIndexing) {
230                        log.info("--- fieldName =\"" + fieldName + ",\" longAttribute = \"" + longAttribute + "\"");
231                    }
232                    document.add(new LongPoint(fieldName, longAttribute));
233                }
234            } else if (language != null && entityAttributeTypeName.equals(EntityAttributeTypes.STRING.name())) {
235                EntityStringAttribute entityStringAttribute = coreControl.getEntityStringAttribute(entityAttribute, entityInstance, language);
236                
237                if(entityStringAttribute != null) {
238                    String stringAttribute = entityStringAttribute.getStringAttribute();
239                    if(IndexerDebugFlags.LogBaseIndexing) {
240                        log.info("--- fieldName = \"" + fieldName + ",\" stringAttribute = \"" + stringAttribute + "\"");
241                    }
242                    document.add(new Field(fieldName, stringAttribute, FieldTypes.NOT_STORED_TOKENIZED));
243                }
244            } else if (entityAttributeTypeName.equals(EntityAttributeTypes.GEOPOINT.name())) {
245                EntityGeoPointAttribute entityGeoPointAttribute = coreControl.getEntityGeoPointAttribute(entityAttribute, entityInstance);
246                
247                if(entityGeoPointAttribute != null) {
248                    Integer latitude = entityGeoPointAttribute.getLatitude();
249                    Integer longitude = entityGeoPointAttribute.getLongitude();
250                    Long elevation = entityGeoPointAttribute.getElevation();
251                    Long altitude = entityGeoPointAttribute.getAltitude();
252                    
253                    if(IndexerDebugFlags.LogBaseIndexing) {
254                        log.info("--- fieldName = \"" + fieldName + ",\" latitude = \"" + latitude + ",\" longitude = \"" + longitude + ",\" elevation = \"" + elevation + ",\" altitude = \"" + altitude + "\"");
255                    }
256                    
257                    document.add(new IntPoint(fieldName + IndexConstants.INDEX_SUBFIELD_SEPARATOR + IndexSubfields.latitude.name(), latitude));
258                    document.add(new IntPoint(fieldName + IndexConstants.INDEX_SUBFIELD_SEPARATOR + IndexSubfields.longitude.name(), longitude));
259                    
260                    if(elevation != null) {
261                        document.add(new LongPoint(fieldName + IndexConstants.INDEX_SUBFIELD_SEPARATOR + IndexSubfields.elevation.name(), elevation));
262                    }
263                    
264                    if(altitude != null) {
265                        document.add(new LongPoint(fieldName + IndexConstants.INDEX_SUBFIELD_SEPARATOR + IndexSubfields.altitude.name(), altitude));
266                    }
267                }
268            } else if (language != null && entityAttributeTypeName.equals(EntityAttributeTypes.CLOB.name())) {
269                // TODO: MIME type should be taken into account
270                EntityClobAttribute entityClobAttribute = coreControl.getEntityClobAttribute(entityAttribute, entityInstance, language);
271                
272                if(entityClobAttribute != null) {
273                    String clobAttribute = entityClobAttribute.getClobAttribute();
274                    if(IndexerDebugFlags.LogBaseIndexing) {
275                        log.info("--- fieldName =\"" + fieldName + ",\" clobAttribute = \"" + clobAttribute + "\"");
276                    }
277                    document.add(new Field(fieldName, clobAttribute, FieldTypes.NOT_STORED_TOKENIZED));
278                }
279            } else if (entityAttributeTypeName.equals(EntityAttributeTypes.DATE.name())) {
280                EntityDateAttribute entityDateAttribute = coreControl.getEntityDateAttribute(entityAttribute, entityInstance);
281                
282                if(entityDateAttribute != null) {
283                    Integer dateAttribute = entityDateAttribute.getDateAttribute();
284                    if(IndexerDebugFlags.LogBaseIndexing) {
285                        log.info("--- fieldName =\"" + fieldName + ",\" dateAttribute = \"" + dateAttribute + "\"");
286                    }
287                    document.add(new IntPoint(fieldName, dateAttribute));
288                }
289            } else if (entityAttributeTypeName.equals(EntityAttributeTypes.TIME.name())) {
290                EntityTimeAttribute entityTimeAttribute = coreControl.getEntityTimeAttribute(entityAttribute, entityInstance);
291                
292                if(entityTimeAttribute != null) {
293                    Long timeAttribute = entityTimeAttribute.getTimeAttribute();
294                    if(IndexerDebugFlags.LogBaseIndexing) {
295                        log.info("--- fieldName =\"" + fieldName + ",\" dateAttribute = \"" + timeAttribute + "\"");
296                    }
297                    document.add(new LongPoint(fieldName, timeAttribute));
298                }
299            } else if (entityAttributeTypeName.equals(EntityAttributeTypes.LISTITEM.name())) {
300                EntityListItemAttribute entityListItemAttribute = coreControl.getEntityListItemAttribute(entityAttribute, entityInstance);
301                
302                if(entityListItemAttribute != null) {
303                    String entityListItemName = entityListItemAttribute.getEntityListItem().getLastDetail().getEntityListItemName();
304                    if(IndexerDebugFlags.LogBaseIndexing) {
305                        log.info("--- fieldName =\"" + fieldName + ",\" entityListItemName = \"" + entityListItemName + "\"");
306                    }
307                    document.add(new Field(fieldName, entityListItemName, FieldTypes.NOT_STORED_TOKENIZED));
308                }
309            } else if (entityAttributeTypeName.equals(EntityAttributeTypes.MULTIPLELISTITEM.name())) {
310                List<EntityMultipleListItemAttribute> entityMultipleListItemAttributes = coreControl.getEntityMultipleListItemAttributes(entityAttribute, entityInstance);
311                if (entityMultipleListItemAttributes != null && !entityMultipleListItemAttributes.isEmpty()) {
312                    StringBuilder entityListItemNamesBuilder = new StringBuilder();
313                    entityMultipleListItemAttributes.forEach((entityMultipleListItemAttribute) -> {
314                        if(entityListItemNamesBuilder.length() != 0) {
315                            entityListItemNamesBuilder.append(' ');
316                        }
317                        
318                        entityListItemNamesBuilder.append(entityMultipleListItemAttribute.getEntityListItem().getLastDetail().getEntityListItemName());
319                    });
320                    String entityListItemNames = entityListItemNamesBuilder.toString();
321                    if(IndexerDebugFlags.LogBaseIndexing) {
322                        log.info("--- fieldName =\"" + fieldName + ",\" entityListItemNames = \"" + entityListItemNames + "\"");
323                    }
324                    document.add(new Field(fieldName, entityListItemNames, FieldTypes.NOT_STORED_TOKENIZED));
325                }
326            }
327        });
328    }
329
330    private void indexEntityTags(final Document document, final EntityInstance entityInstance) {
331        List<EntityTag> entityTags = tagControl.getEntityTagsByTaggedEntityInstance(entityInstance);
332
333        entityTags.stream().map((entityTag) -> entityTag.getTag().getLastDetail()).forEach((tagDetail) -> {
334            String tagScopeName = tagDetail.getTagScope().getLastDetail().getTagScopeName();
335            String tagName = tagDetail.getTagName();
336
337            document.add(new Field(tagScopeName, tagName, FieldTypes.NOT_STORED_TOKENIZED));
338        });
339    }
340
341    private void indexEntityAppearance(final Document document, final EntityInstance entityInstance) {
342        var entityAppearance = coreControl.getEntityAppearance(entityInstance);
343
344        if(entityAppearance != null) {
345            var entityAppearanceName = entityAppearance.getAppearance().getLastDetail().getAppearanceName();
346
347            document.add(new Field(IndexFields.appearance.name(), entityAppearanceName, FieldTypes.NOT_STORED_TOKENIZED));
348        }
349    }
350
351    protected Document newDocumentWithEntityInstanceFields(final EntityInstance entityInstance, final BasePK basePK) {
352        var document = new Document();
353
354        document.add(new Field(IndexFields.entityRef.name(), basePK.getEntityRef(), FieldTypes.STORED_NOT_TOKENIZED));
355        document.add(new Field(IndexFields.entityInstanceId.name(), entityInstance.getPrimaryKey().getEntityId().toString(), FieldTypes.STORED_NOT_TOKENIZED));
356
357        indexWorkflowEntityStatuses(document, entityInstance);
358        indexEntityTimes(document, entityInstance);
359        indexEntityAliases(document, entityInstance);
360        indexEntityAttributes(document, entityInstance);
361        indexEntityTags(document, entityInstance);
362        indexEntityAppearance(document, entityInstance);
363
364        return document;
365    }
366
367    /**
368     * Create a Lucene IndexWriter.
369     */
370    protected void openIndexWriter(final Analyzer analyzer) {
371        if(IndexerDebugFlags.LogBaseIndexing) {
372            log.info(">>> getIndexWriter");
373        }
374        
375        boolean createIndex = indexStatus.getCreatedTime() != null ? false : true;
376        
377        if(createIndex) {
378            checkIndexDirectory(eea, indexStatus);
379        }
380
381        // indexCreatedTime is rechecked because if checkIndexDirectory was called,
382        // it will be set to the time the directory was created, vs. remaining null.
383        if(!hasExecutionErrors(eea)) {
384            try {
385                Directory fsDir = FSDirectory.open(Paths.get(index.getLastDetail().getDirectory()));
386                IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
387
388                indexWriterConfig.setMergeScheduler(new SerialMergeScheduler());
389                indexWriterConfig.setOpenMode(createIndex ? IndexWriterConfig.OpenMode.CREATE : IndexWriterConfig.OpenMode.APPEND);
390
391                indexWriter = new IndexWriter(fsDir, indexWriterConfig);
392            } catch (IOException ioe1) {
393                // indexWriter will remain null, signaling that index was not able to be opened
394                if(IndexerDebugFlags.LogBaseIndexing) {
395                    log.info("--- new IndexWriter failed");
396                }
397                
398                try {
399                    if(indexWriter != null) {
400                        indexWriter.close();
401                    }
402                } catch (IOException ioe2) {
403                    // ioe2 discarded.
404                } finally {
405                    indexWriter = null;
406                }
407                
408                handleExecutionError(IndexIOErrorException.class, eea, ExecutionErrors.IndexIOError.name(), ioe1.getMessage());
409            }
410        }
411        
412        if(IndexerDebugFlags.LogBaseIndexing) {
413            log.info("<<< getIndexWriter");
414        }
415    }
416    
417    protected void closeIndexWriter() {
418        if(IndexerDebugFlags.LogBaseIndexing) {
419            log.info(">>> closeIndexWriter");
420        }
421        
422        try {
423            indexWriter.commit();
424            indexWriter.close();
425        } catch(IOException ioe) {
426            // unrecoverable error
427            // TODO: Index should be marked as possibly invalid
428            if(IndexerDebugFlags.LogBaseIndexing) {
429                log.info("--- indexWriter.close failed");
430            }
431
432            handleExecutionError(IndexIOErrorException.class, eea, ExecutionErrors.IndexIOError.name(), ioe.getMessage());
433        }
434
435        if(IndexerDebugFlags.LogBaseIndexing) {
436            log.info("<<< closeIndexWriter");
437        }
438    }
439    
440    private void checkIndexDirectory(final ExecutionErrorAccumulator eea, final IndexStatus indexStatus) {
441        if(IndexerDebugFlags.LogBaseIndexing) {
442            log.info("--- checkIndexDirectory, index = " + index);
443        }
444        Long indexCreatedTime = indexStatus.getCreatedTime();
445        
446        if(indexCreatedTime == null) {
447            indexCreatedTime = createIndexDirectory(eea);
448            
449            if(!hasExecutionErrors(eea)) {
450                indexStatus.setCreatedTime(indexCreatedTime);
451            }
452        }
453    }
454    
455    private Long createIndexDirectory(final ExecutionErrorAccumulator eea) {
456        if(IndexerDebugFlags.LogBaseIndexing) {
457            log.info(">>> createIndexDirectory, index = " + index);
458        }
459        Long indexCreatedTime = null;
460        String strDirectory = index.getLastDetail().getDirectory();
461        File directory = new File(strDirectory);
462        
463        if(directory.exists()) {
464            if(directory.isDirectory()) {
465                File[] files = directory.listFiles();
466                
467                if(files == null) {
468                    handleExecutionError(IndexIOErrorException.class, eea, ExecutionErrors.IndexIOError.name(), "listFiles failed for " + strDirectory);
469                } else {
470                    int numFiles = files.length;
471
472                    for(int i = 0 ; i < numFiles ; i++) {
473                        File indivFile = files[i];
474
475                        if(indivFile.isFile()) {
476                            if(!indivFile.delete()) {
477                                handleExecutionError(IndexIOErrorException.class, eea, ExecutionErrors.IndexIOError.name(), "delete failed for " + indivFile.getPath());
478                            }
479                        }
480                    }
481                }
482            } else {
483                handleExecutionError(IndexIOErrorException.class, eea, ExecutionErrors.IndexIOError.name(), "directory isn't a directory:  " + directory.getPath());
484            }
485        } else {
486            if(!directory.mkdirs()) {
487                handleExecutionError(IndexIOErrorException.class, eea, ExecutionErrors.IndexIOError.name(), "mkdirs failed for " + directory.getPath());
488            }
489        }
490        
491        if(!hasExecutionErrors(eea)) {
492            indexCreatedTime = System.currentTimeMillis();
493        }
494        
495        if(IndexerDebugFlags.LogBaseIndexing) {
496            log.info("<<< createIndexDirectory, indexCreatedTime = " + indexCreatedTime);
497        }
498        return indexCreatedTime;
499    }
500    
501    public void forceReindex() {
502        indexStatus.setCreatedTime(null);
503    }
504    
505    protected Analyzer getAnalyzer() {
506        return new BasicAnalyzer(eea, language, entityType, entityAliasTypes, entityAttributes, tagScopes);
507    }
508
509    protected abstract BE getEntity(final EntityInstance entityInstance);
510    
511    protected abstract Document convertToDocument(final EntityInstance entityInstance, final BE item);
512    
513    protected void addEntityToIndex(final EntityInstance entityInstance, final BE baseEntity)
514        throws IOException {
515        Document document = convertToDocument(entityInstance, baseEntity);
516        
517        if(document != null) {
518            indexWriter.addDocument(document);
519        }
520    }
521    
522    protected void removeEntityFromIndex(final BE baseEntity)
523        throws IOException {
524        indexWriter.deleteDocuments(new Term(IndexFields.entityRef.name(), baseEntity.getPrimaryKey().getEntityRef()));
525    }
526
527    public void updateIndex(final EntityInstance entityInstance) {
528        BE baseEntity = getEntity(entityInstance);
529        
530        if(baseEntity != null) {
531            EntityTime entityTime = coreControl.getEntityTime(entityInstance);
532            
533            if(entityTime != null) {
534                Long modifiedTime = entityTime.getModifiedTime();
535                Long deletedTime = entityTime.getDeletedTime();
536                
537                try {
538                    if(modifiedTime != null || deletedTime != null) {
539                        removeEntityFromIndex(baseEntity);
540                    }
541
542                    if(deletedTime == null) {
543                        addEntityToIndex(entityInstance, baseEntity);
544                    }
545                } catch(IOException ioe) {
546                    handleExecutionError(IndexIOErrorException.class, eea, ExecutionErrors.IndexIOError.name(), ioe.getMessage());
547                }
548            }
549        }
550    }
551
552}