/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.search.elasticsearch.internal; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.search.BaseSearchEngine; import com.liferay.portal.kernel.search.IndexSearcher; import com.liferay.portal.kernel.search.IndexWriter; import com.liferay.portal.kernel.search.SearchEngine; import com.liferay.portal.kernel.search.SearchException; import com.liferay.portal.kernel.util.MapUtil; import com.liferay.portal.kernel.util.PortalRunMode; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.Time; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.search.elasticsearch.connection.ElasticsearchConnectionManager; import com.liferay.portal.search.elasticsearch.index.IndexFactory; import com.liferay.portal.search.elasticsearch.index.IndexNameBuilder; import com.liferay.portal.search.elasticsearch.internal.util.LogUtil; import java.util.List; import java.util.Map; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesRequestBuilder; import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesResponse; import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequestBuilder; import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryResponse; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequestBuilder; import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequestBuilder; import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotResponse; import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequestBuilder; import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse; import org.elasticsearch.action.admin.indices.close.CloseIndexRequestBuilder; import org.elasticsearch.action.admin.indices.close.CloseIndexResponse; import org.elasticsearch.client.AdminClient; import org.elasticsearch.client.ClusterAdminClient; import org.elasticsearch.client.IndicesAdminClient; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.cluster.metadata.RepositoryMetaData; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.repositories.RepositoryMissingException; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; /** * @author Michael C. Han */ @Component( immediate = true, property = { "search.engine.id=SYSTEM_ENGINE", "search.engine.impl=Elasticsearch" }, service = {ElasticsearchSearchEngine.class, SearchEngine.class} ) public class ElasticsearchSearchEngine extends BaseSearchEngine { @Override public synchronized String backup(long companyId, String backupName) throws SearchException { backupName = StringUtil.toLowerCase(backupName); validateBackupName(backupName); ClusterAdminClient clusterAdminClient = elasticsearchConnectionManager.getClusterAdminClient(); CreateSnapshotRequestBuilder createSnapshotRequestBuilder = clusterAdminClient.prepareCreateSnapshot( _BACKUP_REPOSITORY_NAME, backupName); createSnapshotRequestBuilder.setWaitForCompletion(true); try { createBackupRepository(clusterAdminClient); CreateSnapshotResponse createSnapshotResponse = createSnapshotRequestBuilder.get(); LogUtil.logActionResponse(_log, createSnapshotResponse); return backupName; } catch (Exception e) { throw new SearchException(e); } } @Override public void initialize(long companyId) { super.initialize(companyId); waitForYellowStatus(); try { indexFactory.createIndices( elasticsearchConnectionManager.getAdminClient(), companyId); elasticsearchConnectionManager.registerCompanyId(companyId); } catch (Exception e) { throw new IllegalStateException(e); } waitForYellowStatus(); } @Override public synchronized void removeBackup(long companyId, String backupName) throws SearchException { ClusterAdminClient clusterAdminClient = elasticsearchConnectionManager.getClusterAdminClient(); try { if (!hasBackupRepository(clusterAdminClient)) { return; } DeleteSnapshotRequestBuilder deleteSnapshotRequestBuilder = clusterAdminClient.prepareDeleteSnapshot( _BACKUP_REPOSITORY_NAME, backupName); DeleteSnapshotResponse deleteSnapshotResponse = deleteSnapshotRequestBuilder.get(); LogUtil.logActionResponse(_log, deleteSnapshotResponse); } catch (Exception e) { throw new SearchException(e); } } @Override public void removeCompany(long companyId) { super.removeCompany(companyId); try { indexFactory.deleteIndices( elasticsearchConnectionManager.getAdminClient(), companyId); elasticsearchConnectionManager.unregisterCompanyId(companyId); } catch (Exception e) { if (_log.isWarnEnabled()) { _log.warn("Unable to delete index for " + companyId, e); } } } @Override public synchronized void restore(long companyId, String backupName) throws SearchException { backupName = StringUtil.toLowerCase(backupName); validateBackupName(backupName); AdminClient adminClient = elasticsearchConnectionManager.getAdminClient(); IndicesAdminClient indicesAdminClient = adminClient.indices(); CloseIndexRequestBuilder closeIndexRequestBuilder = indicesAdminClient.prepareClose( indexNameBuilder.getIndexName(companyId)); try { CloseIndexResponse closeIndexResponse = closeIndexRequestBuilder.get(); LogUtil.logActionResponse(_log, closeIndexResponse); } catch (Exception e) { throw new SearchException(e); } ClusterAdminClient clusterAdminClient = elasticsearchConnectionManager.getClusterAdminClient(); RestoreSnapshotRequestBuilder restoreSnapshotRequestBuilder = clusterAdminClient.prepareRestoreSnapshot( _BACKUP_REPOSITORY_NAME, backupName); restoreSnapshotRequestBuilder.setIndices( indexNameBuilder.getIndexName(companyId)); restoreSnapshotRequestBuilder.setWaitForCompletion(true); try { RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotRequestBuilder.get(); LogUtil.logActionResponse(_log, restoreSnapshotResponse); } catch (Exception e) { throw new SearchException(e); } waitForYellowStatus(); } @Override @Reference(target = "(search.engine.impl=Elasticsearch)", unbind = "-") public void setIndexSearcher(IndexSearcher indexSearcher) { super.setIndexSearcher(indexSearcher); } @Override @Reference(target = "(search.engine.impl=Elasticsearch)", unbind = "-") public void setIndexWriter(IndexWriter indexWriter) { super.setIndexWriter(indexWriter); } public void unsetElasticsearchConnectionManager( ElasticsearchConnectionManager elasticsearchConnectionManager) { this.elasticsearchConnectionManager = null; } public void unsetIndexFactory(IndexFactory indexFactory) { this.indexFactory = null; } @Activate protected void activate(Map<String, Object> properties) { setVendor(MapUtil.getString(properties, "search.engine.impl")); } protected void createBackupRepository(ClusterAdminClient clusterAdminClient) throws Exception { if (hasBackupRepository(clusterAdminClient)) { return; } PutRepositoryRequestBuilder putRepositoryRequestBuilder = clusterAdminClient.preparePutRepository(_BACKUP_REPOSITORY_NAME); Settings.Builder builder = Settings.builder(); builder.put("location", "es_backup"); putRepositoryRequestBuilder.setSettings(builder); putRepositoryRequestBuilder.setType("fs"); PutRepositoryResponse putRepositoryResponse = putRepositoryRequestBuilder.get(); LogUtil.logActionResponse(_log, putRepositoryResponse); } protected boolean hasBackupRepository(ClusterAdminClient clusterAdminClient) throws Exception { GetRepositoriesRequestBuilder getRepositoriesRequestBuilder = clusterAdminClient.prepareGetRepositories(_BACKUP_REPOSITORY_NAME); try { GetRepositoriesResponse getRepositoriesResponse = getRepositoriesRequestBuilder.get(); List<RepositoryMetaData> repositoryMetaDatas = getRepositoriesResponse.repositories(); if (repositoryMetaDatas.isEmpty()) { return false; } return true; } catch (RepositoryMissingException rme) { return false; } } protected void validateBackupName(String backupName) throws SearchException { if (Validator.isNull(backupName)) { throw new SearchException( "Backup name must not be an empty string"); } if (StringUtil.contains(backupName, StringPool.COMMA)) { throw new SearchException("Backup name must not contain comma"); } if (StringUtil.startsWith(backupName, StringPool.DASH)) { throw new SearchException("Backup name must not start with dash"); } if (StringUtil.contains(backupName, StringPool.POUND)) { throw new SearchException("Backup name must not contain pounds"); } if (StringUtil.contains(backupName, StringPool.SPACE)) { throw new SearchException("Backup name must not contain spaces"); } if (StringUtil.contains(backupName, StringPool.TAB)) { throw new SearchException("Backup name must not contain tabs"); } for (char c : backupName.toCharArray()) { if (Strings.INVALID_FILENAME_CHARS.contains(c)) { throw new SearchException( "Backup name must not contain invalid file name " + "characters"); } } } protected void waitForYellowStatus() { long timeout = 30 * Time.SECOND; if (PortalRunMode.isTestMode()) { timeout = Time.HOUR; } ClusterHealthResponse clusterHealthResponse = elasticsearchConnectionManager.getClusterHealthResponse(timeout); if (clusterHealthResponse.getStatus() == ClusterHealthStatus.RED) { throw new IllegalStateException( "Unable to initialize Elasticsearch cluster: " + clusterHealthResponse); } } @Reference protected ElasticsearchConnectionManager elasticsearchConnectionManager; @Reference protected IndexFactory indexFactory; @Reference protected IndexNameBuilder indexNameBuilder; private static final String _BACKUP_REPOSITORY_NAME = "liferay_backup"; private static final Log _log = LogFactoryUtil.getLog( ElasticsearchSearchEngine.class); }