/*
* Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.entando.entando.plugins.jpsolrclient.aps.system.services.content;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.entando.entando.plugins.jpsolrclient.aps.system.SolrConnectorSystemConstants;
import org.entando.entando.plugins.jpsolrclient.aps.system.services.config.SolrConfigDOM;
import org.entando.entando.plugins.jpsolrclient.aps.system.services.config.model.SolrConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.agiletec.aps.system.common.AbstractService;
import com.agiletec.aps.system.common.IManager;
import com.agiletec.aps.system.common.entity.event.EntityTypesChangingEvent;
import com.agiletec.aps.system.common.entity.event.EntityTypesChangingObserver;
import com.agiletec.aps.system.common.entity.model.IApsEntity;
import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface;
import com.agiletec.aps.system.common.notify.ApsEvent;
import com.agiletec.aps.system.common.searchengine.IndexableAttributeInterface;
import com.agiletec.aps.system.exception.ApsSystemException;
import com.agiletec.aps.system.services.baseconfig.ConfigInterface;
import com.agiletec.aps.system.services.lang.ILangManager;
import com.agiletec.aps.util.DateConverter;
import com.agiletec.plugins.jacms.aps.system.services.content.IContentManager;
import com.agiletec.plugins.jacms.aps.system.services.content.event.PublicContentChangedEvent;
import com.agiletec.plugins.jacms.aps.system.services.content.event.PublicContentChangedObserver;
import com.agiletec.plugins.jacms.aps.system.services.content.model.Content;
import com.agiletec.plugins.jacms.aps.system.services.searchengine.ICmsSearchEngineManager;
import com.agiletec.plugins.jacms.aps.system.services.searchengine.LastReloadInfo;
/**
* Servizio detentore delle operazioni di indicizzazione
* di oggetti ricercabili tramite motore di ricerca.
* @author W.Ambu - M.Diana - E.Santoboni
*/
public class CmsSearchEngineManager extends AbstractService
implements ICmsSearchEngineManager, PublicContentChangedObserver, EntityTypesChangingObserver {
private static final Logger _logger = LoggerFactory.getLogger(CmsSearchEngineManager.class);
@Override
public void init() throws Exception {
_logger.debug("{} ready", this.getClass().getName());
}
@Override
public void refresh() throws Throwable {
this.release();
this._lastReloadInfo = null;
this.init();
}
@Override
public void updateFromPublicContentChanged(PublicContentChangedEvent event) {
if (this.getStatus() == STATUS_RELOADING_INDEXES_IN_PROGRESS) {
this._publicContentChangedEventQueue.add(0, event);
} else {
this.manageEvent(event);
}
}
private void manageEvent(PublicContentChangedEvent event) {
Content content = event.getContent();
try {
switch (event.getOperationCode()) {
case PublicContentChangedEvent.INSERT_OPERATION_CODE:
this.addEntityToIndex(content);
break;
case PublicContentChangedEvent.REMOVE_OPERATION_CODE:
this.deleteIndexedEntity(content.getId());
break;
case PublicContentChangedEvent.UPDATE_OPERATION_CODE:
this.updateIndexedEntity(content);
break;
}
} catch (Throwable t) {
_logger.error("Error notifing event", t);
}
}
protected void sellOfQueueEvents() {
int size = this._publicContentChangedEventQueue.size();
if (size>0) {
for (int i=0; i<size; i++) {
PublicContentChangedEvent event = (PublicContentChangedEvent) this._publicContentChangedEventQueue.get(0);
this.manageEvent(event);
this._publicContentChangedEventQueue.remove(0);
}
}
}
@Override
public Thread startReloadContentsReferences(String subDirectory) throws ApsSystemException {
return this.startReloadContentsReferences();
}
@Override
public Thread startReloadContentsReferences() throws ApsSystemException {
IndexLoaderThread loaderThread = null;
if (this.getStatus() == STATUS_READY || this.getStatus() == STATUS_NEED_TO_RELOAD_INDEXES) {
try {
this.setStatus(STATUS_RELOADING_INDEXES_IN_PROGRESS);
Searcher searcher = new Searcher(this.getSolrPath());
List<String> contentsId = searcher.extractAllContentsId();
Indexer indexer = new Indexer(this.getSolrPath(), this.getLangManager());
loaderThread = new IndexLoaderThread(this, this.getContentManager(), indexer, contentsId);
String threadName = RELOAD_THREAD_NAME_PREFIX + DateConverter.getFormattedDate(new Date(), "yyyyMMddHHmmss");
loaderThread.setName(threadName);
loaderThread.start();
_logger.debug("Index reloader started");
} catch (Throwable t) {
throw new ApsSystemException("Error reloading indexes", t);
}
} else {
_logger.debug("Index reloader stopped : status {}", this.getStatus());
}
return loaderThread;
}
@Override
public void updateFromEntityTypesChanging(EntityTypesChangingEvent event) {
if (((IManager) this.getContentManager()).getName().equals(event.getEntityManagerName())) {
if (this.getStatus() == STATUS_NEED_TO_RELOAD_INDEXES) {
return;
}
boolean needToReload = false;
if (event.getOperationCode() == EntityTypesChangingEvent.INSERT_OPERATION_CODE) {
return;
} else if (event.getOperationCode() == EntityTypesChangingEvent.REMOVE_OPERATION_CODE) {
needToReload = true;
} else if (event.getOperationCode() == EntityTypesChangingEvent.UPDATE_OPERATION_CODE) {
needToReload = this.verifyReloadingNeeded(event.getOldEntityType(), event.getNewEntityType());
}
if (needToReload == true) {
this.setStatus(STATUS_NEED_TO_RELOAD_INDEXES);
}
}
}
protected boolean verifyReloadingNeeded(IApsEntity oldEntityType, IApsEntity newEntityType) {
List<AttributeInterface> attributes = oldEntityType.getAttributeList();
for (int i = 0; i < attributes.size(); i++) {
AttributeInterface oldAttribute = attributes.get(i);
AttributeInterface newAttribute = (AttributeInterface) newEntityType.getAttribute(oldAttribute.getName());
boolean isOldAttributeIndexeable = (oldAttribute.getIndexingType() != null && oldAttribute.getIndexingType().equalsIgnoreCase(IndexableAttributeInterface.INDEXING_TYPE_TEXT));
boolean isNewAttributeIndexeable = (newAttribute != null && newAttribute.getIndexingType() != null && newAttribute.getIndexingType().equalsIgnoreCase(IndexableAttributeInterface.INDEXING_TYPE_TEXT));
if ((isOldAttributeIndexeable && !isNewAttributeIndexeable) || (!isOldAttributeIndexeable && isNewAttributeIndexeable)) {
return true;
}
}
return false;
}
@Override
public void addEntityToIndex(IApsEntity entity) throws ApsSystemException {
try {
Indexer indexer = new Indexer(this.getSolrPath(), this.getLangManager());
indexer.addContent(entity);
} catch (Throwable t) {
_logger.error("Error adding new content", t);
throw new ApsSystemException("Error adding new content", t);
}
}
@Override
public void deleteIndexedEntity(String entityId) throws ApsSystemException {
try {
Indexer indexer = new Indexer(this.getSolrPath(), this.getLangManager());
indexer.delete(entityId);
} catch (Throwable t) {
_logger.error("error deleting content {} from index", entityId, t);
throw new ApsSystemException("Error deleting new content", t);
}
}
protected void notifyEndingIndexLoading(LastReloadInfo info) {
try {
if (info.getResult() == LastReloadInfo.ID_SUCCESS_RESULT) {
this._lastReloadInfo = info;
}
} catch (Throwable t) {
_logger.error("Error creating LastReloadInfo", t);
} finally {
if (this.getStatus() != STATUS_NEED_TO_RELOAD_INDEXES) {
this.setStatus(STATUS_READY);
}
}
}
@Override
public LastReloadInfo getLastReloadInfo() {
return this._lastReloadInfo;
}
@Override
public List<String> searchId(String sectionCode, String langCode, String word, Collection<String> allowedGroups) throws ApsSystemException {
return this.searchEntityId(langCode, word, allowedGroups);
}
@Override
public List<String> searchEntityId(String langCode, String word,
Collection<String> allowedGroups) throws ApsSystemException {
List<String> contentsId = new ArrayList<String>();
try {
Searcher searcher = new Searcher(this.getSolrPath());
contentsId = searcher.searchContentsId(langCode, word, allowedGroups, this.getMaxResultSize());
} catch (ApsSystemException e) {
_logger.error("Error searching contents id", e);
throw e;
}
return contentsId;
}
@Override
public int getStatus() {
return this._status;
}
protected void setStatus(int status) {
this._status = status;
}
@Override
public void updateIndexedEntity(IApsEntity entity) throws ApsSystemException {
try {
this.deleteIndexedEntity(entity.getId());
this.addEntityToIndex(entity);
} catch (ApsSystemException e) {
_logger.error("Error updating content indexes", e);
throw e;
}
}
public void writeConfiguration(SolrConfig solrConfig) throws ApsSystemException {
try {
String marshalInfo = SolrConfigDOM.marshalConfig(solrConfig);
this.getConfigManager().updateConfigItem(SolrConnectorSystemConstants.JPSOLRCLIENT_SYSTEM_CONFIG_NAME, marshalInfo);
} catch (ApsSystemException e) {
_logger.error("Error updating configuration", e);
throw e;
}
}
public SolrConfig readConfiguration() throws ApsSystemException {
SolrConfig solrConfig = new SolrConfig();
try {
String configItem = this.getConfigManager().getConfigItem(SolrConnectorSystemConstants.JPSOLRCLIENT_SYSTEM_CONFIG_NAME);
if(StringUtils.isNotEmpty(configItem)) {
solrConfig = SolrConfigDOM.unmarshalConfig(configItem);
}
} catch (ApsSystemException e) {
_logger.error("Error in readConfiguration", e);
throw e;
}
return solrConfig;
}
protected String getSolrPath() throws ApsSystemException {
SolrConfig solrConfiguration = this.readConfiguration();
String serverUrl = "";
if(null != solrConfiguration){
serverUrl = solrConfiguration.getProviderUrl();
}
if (StringUtils.isBlank(serverUrl)) {
serverUrl = SolrConnectorSystemConstants.SERVER_URL_DEFAULT_VALUE;
}
return serverUrl;
}
protected int getMaxResultSize() throws ApsSystemException {
SolrConfig solrConfiguration = this.readConfiguration();
String maxResultSizeString = "";
if(null != solrConfiguration){
maxResultSizeString = solrConfiguration.getMaxResultSize();
}
int maxResultSize = -1;
try {
maxResultSize = Integer.parseInt(maxResultSizeString);
} catch (Exception e) {}
if (maxResultSize < 1) {
maxResultSize = SolrConnectorSystemConstants.MAX_RESULT_SIZE_DEFAULT_VALUE;
}
return maxResultSize;
}
protected ILangManager getLangManager() {
return _langManager;
}
public void setLangManager(ILangManager langManager) {
this._langManager = langManager;
}
protected IContentManager getContentManager() {
return _contentManager;
}
public void setContentManager(IContentManager contentManager) {
this._contentManager = contentManager;
}
protected ConfigInterface getConfigManager() {
return _configManager;
}
public void setConfigManager(ConfigInterface configManager) {
this._configManager = configManager;
}
private int _status;
private LastReloadInfo _lastReloadInfo;
private List<ApsEvent> _publicContentChangedEventQueue = new ArrayList<ApsEvent>();
private ILangManager _langManager;
private IContentManager _contentManager;
private ConfigInterface _configManager;
public static final String RELOAD_THREAD_NAME_PREFIX = "RELOAD_INDEX_";
public static final String CONTENT_ID_FIELD_NAME = "id";
public static final String CONTENT_GROUP_FIELD_NAME = "group";
}