/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.olingo.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.olingo.commons.api.edm.EdmEntitySet;
import org.apache.olingo.commons.api.edm.EdmEntityType;
import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.edm.EdmType;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.commons.core.edm.EdmPropertyImpl;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriParameter;
import org.apache.olingo.server.core.uri.queryoption.expression.LiteralImpl;
import org.teiid.core.TeiidException;
import org.teiid.metadata.Column;
import org.teiid.metadata.ForeignKey;
import org.teiid.metadata.KeyRecord;
import org.teiid.metadata.MetadataStore;
import org.teiid.metadata.Schema;
import org.teiid.metadata.Table;
import org.teiid.olingo.ODataPlugin;
import org.teiid.olingo.ProjectedColumn;
import org.teiid.olingo.common.ODataTypeManager;
import org.teiid.olingo.service.ODataSQLBuilder.URLParseService;
import org.teiid.olingo.service.TeiidServiceHandler.UniqueNameGenerator;
import org.teiid.query.sql.lang.*;
import org.teiid.query.sql.symbol.ElementSymbol;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.sql.symbol.GroupSymbol;
public class DocumentNode {
private Table table;
private GroupSymbol groupSymbol;
private EdmEntityType edmEntityType;
private List<UriParameter> keyPredicates;
private FromClause fromClause;
private Criteria criteria;
private LinkedHashMap<Expression, ProjectedColumn> projectedColumns = new LinkedHashMap<Expression, ProjectedColumn>();
private List<DocumentNode> sibilings = new ArrayList<DocumentNode>();
private List<ExpandDocumentNode> expands = new ArrayList<ExpandDocumentNode>();
private DocumentNode iterator;
public static DocumentNode build(EdmEntityType type,
List<UriParameter> keyPredicates, MetadataStore metadata, OData odata,
UniqueNameGenerator nameGenerator, boolean useAlias,
UriInfo uriInfo, URLParseService parseService)
throws TeiidException {
DocumentNode resource = new DocumentNode();
return build(resource, type, keyPredicates, metadata, odata,
nameGenerator, useAlias, uriInfo, parseService);
}
public static DocumentNode build(DocumentNode resource,
EdmEntityType type, List<UriParameter> keyPredicates,
MetadataStore metadata, OData odata, UniqueNameGenerator nameGenerator,
boolean useAlias, UriInfo uriInfo, URLParseService parseService)
throws TeiidException {
Table table = findTable(type, metadata);
GroupSymbol gs = null;
if (useAlias) {
gs = new GroupSymbol(nameGenerator.getNextGroup(), table.getFullName());
} else {
gs = new GroupSymbol(table.getFullName());
}
resource.setTable(table);
resource.setGroupSymbol(gs);
resource.setEdmEntityType(type);
resource.setKeyPredicates(keyPredicates);
resource.setFromClause(new UnaryFromClause(gs));
if (keyPredicates != null && !keyPredicates.isEmpty()) {
Criteria criteria = DocumentNode.buildEntityKeyCriteria(resource,
uriInfo, metadata, odata, nameGenerator, parseService);
resource.setCriteria(criteria);
}
return resource;
}
static Table findTable(EdmEntityType entityType, MetadataStore store) {
FullQualifiedName fqn = entityType.getFullQualifiedName();
// remove the vdb name
String withoutVDB = fqn.getNamespace().substring(fqn.getNamespace().lastIndexOf('.')+1);
Schema schema = store.getSchema(withoutVDB);
return schema.getTable(entityType.getName());
}
static Table findTable(EdmEntitySet entitySet, MetadataStore store) {
return findTable(entitySet.getEntityType(), store);
}
static Criteria buildEntityKeyCriteria(DocumentNode resource,
UriInfo uriInfo, MetadataStore store, OData odata,
UniqueNameGenerator nameGenerator, URLParseService parseService)
throws TeiidException {
List<Column> pk = getPKColumns(resource.getTable());
if (resource.getKeyPredicates().size() == 1) {
if (pk.size() != 1) {
throw new TeiidException(ODataPlugin.Event.TEIID16015,
ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16015, resource.getTable().getFullName()));
}
Column column = pk.get(0);
ODataExpressionToSQLVisitor visitor = new ODataExpressionToSQLVisitor(
resource, false, uriInfo, store, odata, nameGenerator, null, parseService);
UriParameter key = resource.getKeyPredicates().get(0);
org.apache.olingo.server.api.uri.queryoption.expression.Expression expr = getKeyPredicateExpression(
key, odata, column);
return new CompareCriteria(new ElementSymbol(column.getName(),
resource.getGroupSymbol()), CompareCriteria.EQ,
visitor.getExpression(expr));
}
// complex (multi-keyed)
List<Criteria> critList = new ArrayList<Criteria>();
if (pk.size() != resource.getKeyPredicates().size()) {
throw new TeiidException(ODataPlugin.Event.TEIID16015,
ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16015, resource.getTable().getFullName()));
}
for (UriParameter key : resource.getKeyPredicates()) {
Column column = findColumn(resource.getTable(), key.getName());
ODataExpressionToSQLVisitor visitor = new ODataExpressionToSQLVisitor(
resource, false, uriInfo, store, odata, nameGenerator, null, parseService);
org.apache.olingo.server.api.uri.queryoption.expression.Expression expr = getKeyPredicateExpression(
key, odata, column);
critList.add(new CompareCriteria(new ElementSymbol(column.getName(), resource.getGroupSymbol()),
CompareCriteria.EQ, visitor.getExpression(expr)));
}
return new CompoundCriteria(CompoundCriteria.AND, critList);
}
private static org.apache.olingo.server.api.uri.queryoption.expression.Expression getKeyPredicateExpression(
UriParameter key, OData odata, Column column) {
org.apache.olingo.server.api.uri.queryoption.expression.Expression expr = key.getExpression();
if ( expr == null) {
EdmPrimitiveTypeKind primitiveTypeKind = ODataTypeManager.odataType(column.getRuntimeType());
expr = new LiteralImpl(key.getText(), odata.createPrimitiveTypeInstance(primitiveTypeKind));
}
return expr;
}
static Column findColumn(Table table, String propertyName) {
return table.getColumnByName(propertyName);
}
public void buildEntityKeyCriteria(UriInfo uriInfo, MetadataStore metadata, OData odata,
UniqueNameGenerator nameGenerator, URLParseService parseService) throws TeiidException {
// URL is like /entitySet(key)s
if (getKeyPredicates() != null && !getKeyPredicates().isEmpty()) {
this.criteria = buildEntityKeyCriteria(this, uriInfo, metadata, odata, nameGenerator, parseService);
}
}
public DocumentNode() {
}
public DocumentNode(Table table, GroupSymbol gs, EdmEntityType type) {
this.table = table;
this.groupSymbol = gs;
this.edmEntityType = type;
}
private Table getTable() {
return table;
}
public String getName() {
return table.getName();
}
public Column getColumnByName(String name) {
return this.table.getColumnByName(name);
}
public String getFullName() {
return table.getFullName();
}
public GroupSymbol getGroupSymbol() {
return groupSymbol;
}
public EdmEntityType getEdmEntityType() {
return edmEntityType;
}
public FromClause getFromClause() {
return fromClause;
}
public void setFromClause(FromClause fromClause) {
this.fromClause = fromClause;
}
public Criteria getCriteria() {
return criteria;
}
public void setCriteria(Criteria criteria) {
this.criteria = criteria;
}
public void setTable(Table table) {
this.table = table;
}
public void setGroupSymbol(GroupSymbol groupSymbol) {
this.groupSymbol = groupSymbol;
}
public void setEdmEntityType(EdmEntityType edmEntityType) {
this.edmEntityType = edmEntityType;
}
protected void addAllColumns(boolean onlyPK) {
if (onlyPK) {
List<Column> columns = getPKColumns(getTable());
for (final Column column : columns) {
if (column.isSelectable()) {
addProjectedColumn(column.getName(), new ElementSymbol(column.getName(), getGroupSymbol()));
}
}
}
else {
for (final Column column : getTable().getColumns()) {
if (column.isSelectable()) {
addProjectedColumn(column.getName(), new ElementSymbol(column.getName(), getGroupSymbol()));
}
}
}
}
protected void addProjectedColumn(final String columnName,
final Expression expr) {
EdmPropertyImpl edmProperty = (EdmPropertyImpl) this.edmEntityType.getProperty(columnName);
Column c = getColumnByName(columnName);
ProjectedColumn pc = addProjectedColumn(expr, edmProperty.getType(), edmProperty, edmProperty.isCollection());
pc.setOrdinal(c.getPosition());
}
protected ProjectedColumn addProjectedColumn(final Expression expr, final EdmType type,
EdmProperty property, final boolean collection) {
ProjectedColumn pc = this.projectedColumns.get(expr);
if (pc != null) {
return pc;
}
pc = new ProjectedColumn(expr, type, property, collection);
pc.setOrdinal(Integer.MAX_VALUE);
this.projectedColumns.put(expr, pc);
return pc;
}
OrderBy addDefaultOrderBy() {
if (this.table == null) {
return null;
}
OrderBy orderBy = new OrderBy();
// provide implicit ordering for cursor logic
KeyRecord record = this.table.getPrimaryKey();
if (record == null) {
// if PK is not available there MUST at least one unique key
record = this.table.getUniqueKeys().get(0);
}
// provide implicit ordering for cursor logic
for (Column column:record.getColumns()) {
ElementSymbol expr = new ElementSymbol(column.getName(), this.groupSymbol);
orderBy.addVariable(expr);
addProjectedColumn(column.getName(), expr);
}
return orderBy;
}
public LinkedHashMap<Expression, ProjectedColumn> getProjectedColumns() {
return projectedColumns;
}
public List<ProjectedColumn> getAllProjectedColumns() {
ArrayList<ProjectedColumn> columns = new ArrayList<ProjectedColumn>();
columns.addAll(this.projectedColumns.values());
for (DocumentNode er:this.sibilings) {
columns.addAll(er.getAllProjectedColumns());
}
if (this.iterator != null) {
columns.addAll(this.iterator.getAllProjectedColumns());
}
return columns;
}
public List<UriParameter> getKeyPredicates() {
return keyPredicates;
}
public List<String> getKeyColumnNames(){
return this.edmEntityType.getKeyPredicateNames();
}
public void setKeyPredicates(List<UriParameter> keyPredicates) {
this.keyPredicates = keyPredicates;
}
public void addSibiling(DocumentNode resource) {
this.sibilings.add(resource);
}
public List<DocumentNode> getSibilings(){
return this.sibilings;
}
public void addExpand(ExpandDocumentNode resource) {
this.expands.add(resource);
}
public List<ExpandDocumentNode> getExpands(){
return this.expands;
}
public Query buildQuery() {
Select select = new Select();
AtomicInteger ordinal = new AtomicInteger(1);
addProjectedColumns(select, ordinal, sortColumns(getProjectedColumns().values()));
for (DocumentNode sibiling:this.sibilings) {
addProjectedColumns(select, ordinal, sortColumns(sibiling.getProjectedColumns().values()));
}
Query query = new Query();
From from = new From();
from.addClause(this.fromClause);
for (DocumentNode sibiling:this.sibilings) {
from.addClause(sibiling.getFromClause());
}
if (this.iterator != null) {
addProjectedColumns(select, ordinal, sortColumns(this.iterator.getProjectedColumns().values()));
from.addClause(this.iterator.getFromClause());
GroupBy groupBy = new GroupBy();
for (String keyCol : this.getKeyColumnNames()) {
groupBy.addSymbol(new ElementSymbol(keyCol, this.groupSymbol));
}
query.setGroupBy(groupBy);
}
query.setSelect(select);
query.setFrom(from);
query.setCriteria(this.criteria);
return query;
}
private List<ProjectedColumn> sortColumns(Collection<ProjectedColumn> toSort) {
//provide a stable sort of the columns regardless of visitation order.
ArrayList<ProjectedColumn> list = new ArrayList<ProjectedColumn>(toSort);
Collections.sort(list, new Comparator<ProjectedColumn>() {
@Override
public int compare(ProjectedColumn o1, ProjectedColumn o2) {
return Integer.compare(o1.getOrdinal(), o2.getOrdinal());
}
});
return list;
}
private void addProjectedColumns(Select select, AtomicInteger ordinal,
List<ProjectedColumn> projected) {
for (ProjectedColumn column:projected) {
select.addSymbol(column.getExpression());
column.setOrdinal(ordinal.getAndIncrement());
}
}
DocumentNode joinTable(DocumentNode joinResource, EdmNavigationProperty property, JoinType joinType) throws TeiidException {
ForeignKey fk = null;
boolean reverse = false;
if (property.isCollection()) {
fk = joinFK(joinResource.getTable(), getTable(), property);
reverse = true;
}
else {
fk = joinFK(getTable(), joinResource.getTable(), property);
}
// reverse lookup
if (fk == null) {
if (property.isCollection()) {
fk = joinFK(getTable(), joinResource.getTable(), property);
}
else {
fk = joinFK(joinResource.getTable(), getTable(), property);
reverse = true;
}
}
if (fk == null && !joinType.equals(JoinType.JOIN_CROSS)) {
throw new TeiidException("Fk not found");
}
FromClause fromClause;
if (joinResource.getKeyPredicates() != null && joinResource.getKeyPredicates().size() > 0) {
// here the previous entityset is verbose; need to be canonicalized
fromClause = new UnaryFromClause(joinResource.getGroupSymbol());
}
else {
Criteria crit = null;
if (!joinType.equals(JoinType.JOIN_CROSS)) {
crit = buildCriteria(reverse?joinResource:this, reverse?this:joinResource, fk);
}
fromClause = new JoinPredicate(this.getFromClause(), new UnaryFromClause(joinResource.getGroupSymbol()), joinType, crit);
}
joinResource.setFromClause(fromClause);
return joinResource;
}
static ForeignKey joinFK(DocumentNode current, DocumentNode reference, EdmNavigationProperty property) {
Table currentTable = current.getTable();
Table referenceTable = reference.getTable();
if (currentTable == null || referenceTable == null) {
return null;
}
return joinFK(currentTable, referenceTable, property);
}
private static ForeignKey joinFK(Table currentTable, Table referenceTable, EdmNavigationProperty property) {
for (ForeignKey fk : currentTable.getForeignKeys()) {
String refSchemaName = fk.getReferenceKey().getParent().getParent().getName();
if (((!property.isCollection() && property.getName().equals(fk.getName()))
|| (property.isCollection() && property.getName().equals(currentTable.getName() + "_" + fk.getName()))) //$NON-NLS-1$
&& referenceTable.getParent().getName().equals(refSchemaName)
&& referenceTable.getName().equals(fk.getReferenceTableName())) {
return fk;
}
}
return null;
}
static Criteria buildJoinCriteria(DocumentNode from, DocumentNode to) {
for (ForeignKey fk:from.getTable().getForeignKeys()) {
if (fk.getReferenceKey().getParent().equals(to.getTable())) {
return buildCriteria(from, to, fk);
}
}
return null;
}
private static Criteria buildCriteria(DocumentNode from, DocumentNode to,
ForeignKey fk) {
Criteria criteria;
List<String> fkColumns = DocumentNode.getColumnNames(fk.getColumns());
if (fkColumns == null) {
fkColumns = DocumentNode.getColumnNames(getPKColumns(from.getTable()));
}
List<String> pkColumns = DocumentNode.getColumnNames(getPKColumns(to.getTable()));
criteria = DocumentNode.buildJoinCriteria(
from.getGroupSymbol(),
to.getGroupSymbol(), pkColumns, fkColumns);
return criteria;
}
static List<Column> getPKColumns(Table table){
return ODataSchemaBuilder.getIdentifier(table).getColumns();
}
static Criteria buildJoinCriteria(final GroupSymbol joinGroup,
final GroupSymbol entityGroup, List<String> pkColumns,
List<String> refColumns) {
List<Criteria> critList = new ArrayList<Criteria>();
for (int i = 0; i < refColumns.size(); i++) {
critList.add(new CompareCriteria(new ElementSymbol(pkColumns.get(i), entityGroup), CompareCriteria.EQ, new ElementSymbol(refColumns.get(i), joinGroup)));
}
Criteria crit = critList.get(0);
for (int i = 1; i < critList.size(); i++) {
crit = new CompoundCriteria(CompoundCriteria.AND, crit, critList.get(i));
}
return crit;
}
static List<String> getColumnNames(List<Column> columns){
if (columns == null || columns.isEmpty()) {
return null;
}
ArrayList<String> columnNames = new ArrayList<String>();
for (Column column:columns) {
columnNames.add(column.getName());
}
return columnNames;
}
public void addCriteria(Expression filter) {
if (filter != null) {
Criteria crit = null;
if (filter instanceof Criteria) {
crit = (Criteria)filter;
} else {
crit = new ExpressionCriteria(filter);
}
this.criteria = Criteria.combineCriteria(this.criteria, crit);
}
}
public String toString() {
return table.getFullName();
}
public void setIterator(DocumentNode itResource) {
this.iterator = itResource;
}
public DocumentNode getIterator() {
return this.iterator;
}
}