Skip to content
Snippets Groups Projects
Commit edf2fb07 authored by Martin Lowe's avatar Martin Lowe :flag_ca:
Browse files

Remove unused search module from commons library

Originally created for the marketplace API, that API was scrapped and
this package was never used again. As it's been ~3y, I think we can
safely remove this code and if needed look it up in history.
parent 3c12a34e
No related branches found
No related tags found
1 merge request!112Remove unused search module from commons library
Pipeline #18753 passed
Showing
with 2 additions and 1067 deletions
......@@ -52,9 +52,8 @@
<modules>
<module>core</module>
<module>testing</module>
<module>persistence</module>
<module>search</module>
<module>efservices</module>
<module>persistence</module>
<module>efservices</module>
</modules>
<build>
<pluginManagement>
......
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<name>Search - Runtime</name>
<artifactId>quarkus-search</artifactId>
<parent>
<groupId>org.eclipsefoundation</groupId>
<artifactId>quarkus-commons</artifactId>
<version>0.7.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties></properties>
<dependencies>
<dependency>
<groupId>org.eclipsefoundation</groupId>
<artifactId>quarkus-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipsefoundation</groupId>
<artifactId>quarkus-persistence</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>8.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>8.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queries</artifactId>
<version>8.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.quarkus/quarkus-devservices-h2 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-h2</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.quarkus/quarkus-jdbc-h2 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.search.dao;
import java.io.Closeable;
import java.util.List;
import org.apache.solr.common.SolrDocument;
import org.eclipsefoundation.persistence.dto.BareNode;
import org.eclipsefoundation.search.model.IndexerResponse;
/**
* Interface for interacting with a search indexing engine. This DAO should be
* able to write new entries, as well as retrieve, update or delete existing
* entries.
*
* @author Martin Lowe
*
*/
public interface SearchIndexDao extends Closeable {
/**
* Retrieves indexed and ranked information for the given query. This
* information
*
* @param <T> the type of entity to be searched
* @param q the current RDBMS query to get ranked indexed documents for
* @return an ordered list of bare documents
*/
<T extends BareNode> List<SolrDocument> get(String searchTerm, Class<T> docType);
/**
* Update or create entries in the search indexer for the given entities.
*
* @param <T> the type of entities that will be persisted to the service.
* @param entities the list of new or updated database entities to be indexed.
* @return
*/
<T extends BareNode> IndexerResponse createOrUpdate(List<T> entities, Class<T> docType);
/**
* Remove the given list of entities from the search index.
*
* @param <T> the type of entities that will be removed from the index.
* @param entities the list of (potentially) existing indexed entities to remove
* from the index.
*/
<T extends BareNode> IndexerResponse remove(List<T> entities);
}
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.search.dao.impl;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrClient;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.SolrParams;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipsefoundation.persistence.dto.BareNode;
import org.eclipsefoundation.search.dao.SearchIndexDao;
import org.eclipsefoundation.search.model.IndexerResponse;
import org.eclipsefoundation.search.model.SolrDocumentConverter;
import org.eclipsefoundation.search.namespace.IndexerResponseStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ApplicationScoped
public class SolrIndexDAO implements SearchIndexDao {
private static final Logger LOGGER = LoggerFactory.getLogger(SolrIndexDAO.class);
// DAO settings
@ConfigProperty(name = "eclipse.solr.enabled", defaultValue = "false")
boolean solrEnabled;
@ConfigProperty(name = "eclipse.solr.maintenance", defaultValue = "false")
boolean solrMaintenance;
// Solr settings
@ConfigProperty(name = "eclipse.solr.host", defaultValue = "")
String solrURL;
@ConfigProperty(name = "eclipse.solr.core", defaultValue = "")
String core;
@ConfigProperty(name = "eclipse.solr.timeout", defaultValue = "10000")
int solrTimeout;
@ConfigProperty(name = "eclipse.solr.queue", defaultValue = "100")
int queueSize;
@ConfigProperty(name = "eclipse.solr.threads", defaultValue = "25")
int threadCount;
// internal state members
private ConcurrentUpdateSolrClient solrServer;
private Map<Class, SolrDocumentConverter> converters;
@PostConstruct
public void init() {
if (!solrEnabled) {
LOGGER.info("Eclipse Solr server not started, it is not currently enabled");
} else {
// create solr server
ConcurrentUpdateSolrClient.Builder b = new ConcurrentUpdateSolrClient.Builder(solrURL + '/' + core)
.withConnectionTimeout(solrTimeout).withQueueSize(queueSize).withThreadCount(threadCount);
this.solrServer = b.build();
this.converters = new HashMap<>();
LOGGER.debug("Started Solr server for index processing");
}
}
@Override
public void close() {
// shut down threads to solr server
this.solrServer.close();
LOGGER.error("Closed Solr server for index processing");
}
@Override
public <T extends BareNode> List<SolrDocument> get(String searchTerm, Class<T> docType) {
// check whether call should proceed
if (!stateCheck() || StringUtils.isBlank(searchTerm)) {
return Collections.emptyList();
}
// get the current doctype converter
SolrDocumentConverter<T> converter = getConverter(docType);
SolrParams query = converter.getBaseQuery(searchTerm);
try {
QueryResponse response = solrServer.query(query);
return response.getResults();
} catch (SolrServerException | IOException e) {
LOGGER.error("Error while retrieving search results",e);
return null;
}
}
@Override
public <T extends BareNode> IndexerResponse createOrUpdate(List<T> entities, Class<T> docType) {
// check whether call should proceed
if (!stateCheck()) {
return IndexerResponse.getMaintenanceResponse();
}
// get the current doctype converter
SolrDocumentConverter<T> converter = getConverter(docType);
// convert the documents
List<SolrInputDocument> docs = entities.stream().map(converter::convert).filter(Objects::nonNull)
.collect(Collectors.toList());
// attempt to update + commit the changes
long now = System.currentTimeMillis();
if (docs.isEmpty()) {
LOGGER.debug("No documents to be indexed for current call (recieved {} entities)", docs.size());
// TODO: should return some empty response base line rather than maint
return IndexerResponse.getMaintenanceResponse();
}
try {
// attempting to add documents to solr core
solrServer.add(docs);
return generateResponse(solrServer.commit(false, false, true),
"Success! Indexed " + entities.size() + " documents",
"Non-fatal error encountered while indexing documents");
} catch (SolrServerException | IOException e) {
LOGGER.error("Error while adding indexed documents to Solr server", e);
return new IndexerResponse("Error while creating/updating index documents", IndexerResponseStatus.FAILED,
System.currentTimeMillis() - now, e);
}
}
@Override
public <T extends BareNode> IndexerResponse remove(List<T> entities) {
// check whether call should proceed
if (!stateCheck()) {
return IndexerResponse.getMaintenanceResponse();
}
if (entities == null || entities.isEmpty()) {
return new IndexerResponse();
}
// retrieve the list of IDs of indexed documents to delete
List<String> ids = entities.stream().map(BareNode::getId).map(Object::toString).collect(Collectors.toList());
// attempt to update + commit the changes
long now = System.currentTimeMillis();
try {
// run the update to remove responses
solrServer.deleteById(ids);
return generateResponse(solrServer.commit(false, false, true),
"Success! Removed " + entities.size() + " documents",
"Non-fatal error encountered while removing documents");
} catch (SolrServerException | IOException e) {
LOGGER.error("Error while removing indexed documents from Solr server", e);
return new IndexerResponse("Error while removing indexed documents from Solr server",
IndexerResponseStatus.FAILED, System.currentTimeMillis() - now, e);
}
}
@SuppressWarnings("unchecked")
private <T extends BareNode> SolrDocumentConverter<T> getConverter(Class<T> docType) {
return converters.computeIfAbsent(docType, SolrDocumentConverter::new);
}
/**
* Generate a generic indexer response object based on the Solr update response
* document and a set of messages that will be set into the response depending
* on operation success or failure..
*
* @param solrResponse the response object from Solr on commit
* @param successMessage the message to include for successful calls
* @param failureMessage the message to include for failed calls
* @return a populated generic indexer response object
*/
private IndexerResponse generateResponse(UpdateResponse solrResponse, String successMessage,
String failureMessage) {
// create output response
IndexerResponse r = new IndexerResponse();
r.setElapsedTimeMS(solrResponse.getElapsedTime());
// check for an error, and update the response
if (solrResponse.getException() != null) {
r.setException(solrResponse.getException());
r.setStatus(IndexerResponseStatus.FAILED);
r.setMessage(failureMessage);
} else {
r.setStatus(IndexerResponseStatus.SUCCESSFUL);
r.setMessage(successMessage);
}
return r;
}
private boolean stateCheck() {
// check state to ensure call should run
if (!solrEnabled) {
return false;
} else if (solrMaintenance) {
LOGGER.warn("Solr DAO set to maintenance, not indexing content");
return false;
}
return true;
}
}
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.search.helper;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.eclipsefoundation.search.model.IndexedClassDescriptor.IndexedDescriptor;
import org.eclipsefoundation.search.namespace.IndexerTextProcessingType;
public class FieldHelper {
public static String convertNameToField(IndexedDescriptor c) {
return convertNameToField(c, false);
}
public static String convertNameToField(IndexedDescriptor c, boolean nested) {
String name = c.getName();
if (c.getType().equals(List.class) && !nested) {
// not supported operation
if (!c.getTextProcessing().equals(IndexerTextProcessingType.NONE)) {
return "ignored_" + name;
}
return convertNameToField(c, true) + "s";
} else if (c.getType().equals(String.class)) {
return name + getTextProcessingSuffix(c);
} else if (c.getType().equals(UUID.class)) {
return name + "_s";
} else if (c.getType().equals(Integer.class)) {
return name + "_i";
} else if (c.getType().equals(Long.class)) {
return name + "_l";
} else if (c.getType().equals(Float.class)) {
return name + "_f";
} else if (c.getType().equals(Boolean.class)) {
return name + "_b";
} else if (c.getType().equals(Double.class)) {
return name + "_d";
} else if (c.getType().equals(Date.class)) {
return name + "_dt";
} else {
// fallback for unknown types getting indexed
return "ignored_" + name;
}
}
public static String getTextProcessingSuffix(IndexedDescriptor c) {
switch (c.getTextProcessing()) {
case GENERAL:
return "_txt_gen";
case STANDARD:
return "_txt_en";
case AGGRESSIVE:
return "_txt_en_split";
default:
return "_s";
}
}
private FieldHelper() {
}
}
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.search.model;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.eclipsefoundation.search.namespace.IndexerTextProcessingType;
/**
* Annotation controlling how entities are indexed via the search DAO.
*
* @author Martin Lowe
*
*/
@Retention(RUNTIME)
@Target({ TYPE, FIELD })
public @interface Indexed {
String fieldName() default "";
/**
* Boost value for the given field.
*
* @return the boost value for the field, or the default value of 1
*/
float boost() default 1f;
/**
* Whether the value should be stored as is and returned.
*
* @return true if the value should be stored on index, false otherwise.
*/
boolean stored() default false;
IndexerTextProcessingType textProcessing() default IndexerTextProcessingType.NONE;
}
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.search.model;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.eclipsefoundation.persistence.dto.BareNode;
import org.eclipsefoundation.search.namespace.IndexerTextProcessingType;
/**
* Describes an entity type that should be indexed. This class works on
* reflection of the Runtime class to retrieve internal fields that have been
* annotated with the Indexed field
*
* @author Martin Lowe
*
* @param <T> the entity type this descriptor describes.
*/
public class IndexedClassDescriptor<T extends BareNode> {
private List<IndexedDescriptor> internal;
public IndexedClassDescriptor(Class<T> clazz) {
this.internal = new ArrayList<>();
try {
for (Field f : clazz.getDeclaredFields()) {
Indexed annotation = f.getAnnotation(Indexed.class);
if (annotation != null) {
String name = f.getName();
Optional<PropertyDescriptor> propertyOpt = Arrays
.asList(Introspector.getBeanInfo(clazz).getPropertyDescriptors()).stream()
.filter(pd -> name.equals(pd.getName())).findFirst();
if (!propertyOpt.isPresent()) {
throw new RuntimeException("Could not generate SolrDocumentConverter for " + clazz.getName());
}
PropertyDescriptor property = propertyOpt.get();
internal.add(new IndexedDescriptor(f, property.getReadMethod(), annotation));
}
}
} catch (IntrospectionException e) {
throw new RuntimeException("Could not generate SolrDocumentConverter for " + clazz.getName(), e);
}
}
public List<IndexedDescriptor> getDescriptors() {
return new ArrayList<>(internal);
}
public static class IndexedDescriptor {
final String name;
final Method getter;
final float boost;
final boolean stored;
final IndexerTextProcessingType textProcessing;
final Class<?> type;
final Class<?> subtype;
private IndexedDescriptor(Field field, Method getter, Indexed annotation) {
this.name = field.getName();
this.getter = getter;
this.boost = annotation.boost();
this.stored = annotation.stored();
this.textProcessing = annotation.textProcessing();
this.type = field.getType();
// if the generic type is different from the base, indicates a generic
// this will not work for entity types nested within themselves(List in List, Map in Map etc.)
if (field.getGenericType() instanceof ParameterizedType) {
// using reflection, gets declared type from the raw source
this.subtype = (Class<?>)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0];
} else {
this.subtype = null;
}
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @return the getter
*/
public Method getGetter() {
return getter;
}
/**
* @return the boost
*/
public float getBoost() {
return boost;
}
/**
* @return the stored
*/
public boolean isStored() {
return stored;
}
/**
* @return the textProcessing
*/
public IndexerTextProcessingType getTextProcessing() {
return textProcessing;
}
public Class<?> getType() {
return this.type;
}
public Class<?> getSubtype() {
return this.subtype;
}
}
}
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.search.model;
import org.eclipsefoundation.search.namespace.IndexerResponseStatus;
public class IndexerResponse {
private String message;
private IndexerResponseStatus status;
private Exception exception;
private long elapsedTimeMS;
public IndexerResponse() {
this("", null, 0);
}
public IndexerResponse(String message, IndexerResponseStatus status, long elapsedTimeMS) {
this(message, status, elapsedTimeMS, null);
}
public IndexerResponse(String message, IndexerResponseStatus status, long elapsedTimeMS, Exception exception) {
this.message = message;
this.status = status;
this.elapsedTimeMS = elapsedTimeMS;
this.setException(exception);
}
/**
* @return the message
*/
public String getMessage() {
return message;
}
/**
* @param message the message to set
*/
public void setMessage(String message) {
this.message = message;
}
/**
* @return the status
*/
public IndexerResponseStatus getStatus() {
return status;
}
/**
* @param status the status to set
*/
public void setStatus(IndexerResponseStatus status) {
this.status = status;
}
/**
* @return the exception
*/
public Exception getException() {
return exception;
}
/**
* @param exception the exception to set
*/
public void setException(Exception exception) {
this.exception = exception;
}
/**
* @return the elapsedTimeMS
*/
public long getElapsedTimeMS() {
return elapsedTimeMS;
}
/**
* @param elapsedTimeMS the elapsedTimeMS to set
*/
public void setElapsedTimeMS(long elapsedTimeMS) {
this.elapsedTimeMS = elapsedTimeMS;
}
public static IndexerResponse getMaintenanceResponse() {
IndexerResponse out = new IndexerResponse();
out.message = "";
out.elapsedTimeMS = 0;
out.setStatus(IndexerResponseStatus.MAINTENANCE);
return out;
}
}
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.search.model;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import org.apache.lucene.index.Term;
import org.apache.lucene.queries.function.FunctionScoreQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery.Builder;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.TermQuery;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.eclipsefoundation.persistence.dto.BareNode;
import org.eclipsefoundation.search.helper.FieldHelper;
import org.eclipsefoundation.search.model.IndexedClassDescriptor.IndexedDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SolrDocumentConverter<T extends BareNode> {
private static final Logger LOGGER = LoggerFactory.getLogger(SolrDocumentConverter.class);
private Class<T> clazz;
private IndexedClassDescriptor<T> internal;
public SolrDocumentConverter(Class<T> clazz) {
this.clazz = clazz;
this.internal = new IndexedClassDescriptor<>(clazz);
}
public SolrInputDocument convert(T entity) {
// don't index documents with no fields to index
if (internal.getDescriptors().isEmpty()) {
return null;
}
SolrInputDocument in = new SolrInputDocument();
try {
for (IndexedDescriptor c : internal.getDescriptors()) {
Object data = c.getter.invoke(entity);
in.addField(FieldHelper.convertNameToField(c), data);
}
// get the standard fields
in.addField("id", entity.getId().toString());
in.addField("type_s", clazz.getName());
} catch (IllegalAccessException e) {
LOGGER.error("Could not invoke getter while converting entity of type {}", clazz.getName(), e);
} catch (IllegalArgumentException e) {
LOGGER.error("Unexpected argument encountered in getter while converting entity of type {}",
clazz.getName(), e);
} catch (InvocationTargetException e) {
LOGGER.error("Unknown exception while converting entity of type {}", clazz.getName(), e);
}
return in;
}
public SolrParams getBaseQuery(String searchTerm) {
// build text match query, where at least 1 values needs to match
Builder textMatches = new Builder();
// add title manually
textMatches.add(new TermQuery(new Term("title_s", searchTerm)), Occur.SHOULD);
for (IndexedDescriptor c : internal.getDescriptors()) {
TermQuery base = new TermQuery(new Term(FieldHelper.convertNameToField(c), searchTerm));
if (c.getBoost() != 1.0f) {
textMatches.add(
new BooleanClause(new FunctionScoreQuery(base, DoubleValuesSource.constant(c.getBoost())), Occur.SHOULD));
} else {
textMatches.add(base, Occur.SHOULD);
}
}
// build document type + text match boolean query
Builder textAndTypeBuilder = new Builder();
textAndTypeBuilder.add(textMatches.build(), Occur.MUST);
textAndTypeBuilder.add(new TermQuery(new Term("type_s", clazz.getName())), Occur.MUST);
// set up base query from the required values
Map<String, String> queryParamMap = new HashMap<>();
queryParamMap.put("q", textAndTypeBuilder.build().toString());
queryParamMap.put("fl", "id");
queryParamMap.put("df", "*");
return new MapSolrParams(queryParamMap);
}
}
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.search.namespace;
public enum IndexerResponseStatus {
SUCCESSFUL, FAILED, MAINTENANCE;
}
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.search.namespace;
/**
* Allows for the support of multiple different types of text post-processing
* based on the types of requirements on the data. Generally the more complex
* the sort the more expensive it is computationally both at query and index
* time.
*
* <ul>
* <li>
* <p>
* <strong>GENERAL:</strong>
* </p>
* <p>
* Uses generic cross-language processing when available. This is appropriate
* for fields/searches where English may not be the primary language but complex
* indexing and searching is desired.
* </p>
* </li>
* <li>
* <p>
* <strong>STANDARD:</strong>
* </p>
* <p>
* Uses standard processing given English language text stopwords and protected
* words. This field should be used when the language of the data entered is
* known to be English as it provides more accurate results and allows for qtime
* analysis for synonyms.
* </p>
* </li>
* <li>
* <p>
* <strong>AGGRESSIVE:</strong>
* </p>
* <p>
* Uses aggressive processing given English language text stopwords and
* protected words. This type of processing will also process individual words
* to allow matching on potentially split words using indicators of word
* boundaries like changed casing, numbers, and non alpha-numeric characters.
* This will allow for matches on things like brand names like ASCIIDoc on match
* for searches like "ascii doc".
* </p>
* </li>
* <li>
* <p>
* <strong>NONE:</strong>
* </p>
* <p>
* No text processing will be done on this text and it will be posted and processed as is.
* </p>
* </li>
* </ul>
*
* @author Martin Lowe
*
*/
public enum IndexerTextProcessingType {
GENERAL, STANDARD, AGGRESSIVE, NONE;
}
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.search.service;
import java.util.List;
import org.eclipsefoundation.core.model.RequestWrapper;
import org.eclipsefoundation.persistence.dto.BareNode;
import org.eclipsefoundation.persistence.dto.filter.DtoFilter;
public interface PersistenceBackedSearchService {
<T extends BareNode> List<T> find(RequestWrapper wrap, DtoFilter<T> filter);
}
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.search.service.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.apache.solr.common.SolrDocument;
import org.eclipsefoundation.core.model.RequestWrapper;
import org.eclipsefoundation.core.namespace.DefaultUrlParameterNames;
import org.eclipsefoundation.persistence.dao.PersistenceDao;
import org.eclipsefoundation.persistence.dto.BareNode;
import org.eclipsefoundation.persistence.dto.filter.DtoFilter;
import org.eclipsefoundation.persistence.model.RDBMSQuery;
import org.eclipsefoundation.search.dao.SearchIndexDao;
import org.eclipsefoundation.search.service.PersistenceBackedSearchService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Martin Lowe
*
*/
@ApplicationScoped
public class DefaultPersistenceBackedSearchService implements PersistenceBackedSearchService {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPersistenceBackedSearchService.class);
@Inject
SearchIndexDao searchDAO;
@Inject
PersistenceDao dbDAO;
@Override
public <T extends BareNode> List<T> find(RequestWrapper wrap, DtoFilter<T> filter) {
// get search term
Optional<String> searchTerm = wrap.getFirstParam(DefaultUrlParameterNames.QUERY_STRING);
List<String> resultsOrder = null;
if (searchTerm.isPresent()) {
// get the ranked results from search engine. Results should have docids as id
List<SolrDocument> rankedResults = searchDAO.get(searchTerm.get(), filter.getType());
// if we got results, store the ranked order and set restriction to request
if (rankedResults != null && !rankedResults.isEmpty()) {
resultsOrder = rankedResults.stream().map(d -> (String) d.getFieldValue("id"))
.collect(Collectors.toList());
// restrict id results to given IDs (if supported)
resultsOrder.forEach(id -> wrap.addParam(DefaultUrlParameterNames.IDS, id));
}
}
// get the results from the DB dao
List<T> results = dbDAO.get(new RDBMSQuery<T>(wrap, filter));
// if we have an order to apply
if (resultsOrder != null) {
return getMarshalledList(resultsOrder, results);
}
// if we couldn't properly search, return native order
return results;
}
private <T extends BareNode> List<T> getMarshalledList(List<String> resultsOrder, List<T> results){
// create a sized array list
List<T> marshelledResults = new ArrayList<>(Math.max(resultsOrder.size(), results.size()));
for (String id : resultsOrder) {
// get ID for current ordered result
UUID docid = UUID.fromString(id);
LOGGER.debug("Checking for result document with ID {}", docid);
// iterate through the results and add them to the marshalled results
for (T result : results) {
if (docid.equals(result.getId())) {
marshelledResults.add(result);
LOGGER.debug("Found result document with ID {}", docid);
break;
}
}
}
return marshelledResults;
}
}
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.search;
import javax.enterprise.inject.Produces;
import org.eclipsefoundation.persistence.dao.PersistenceDao;
import org.eclipsefoundation.persistence.dao.impl.PlaceholderPersistenceDao;
import org.junit.jupiter.api.Test;
import io.quarkus.arc.DefaultBean;
import io.quarkus.test.junit.QuarkusTest;
/**
* Forces the application to start and compile a dunning test instance to
* validate the server can start up with bare configurations (which it should).
*
* @author Martin Lowe
*
*/
@QuarkusTest
class BaseTest {
@Test
void running() {
}
/**
* Required as there are no DB beans configured in the search package. This
* allows us to by default provide the placeholder DAO, which is removed by
* Quarkus regardless of the unremovable property.
*
* @return
*/
@Produces
@DefaultBean
public PersistenceDao defaultTestingPersistenceDao() {
return new PlaceholderPersistenceDao();
}
}
quarkus.datasource.db-kind=h2
## OAUTH CONFIG
quarkus.oauth2.enabled=false
quarkus.oidc.enabled=false
quarkus.keycloak.devservices.enabled=false
quarkus.oidc-client.enabled=false
quarkus.oidc.auth-server-url=/realms/quarkus/
quarkus.oidc.client-id=quarkus-service-app
quarkus.oidc.application-type=service
# Sample SOLR configuration
eclipse.solr.core=test
eclipse.solr.host=http://sample.host.co
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment