/*
* 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.translator.odata4;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.teiid.language.ColumnReference;
import org.teiid.language.Comparison;
import org.teiid.language.Condition;
import org.teiid.language.Join.JoinType;
import org.teiid.language.LanguageUtil;
import org.teiid.metadata.Column;
import org.teiid.metadata.ForeignKey;
import org.teiid.metadata.RuntimeMetadata;
import org.teiid.metadata.Table;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.document.DocumentNode;
import org.teiid.translator.odata4.ODataDocumentNode.ODataDocumentType;
public class ODataQuery {
protected ODataExecutionFactory executionFactory;
protected RuntimeMetadata metadata;
protected ODataDocumentNode rootDocument;
protected DocumentNode joinNode;
protected ArrayList<ODataDocumentNode> complexTables = new ArrayList<ODataDocumentNode>();
protected ArrayList<ODataDocumentNode> expandTables = new ArrayList<ODataDocumentNode>();
public ODataQuery(ODataExecutionFactory executionFactory, RuntimeMetadata metadata) {
this.executionFactory = executionFactory;
this.metadata = metadata;
}
public void addRootDocument(Table table) throws TranslatorException {
ODataDocumentNode node = null;
if (this.rootDocument == null) {
if (ODataMetadataProcessor.isEntitySet(table)) {
node = new ODataDocumentNode(table, ODataDocumentType.PRIMARY,
ODataMetadataProcessor.isCollection(table));
this.rootDocument = node;
this.joinNode = node;
} else {
// add the complex or expand tables
String parentTable = table.getProperty(ODataMetadataProcessor.MERGE, false);
if (parentTable == null) {
throw new TranslatorException(ODataPlugin.Event.TEIID17028,
ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17028, table.getName()));
}
addRootDocument(this.metadata.getTable(parentTable));
// if this is not complex/navigation we already added
// this as the parent document; no need to join
if (ODataMetadataProcessor.isComplexType(table)
|| ODataMetadataProcessor.isNavigationType(table)) {
joinChildDocument(table, JoinType.INNER_JOIN);
}
}
} else {
joinChildDocument(table, JoinType.INNER_JOIN);
}
}
private ODataDocumentNode addComplexOrNavigation(Table table) {
ODataDocumentNode node;
if (ODataMetadataProcessor.isComplexType(table)) {
node = new ODataDocumentNode(table, ODataDocumentType.COMPLEX,
ODataMetadataProcessor.isCollection(table));
this.complexTables.add(node);
} else if (ODataMetadataProcessor.isNavigationType(table)){
node = new ODataDocumentNode(table, ODataDocumentType.EXPAND,
ODataMetadataProcessor.isCollection(table));
this.expandTables.add(node);
} else {
node = new ODataDocumentNode(table, ODataDocumentType.EXPAND,
ODataMetadataProcessor.isCollection(table));
this.expandTables.add(node);
}
return node;
}
private void joinChildDocument(Table table, JoinType joinType) throws TranslatorException {
ODataDocumentNode node = addComplexOrNavigation(table);
this.joinNode = this.joinNode.joinWith(joinType, node);
}
public Condition addNavigation(Condition obj, JoinType joinType, Table right)
throws TranslatorException {
joinChildDocument(right, joinType);
return parseKeySegmentFromCondition(obj);
}
public Condition addNavigation(Condition obj, JoinType joinType, Table left, Table right)
throws TranslatorException {
addRootDocument(left);
joinChildDocument(right, joinType);
return parseKeySegmentFromCondition(obj);
}
public DocumentNode getRootDocument() {
return this.rootDocument;
}
protected String processFilter(Condition condition) throws TranslatorException {
List<Condition> crits = LanguageUtil.separateCriteriaByAnd(condition);
if (!crits.isEmpty()) {
for(Iterator<Condition> iter = crits.iterator(); iter.hasNext();) {
Condition crit = iter.next();
ODataFilterVisitor visitor = new ODataFilterVisitor(this.executionFactory, this.metadata, this);
visitor.appendFilter(crit);
}
}
StringBuilder sb = new StringBuilder();
if (this.rootDocument.getFilter() != null) {
sb.append(this.rootDocument.getFilter());
}
for (ODataDocumentNode use:this.complexTables) {
if (use.getFilter() != null) {
if (sb.length() > 0) {
sb.append(" and ");
}
sb.append(use.getFilter());
}
}
return sb.length() == 0?null:sb.toString();
}
protected Condition parseKeySegmentFromCondition(Condition obj)
throws TranslatorException {
List<Condition> crits = LanguageUtil.separateCriteriaByAnd(obj);
if (!crits.isEmpty()) {
boolean modified = false;
for(Iterator<Condition> iter = crits.iterator(); iter.hasNext();) {
Condition crit = iter.next();
if (crit instanceof Comparison) {
Comparison left = (Comparison) crit;
boolean leftAdded = parseKeySegmentFromComparison(left);
if (leftAdded) {
iter.remove();
modified = true;
}
}
}
if (modified) {
return LanguageUtil.combineCriteria(crits);
}
}
return obj;
}
private boolean parseKeySegmentFromComparison(Comparison obj) throws TranslatorException {
if (obj.getOperator().equals(Comparison.Operator.EQ)) {
if (obj.getLeftExpression() instanceof ColumnReference
&& obj.getRightExpression() instanceof ColumnReference) {
Column left = ((ColumnReference)obj.getLeftExpression()).getMetadataObject();
Column right = ((ColumnReference)obj.getRightExpression()).getMetadataObject();
if (isJoinOrPkColumn(left) && isJoinOrPkColumn(right)) {
// in odata the navigation from parent to child implicit by their keys
return true;
}
}
}
return false;
}
ODataDocumentNode getSchemaElement(Table table) {
if (this.rootDocument != null && this.rootDocument.getTable().equals(table)) {
return this.rootDocument;
}
for (ODataDocumentNode schemaElement:this.complexTables) {
if (schemaElement.getTable().equals(table)) {
return schemaElement;
}
}
for (ODataDocumentNode schemaElement:this.expandTables) {
if (schemaElement.getTable().equals(table)) {
return schemaElement;
}
}
return null;
}
private boolean isJoinOrPkColumn(Column column) {
Table table = (Table)column.getParent();
boolean isKey = (table.getPrimaryKey().getColumnByName(column.getName()) != null);
if (!isKey) {
for(ForeignKey fk:table.getForeignKeys()) {
if (fk.getColumnByName(column.getName()) != null) {
isKey = true;
}
}
}
return isKey;
}
}