/* * 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.jpa; import static org.teiid.language.SQLConstants.Reserved.*; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.concurrent.atomic.AtomicInteger; import org.teiid.language.ColumnReference; import org.teiid.language.Function; import org.teiid.language.Join; import org.teiid.language.Join.JoinType; import org.teiid.language.NamedTable; import org.teiid.language.SQLConstants.Tokens; import org.teiid.language.Select; import org.teiid.language.TableReference; import org.teiid.language.visitor.HierarchyVisitor; import org.teiid.language.visitor.SQLStringVisitor; import org.teiid.metadata.AbstractMetadataRecord; import org.teiid.metadata.ForeignKey; import org.teiid.metadata.RuntimeMetadata; import org.teiid.metadata.Table; import org.teiid.translator.TranslatorException; /** * This visitor converts the Teiid command into JPQL string */ public class JPQLSelectVisitor extends HierarchyVisitor { protected JPA2ExecutionFactory executionFactory; protected static final String UNDEFINED = "<undefined>"; //$NON-NLS-1$ private LinkedList<JoinTable> joins = new LinkedList<JoinTable>(); protected ArrayList<TranslatorException> exceptions = new ArrayList<TranslatorException>(); protected LinkedHashMap<String, NamedTable> implicitGroups = new LinkedHashMap<String, NamedTable>(); protected AtomicInteger aliasCounter = new AtomicInteger(0); protected RuntimeMetadata metadata; public JPQLSelectVisitor(JPA2ExecutionFactory executionFactory, RuntimeMetadata metadata) { super(false); this.executionFactory = executionFactory; this.metadata = metadata; } public static String getJPQLString(Select obj, JPA2ExecutionFactory executionFactory, RuntimeMetadata metadata) throws TranslatorException { JPQLSelectVisitor visitor = new JPQLSelectVisitor(executionFactory, metadata); visitor.visitNode(obj); if (!visitor.exceptions.isEmpty()) { throw visitor.exceptions.get(0); } return visitor.convertToQuery(obj); } private String convertToQuery(Select obj) { JPQLSelectStringVisitor visitor = new JPQLSelectStringVisitor(this); visitor.visitNode(obj); return visitor.toString(); } @Override public void visit(Select obj) { visitNodes(obj.getDerivedColumns()); visitNodes(obj.getFrom()); visitNode(obj.getWhere()); visitNode(obj.getGroupBy()); visitNode(obj.getHaving()); visitNode(obj.getOrderBy()); } @Override public void visit(ColumnReference obj) { AbstractMetadataRecord record = obj.getMetadataObject(); if (record != null) { String name = record.getProperty(JPAMetadataProcessor.KEY_ASSOSIATED_WITH_FOREIGN_TABLE, false); if (name != null) { try { Table t = this.metadata.getTable(name); if (this.implicitGroups.get(name) == null) { this.implicitGroups.put(name, new NamedTable(t.getName(), "J_"+this.aliasCounter.getAndIncrement(), t)); //$NON-NLS-1$ } } catch (TranslatorException e) { exceptions.add(e); } } } } private boolean alreadyInJoin(String tableName) { for(JoinTable joinTable:this.joins) { if ((joinTable.left != null && joinTable.left.getMetadataObject().getFullName().equals(tableName)) || (joinTable.right != null && joinTable.right.getMetadataObject().getFullName().equals(tableName))) { return true; } } return false; } @Override public void visit(NamedTable obj) { if (implicitGroups.isEmpty()) { this.joins.add(new JoinTable(obj, null, JoinType.INNER_JOIN)); } else { for (NamedTable table:this.implicitGroups.values()) { this.joins.add(new JoinTable(obj, table, JoinType.INNER_JOIN)); } } } @Override public void visit(Join obj) { try { handleJoin(obj); for (NamedTable table:this.implicitGroups.values()) { NamedTable parent = findParent(table); if (parent != null) { if (!alreadyInJoin(table.getMetadataObject().getFullName())) { this.joins.add(new JoinTable(parent, table, JoinType.INNER_JOIN)); } } else { exceptions.add(new TranslatorException(JPAPlugin.Util.gs(JPAPlugin.Event.TEIID14004, table.getName()))); } } } catch (TranslatorException e) { exceptions.add(e); } } private NamedTable findParent(NamedTable child) { for (JoinTable jt:this.joins) { if (jt.getParent() != null) { if (isParentOf(jt.getParent(), child)) { return jt.getParent(); } } if (jt.getChild() != null) { if (isParentOf(jt.getChild(), child)) { return jt.getChild(); } } } return null; } private JoinTable handleJoin(Join obj) throws TranslatorException { TableReference left = obj.getLeftItem(); TableReference right = obj.getRightItem(); if ((left instanceof NamedTable) && (right instanceof NamedTable)) { JoinTable join = handleJoin(obj.getJoinType(), left, right, true); this.joins.add(join); return join; } JoinTable leftJoin = null; if (left instanceof Join) { leftJoin = handleJoin((Join)left); if (right instanceof NamedTable) { JoinTable join = handleJoin(obj.getJoinType(), leftJoin, (NamedTable)right); this.joins.add(join); return join; } } JoinTable rightJoin = null; if (right instanceof Join) { rightJoin = handleJoin((Join)right); if (left instanceof NamedTable) { JoinTable join = handleJoin(obj.getJoinType(), (NamedTable)left, rightJoin); this.joins.add(join); return join; } } throw new TranslatorException(JPAPlugin.Util.gs(JPAPlugin.Event.TEIID14005)); } private JoinTable handleJoin(Join.JoinType joinType, JoinTable left, NamedTable right) throws TranslatorException { // first we need to find correct parent for the right JoinTable withParent = handleJoin(joinType, left.getParent(), right, false); JoinTable withChild = handleJoin(joinType, left.getChild(), right, false); NamedTable parent = (withParent.getParent() != null)?withParent.getParent():withChild.getParent(); if (parent != null) { return handleJoin(joinType, parent, right, true); } throw new TranslatorException(JPAPlugin.Util.gs(JPAPlugin.Event.TEIID14006)); } private JoinTable handleJoin(Join.JoinType joinType, NamedTable left, JoinTable right) throws TranslatorException { // first we need to find correct parent for the left JoinTable withParent = handleJoin(joinType, left, right.getParent(), false); JoinTable withChild = handleJoin(joinType, left, right.getChild(), false); NamedTable parent = (withParent.getParent() != null)?withParent.getParent():withChild.getParent(); if (parent != null) { return handleJoin(joinType, left, parent, true); } throw new TranslatorException(JPAPlugin.Util.gs(JPAPlugin.Event.TEIID14006)); } private JoinTable handleJoin(Join.JoinType joinType, TableReference left, TableReference right, boolean fixCorrelatedNames) { // both sides are named tables NamedTable leftTable = (NamedTable)left; NamedTable rightTable = (NamedTable)right; JoinTable joinTable = new JoinTable(leftTable, rightTable, joinType); if (fixCorrelatedNames) { // fix left table's correleated name NamedTable table = this.implicitGroups.get(leftTable.getMetadataObject().getFullName()); if (table != null) { table.setCorrelationName(leftTable.getCorrelationName()); } // fix right table's correleated name table = this.implicitGroups.get(rightTable.getMetadataObject().getFullName()); if (table != null) { table.setCorrelationName(rightTable.getCorrelationName()); } } return joinTable; } private boolean isParentOf(NamedTable parent, NamedTable child) { for (ForeignKey fk:parent.getMetadataObject().getForeignKeys()){ if (fk.getPrimaryKey().getParent().equals(child.getMetadataObject())) { return true; } } return false; } static class JoinTable { NamedTable parent; NamedTable child; NamedTable left; NamedTable right; String childAttributeName; String parentAttributeName; JoinType joinType; JoinTable(NamedTable customer, NamedTable address, JoinType type) { this.left = customer; this.right = address; this.joinType = type; if (address == null) { this.parent = customer; this.parentAttributeName = customer.getName(); } else { for (ForeignKey fk:customer.getMetadataObject().getForeignKeys()){ if (fk.getReferenceKey().getParent().equals(address.getMetadataObject())) { this.parent = customer; this.child = address; this.childAttributeName = fk.getSourceName(); this.parentAttributeName = customer.getName(); } } if (this.parent == null) { for (ForeignKey fk:address.getMetadataObject().getForeignKeys()){ if (fk.getReferenceKey().getParent().equals(customer.getMetadataObject())) { this.parent = address; this.child = customer; this.childAttributeName = fk.getSourceName(); this.parentAttributeName = customer.getName(); } } } } } NamedTable getParent() { return this.parent; } NamedTable getChild() { return this.child; } NamedTable getLeft() { return this.left; } NamedTable getRight() { return this.right; } String childAttributeName() { return this.childAttributeName; } String parentAttributeName() { return this.parentAttributeName; } public boolean isLeftParent() { return this.left == this.parent; } } static class JPQLSelectStringVisitor extends SQLStringVisitor { private JPQLSelectVisitor visitor; public JPQLSelectStringVisitor(JPQLSelectVisitor visitor) { this.visitor = visitor; } @Override public void visit(Select obj) { buffer.append(SELECT).append(Tokens.SPACE); if (obj.isDistinct()) { buffer.append(DISTINCT).append(Tokens.SPACE); } append(obj.getDerivedColumns()); if (obj.getFrom() != null && !obj.getFrom().isEmpty()) { buffer.append(Tokens.SPACE).append(FROM).append(Tokens.SPACE); append(obj.getFrom()); } if (obj.getWhere() != null) { buffer.append(Tokens.SPACE) .append(WHERE) .append(Tokens.SPACE); append(obj.getWhere()); } if (obj.getGroupBy() != null) { buffer.append(Tokens.SPACE); append(obj.getGroupBy()); } if (obj.getHaving() != null) { buffer.append(Tokens.SPACE) .append(HAVING) .append(Tokens.SPACE); append(obj.getHaving()); } if (obj.getOrderBy() != null) { buffer.append(Tokens.SPACE); append(obj.getOrderBy()); } } @Override public void visit(ColumnReference column) { AbstractMetadataRecord record = column.getMetadataObject(); if (record != null) { String name = record.getProperty(JPAMetadataProcessor.KEY_ASSOSIATED_WITH_FOREIGN_TABLE, false); if (name == null) { buffer.append(column.getTable().getCorrelationName()).append(Tokens.DOT).append(column.getMetadataObject().getName()); } else { buffer.append(this.visitor.implicitGroups.get(name).getCorrelationName()).append(Tokens.DOT).append(column.getMetadataObject().getName()); } } else { buffer.append(column.getName()); } } @Override public void visit(Join obj) { addFromClause(); } @Override public void visit(Function func) { if (visitor.executionFactory.getFunctionModifiers().containsKey(func.getName())) { visitor.executionFactory.getFunctionModifiers().get(func.getName()).translate(func); } super.visit(func); } @Override public void visit(NamedTable obj) { addFromClause(); } private void addFromClause() { boolean first = true; for (JoinTable joinTable:this.visitor.joins) { if (!joinTable.isLeftParent() && joinTable.joinType == JoinType.LEFT_OUTER_JOIN) { joinTable.joinType = JoinType.RIGHT_OUTER_JOIN; } if (first) { buffer.append(joinTable.getParent().getName()); buffer.append(Tokens.SPACE); buffer.append(AS).append(Tokens.SPACE); buffer.append(joinTable.getParent().getCorrelationName()); first = false; } if (joinTable.getChild() != null) { buffer.append(Tokens.SPACE); switch(joinTable.joinType) { case CROSS_JOIN: buffer.append(CROSS); break; case FULL_OUTER_JOIN: buffer.append(FULL) .append(Tokens.SPACE) .append(OUTER); break; case INNER_JOIN: buffer.append(INNER); break; case LEFT_OUTER_JOIN: buffer.append(LEFT) .append(Tokens.SPACE) .append(OUTER); break; case RIGHT_OUTER_JOIN: buffer.append(RIGHT) .append(Tokens.SPACE) .append(OUTER); break; default: buffer.append(UNDEFINED); } buffer.append(Tokens.SPACE).append(JOIN).append(Tokens.SPACE); buffer.append(joinTable.getParent().getCorrelationName()).append(Tokens.DOT).append(joinTable.childAttributeName()); buffer.append(Tokens.SPACE); buffer.append(AS).append(Tokens.SPACE); buffer.append(joinTable.getChild().getCorrelationName()); } } } } }