package de.danielbasedow.prospecter.core.schema;
import de.danielbasedow.prospecter.core.*;
import de.danielbasedow.prospecter.core.document.*;
import de.danielbasedow.prospecter.core.index.FieldIndex;
import de.danielbasedow.prospecter.core.persistence.QueryStorage;
import de.danielbasedow.prospecter.core.query.Condition;
import de.danielbasedow.prospecter.core.query.Query;
import de.danielbasedow.prospecter.core.query.QueryManager;
import de.danielbasedow.prospecter.core.query.build.AdvancedQueryBuilder;
import gnu.trove.list.TLongList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class SchemaImpl implements Schema {
private static final Logger LOGGER = LoggerFactory.getLogger(SchemaImpl.class);
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
protected final ConcurrentHashMap<String, FieldIndex> indices;
protected final AdvancedQueryBuilder queryBuilder;
protected final DocumentBuilder documentBuilder;
protected final QueryManager queryManager;
protected QueryStorage queryStorage;
private boolean writeNewQueries;
public SchemaImpl() {
indices = new ConcurrentHashMap<String, FieldIndex>();
queryBuilder = new AdvancedQueryBuilder(this);
documentBuilder = new DocumentBuilder(this);
queryManager = new QueryManager();
}
@Override
public void addFieldIndex(String fieldName, FieldIndex index) throws SchemaConfigurationError {
if (indices.containsKey(fieldName)) {
throw new SchemaConfigurationError("Field '" + fieldName + "' is defined more than once!");
}
indices.put(fieldName, index);
}
@Override
public void matchField(String fieldIndexName, Field field, Matcher matcher) throws UndefinedIndexFieldException {
if (!indices.containsKey(fieldIndexName)) {
throw new UndefinedIndexFieldException("No field named '" + fieldIndexName + "'");
}
indices.get(fieldIndexName).match(field, matcher);
}
public void addQuery(Query query) throws UndefinedIndexFieldException {
writeLock.lock();
try {
Map<Condition, Long> postings = query.getPostings();
for (Map.Entry<Condition, Long> entry : postings.entrySet()) {
Condition condition = entry.getKey();
Long posting = entry.getValue();
if (!indices.containsKey(condition.getFieldName())) {
throw new UndefinedIndexFieldException("No field named '" + condition.getFieldName() + "'");
}
indices.get(condition.getFieldName()).addPosting(condition.getToken(), posting);
}
queryManager.addQuery(query);
} finally {
writeLock.unlock();
}
}
@Override
public void addQuery(String json) throws UndefinedIndexFieldException, MalformedQueryException {
Query query = queryBuilder.buildFromJSON(json);
if (queryStorage != null && writeNewQueries) {
queryStorage.addQuery(query.getQueryId(), json);
}
addQuery(query);
}
@Override
public Matcher matchDocument(Document doc) {
return matchDocument(doc, getMatcher());
}
@Override
public Matcher matchDocument(Document doc, Matcher matcher) {
readLock.lock();
try {
FieldIterator fields = doc.getFields();
while (fields.hasNext()) {
Field field = fields.next();
try {
matchField(field.getName(), field, matcher);
} catch (UndefinedIndexFieldException e) {
e.printStackTrace();
}
}
} finally {
readLock.unlock();
}
return matcher;
}
@Override
public int getFieldCount() {
return indices.size();
}
@Override
public FieldIndex getFieldIndex(String name) {
return indices.get(name);
}
@Override
public AdvancedQueryBuilder getQueryBuilder() {
return queryBuilder;
}
@Override
public DocumentBuilder getDocumentBuilder() {
return documentBuilder;
}
@Override
public Matcher getMatcher() {
return new Matcher(queryManager);
}
@Override
public QueryManager getQueryManager() {
return queryManager;
}
@Override
public void setQueryStorage(QueryStorage queryStorage) {
this.queryStorage = queryStorage;
}
@Override
public void close() {
this.queryStorage.close();
}
@Override
public void init() {
//Disable persistence for new queries so we don't try updating every single query
writeNewQueries = false;
if (queryStorage != null) {
LOGGER.info("initializing schema with stored queries");
int loadedQueries = 0;
try {
for (Map.Entry<Integer, String> entry : queryStorage.getAllQueries()) {
addQuery(entry.getValue());
loadedQueries++;
}
} catch (Exception e) {
LOGGER.error("problem loading stored queries", e);
}
LOGGER.info("done loading " + String.valueOf(loadedQueries) + " queries");
}
trim();
writeNewQueries = true;
}
@Override
public void deleteQuery(Integer queryId) {
writeLock.lock();
try {
String rawQuery = queryStorage.getRawQuery(queryId);
if (rawQuery != null) {
try {
Query query = queryBuilder.buildFromJSON(rawQuery);
removePostings(query);
} catch (MalformedQueryException e) {
LOGGER.warn("Error parsing query", e);
}
}
queryManager.deleteQuery(queryId);
queryStorage.deleteQuery(queryId);
} finally {
writeLock.unlock();
}
}
private void removePostings(Query query) {
Map<Condition, Long> postings = query.getPostings();
for (Map.Entry<Condition, Long> entry : postings.entrySet()) {
Condition condition = entry.getKey();
Long posting = entry.getValue();
indices.get(condition.getFieldName()).removePosting(condition.getToken(), posting);
}
queryManager.addQuery(query);
}
@Override
public void trim() {
writeLock.lock();
try {
for (FieldIndex field : indices.values()) {
field.trim();
}
} finally {
writeLock.unlock();
}
}
}