github.com/mattermost/mattermost-server/v5@v5.39.3/services/searchengine/bleveengine/bleve.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package bleveengine
     5  
     6  import (
     7  	"net/http"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"sync"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	"github.com/blevesearch/bleve"
    16  	"github.com/blevesearch/bleve/analysis/analyzer/keyword"
    17  	"github.com/blevesearch/bleve/analysis/analyzer/standard"
    18  	"github.com/blevesearch/bleve/mapping"
    19  
    20  	"github.com/mattermost/mattermost-server/v5/jobs"
    21  	"github.com/mattermost/mattermost-server/v5/model"
    22  	"github.com/mattermost/mattermost-server/v5/shared/mlog"
    23  )
    24  
    25  const (
    26  	EngineName   = "bleve"
    27  	PostIndex    = "posts"
    28  	FileIndex    = "files"
    29  	UserIndex    = "users"
    30  	ChannelIndex = "channels"
    31  )
    32  
    33  type BleveEngine struct {
    34  	PostIndex    bleve.Index
    35  	FileIndex    bleve.Index
    36  	UserIndex    bleve.Index
    37  	ChannelIndex bleve.Index
    38  	Mutex        sync.RWMutex
    39  	ready        int32
    40  	cfg          *model.Config
    41  	jobServer    *jobs.JobServer
    42  	indexSync    bool
    43  }
    44  
    45  var keywordMapping *mapping.FieldMapping
    46  var standardMapping *mapping.FieldMapping
    47  var dateMapping *mapping.FieldMapping
    48  
    49  func init() {
    50  	keywordMapping = bleve.NewTextFieldMapping()
    51  	keywordMapping.Analyzer = keyword.Name
    52  
    53  	standardMapping = bleve.NewTextFieldMapping()
    54  	standardMapping.Analyzer = standard.Name
    55  
    56  	dateMapping = bleve.NewNumericFieldMapping()
    57  }
    58  
    59  func getChannelIndexMapping() *mapping.IndexMappingImpl {
    60  	channelMapping := bleve.NewDocumentMapping()
    61  	channelMapping.AddFieldMappingsAt("Id", keywordMapping)
    62  	channelMapping.AddFieldMappingsAt("TeamId", keywordMapping)
    63  	channelMapping.AddFieldMappingsAt("NameSuggest", keywordMapping)
    64  
    65  	indexMapping := bleve.NewIndexMapping()
    66  	indexMapping.AddDocumentMapping("_default", channelMapping)
    67  
    68  	return indexMapping
    69  }
    70  
    71  func getPostIndexMapping() *mapping.IndexMappingImpl {
    72  	postMapping := bleve.NewDocumentMapping()
    73  	postMapping.AddFieldMappingsAt("Id", keywordMapping)
    74  	postMapping.AddFieldMappingsAt("TeamId", keywordMapping)
    75  	postMapping.AddFieldMappingsAt("ChannelId", keywordMapping)
    76  	postMapping.AddFieldMappingsAt("UserId", keywordMapping)
    77  	postMapping.AddFieldMappingsAt("CreateAt", dateMapping)
    78  	postMapping.AddFieldMappingsAt("Message", standardMapping)
    79  	postMapping.AddFieldMappingsAt("Type", keywordMapping)
    80  	postMapping.AddFieldMappingsAt("Hashtags", standardMapping)
    81  	postMapping.AddFieldMappingsAt("Attachments", standardMapping)
    82  
    83  	indexMapping := bleve.NewIndexMapping()
    84  	indexMapping.AddDocumentMapping("_default", postMapping)
    85  
    86  	return indexMapping
    87  }
    88  
    89  func getFileIndexMapping() *mapping.IndexMappingImpl {
    90  	fileMapping := bleve.NewDocumentMapping()
    91  	fileMapping.AddFieldMappingsAt("Id", keywordMapping)
    92  	fileMapping.AddFieldMappingsAt("CreatorId", keywordMapping)
    93  	fileMapping.AddFieldMappingsAt("ChannelId", keywordMapping)
    94  	fileMapping.AddFieldMappingsAt("CreateAt", dateMapping)
    95  	fileMapping.AddFieldMappingsAt("Name", standardMapping)
    96  	fileMapping.AddFieldMappingsAt("Content", standardMapping)
    97  	fileMapping.AddFieldMappingsAt("Extension", keywordMapping)
    98  	fileMapping.AddFieldMappingsAt("Content", standardMapping)
    99  
   100  	indexMapping := bleve.NewIndexMapping()
   101  	indexMapping.AddDocumentMapping("_default", fileMapping)
   102  
   103  	return indexMapping
   104  }
   105  
   106  func getUserIndexMapping() *mapping.IndexMappingImpl {
   107  	userMapping := bleve.NewDocumentMapping()
   108  	userMapping.AddFieldMappingsAt("Id", keywordMapping)
   109  	userMapping.AddFieldMappingsAt("SuggestionsWithFullname", keywordMapping)
   110  	userMapping.AddFieldMappingsAt("SuggestionsWithoutFullname", keywordMapping)
   111  	userMapping.AddFieldMappingsAt("TeamsIds", keywordMapping)
   112  	userMapping.AddFieldMappingsAt("ChannelsIds", keywordMapping)
   113  
   114  	indexMapping := bleve.NewIndexMapping()
   115  	indexMapping.AddDocumentMapping("_default", userMapping)
   116  
   117  	return indexMapping
   118  }
   119  
   120  func NewBleveEngine(cfg *model.Config, jobServer *jobs.JobServer) *BleveEngine {
   121  	return &BleveEngine{
   122  		cfg:       cfg,
   123  		jobServer: jobServer,
   124  	}
   125  }
   126  
   127  func (b *BleveEngine) getIndexDir(indexName string) string {
   128  	return filepath.Join(*b.cfg.BleveSettings.IndexDir, indexName+".bleve")
   129  }
   130  
   131  func (b *BleveEngine) createOrOpenIndex(indexName string, mapping *mapping.IndexMappingImpl) (bleve.Index, error) {
   132  	indexPath := b.getIndexDir(indexName)
   133  	if index, err := bleve.Open(indexPath); err == nil {
   134  		return index, nil
   135  	}
   136  
   137  	index, err := bleve.New(indexPath, mapping)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	return index, nil
   142  }
   143  
   144  func (b *BleveEngine) openIndexes() *model.AppError {
   145  	if atomic.LoadInt32(&b.ready) != 0 {
   146  		return model.NewAppError("Bleveengine.Start", "bleveengine.already_started.error", nil, "", http.StatusInternalServerError)
   147  	}
   148  
   149  	var err error
   150  	b.PostIndex, err = b.createOrOpenIndex(PostIndex, getPostIndexMapping())
   151  	if err != nil {
   152  		return model.NewAppError("Bleveengine.Start", "bleveengine.create_post_index.error", nil, err.Error(), http.StatusInternalServerError)
   153  	}
   154  
   155  	b.FileIndex, err = b.createOrOpenIndex(FileIndex, getFileIndexMapping())
   156  	if err != nil {
   157  		return model.NewAppError("Bleveengine.Start", "bleveengine.create_file_index.error", nil, err.Error(), http.StatusInternalServerError)
   158  	}
   159  
   160  	b.UserIndex, err = b.createOrOpenIndex(UserIndex, getUserIndexMapping())
   161  	if err != nil {
   162  		return model.NewAppError("Bleveengine.Start", "bleveengine.create_user_index.error", nil, err.Error(), http.StatusInternalServerError)
   163  	}
   164  
   165  	b.ChannelIndex, err = b.createOrOpenIndex(ChannelIndex, getChannelIndexMapping())
   166  	if err != nil {
   167  		return model.NewAppError("Bleveengine.Start", "bleveengine.create_channel_index.error", nil, err.Error(), http.StatusInternalServerError)
   168  	}
   169  
   170  	atomic.StoreInt32(&b.ready, 1)
   171  	return nil
   172  }
   173  
   174  func (b *BleveEngine) Start() *model.AppError {
   175  	if !*b.cfg.BleveSettings.EnableIndexing || *b.cfg.BleveSettings.IndexDir == "" {
   176  		return nil
   177  	}
   178  
   179  	b.Mutex.Lock()
   180  	defer b.Mutex.Unlock()
   181  
   182  	mlog.Info("EXPERIMENTAL: Starting Bleve")
   183  
   184  	return b.openIndexes()
   185  }
   186  
   187  func (b *BleveEngine) closeIndexes() *model.AppError {
   188  	if b.IsActive() {
   189  		if err := b.PostIndex.Close(); err != nil {
   190  			return model.NewAppError("Bleveengine.Stop", "bleveengine.stop_post_index.error", nil, err.Error(), http.StatusInternalServerError)
   191  		}
   192  
   193  		if err := b.FileIndex.Close(); err != nil {
   194  			return model.NewAppError("Bleveengine.Stop", "bleveengine.stop_file_index.error", nil, err.Error(), http.StatusInternalServerError)
   195  		}
   196  
   197  		if err := b.UserIndex.Close(); err != nil {
   198  			return model.NewAppError("Bleveengine.Stop", "bleveengine.stop_user_index.error", nil, err.Error(), http.StatusInternalServerError)
   199  		}
   200  
   201  		if err := b.ChannelIndex.Close(); err != nil {
   202  			return model.NewAppError("Bleveengine.Stop", "bleveengine.stop_channel_index.error", nil, err.Error(), http.StatusInternalServerError)
   203  		}
   204  	}
   205  
   206  	atomic.StoreInt32(&b.ready, 0)
   207  	return nil
   208  }
   209  
   210  func (b *BleveEngine) Stop() *model.AppError {
   211  	b.Mutex.Lock()
   212  	defer b.Mutex.Unlock()
   213  
   214  	mlog.Info("Stopping Bleve")
   215  
   216  	return b.closeIndexes()
   217  }
   218  
   219  func (b *BleveEngine) IsActive() bool {
   220  	return atomic.LoadInt32(&b.ready) == 1
   221  }
   222  
   223  func (b *BleveEngine) IsIndexingSync() bool {
   224  	return b.indexSync
   225  }
   226  
   227  func (b *BleveEngine) RefreshIndexes() *model.AppError {
   228  	return nil
   229  }
   230  
   231  func (b *BleveEngine) GetVersion() int {
   232  	return 0
   233  }
   234  
   235  func (b *BleveEngine) GetFullVersion() string {
   236  	return "0"
   237  }
   238  
   239  func (b *BleveEngine) GetPlugins() []string {
   240  	return []string{}
   241  }
   242  
   243  func (b *BleveEngine) GetName() string {
   244  	return EngineName
   245  }
   246  
   247  func (b *BleveEngine) TestConfig(cfg *model.Config) *model.AppError {
   248  	return nil
   249  }
   250  
   251  func (b *BleveEngine) deleteIndexes() *model.AppError {
   252  	if err := os.RemoveAll(b.getIndexDir(PostIndex)); err != nil {
   253  		return model.NewAppError("Bleveengine.PurgeIndexes", "bleveengine.purge_post_index.error", nil, err.Error(), http.StatusInternalServerError)
   254  	}
   255  	if err := os.RemoveAll(b.getIndexDir(UserIndex)); err != nil {
   256  		return model.NewAppError("Bleveengine.PurgeIndexes", "bleveengine.purge_user_index.error", nil, err.Error(), http.StatusInternalServerError)
   257  	}
   258  	if err := os.RemoveAll(b.getIndexDir(ChannelIndex)); err != nil {
   259  		return model.NewAppError("Bleveengine.PurgeIndexes", "bleveengine.purge_channel_index.error", nil, err.Error(), http.StatusInternalServerError)
   260  	}
   261  	if err := os.RemoveAll(b.getIndexDir(FileIndex)); err != nil {
   262  		return model.NewAppError("Bleveengine.PurgeIndexes", "bleveengine.purge_file_index.error", nil, err.Error(), http.StatusInternalServerError)
   263  	}
   264  	return nil
   265  }
   266  
   267  func (b *BleveEngine) PurgeIndexes() *model.AppError {
   268  	if *b.cfg.BleveSettings.IndexDir == "" {
   269  		return nil
   270  	}
   271  
   272  	b.Mutex.Lock()
   273  	defer b.Mutex.Unlock()
   274  
   275  	mlog.Info("PurgeIndexes Bleve")
   276  	if err := b.closeIndexes(); err != nil {
   277  		return err
   278  	}
   279  
   280  	if err := b.deleteIndexes(); err != nil {
   281  		return err
   282  	}
   283  
   284  	return b.openIndexes()
   285  }
   286  
   287  func (b *BleveEngine) DataRetentionDeleteIndexes(cutoff time.Time) *model.AppError {
   288  	return nil
   289  }
   290  
   291  func (b *BleveEngine) IsAutocompletionEnabled() bool {
   292  	return *b.cfg.BleveSettings.EnableAutocomplete
   293  }
   294  
   295  func (b *BleveEngine) IsIndexingEnabled() bool {
   296  	return *b.cfg.BleveSettings.EnableIndexing
   297  }
   298  
   299  func (b *BleveEngine) IsSearchEnabled() bool {
   300  	return *b.cfg.BleveSettings.EnableSearching
   301  }
   302  
   303  func (b *BleveEngine) UpdateConfig(cfg *model.Config) {
   304  	b.Mutex.Lock()
   305  	defer b.Mutex.Unlock()
   306  
   307  	if reflect.DeepEqual(cfg.BleveSettings, b.cfg.BleveSettings) {
   308  		return
   309  	}
   310  
   311  	mlog.Info("UpdateConf Bleve")
   312  
   313  	if *cfg.BleveSettings.EnableIndexing != *b.cfg.BleveSettings.EnableIndexing || *cfg.BleveSettings.IndexDir != *b.cfg.BleveSettings.IndexDir {
   314  		if err := b.closeIndexes(); err != nil {
   315  			mlog.Error("Error closing Bleve indexes to update the config", mlog.Err(err))
   316  			return
   317  		}
   318  		b.cfg = cfg
   319  		if err := b.openIndexes(); err != nil {
   320  			mlog.Error("Error opening Bleve indexes after updating the config", mlog.Err(err))
   321  		}
   322  		return
   323  	}
   324  	b.cfg = cfg
   325  }