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.engine.query;
26
27 import org.hibernate.util.SimpleMRUCache;
28 import org.hibernate.util.SoftLimitMRUCache;
29 import org.hibernate.engine.SessionFactoryImplementor;
30 import org.hibernate.engine.query.sql.NativeSQLQuerySpecification;
31 import org.hibernate.QueryException;
32 import org.hibernate.MappingException;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 import java.io.Serializable;
37 import java.util.Map;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.Set;
41 import java.util.HashSet;
42 import java.util.Collections;
43
44 /**
45 * Acts as a cache for compiled query plans, as well as query-parameter metadata.
46 *
47 * @author Steve Ebersole
48 */
49 public class QueryPlanCache implements Serializable {
50
51 private static final Logger log = LoggerFactory.getLogger( QueryPlanCache.class );
52
53 private SessionFactoryImplementor factory;
54
55 public QueryPlanCache(SessionFactoryImplementor factory) {
56 this.factory = factory;
57 }
58
59 // simple cache of param metadata based on query string. Ideally, the
60 // original "user-supplied query" string should be used to retreive this
61 // metadata (i.e., not the para-list-expanded query string) to avoid
62 // unnecessary cache entries.
63 // Used solely for caching param metadata for native-sql queries, see
64 // getSQLParameterMetadata() for a discussion as to why...
65 private final SimpleMRUCache sqlParamMetadataCache = new SimpleMRUCache();
66
67 // the cache of the actual plans...
68 private final SoftLimitMRUCache planCache = new SoftLimitMRUCache( 128 );
69
70
71 public ParameterMetadata getSQLParameterMetadata(String query) {
72 ParameterMetadata metadata = ( ParameterMetadata ) sqlParamMetadataCache.get( query );
73 if ( metadata == null ) {
74 // for native-sql queries, the param metadata is determined outside
75 // any relation to a query plan, because query plan creation and/or
76 // retreival for a native-sql query depends on all of the return
77 // types having been set, which might not be the case up-front when
78 // param metadata would be most useful
79 metadata = buildNativeSQLParameterMetadata( query );
80 sqlParamMetadataCache.put( query, metadata );
81 }
82 return metadata;
83 }
84
85 public HQLQueryPlan getHQLQueryPlan(String queryString, boolean shallow, Map enabledFilters)
86 throws QueryException, MappingException {
87 HQLQueryPlanKey key = new HQLQueryPlanKey( queryString, shallow, enabledFilters );
88 HQLQueryPlan plan = ( HQLQueryPlan ) planCache.get ( key );
89
90 if ( plan == null ) {
91 if ( log.isTraceEnabled() ) {
92 log.trace( "unable to locate HQL query plan in cache; generating (" + queryString + ")" );
93 }
94 plan = new HQLQueryPlan(queryString, shallow, enabledFilters, factory );
95 }
96 else {
97 if ( log.isTraceEnabled() ) {
98 log.trace( "located HQL query plan in cache (" + queryString + ")" );
99 }
100 }
101
102 planCache.put( key, plan );
103
104 return plan;
105 }
106
107 public FilterQueryPlan getFilterQueryPlan(String filterString, String collectionRole, boolean shallow, Map enabledFilters)
108 throws QueryException, MappingException {
109 FilterQueryPlanKey key = new FilterQueryPlanKey( filterString, collectionRole, shallow, enabledFilters );
110 FilterQueryPlan plan = ( FilterQueryPlan ) planCache.get ( key );
111
112 if ( plan == null ) {
113 if ( log.isTraceEnabled() ) {
114 log.trace( "unable to locate collection-filter query plan in cache; generating (" + collectionRole + " : " + filterString + ")" );
115 }
116 plan = new FilterQueryPlan( filterString, collectionRole, shallow, enabledFilters, factory );
117 }
118 else {
119 if ( log.isTraceEnabled() ) {
120 log.trace( "located collection-filter query plan in cache (" + collectionRole + " : " + filterString + ")" );
121 }
122 }
123
124 planCache.put( key, plan );
125
126 return plan;
127 }
128
129 public NativeSQLQueryPlan getNativeSQLQueryPlan(NativeSQLQuerySpecification spec) {
130 NativeSQLQueryPlan plan = ( NativeSQLQueryPlan ) planCache.get( spec );
131
132 if ( plan == null ) {
133 if ( log.isTraceEnabled() ) {
134 log.trace( "unable to locate native-sql query plan in cache; generating (" + spec.getQueryString() + ")" );
135 }
136 plan = new NativeSQLQueryPlan( spec, factory );
137 }
138 else {
139 if ( log.isTraceEnabled() ) {
140 log.trace( "located native-sql query plan in cache (" + spec.getQueryString() + ")" );
141 }
142 }
143
144 planCache.put( spec, plan );
145 return plan;
146 }
147
148 private ParameterMetadata buildNativeSQLParameterMetadata(String sqlString) {
149 ParamLocationRecognizer recognizer = ParamLocationRecognizer.parseLocations( sqlString );
150
151 OrdinalParameterDescriptor[] ordinalDescriptors =
152 new OrdinalParameterDescriptor[ recognizer.getOrdinalParameterLocationList().size() ];
153 for ( int i = 0; i < recognizer.getOrdinalParameterLocationList().size(); i++ ) {
154 final Integer position = ( Integer ) recognizer.getOrdinalParameterLocationList().get( i );
155 ordinalDescriptors[i] = new OrdinalParameterDescriptor( i, null, position.intValue() );
156 }
157
158 Iterator itr = recognizer.getNamedParameterDescriptionMap().entrySet().iterator();
159 Map namedParamDescriptorMap = new HashMap();
160 while( itr.hasNext() ) {
161 final Map.Entry entry = ( Map.Entry ) itr.next();
162 final String name = ( String ) entry.getKey();
163 final ParamLocationRecognizer.NamedParameterDescription description =
164 ( ParamLocationRecognizer.NamedParameterDescription ) entry.getValue();
165 namedParamDescriptorMap.put(
166 name ,
167 new NamedParameterDescriptor( name, null, description.buildPositionsArray(), description.isJpaStyle() )
168 );
169 }
170
171 return new ParameterMetadata( ordinalDescriptors, namedParamDescriptorMap );
172 }
173
174 private static class HQLQueryPlanKey implements Serializable {
175 private final String query;
176 private final boolean shallow;
177 private final Set filterNames;
178 private final int hashCode;
179
180 public HQLQueryPlanKey(String query, boolean shallow, Map enabledFilters) {
181 this.query = query;
182 this.shallow = shallow;
183
184 if ( enabledFilters == null || enabledFilters.isEmpty() ) {
185 filterNames = Collections.EMPTY_SET;
186 }
187 else {
188 Set tmp = new HashSet();
189 tmp.addAll( enabledFilters.keySet() );
190 this.filterNames = Collections.unmodifiableSet( tmp );
191 }
192
193 int hash = query.hashCode();
194 hash = 29 * hash + ( shallow ? 1 : 0 );
195 hash = 29 * hash + filterNames.hashCode();
196 this.hashCode = hash;
197 }
198
199 public boolean equals(Object o) {
200 if ( this == o ) {
201 return true;
202 }
203 if ( o == null || getClass() != o.getClass() ) {
204 return false;
205 }
206
207 final HQLQueryPlanKey that = ( HQLQueryPlanKey ) o;
208
209 if ( shallow != that.shallow ) {
210 return false;
211 }
212 if ( !filterNames.equals( that.filterNames ) ) {
213 return false;
214 }
215 if ( !query.equals( that.query ) ) {
216 return false;
217 }
218
219 return true;
220 }
221
222 public int hashCode() {
223 return hashCode;
224 }
225 }
226
227 private static class FilterQueryPlanKey implements Serializable {
228 private final String query;
229 private final String collectionRole;
230 private final boolean shallow;
231 private final Set filterNames;
232 private final int hashCode;
233
234 public FilterQueryPlanKey(String query, String collectionRole, boolean shallow, Map enabledFilters) {
235 this.query = query;
236 this.collectionRole = collectionRole;
237 this.shallow = shallow;
238
239 if ( enabledFilters == null || enabledFilters.isEmpty() ) {
240 filterNames = Collections.EMPTY_SET;
241 }
242 else {
243 Set tmp = new HashSet();
244 tmp.addAll( enabledFilters.keySet() );
245 this.filterNames = Collections.unmodifiableSet( tmp );
246 }
247
248 int hash = query.hashCode();
249 hash = 29 * hash + collectionRole.hashCode();
250 hash = 29 * hash + ( shallow ? 1 : 0 );
251 hash = 29 * hash + filterNames.hashCode();
252 this.hashCode = hash;
253 }
254
255 public boolean equals(Object o) {
256 if ( this == o ) {
257 return true;
258 }
259 if ( o == null || getClass() != o.getClass() ) {
260 return false;
261 }
262
263 final FilterQueryPlanKey that = ( FilterQueryPlanKey ) o;
264
265 if ( shallow != that.shallow ) {
266 return false;
267 }
268 if ( !filterNames.equals( that.filterNames ) ) {
269 return false;
270 }
271 if ( !query.equals( that.query ) ) {
272 return false;
273 }
274 if ( !collectionRole.equals( that.collectionRole ) ) {
275 return false;
276 }
277
278 return true;
279 }
280
281 public int hashCode() {
282 return hashCode;
283 }
284 }
285 }