1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.hql.ast.util;
26
27 import java.util.HashMap;
28 import java.util.Map;
29
30 import org.hibernate.MappingException;
31 import org.hibernate.QueryException;
32 import org.hibernate.dialect.function.SQLFunction;
33 import org.hibernate.engine.JoinSequence;
34 import org.hibernate.engine.SessionFactoryImplementor;
35 import org.hibernate.hql.NameGenerator;
36 import org.hibernate.hql.ast.DetailedSemanticException;
37 import org.hibernate.hql.ast.QuerySyntaxException;
38 import org.hibernate.hql.ast.tree.SqlNode;
39 import org.hibernate.persister.collection.CollectionPropertyMapping;
40 import org.hibernate.persister.collection.CollectionPropertyNames;
41 import org.hibernate.persister.collection.QueryableCollection;
42 import org.hibernate.persister.entity.EntityPersister;
43 import org.hibernate.persister.entity.PropertyMapping;
44 import org.hibernate.persister.entity.Queryable;
45 import org.hibernate.type.AssociationType;
46 import org.hibernate.type.CollectionType;
47 import org.hibernate.type.EntityType;
48 import org.hibernate.type.Type;
49 import org.hibernate.type.TypeFactory;
50
51 import antlr.SemanticException;
52 import antlr.collections.AST;
53
54 /**
55 * Helper for performing common and/or complex operations with the
56 * {@link SessionFactoryImplementor} during translation of an HQL query.
57 *
58 * @author Joshua Davis
59 */
60 public class SessionFactoryHelper {
61
62 private SessionFactoryImplementor sfi;
63 private Map collectionPropertyMappingByRole;
64
65 /**
66 * Construct a new SessionFactoryHelper instance.
67 *
68 * @param sfi The SessionFactory impl to be encapsualted.
69 */
70 public SessionFactoryHelper(SessionFactoryImplementor sfi) {
71 this.sfi = sfi;
72 collectionPropertyMappingByRole = new HashMap();
73 }
74
75 /**
76 * Get a handle to the encapsulated SessionFactory.
77 *
78 * @return The encapsulated SessionFactory.
79 */
80 public SessionFactoryImplementor getFactory() {
81 return sfi;
82 }
83
84 /**
85 * Does the given persister define a physical discriminator column
86 * for the purpose of inheritence discrimination?
87 *
88 * @param persister The persister to be checked.
89 * @return True if the persister does define an actual discriminator column.
90 */
91 public boolean hasPhysicalDiscriminatorColumn(Queryable persister) {
92 if ( persister.getDiscriminatorType() != null ) {
93 String discrimColumnName = persister.getDiscriminatorColumnName();
94 // Needed the "clazz_" check to work around union-subclasses
95 // TODO : is there a way to tell whether a persister is truly discrim-column based inheritence?
96 if ( discrimColumnName != null && !"clazz_".equals( discrimColumnName ) ) {
97 return true;
98 }
99 }
100 return false;
101 }
102
103 /**
104 * Given a (potentially unqualified) class name, locate its imported qualified name.
105 *
106 * @param className The potentially unqualified class name
107 * @return The qualified class name.
108 */
109 public String getImportedClassName(String className) {
110 return sfi.getImportedClassName( className );
111 }
112
113 /**
114 * Given a (potentially unqualified) class name, locate its persister.
115 *
116 * @param className The (potentially unqualified) class name.
117 * @return The defined persister for this class, or null if none found.
118 */
119 public Queryable findQueryableUsingImports(String className) {
120 return findQueryableUsingImports( sfi, className );
121 }
122
123
124 /**
125 * Given a (potentially unqualified) class name, locate its persister.
126 *
127 * @param sfi The session factory implementor.
128 * @param className The (potentially unqualified) class name.
129 * @return The defined persister for this class, or null if none found.
130 */
131 public static Queryable findQueryableUsingImports(SessionFactoryImplementor sfi, String className) {
132 final String importedClassName = sfi.getImportedClassName( className );
133 if ( importedClassName == null ) {
134 return null;
135 }
136 try {
137 return ( Queryable ) sfi.getEntityPersister( importedClassName );
138 }
139 catch ( MappingException me ) {
140 return null;
141 }
142 }
143
144 /**
145 * Locate the persister by class or entity name.
146 *
147 * @param name The class or entity name
148 * @return The defined persister for this entity, or null if none found.
149 * @throws MappingException
150 */
151 private EntityPersister findEntityPersisterByName(String name) throws MappingException {
152 // First, try to get the persister using the given name directly.
153 try {
154 return sfi.getEntityPersister( name );
155 }
156 catch ( MappingException ignore ) {
157 // unable to locate it using this name
158 }
159
160 // If that didn't work, try using the 'import' name.
161 String importedClassName = sfi.getImportedClassName( name );
162 if ( importedClassName == null ) {
163 return null;
164 }
165 return sfi.getEntityPersister( importedClassName );
166 }
167
168 /**
169 * Locate the persister by class or entity name, requiring that such a persister
170 * exist.
171 *
172 * @param name The class or entity name
173 * @return The defined persister for this entity
174 * @throws SemanticException Indicates the persister could not be found
175 */
176 public EntityPersister requireClassPersister(String name) throws SemanticException {
177 EntityPersister cp;
178 try {
179 cp = findEntityPersisterByName( name );
180 if ( cp == null ) {
181 throw new QuerySyntaxException( name + " is not mapped" );
182 }
183 }
184 catch ( MappingException e ) {
185 throw new DetailedSemanticException( e.getMessage(), e );
186 }
187 return cp;
188 }
189
190 /**
191 * Locate the collection persister by the collection role.
192 *
193 * @param role The collection role name.
194 * @return The defined CollectionPersister for this collection role, or null.
195 */
196 public QueryableCollection getCollectionPersister(String role) {
197 try {
198 return ( QueryableCollection ) sfi.getCollectionPersister( role );
199 }
200 catch ( ClassCastException cce ) {
201 throw new QueryException( "collection is not queryable: " + role );
202 }
203 catch ( Exception e ) {
204 throw new QueryException( "collection not found: " + role );
205 }
206 }
207
208 /**
209 * Locate the collection persister by the collection role, requiring that
210 * such a persister exist.
211 *
212 * @param role The collection role name.
213 * @return The defined CollectionPersister for this collection role.
214 * @throws QueryException Indicates that the collection persister could not be found.
215 */
216 public QueryableCollection requireQueryableCollection(String role) throws QueryException {
217 try {
218 QueryableCollection queryableCollection = ( QueryableCollection ) sfi.getCollectionPersister( role );
219 if ( queryableCollection != null ) {
220 collectionPropertyMappingByRole.put( role, new CollectionPropertyMapping( queryableCollection ) );
221 }
222 return queryableCollection;
223 }
224 catch ( ClassCastException cce ) {
225 throw new QueryException( "collection role is not queryable: " + role );
226 }
227 catch ( Exception e ) {
228 throw new QueryException( "collection role not found: " + role );
229 }
230 }
231
232 /**
233 * Retreive a PropertyMapping describing the given collection role.
234 *
235 * @param role The collection role for whcih to retrieve the property mapping.
236 * @return The property mapping.
237 */
238 private PropertyMapping getCollectionPropertyMapping(String role) {
239 return ( PropertyMapping ) collectionPropertyMappingByRole.get( role );
240 }
241
242 /**
243 * Retrieves the column names corresponding to the collection elements for the given
244 * collection role.
245 *
246 * @param role The collection role
247 * @param roleAlias The sql column-qualification alias (i.e., the table alias)
248 * @return the collection element columns
249 */
250 public String[] getCollectionElementColumns(String role, String roleAlias) {
251 return getCollectionPropertyMapping( role ).toColumns( roleAlias, CollectionPropertyNames.COLLECTION_ELEMENTS );
252 }
253
254 /**
255 * Generate an empty join sequence instance.
256 *
257 * @return The generate join sequence.
258 */
259 public JoinSequence createJoinSequence() {
260 return new JoinSequence( sfi );
261 }
262
263 /**
264 * Generate a join sequence representing the given association type.
265 *
266 * @param implicit Should implicit joins (theta-style) or explicit joins (ANSI-style) be rendered
267 * @param associationType The type representing the thing to be joined into.
268 * @param tableAlias The table alias to use in qualifing the join conditions
269 * @param joinType The type of join to render (inner, outer, etc); see {@link org.hibernate.sql.JoinFragment}
270 * @param columns The columns making up the condition of the join.
271 * @return The generated join sequence.
272 */
273 public JoinSequence createJoinSequence(boolean implicit, AssociationType associationType, String tableAlias, int joinType, String[] columns) {
274 JoinSequence joinSequence = createJoinSequence();
275 joinSequence.setUseThetaStyle( implicit ); // Implicit joins use theta style (WHERE pk = fk), explicit joins use JOIN (after from)
276 joinSequence.addJoin( associationType, tableAlias, joinType, columns );
277 return joinSequence;
278 }
279
280 /**
281 * Create a join sequence rooted at the given collection.
282 *
283 * @param collPersister The persister for the collection at which the join should be rooted.
284 * @param collectionName The alias to use for qualifying column references.
285 * @return The generated join sequence.
286 */
287 public JoinSequence createCollectionJoinSequence(QueryableCollection collPersister, String collectionName) {
288 JoinSequence joinSequence = createJoinSequence();
289 joinSequence.setRoot( collPersister, collectionName );
290 joinSequence.setUseThetaStyle( true ); // TODO: figure out how this should be set.
291 ///////////////////////////////////////////////////////////////////////////////
292 // This was the reason for failures regarding INDEX_OP and subclass joins on
293 // theta-join dialects; not sure what behaviour we were trying to emulate ;)
294 // joinSequence = joinSequence.getFromPart(); // Emulate the old addFromOnly behavior.
295 return joinSequence;
296 }
297
298 /**
299 * Determine the name of the property for the entity encapsulated by the
300 * given type which represents the id or unique-key.
301 *
302 * @param entityType The type representing the entity.
303 * @return The corresponding property name
304 * @throws QueryException Indicates such a property could not be found.
305 */
306 public String getIdentifierOrUniqueKeyPropertyName(EntityType entityType) {
307 try {
308 return entityType.getIdentifierOrUniqueKeyPropertyName( sfi );
309 }
310 catch ( MappingException me ) {
311 throw new QueryException( me );
312 }
313 }
314
315 /**
316 * Retreive the number of columns represented by this type.
317 *
318 * @param type The type.
319 * @return The number of columns.
320 */
321 public int getColumnSpan(Type type) {
322 return type.getColumnSpan( sfi );
323 }
324
325 /**
326 * Given a collection type, determine the entity name of the elements
327 * contained within instance of that collection.
328 *
329 * @param collectionType The collection type to check.
330 * @return The entity name of the elements of this collection.
331 */
332 public String getAssociatedEntityName(CollectionType collectionType) {
333 return collectionType.getAssociatedEntityName( sfi );
334 }
335
336 /**
337 * Given a collection type, determine the Type representing elements
338 * within instances of that collection.
339 *
340 * @param collectionType The collection type to be checked.
341 * @return The Type of the elements of the collection.
342 */
343 private Type getElementType(CollectionType collectionType) {
344 return collectionType.getElementType( sfi );
345 }
346
347 /**
348 * Essentially the same as {@link #getElementType}, but requiring that the
349 * element type be an association type.
350 *
351 * @param collectionType The collection type to be checked.
352 * @return The AssociationType of the elements of the collection.
353 */
354 public AssociationType getElementAssociationType(CollectionType collectionType) {
355 return ( AssociationType ) getElementType( collectionType );
356 }
357
358 /**
359 * Locate a registered sql function by name.
360 *
361 * @param functionName The name of the function to locate
362 * @return The sql function, or null if not found.
363 */
364 public SQLFunction findSQLFunction(String functionName) {
365 return sfi.getSqlFunctionRegistry().findSQLFunction( functionName.toLowerCase() );
366 }
367
368 /**
369 * Locate a registered sql function by name, requiring that such a registered function exist.
370 *
371 * @param functionName The name of the function to locate
372 * @return The sql function.
373 * @throws QueryException Indicates no matching sql functions could be found.
374 */
375 private SQLFunction requireSQLFunction(String functionName) {
376 SQLFunction f = findSQLFunction( functionName );
377 if ( f == null ) {
378 throw new QueryException( "Unable to find SQL function: " + functionName );
379 }
380 return f;
381 }
382
383 /**
384 * Find the function return type given the function name and the first argument expression node.
385 *
386 * @param functionName The function name.
387 * @param first The first argument expression.
388 * @return the function return type given the function name and the first argument expression node.
389 */
390 public Type findFunctionReturnType(String functionName, AST first) {
391 // locate the registered function by the given name
392 SQLFunction sqlFunction = requireSQLFunction( functionName );
393
394 // determine the type of the first argument...
395 Type argumentType = null;
396 if ( first != null ) {
397 if ( "cast".equals(functionName) ) {
398 argumentType = TypeFactory.heuristicType( first.getNextSibling().getText() );
399 }
400 else if ( first instanceof SqlNode ) {
401 argumentType = ( (SqlNode) first ).getDataType();
402 }
403 }
404
405 return sqlFunction.getReturnType( argumentType, sfi );
406 }
407
408 public String[][] generateColumnNames(Type[] sqlResultTypes) {
409 return NameGenerator.generateColumnNames( sqlResultTypes, sfi );
410 }
411
412 public boolean isStrictJPAQLComplianceEnabled() {
413 return sfi.getSettings().isStrictJPAQLCompliance();
414 }
415 }