001: package org.hibernate.engine.query;
002:
003: import org.hibernate.util.ArrayHelper;
004: import org.hibernate.util.SimpleMRUCache;
005: import org.hibernate.util.SoftLimitMRUCache;
006: import org.hibernate.engine.SessionFactoryImplementor;
007: import org.hibernate.engine.query.sql.NativeSQLQuerySpecification;
008: import org.hibernate.QueryException;
009: import org.hibernate.MappingException;
010: import org.apache.commons.logging.Log;
011: import org.apache.commons.logging.LogFactory;
012:
013: import java.io.Serializable;
014: import java.util.Map;
015: import java.util.HashMap;
016: import java.util.List;
017: import java.util.Iterator;
018: import java.util.Set;
019: import java.util.HashSet;
020: import java.util.Collections;
021:
022: /**
023: * Acts as a cache for compiled query plans, as well as query-parameter metadata.
024: *
025: * @author <a href="mailto:steve@hibernate.org">Steve Ebersole </a>
026: */
027: public class QueryPlanCache implements Serializable {
028:
029: private static final Log log = LogFactory
030: .getLog(QueryPlanCache.class);
031:
032: private SessionFactoryImplementor factory;
033:
034: public QueryPlanCache(SessionFactoryImplementor factory) {
035: this .factory = factory;
036: }
037:
038: // simple cache of param metadata based on query string. Ideally, the
039: // original "user-supplied query" string should be used to retreive this
040: // metadata (i.e., not the para-list-expanded query string) to avoid
041: // unnecessary cache entries.
042: // Used solely for caching param metadata for native-sql queries, see
043: // getSQLParameterMetadata() for a discussion as to why...
044: private final SimpleMRUCache sqlParamMetadataCache = new SimpleMRUCache();
045:
046: // the cache of the actual plans...
047: private final SoftLimitMRUCache planCache = new SoftLimitMRUCache(
048: 128);
049:
050: public ParameterMetadata getSQLParameterMetadata(String query) {
051: ParameterMetadata metadata = (ParameterMetadata) sqlParamMetadataCache
052: .get(query);
053: if (metadata == null) {
054: // for native-sql queries, the param metadata is determined outside
055: // any relation to a query plan, because query plan creation and/or
056: // retreival for a native-sql query depends on all of the return
057: // types having been set, which might not be the case up-front when
058: // param metadata would be most useful
059: metadata = buildNativeSQLParameterMetadata(query);
060: sqlParamMetadataCache.put(query, metadata);
061: }
062: return metadata;
063: }
064:
065: public HQLQueryPlan getHQLQueryPlan(String queryString,
066: boolean shallow, Map enabledFilters) throws QueryException,
067: MappingException {
068: HQLQueryPlanKey key = new HQLQueryPlanKey(queryString, shallow,
069: enabledFilters);
070: HQLQueryPlan plan = (HQLQueryPlan) planCache.get(key);
071:
072: if (plan == null) {
073: if (log.isTraceEnabled()) {
074: log
075: .trace("unable to locate HQL query plan in cache; generating ("
076: + queryString + ")");
077: }
078: plan = new HQLQueryPlan(queryString, shallow,
079: enabledFilters, factory);
080: } else {
081: if (log.isTraceEnabled()) {
082: log.trace("located HQL query plan in cache ("
083: + queryString + ")");
084: }
085: }
086:
087: planCache.put(key, plan);
088:
089: return plan;
090: }
091:
092: public FilterQueryPlan getFilterQueryPlan(String filterString,
093: String collectionRole, boolean shallow, Map enabledFilters)
094: throws QueryException, MappingException {
095: FilterQueryPlanKey key = new FilterQueryPlanKey(filterString,
096: collectionRole, shallow, enabledFilters);
097: FilterQueryPlan plan = (FilterQueryPlan) planCache.get(key);
098:
099: if (plan == null) {
100: if (log.isTraceEnabled()) {
101: log
102: .trace("unable to locate collection-filter query plan in cache; generating ("
103: + collectionRole
104: + " : "
105: + filterString
106: + ")");
107: }
108: plan = new FilterQueryPlan(filterString, collectionRole,
109: shallow, enabledFilters, factory);
110: } else {
111: if (log.isTraceEnabled()) {
112: log
113: .trace("located collection-filter query plan in cache ("
114: + collectionRole
115: + " : "
116: + filterString
117: + ")");
118: }
119: }
120:
121: planCache.put(key, plan);
122:
123: return plan;
124: }
125:
126: public NativeSQLQueryPlan getNativeSQLQueryPlan(
127: NativeSQLQuerySpecification spec) {
128: NativeSQLQueryPlan plan = (NativeSQLQueryPlan) planCache
129: .get(spec);
130:
131: if (plan == null) {
132: if (log.isTraceEnabled()) {
133: log
134: .trace("unable to locate native-sql query plan in cache; generating ("
135: + spec.getQueryString() + ")");
136: }
137: plan = new NativeSQLQueryPlan(spec, factory);
138: } else {
139: if (log.isTraceEnabled()) {
140: log.trace("located native-sql query plan in cache ("
141: + spec.getQueryString() + ")");
142: }
143: }
144:
145: planCache.put(spec, plan);
146: return plan;
147: }
148:
149: private ParameterMetadata buildNativeSQLParameterMetadata(
150: String sqlString) {
151: ParamLocationRecognizer recognizer = ParamLocationRecognizer
152: .parseLocations(sqlString);
153:
154: OrdinalParameterDescriptor[] ordinalDescriptors = new OrdinalParameterDescriptor[recognizer
155: .getOrdinalParameterLocationList().size()];
156: for (int i = 0; i < recognizer
157: .getOrdinalParameterLocationList().size(); i++) {
158: final Integer position = (Integer) recognizer
159: .getOrdinalParameterLocationList().get(i);
160: ordinalDescriptors[i] = new OrdinalParameterDescriptor(i,
161: null, position.intValue());
162: }
163:
164: Iterator itr = recognizer.getNamedParameterDescriptionMap()
165: .entrySet().iterator();
166: Map namedParamDescriptorMap = new HashMap();
167: while (itr.hasNext()) {
168: final Map.Entry entry = (Map.Entry) itr.next();
169: final String name = (String) entry.getKey();
170: final ParamLocationRecognizer.NamedParameterDescription description = (ParamLocationRecognizer.NamedParameterDescription) entry
171: .getValue();
172: namedParamDescriptorMap.put(name,
173: new NamedParameterDescriptor(name, null,
174: description.buildPositionsArray(),
175: description.isJpaStyle()));
176: }
177:
178: return new ParameterMetadata(ordinalDescriptors,
179: namedParamDescriptorMap);
180: }
181:
182: private static class HQLQueryPlanKey implements Serializable {
183: private final String query;
184: private final boolean shallow;
185: private final Set filterNames;
186: private final int hashCode;
187:
188: public HQLQueryPlanKey(String query, boolean shallow,
189: Map enabledFilters) {
190: this .query = query;
191: this .shallow = shallow;
192:
193: if (enabledFilters == null || enabledFilters.isEmpty()) {
194: filterNames = Collections.EMPTY_SET;
195: } else {
196: Set tmp = new HashSet();
197: tmp.addAll(enabledFilters.keySet());
198: this .filterNames = Collections.unmodifiableSet(tmp);
199: }
200:
201: int hash = query.hashCode();
202: hash = 29 * hash + (shallow ? 1 : 0);
203: hash = 29 * hash + filterNames.hashCode();
204: this .hashCode = hash;
205: }
206:
207: public boolean equals(Object o) {
208: if (this == o) {
209: return true;
210: }
211: if (o == null || getClass() != o.getClass()) {
212: return false;
213: }
214:
215: final HQLQueryPlanKey that = (HQLQueryPlanKey) o;
216:
217: if (shallow != that.shallow) {
218: return false;
219: }
220: if (!filterNames.equals(that.filterNames)) {
221: return false;
222: }
223: if (!query.equals(that.query)) {
224: return false;
225: }
226:
227: return true;
228: }
229:
230: public int hashCode() {
231: return hashCode;
232: }
233: }
234:
235: private static class FilterQueryPlanKey implements Serializable {
236: private final String query;
237: private final String collectionRole;
238: private final boolean shallow;
239: private final Set filterNames;
240: private final int hashCode;
241:
242: public FilterQueryPlanKey(String query, String collectionRole,
243: boolean shallow, Map enabledFilters) {
244: this .query = query;
245: this .collectionRole = collectionRole;
246: this .shallow = shallow;
247:
248: if (enabledFilters == null || enabledFilters.isEmpty()) {
249: filterNames = Collections.EMPTY_SET;
250: } else {
251: Set tmp = new HashSet();
252: tmp.addAll(enabledFilters.keySet());
253: this .filterNames = Collections.unmodifiableSet(tmp);
254: }
255:
256: int hash = query.hashCode();
257: hash = 29 * hash + collectionRole.hashCode();
258: hash = 29 * hash + (shallow ? 1 : 0);
259: hash = 29 * hash + filterNames.hashCode();
260: this .hashCode = hash;
261: }
262:
263: public boolean equals(Object o) {
264: if (this == o) {
265: return true;
266: }
267: if (o == null || getClass() != o.getClass()) {
268: return false;
269: }
270:
271: final FilterQueryPlanKey that = (FilterQueryPlanKey) o;
272:
273: if (shallow != that.shallow) {
274: return false;
275: }
276: if (!filterNames.equals(that.filterNames)) {
277: return false;
278: }
279: if (!query.equals(that.query)) {
280: return false;
281: }
282: if (!collectionRole.equals(that.collectionRole)) {
283: return false;
284: }
285:
286: return true;
287: }
288:
289: public int hashCode() {
290: return hashCode;
291: }
292: }
293: }
|