001: package org.apache.torque.util;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.util.List;
023:
024: import org.apache.torque.TorqueException;
025: import org.apache.torque.adapter.DB;
026: import org.apache.torque.map.DatabaseMap;
027:
028: /**
029: * Factored out code that is used to generate Join Code. This code comes
030: * from BasePeer and is put here to reduce complexity in the BasePeer class.
031: * You should not use the methods here directly!
032: *
033: * @author <a href="mailto:fischer@seitenbau.de">Thomas Fischer</a>
034: * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
035: * @version $Id: JoinBuilder.java 535615 2007-05-06 14:11:05Z tv $
036: */
037: public final class JoinBuilder {
038: /**
039: * Private constructor to prevent initialisation.
040: *
041: * Class contains only static methods and should therefore not be
042: * instantiated.
043: */
044: private JoinBuilder() {
045: }
046:
047: /**
048: * adds the Joins from the criteria to the query
049: * @param criteria the criteria from which the Joins are taken
050: * @param query the query to which the Joins should be added
051: * @throws TorqueException if the Joins can not be processed
052: */
053: public static void processJoins(final DB db,
054: final DatabaseMap dbMap, final Criteria criteria,
055: final Query query) throws TorqueException {
056: List criteriaJoins = criteria.getJoins();
057:
058: if (criteriaJoins.isEmpty()) {
059: return;
060: }
061:
062: UniqueList queryFromClause = query.getFromClause();
063: UniqueList queryWhereClause = query.getWhereClause();
064:
065: for (int i = 0; i < criteriaJoins.size(); i++) {
066: Criteria.Join join = (Criteria.Join) criteriaJoins.get(i);
067: String leftColumn = join.getLeftColumn();
068: String rightColumn = join.getRightColumn();
069:
070: // check if the column names make sense
071: if (leftColumn.indexOf('.') == -1) {
072: SQLBuilder.throwMalformedColumnNameException("join",
073: leftColumn);
074: }
075: if (rightColumn.indexOf('.') == -1) {
076: SQLBuilder.throwMalformedColumnNameException("join",
077: rightColumn);
078: }
079:
080: // get the table names
081: // (and the alias names for them if necessary))
082: // Also check whether a case insensitive comparison is needed
083: int dot = leftColumn.lastIndexOf('.');
084: String leftTableName = leftColumn.substring(0, dot);
085:
086: leftTableName = SQLBuilder.getTableNameForFromClause(
087: leftTableName, criteria);
088:
089: dot = rightColumn.lastIndexOf('.');
090: String rightTableName = rightColumn.substring(0, dot);
091: String dbTableName = criteria
092: .getTableForAlias(rightTableName);
093:
094: if (dbTableName == null) {
095: dbTableName = rightTableName;
096: }
097:
098: String columnName = rightColumn.substring(dot + 1,
099: rightColumn.length());
100:
101: boolean ignoreCase = (criteria.isIgnoreCase() && (dbMap
102: .getTable(dbTableName).getColumn(columnName)
103: .getType() instanceof String));
104:
105: rightTableName = SQLBuilder.getTableNameForFromClause(
106: rightTableName, criteria);
107:
108: // now check the join type and add the join to the
109: // appropriate places in the query
110: SqlEnum joinType = join.getJoinType();
111:
112: if (joinType == null) {
113: // Do not treat join as explicit join, but add
114: // the join condition to the where clauses
115: if (!SQLBuilder.fromClauseContainsTableName(
116: queryFromClause, leftTableName)) {
117: Query.FromElement fromElement = new Query.FromElement(
118: leftTableName, null, null);
119: queryFromClause.add(fromElement);
120: }
121: if (!SQLBuilder.fromClauseContainsTableName(
122: queryFromClause, rightTableName)) {
123: Query.FromElement fromElement = new Query.FromElement(
124: rightTableName, null, null);
125: queryFromClause.add(fromElement);
126: }
127: queryWhereClause.add(SqlExpression.buildInnerJoin(
128: leftColumn, rightColumn, ignoreCase, db));
129: } else {
130: // check whether the order of the join must be "reversed"
131: // This if the case if the fromClause already contains
132: // rightTableName
133:
134: if (!SQLBuilder.fromClauseContainsTableName(
135: queryFromClause, rightTableName)) {
136: if (!SQLBuilder.fromClauseContainsTableName(
137: queryFromClause, leftTableName)) {
138: Query.FromElement fromElement = new Query.FromElement(
139: leftTableName, null, null);
140: queryFromClause.add(fromElement);
141: }
142:
143: Query.FromElement fromElement = new Query.FromElement(
144: rightTableName, joinType,
145: SqlExpression.buildInnerJoin(leftColumn,
146: rightColumn, ignoreCase, db));
147: queryFromClause.add(fromElement);
148: } else {
149: if (SQLBuilder.fromClauseContainsTableName(
150: queryFromClause, leftTableName)) {
151: // We cannot add an explicit join if both tables
152: // are alredy present in the from clause
153: throw new TorqueException("Unable to create a "
154: + joinType
155: + "because both table names "
156: + leftTableName + " and "
157: + rightTableName
158: + " are already in use. "
159: + "Try to create an(other) alias.");
160: }
161: // now add the join in reverse order
162: // rightTableName must not be added
163: // because it is already present
164: Query.FromElement fromElement = new Query.FromElement(
165: leftTableName, reverseJoinType(joinType),
166: SqlExpression.buildInnerJoin(rightColumn,
167: leftColumn, ignoreCase, db));
168: queryFromClause.add(fromElement);
169: }
170: }
171: }
172: }
173:
174: /**
175: * returns the reversed Join type, i.e. the join type which would produce
176: * the same result if also the joined tables were exchanged:
177: * Example:<br />
178: * table_a left join table_b <br />
179: * produces the same result as <br />
180: * table_b right join table_a<br />
181: * So "left join" is the reverse of "right join"
182: * @param joinType the join type to be reversed
183: * @return the reversed join type
184: */
185: private static SqlEnum reverseJoinType(final SqlEnum joinType) {
186: if (SqlEnum.LEFT_JOIN.equals(joinType)) {
187: return SqlEnum.RIGHT_JOIN;
188: } else if (SqlEnum.RIGHT_JOIN.equals(joinType)) {
189: return SqlEnum.LEFT_JOIN;
190: } else {
191: return joinType;
192: }
193: }
194: }
|