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