/**
* AnalyzerBeans
* Copyright (C) 2014 Neopost - Customer Information Management
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.eobjects.analyzer.reference;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.inject.Inject;
import org.eobjects.analyzer.beans.api.Close;
import org.eobjects.analyzer.beans.api.Initialize;
import org.eobjects.analyzer.beans.api.Provided;
import org.eobjects.analyzer.beans.convert.ConvertToNumberTransformer;
import org.eobjects.analyzer.beans.convert.ConvertToStringTransformer;
import org.eobjects.analyzer.connection.Datastore;
import org.eobjects.analyzer.connection.DatastoreCatalog;
import org.eobjects.analyzer.connection.DatastoreConnection;
import org.eobjects.analyzer.util.CollectionUtils2;
import org.eobjects.analyzer.util.ReadObjectBuilder;
import org.eobjects.analyzer.util.SchemaNavigator;
import org.eobjects.analyzer.util.StringUtils;
import org.apache.metamodel.DataContext;
import org.apache.metamodel.data.DataSet;
import org.apache.metamodel.data.Row;
import org.apache.metamodel.query.FilterItem;
import org.apache.metamodel.query.OperatorType;
import org.apache.metamodel.query.Query;
import org.apache.metamodel.query.SelectItem;
import org.apache.metamodel.schema.Column;
import org.apache.metamodel.schema.Table;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.cache.Cache;
public final class DatastoreSynonymCatalog extends AbstractReferenceData implements SynonymCatalog {
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(DatastoreSynonymCatalog.class);
private transient Cache<String, String> _masterTermCache;
private transient BlockingQueue<DatastoreConnection> _dataContextProviders = new LinkedBlockingQueue<DatastoreConnection>();
private final String _datastoreName;
private final String _masterTermColumnPath;
private final String[] _synonymColumnPaths;
@Inject
@Provided
transient DatastoreCatalog _datastoreCatalog;
public DatastoreSynonymCatalog(String name, String datastoreName, String masterTermColumnPath,
String[] synonymColumnPaths) {
super(name);
_datastoreName = datastoreName;
_masterTermColumnPath = masterTermColumnPath;
_synonymColumnPaths = synonymColumnPaths;
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
ReadObjectBuilder.create(this, DatastoreSynonymCatalog.class).readObject(stream);
}
@Override
protected void decorateIdentity(List<Object> identifiers) {
super.decorateIdentity(identifiers);
identifiers.add(_datastoreName);
identifiers.add(_masterTermColumnPath);
identifiers.add(_synonymColumnPaths);
}
/**
* Initializes a DatastoreConnection, which will keep the connection open
*/
@Initialize
public void init() {
logger.info("Initializing dictionary: {}", this);
Datastore datastore = getDatastore();
DatastoreConnection dataContextProvider = datastore.openConnection();
getDatastoreConnections().add(dataContextProvider);
}
/**
* Closes a DatastoreConnection, potentially closing the connection (if no
* other DatastoreConnections are open).
*/
@Close
public void close() {
DatastoreConnection datastoreConnection = getDatastoreConnections().poll();
if (datastoreConnection != null) {
logger.info("Closing dictionary: {}", this);
datastoreConnection.close();
}
}
private Cache<String, String> getMasterTermCache() {
if (_masterTermCache == null) {
synchronized (this) {
if (_masterTermCache == null) {
_masterTermCache = CollectionUtils2.createCache(1000, 5 * 60);
}
}
}
return _masterTermCache;
}
private BlockingQueue<DatastoreConnection> getDatastoreConnections() {
if (_dataContextProviders == null) {
synchronized (this) {
if (_dataContextProviders == null) {
_dataContextProviders = new LinkedBlockingQueue<DatastoreConnection>();
}
}
}
return _dataContextProviders;
}
private Datastore getDatastore() {
Datastore datastore = _datastoreCatalog.getDatastore(_datastoreName);
if (datastore == null) {
throw new IllegalStateException("Could not resolve datastore " + _datastoreName);
}
return datastore;
}
public String getDatastoreName() {
return _datastoreName;
}
public String getMasterTermColumnPath() {
return _masterTermColumnPath;
}
public String[] getSynonymColumnPaths() {
return Arrays.copyOf(_synonymColumnPaths, _synonymColumnPaths.length);
}
@Override
public Collection<Synonym> getSynonyms() {
final Datastore datastore = getDatastore();
try (DatastoreConnection datastoreConnection = datastore.openConnection()) {
final DataContext dataContext = datastoreConnection.getDataContext();
final SchemaNavigator schemaNavigator = datastoreConnection.getSchemaNavigator();
final Column masterTermColumn = schemaNavigator.convertToColumn(_masterTermColumnPath);
final Column[] columns = schemaNavigator.convertToColumns(_synonymColumnPaths);
final Table table = masterTermColumn.getTable();
final Query query = dataContext.query().from(table.getName()).select(masterTermColumn).select(columns)
.toQuery();
final DataSet results = dataContext.executeQuery(query);
final List<Synonym> synonyms = new ArrayList<Synonym>();
while (results.next()) {
final Row row = results.getRow();
synonyms.add(new SimpleSynonym(getMasterTerm(row, masterTermColumn), getSynonyms(row,
table.getColumns())));
}
return synonyms;
}
}
@Override
public String getMasterTerm(String term) {
if (StringUtils.isNullOrEmpty(term)) {
return null;
}
final Cache<String, String> cache = getMasterTermCache();
String result;
synchronized (cache) {
result = cache.getIfPresent(term);
if (result == null) {
final Datastore datastore = getDatastore();
try (final DatastoreConnection datastoreConnection = datastore.openConnection()) {
final SchemaNavigator schemaNavigator = datastoreConnection.getSchemaNavigator();
final Column masterTermColumn = schemaNavigator.convertToColumn(_masterTermColumnPath);
final Column[] columns = schemaNavigator.convertToColumns(_synonymColumnPaths);
final DataContext dataContext = datastoreConnection.getDataContext();
final Table table = masterTermColumn.getTable();
// create a query that gets the master term where any of the
// synonym columns are equal to the synonym
final Query query = dataContext.query().from(table.getName()).select(masterTermColumn).toQuery();
final List<FilterItem> filterItems = new ArrayList<FilterItem>();
for (int i = 0; i < columns.length; i++) {
final Column column = columns[i];
if (column.getType().isNumber()) {
final Number numberValue = ConvertToNumberTransformer.transformValue(term);
if (numberValue != null) {
filterItems.add(new FilterItem(new SelectItem(column), OperatorType.EQUALS_TO,
numberValue));
}
} else {
filterItems.add(new FilterItem(new SelectItem(column), OperatorType.EQUALS_TO, term));
}
}
if (filterItems.isEmpty()) {
result = "";
} else {
query.where(new FilterItem(filterItems.toArray(new FilterItem[0])));
try (final DataSet dataSet = dataContext.executeQuery(query)) {
if (dataSet.next()) {
final Row row = dataSet.getRow();
result = getMasterTerm(row, masterTermColumn);
} else {
result = "";
}
}
}
}
cache.put(term, result);
}
}
if ("".equals(result)) {
result = null;
}
return result;
}
private String getMasterTerm(Row row, Column column) {
Object value = row.getValue(column);
return ConvertToStringTransformer.transformValue(value);
}
private String[] getSynonyms(Row row, Column[] columns) {
List<String> synonyms = new ArrayList<String>();
for (Column synonymColumn : columns) {
String value = (String) row.getValue(synonymColumn);
synonyms.add(value);
}
return synonyms.toArray(new String[0]);
}
}