/**
* Copyright 2011 Molindo GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package at.molindo.elastic.compass;
import java.util.Arrays;
import java.util.List;
import org.compass.core.Property;
import org.compass.core.Resource;
import org.compass.core.config.RuntimeCompassSettings;
import org.compass.core.engine.SearchEngine;
import org.compass.core.engine.SearchEngineAnalyzerHelper;
import org.compass.core.engine.SearchEngineException;
import org.compass.core.engine.SearchEngineHits;
import org.compass.core.engine.SearchEngineInternalSearch;
import org.compass.core.engine.SearchEngineQuery;
import org.compass.core.engine.SearchEngineQueryBuilder;
import org.compass.core.mapping.ResourceAnalyzerController;
import org.compass.core.mapping.ResourceMapping;
import org.compass.core.spi.InternalResource;
import org.compass.core.spi.MultiResource;
import org.compass.core.spi.ResourceKey;
import org.compass.core.util.StringUtils;
import org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse.AnalyzeToken;
import at.molindo.utils.collections.ArrayUtils;
public class ElasticSearchEngine implements SearchEngine {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory
.getLogger(ElasticSearchEngine.class);
// private final RuntimeCompassSettings _runtimeSettings;
private final ElasticSearchEngineFactory _searchEngineFactory;
private boolean _readOnly;
private ElasticClient _client;
public ElasticSearchEngine(RuntimeCompassSettings runtimeSettings, ElasticSearchEngineFactory searchEngineFactory) {
// _runtimeSettings = runtimeSettings;
_searchEngineFactory = searchEngineFactory;
_client = searchEngineFactory.openElasticClient();
}
@Override
public ElasticSearchEngineFactory getSearchEngineFactory() {
return _searchEngineFactory;
}
public Resource get(Resource idResource) throws SearchEngineException {
ResourceKey resourceKey = ((InternalResource) idResource).getResourceKey();
if (resourceKey.getIds().length == 0) {
throw new SearchEngineException("Cannot load a resource with no ids and alias ["
+ resourceKey.getAlias() + "]");
}
Resource[] result = doGet(resourceKey);
if (result.length == 0) {
// none directly, try and load polymorphic ones
String[] extendingAliases = resourceKey.getResourceMapping().getExtendingAliases();
for (String extendingAlias : extendingAliases) {
ResourceMapping extendingMapping = getSearchEngineFactory().getMapping()
.getMappingByAlias(extendingAlias);
ResourceKey key = new ResourceKey(extendingMapping, resourceKey.getIds());
result = doGet(key);
if (result.length > 0) {
return result[result.length - 1];
}
}
} else if (result.length > 1) {
log.warn("Found several matches in get/load operation for resource alias ["
+ resourceKey.getAlias() + "] and ids ["
+ StringUtils.arrayToCommaDelimitedString(resourceKey.getIds()) + "]");
return result[result.length - 1];
}
// did not find in the extending aliases as well
return result[0];
}
public Resource load(Resource idResource) throws SearchEngineException {
Resource resource = get(idResource);
if (resource == null) {
throw new SearchEngineException("Failed to find resource with alias [" + idResource.getAlias() + "] and ids ["
+ StringUtils.arrayToCommaDelimitedString(idResource.getIds()) + "]");
}
return resource;
}
public void create(Resource resource) throws SearchEngineException {
createOrUpdate(resource, false);
}
public void save(Resource resource) throws SearchEngineException {
createOrUpdate(resource, true);
}
private void createOrUpdate(final Resource resource, boolean update) throws SearchEngineException {
verifyNotReadOnly();
String alias = resource.getAlias();
ResourceMapping resourceMapping = getSearchEngineFactory().getMapping()
.getRootMappingByAlias(alias);
if (resourceMapping == null) {
throw new SearchEngineException("Failed to find mapping for alias [" + alias + "]");
}
if (resource instanceof MultiResource) {
MultiResource multiResource = (MultiResource) resource;
for (int i = 0; i < multiResource.size(); i++) {
InternalResource resource1 = (InternalResource) multiResource.resource(i);
validate(resourceMapping, resource1);
if (update) {
doUpdate(resource1);
if (log.isTraceEnabled()) {
log.trace("RESOURCE SAVE " + resource1);
}
} else {
doCreate(resource1);
if (log.isTraceEnabled()) {
log.trace("RESOURCE CREATE " + resource1);
}
}
}
} else {
InternalResource resource1 = (InternalResource) resource;
validate(resourceMapping, resource1);
if (update) {
doUpdate(resource1);
if (log.isTraceEnabled()) {
log.trace("RESOURCE SAVE " + resource1);
}
} else {
doCreate(resource1);
if (log.isTraceEnabled()) {
log.trace("RESOURCE CREATE " + resource1);
}
}
}
}
private void validate(ResourceMapping resourceMapping, InternalResource resource) {
ResourceAnalyzerController rac = resourceMapping.getAnalyzerController();
if (rac != null) {
Property prop = resource.getProperty(rac.getPath().getPath());
if (prop == null) {
throw new SearchEngineException("analyzer property not set");
}
String value = prop.getStringValue();
if (at.molindo.utils.data.StringUtils.empty(value)) {
throw new SearchEngineException("analyzer property value empty");
}
// TODO check if name is valid
}
}
public void delete(Resource resource) throws SearchEngineException {
verifyNotReadOnly();
if (resource instanceof MultiResource) {
MultiResource multiResource = (MultiResource) resource;
for (int i = 0; i < multiResource.size(); i++) {
delete(((InternalResource) multiResource.resource(i)).getResourceKey());
}
} else {
delete(((InternalResource) resource).getResourceKey());
}
}
private void delete(ResourceKey resourceKey) throws SearchEngineException {
if (resourceKey.getIds().length == 0) {
throw new SearchEngineException("Cannot delete a resource with no ids and alias [" + resourceKey.getAlias() + "]");
}
doDelete(resourceKey);
String[] extendingAliases = resourceKey.getResourceMapping().getExtendingAliases();
for (String extendingAlias : extendingAliases) {
ResourceMapping extendingMapping = getSearchEngineFactory().getMapping().getMappingByAlias(extendingAlias);
ResourceKey key = new ResourceKey(extendingMapping, resourceKey.getIds());
doDelete(key);
}
if (log.isTraceEnabled()) {
log.trace("RESOURCE DELETE {" + resourceKey.getAlias() + "} " + StringUtils.arrayToCommaDelimitedString(resourceKey.getIds()));
}
}
@Override
public SearchEngineQueryBuilder queryBuilder() {
return _searchEngineFactory.queryBuilder();
}
public SearchEngineHits find(SearchEngineQuery searchEngineQuery) {
return doFind(searchEngineQuery);
}
public long count(ElasticSearchEngineQuery searchEngineQuery) {
return doCount(searchEngineQuery);
}
@Override
public void delete(SearchEngineQuery searchEngineQuery) {
doDelete(searchEngineQuery);
}
@Override
public void flush() {
}
public void refresh() {
_client.refresh();
}
public List<AnalyzeToken> analyze(String analyzer, String text) {
return _client.analyze(analyzer, text);
}
protected Resource[] doGet(ResourceKey key) {
return _client.get(key);
}
protected void doCreate(InternalResource resource) {
_client.create((ElasticResource) resource);
}
protected void doUpdate(InternalResource resource) {
_client.update((ElasticResource) resource);
}
protected void doDelete(ResourceKey key) {
_client.delete(key);
}
protected SearchEngineHits doFind(SearchEngineQuery searchEngineQuery) {
return _client.find((ElasticSearchEngineQuery) searchEngineQuery);
}
protected long doCount(SearchEngineQuery searchEngineQuery) {
return _client.count((ElasticSearchEngineQuery) searchEngineQuery);
}
protected void doDelete(SearchEngineQuery searchEngineQuery) {
_client.delete((ElasticSearchEngineQuery) searchEngineQuery);
}
@Override
public void setReadOnly() {
_readOnly = true;
}
@Override
public boolean isReadOnly() {
return _readOnly;
}
public void verifyNotReadOnly() throws SearchEngineException {
if (_readOnly) {
throw new SearchEngineException("Transaction is set as read only");
}
}
@Override
public SearchEngineInternalSearch internalSearch(String[] subIndexes, String[] aliases) throws SearchEngineException {
if (!ArrayUtils.empty(subIndexes)) {
log.warn("sub indexes not supported, ignoring " + Arrays.toString(aliases));
}
return new ElasticSearchEngineInternalSearch(_client, aliases);
}
public SearchEngineAnalyzerHelper analyzerHelper() {
return new ElasticSearchEngineAnalyzerHelper(this);
}
}