/*
* Copyright 2012 NGDATA nv
*
* 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 org.lilyproject.indexer.engine;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.ExecutionException;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Sets;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.solr.common.SolrInputDocument;
import org.lilyproject.indexer.derefmap.DependencyEntry;
import org.lilyproject.indexer.derefmap.DerefMapUtil;
import org.lilyproject.indexer.model.indexerconf.Dep;
import org.lilyproject.indexer.model.indexerconf.FieldTemplatePart;
import org.lilyproject.indexer.model.indexerconf.IndexRecordFilter;
import org.lilyproject.indexer.model.indexerconf.IndexUpdateBuilder;
import org.lilyproject.indexer.model.indexerconf.LiteralTemplatePart;
import org.lilyproject.indexer.model.indexerconf.NameTemplate;
import org.lilyproject.indexer.model.indexerconf.NameTemplateEvaluationException;
import org.lilyproject.indexer.model.indexerconf.NameTemplateResolver;
import org.lilyproject.indexer.model.indexerconf.RecordContext;
import org.lilyproject.indexer.model.indexerconf.TemplatePart;
import org.lilyproject.indexer.model.indexerconf.Value;
import org.lilyproject.indexer.model.indexerconf.VariantPropertyTemplatePart;
import org.lilyproject.repository.api.IdRecord;
import org.lilyproject.repository.api.LRepository;
import org.lilyproject.repository.api.QName;
import org.lilyproject.repository.api.Record;
import org.lilyproject.repository.api.RecordId;
import org.lilyproject.repository.api.RepositoryException;
import org.lilyproject.repository.api.SchemaId;
import org.lilyproject.repository.api.TypeManager;
import org.lilyproject.repository.impl.id.AbsoluteRecordIdImpl;
import org.lilyproject.util.repo.SystemFields;
public class SolrDocumentBuilder implements IndexUpdateBuilder {
private final Log log = LogFactory.getLog(getClass());
private final LRepository repository;
private final IndexRecordFilter indexRecordFilter;
private final SystemFields systemFields;
private final TypeManager typeManager;
private final ValueEvaluator valueEvaluator;
private final NameTemplateResolver nameTemplateResolver;
private final SolrInputDocument solrDoc = new SolrInputDocument();
private boolean emptyDocument = true;
private Stack<RecordContext> contexts;
private LoadingCache<DependencyEntry, Set<SchemaId>> dependencies;
private String table;
private RecordId recordId;
private String key;
private SchemaId vtag;
private long version;
public SolrDocumentBuilder(LRepository repository, IndexRecordFilter indexRecordFilter, SystemFields systemFields,
ValueEvaluator valueEvaluator, String table, IdRecord record, String key, SchemaId vtag, long version) {
this.repository = repository;
this.indexRecordFilter = indexRecordFilter;
this.systemFields = systemFields;
this.typeManager = repository.getTypeManager();
this.valueEvaluator = valueEvaluator;
this.table = table;
this.recordId = record.getId();
this.key = key;
this.vtag = vtag;
this.version = version;
this.nameTemplateResolver = new FieldNameTemplateResolver();
this.contexts = new Stack<RecordContext>();
this.push(record, new Dep(this.recordId, Collections.<String>emptySet()));
this.dependencies = CacheBuilder.newBuilder().build(new CacheLoader<DependencyEntry, Set<SchemaId>>() {
@Override
public Set<SchemaId> load(DependencyEntry arg0) throws Exception {
return Sets.newHashSet();
}
});
}
public boolean isEmptyDocument() {
return emptyDocument;
}
public SolrInputDocument build() throws InterruptedException, RepositoryException {
solrDoc.setField("lily.id", recordId.toString());
solrDoc.setField("lily.table", table);
solrDoc.setField("lily.key", key);
solrDoc.setField("lily.vtagId", vtag.toString());
solrDoc.setField("lily.vtag", typeManager.getFieldTypeById(vtag).getName().getName());
solrDoc.setField("lily.version", version);
return solrDoc;
}
@Override
public LRepository getRepository() {
return repository;
}
@Override
public List<String> eval(Value value) throws RepositoryException, IOException, InterruptedException {
return valueEvaluator.eval(table, value, this);
}
@Override
public void addField(String fieldName, List<String> values) throws InterruptedException, RepositoryException {
if (values != null) {
for (String value : values) {
solrDoc.addField(fieldName, value);
emptyDocument = false;
}
}
}
@Override
public RecordContext getRecordContext() {
return contexts.peek();
}
@Override
public NameTemplateResolver getFieldNameResolver() {
return nameTemplateResolver;
}
private class FieldNameTemplateResolver implements NameTemplateResolver {
@Override
public Object resolve(TemplatePart part) {
RecordContext ctx = contexts.peek();
//TODO: add dependencies caused by resolving name template variables
if (part instanceof FieldTemplatePart) {
QName fieldName = ((FieldTemplatePart) part).getFieldType().getName();
if (ctx.record.hasField(fieldName)) {
return ctx.record.getField(fieldName);
} else {
throw new NameTemplateEvaluationException(
"Error evaluating name template: Record does not have field " + fieldName);
}
} else if (part instanceof VariantPropertyTemplatePart) {
VariantPropertyTemplatePart vpPart = (VariantPropertyTemplatePart) part;
return contexts.peek().contextRecord.getId().getVariantProperties().get(vpPart.getName());
} else if (part instanceof LiteralTemplatePart) {
return ((LiteralTemplatePart) part).getString();
} else {
throw new NameTemplateEvaluationException("Unsupported TemplatePart type " + part.getClass().getName());
}
}
}
@Override
public void addDependency(SchemaId field) {
RecordContext ctx = contexts.peek();
try {
if (!ctx.dep.moreDimensionedVariants.isEmpty() || !ctx.dep.id.equals(recordId)) { // avoid adding unnecesary self-references
dependencies.get(DerefMapUtil.newEntry(
new AbsoluteRecordIdImpl(table, ctx.dep.id), ctx.dep.moreDimensionedVariants)).add(field);
}
} catch (ExecutionException ee) {
throw new RuntimeException("Failed to update dependencies");
}
}
public Map<DependencyEntry, Set<SchemaId>> getDependencies() {
return dependencies.asMap();
}
@Override
public void push(Record record, Dep dep) {
this.contexts.push(new RecordContext(record, dep));
warnForUnmatchedDependencies(record);
}
@Override
public void push(Record record, Record contextRecord, Dep dep) {
this.contexts.push(new RecordContext(record, contextRecord, dep));
warnForUnmatchedDependencies(record);
}
/**
* If the dependency is not matched by the configuration of the indexer, we log a warning because updates to this
* dependency will not trigger 'denormalized data updates'.
*/
private void warnForUnmatchedDependencies(Record dependency) {
if (dependency != null && dependency.getId() != null && this.indexRecordFilter.getIndexCase(table, dependency) == null) {
log.warn(String.format("discovered dependency on record [%s] which will not be matched by the record filter of the index", dependency.getId()));
}
}
@Override
public RecordContext pop() {
return this.contexts.pop();
}
@Override
public SchemaId getVTag() {
return vtag;
}
@Override
public String evalIndexFieldName(NameTemplate nameTemplate) {
if (getRecordContext().record != null) {
try {
return nameTemplate.format(getFieldNameResolver());
} catch (NameTemplateEvaluationException ntve) {
return null;
}
} else {
// collect dependencies introducted by any 'FieldTemplateParts'
for (TemplatePart part : nameTemplate.getParts()) {
if (part instanceof FieldTemplatePart) {
addDependency(((FieldTemplatePart) part).getFieldType().getId());
}
}
return null;
}
}
@Override
public SystemFields getSystemFields() {
return systemFields;
}
@Override
public String getTable() {
return table;
}
}