/*
* Copyright (c) 2017 OBiBa. All rights reserved.
*
* This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.obiba.magma.support;
import java.util.*;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import com.google.common.collect.*;
import org.obiba.magma.*;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
public abstract class AbstractValueTable implements ValueTable, Initialisable {
@NotNull
private final Datasource datasource;
@NotNull
protected String name;
private final Map<String, VariableValueSource> sources = new LinkedHashMap<>();
private VariableEntityProvider variableEntityProvider;
private int entityBatchSize = ENTITY_BATCH_SIZE;
@SuppressWarnings("ConstantConditions")
public AbstractValueTable(@NotNull Datasource datasource, @NotNull String name,
@Nullable VariableEntityProvider variableEntityProvider) {
if(datasource == null) throw new IllegalArgumentException("datasource cannot be null");
if(name == null) throw new IllegalArgumentException("name cannot be null");
this.datasource = datasource;
this.name = name;
this.variableEntityProvider = variableEntityProvider;
}
public AbstractValueTable(Datasource datasource, String name) {
this(datasource, name, null);
}
@Override
@NotNull
public String getName() {
return name;
}
@Override
public String getEntityType() {
return getVariableEntityProvider().getEntityType();
}
@Override
public boolean isForEntityType(String entityType) {
return Objects.equals(getEntityType(), entityType);
}
@Override
@NotNull
public Datasource getDatasource() {
return datasource;
}
@Override
public boolean hasValueSet(VariableEntity entity) {
return getVariableEntityProvider().getVariableEntities().contains(entity);
}
@Override
public Set<VariableEntity> getVariableEntities() {
return Collections.unmodifiableSet(variableEntityProvider.getVariableEntities());
}
@Override
public Iterable<ValueSet> getValueSets() {
return getValueSets(getVariableEntityProvider().getVariableEntities());
}
@Override
public Iterable<ValueSet> getValueSets(Iterable<VariableEntity> entities) {
return () -> new ValueSetIterator(entities);
}
@Override
public boolean canDropValueSets() {
return false;
}
@Override
public void dropValueSets() {
throw new UnsupportedOperationException(
"Cannot drop value sets from a '" + getDatasource().getType() + "' table.");
}
/**
* Simple implementation of a value set fetcher; a more specific one would fetch value sets in a bulk query.
*
* @param entities
* @return
*/
protected ValueSetBatch getValueSetsBatch(final List<VariableEntity> entities) {
return () -> {
ImmutableList.Builder<ValueSet> builder = ImmutableList.builder();
for (VariableEntity entity : entities) {
builder.add(getValueSet(entity));
}
return builder.build();
};
}
@Override
public boolean hasVariable(String variableName) {
return sources.containsKey(variableName);
}
@Override
public Variable getVariable(String variableName) {
return getVariableValueSource(variableName).getVariable();
}
@Override
public Value getValue(Variable variable, ValueSet valueSet) {
return getVariableValueSource(variable.getName()).getValue(valueSet);
}
@Override
public Set<Variable> getVariables() {
List<Variable> variables = Lists.newArrayList();
for(VariableValueSource source : getSources()) {
Variable variable = source.getVariable();
if (variable != null) variables.add(variable);
}
return ImmutableSet.copyOf(variables);
}
@Override
public VariableValueSource getVariableValueSource(String variableName) throws NoSuchVariableException {
VariableValueSource variableValueSource = sources.get(variableName);
if(variableValueSource == null) {
throw new NoSuchVariableException(getName(), variableName);
}
return variableValueSource;
}
@Override
public void initialise() {
Initialisables.initialise(getSources());
}
protected void addVariableValueSources(VariableValueSourceFactory factory) {
for(VariableValueSource variableValueSource : factory.createSources()) {
sources.put(variableValueSource.getName(), variableValueSource);
}
}
protected void addVariableValueSources(Collection<VariableValueSource> sourcesToAdd) {
List<VariableValueSource> list = Lists.newArrayList(sources.values());
for(VariableValueSource variableValueSource : sourcesToAdd) {
int index = list.indexOf(variableValueSource);
if(index >= 0) {
list.remove(index);
list.add(index, variableValueSource);
} else {
list.add(variableValueSource);
}
}
sources.clear();
for(VariableValueSource variableValueSource : list) {
sources.put(variableValueSource.getName(), variableValueSource);
}
}
protected void addVariableValueSource(VariableValueSource source) {
sources.put(source.getName(), source);
}
protected void removeVariableValueSource(String variableName) {
sources.remove(variableName);
}
protected void removeVariableValueSources(Iterable<VariableValueSource> sourcesToRemove) {
for(VariableValueSource variableValueSource : sourcesToRemove) {
removeVariableValueSource(variableValueSource.getVariable().getName());
}
}
protected void setVariableEntityProvider(@NotNull VariableEntityProvider variableEntityProvider) {
//noinspection ConstantConditions
if(variableEntityProvider == null) throw new IllegalArgumentException("variableEntityProvider cannot be null");
this.variableEntityProvider = variableEntityProvider;
}
@NotNull
protected VariableEntityProvider getVariableEntityProvider() {
if(variableEntityProvider == null) throw new IllegalArgumentException("variableEntityProvider cannot be null");
return variableEntityProvider;
}
protected Set<VariableValueSource> getSources() {
return ImmutableSet.copyOf(sources.values());
}
protected void clearSources() {
sources.clear();
}
@Override
public boolean isView() {
return false;
}
@Override
public Timestamps getValueSetTimestamps(VariableEntity entity) throws NoSuchValueSetException {
return getValueSet(entity).getTimestamps();
}
@Override
public Iterable<Timestamps> getValueSetTimestamps(SortedSet<VariableEntity> entities) {
List<Timestamps> timestamps = Lists.newArrayList();
for (VariableEntity entity : entities) {
timestamps.add(getValueSetTimestamps(entity));
}
return timestamps;
}
@Override
public String getTableReference() {
return ValueTable.Reference.getReference(getDatasource() == null ? "null" : getDatasource().getName(), getName());
}
@Override
public int getVariableCount() {
return getVariables().size();
}
@Override
public int getValueSetCount() {
return getVariableEntityCount();
}
@Override
public int getVariableEntityCount() {
return getVariableEntities().size();
}
@Override
public int hashCode() {return Objects.hash(datasource, name);}
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(obj == null || getClass() != obj.getClass()) {
return false;
}
AbstractValueTable other = (AbstractValueTable) obj;
return Objects.equals(datasource, other.datasource) && Objects.equals(name, other.name);
}
@Override
public int getVariableEntityBatchSize() {
return entityBatchSize;
}
protected void setEntityBatchSize(int entityBatchSize) {
this.entityBatchSize = entityBatchSize;
}
/**
* Lazy iterator of value sets: will make batch queries for extracting value sets.
*/
private class ValueSetIterator implements Iterator<ValueSet> {
private final Iterator<List<VariableEntity>> partitions;
private Iterator<ValueSet> currentBatch;
public ValueSetIterator(Iterable<VariableEntity> entities) {
this.partitions = Iterables.partition(entities, getVariableEntityBatchSize()).iterator();
}
@Override
public boolean hasNext() {
synchronized (partitions) {
return partitions.hasNext() || (currentBatch != null && currentBatch.hasNext());
}
}
@Override
public ValueSet next() {
synchronized (partitions) {
if (currentBatch == null || !currentBatch.hasNext()) {
currentBatch = getValueSetsBatch(partitions.next()).getValueSets().iterator();
}
return currentBatch.next();
}
}
}
}