001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.openjpa.datacache;
020:
021: import java.io.Externalizable;
022: import java.io.IOException;
023: import java.io.ObjectInput;
024: import java.io.ObjectOutput;
025: import java.util.ArrayList;
026: import java.util.Collection;
027: import java.util.Collections;
028: import java.util.Date;
029: import java.util.HashMap;
030: import java.util.HashSet;
031: import java.util.Iterator;
032: import java.util.LinkedList;
033: import java.util.List;
034: import java.util.Map;
035: import java.util.Set;
036: import java.util.SortedSet;
037: import java.util.TreeSet;
038:
039: import org.apache.commons.lang.ObjectUtils;
040: import org.apache.commons.lang.StringUtils;
041: import org.apache.openjpa.enhance.PCRegistry;
042: import org.apache.openjpa.kernel.Query;
043: import org.apache.openjpa.kernel.QueryContext;
044: import org.apache.openjpa.kernel.StoreContext;
045: import org.apache.openjpa.meta.ClassMetaData;
046: import org.apache.openjpa.meta.JavaTypes;
047: import org.apache.openjpa.meta.MetaDataRepository;
048: import org.apache.openjpa.util.ImplHelper;
049:
050: /**
051: * This class stores information about a particular invocation of
052: * a query. It contains a reference to the external properties of the
053: * query that was executed, as well as any parameters used to execute
054: * that query, with one exception: first-class objects used as
055: * parameter values are converted to OIDs.
056: *
057: * @author Patrick Linskey
058: */
059: public class QueryKey implements Externalizable {
060:
061: // initialize the set of unmodifiable classes. This allows us
062: // to avoid cloning collections that are not modifiable,
063: // provided that they do not contain mutable objects.
064: private static Collection s_unmod = new HashSet();
065:
066: static {
067: // handle the set types; jdk uses different classes for collection,
068: // set, and sorted set
069: TreeSet s = new TreeSet();
070: s_unmod.add(Collections.unmodifiableCollection(s).getClass());
071: s_unmod.add(Collections.unmodifiableSet(s).getClass());
072: s_unmod.add(Collections.unmodifiableSortedSet(s).getClass());
073:
074: // handle the list types; jdk uses different classes for standard
075: // and random access lists
076: List l = new LinkedList();
077: s_unmod.add(Collections.unmodifiableList(l).getClass());
078: l = new ArrayList(0);
079: s_unmod.add(Collections.unmodifiableList(l).getClass());
080:
081: // handle the constant types
082: s_unmod.add(Collections.EMPTY_SET.getClass());
083: s_unmod.add(Collections.EMPTY_LIST.getClass());
084: }
085:
086: // caching state; no need to include parameter and variable declarations
087: // because they are implicit in the filter
088: private String _candidateClassName;
089: private boolean _subclasses;
090: private Set _accessPathClassNames;
091: private String _query;
092: private boolean _ignoreChanges;
093: private Map _params;
094: private long _rangeStart;
095: private long _rangeEnd;
096:
097: // ### pcl: 2 May 2003: should this timeout take into account the
098: // ### timeouts for classes in the access path of the query?
099: // ### Currently, it only considers the candidate class and its
100: // ### subclasses. Note that this value is used to decide whether
101: // ### or not OIDs should be registered for expiration callbacks
102: private int _timeout = -1;
103:
104: /**
105: * Return a key for the given query, or null if it is not cacheable.
106: */
107: public static QueryKey newInstance(Query q) {
108: return newInstance(q, (Object[]) null);
109: }
110:
111: /**
112: * Return a key for the given query, or null if it is not cacheable.
113: */
114: public static QueryKey newInstance(Query q, Object[] args) {
115: // compile to make sure info encoded in query string is available
116: // via API calls (candidate class, result class, etc)
117: q.compile();
118: return newInstance(q, false, args, q.getCandidateType(), q
119: .hasSubclasses(), q.getStartRange(), q.getEndRange());
120: }
121:
122: /**
123: * Return a key for the given query, or null if it is not cacheable.
124: */
125: public static QueryKey newInstance(Query q, Map args) {
126: // compile to make sure info encoded in query string is available
127: // via API calls (candidate class, result class, etc)
128: q.compile();
129: return newInstance(q, false, args, q.getCandidateType(), q
130: .hasSubclasses(), q.getStartRange(), q.getEndRange());
131: }
132:
133: /**
134: * Return a key for the given query, or null if it is not cacheable.
135: */
136: static QueryKey newInstance(QueryContext q, boolean packed,
137: Object[] args, Class candidate, boolean subs,
138: long startIdx, long endIdx) {
139: QueryKey key = createKey(q, packed, candidate, subs, startIdx,
140: endIdx);
141: if (key != null && setParams(key, q, args))
142: return key;
143: return null;
144: }
145:
146: /**
147: * Return a key for the given query, or null if it is not cacheable.
148: */
149: static QueryKey newInstance(QueryContext q, boolean packed,
150: Map args, Class candidate, boolean subs, long startIdx,
151: long endIdx) {
152: QueryKey key = createKey(q, packed, candidate, subs, startIdx,
153: endIdx);
154: if (key != null
155: && (args == null || args.isEmpty() || setParams(key, q
156: .getStoreContext(), new HashMap(args))))
157: return key;
158: return null;
159: }
160:
161: /**
162: * Extract the relevant identifying information from
163: * <code>q</code>. This includes information such as candidate
164: * class, query filter, etc.
165: */
166: private static QueryKey createKey(QueryContext q, boolean packed,
167: Class candidateClass, boolean subclasses, long startIdx,
168: long endIdx) {
169: if (candidateClass == null)
170: return null;
171:
172: // can only cache datastore queries
173: if (q.getCandidateCollection() != null)
174: return null;
175:
176: // no support already-packed results
177: if (q.getResultType() != null && packed)
178: return null;
179:
180: // can't cache non-serializable non-managed complex types
181: Class[] types = q.getProjectionTypes();
182: for (int i = 0; i < types.length; i++) {
183: switch (JavaTypes.getTypeCode(types[i])) {
184: case JavaTypes.ARRAY:
185: return null;
186: case JavaTypes.COLLECTION:
187: case JavaTypes.MAP:
188: case JavaTypes.OBJECT:
189: if (!ImplHelper.isManagedType(q.getStoreContext()
190: .getConfiguration(), types[i]))
191: return null;
192: break;
193: }
194: }
195:
196: // we can't cache the query if we don't know which classes are in the
197: // access path
198: ClassMetaData[] metas = q.getAccessPathMetaDatas();
199: if (metas.length == 0)
200: return null;
201:
202: Set accessPathClassNames = new HashSet(
203: (int) (metas.length * 1.33 + 1));
204: ClassMetaData meta;
205: for (int i = 0; i < metas.length; i++) {
206: // since the class change framework deals with least-derived types,
207: // record the least-derived access path types
208: meta = metas[i];
209: while (meta.getPCSuperclass() != null)
210: meta = meta.getPCSuperclassMetaData();
211:
212: // ensure that this metadata is cacheable
213: if (meta.getDataCache() == null)
214: return null;
215: accessPathClassNames.add(meta.getDescribedType().getName());
216: }
217:
218: // if any of the types are currently dirty, we can't cache this query
219: StoreContext ctx = q.getStoreContext();
220: if (intersects(accessPathClassNames, ctx.getPersistedTypes())
221: || intersects(accessPathClassNames, ctx
222: .getUpdatedTypes())
223: || intersects(accessPathClassNames, ctx
224: .getDeletedTypes()))
225: return null;
226:
227: // calculate the timeout for the key
228: MetaDataRepository repos = ctx.getConfiguration()
229: .getMetaDataRepositoryInstance();
230:
231: // won't find metadata for interfaces.
232: if (candidateClass.isInterface())
233: return null;
234: meta = repos.getMetaData(candidateClass, ctx.getClassLoader(),
235: true);
236: int timeout = meta.getDataCacheTimeout();
237: if (subclasses) {
238: metas = meta.getPCSubclassMetaDatas();
239: int subTimeout;
240: for (int i = 0; i < metas.length; i++) {
241: if (metas[i].getDataCache() == null)
242: return null;
243:
244: subTimeout = metas[i].getDataCacheTimeout();
245: if (subTimeout != -1 && subTimeout < timeout)
246: timeout = subTimeout;
247: }
248: }
249:
250: // tests all passed; cacheable
251: QueryKey key = new QueryKey();
252: key._candidateClassName = candidateClass.getName();
253: key._subclasses = subclasses;
254: key._accessPathClassNames = accessPathClassNames;
255: key._timeout = timeout;
256: key._query = q.getQueryString();
257: key._ignoreChanges = q.getIgnoreChanges();
258: key._rangeStart = startIdx;
259: key._rangeEnd = endIdx;
260: return key;
261: }
262:
263: /**
264: * Convert an array of arguments into the corresponding parameter
265: * map, and do any PC to OID conversion necessary.
266: */
267: private static boolean setParams(QueryKey key, QueryContext q,
268: Object[] args) {
269: if (args == null || args.length == 0)
270: return true;
271:
272: // Create a map for the given parameters, and convert the
273: // parameter list into a map, using the query's parameter
274: // declaration to determine ordering etc.
275: Map types = q.getParameterTypes();
276: Map map = new HashMap((int) (types.size() * 1.33 + 1));
277: int idx = 0;
278: for (Iterator iter = types.keySet().iterator(); iter.hasNext(); idx++)
279: map.put(iter.next(), args[idx]);
280: return setParams(key, q.getStoreContext(), map);
281: }
282:
283: /**
284: * Convert parameters to a form that is cacheable. Mutable params
285: * will be cloned.
286: */
287: private static boolean setParams(QueryKey key, StoreContext ctx,
288: Map params) {
289: if (params == null || params.isEmpty())
290: return true;
291:
292: Map.Entry e;
293: Object v;
294: for (Iterator iter = params.entrySet().iterator(); iter
295: .hasNext();) {
296: e = (Map.Entry) iter.next();
297: v = e.getValue();
298: if (ImplHelper.isManageable(v)) {
299: if (!ctx.isPersistent(v) || ctx.isNew(v)
300: || ctx.isDeleted(v))
301: return false;
302: e.setValue(ctx.getObjectId(v));
303: }
304:
305: if (v instanceof Collection) {
306: Collection c = (Collection) v;
307: boolean contentsAreDates = false;
308: if (c.iterator().hasNext()) {
309: // this assumes that the collection is homogeneous
310: Object o = c.iterator().next();
311: if (ImplHelper.isManageable(o))
312: return false;
313:
314: // pcl: 27 Jun 2004: if we grow this logic to
315: // handle other mutable types that are not
316: // known to be cloneable, we will have to add
317: // logic to handle them. This is because we
318: // can't just cast to Cloneable and invoke
319: // clone(), as clone() is a protected method
320: // in Object.
321: if (o instanceof Date)
322: contentsAreDates = true;
323:
324: // if the collection is not a known immutable
325: // type, or if it contains mutable instances,
326: // clone it for good measure.
327: if (contentsAreDates
328: || !s_unmod.contains(c.getClass())) {
329: // copy the collection
330: Collection copy;
331: if (c instanceof SortedSet)
332: copy = new TreeSet();
333: else if (c instanceof Set)
334: copy = new HashSet();
335: else
336: copy = new ArrayList(c.size());
337:
338: if (contentsAreDates) {
339: // must go through by hand and do the
340: // copy, since Date is mutable.
341: for (Iterator itr2 = c.iterator(); itr2
342: .hasNext();)
343: copy.add(((Date) itr2.next()).clone());
344: } else
345: copy.addAll(c);
346:
347: e.setValue(copy);
348: }
349: }
350: } else if (v instanceof Date)
351: e.setValue(((Date) v).clone());
352: }
353:
354: key._params = params;
355: return true;
356: }
357:
358: /**
359: * Public constructor for externalization only.
360: */
361: public QueryKey() {
362: }
363:
364: /**
365: * Returns the candidate class name for this query.
366: */
367: public String getCandidateTypeName() {
368: return _candidateClassName;
369: }
370:
371: /**
372: * Return the amount of time this key is good for.
373: */
374: public int getTimeout() {
375: return _timeout;
376: }
377:
378: /**
379: * Returns <code>true</code> if modifications to any of the
380: * classes in <code>changed</code> results in a possible
381: * invalidation of this query; otherwise returns
382: * <code>false</code>. Invalidation is possible if one or more of
383: * the classes in this query key's access path has been changed.
384: */
385: public boolean changeInvalidatesQuery(Collection changed) {
386: return intersects(_accessPathClassNames, changed);
387: }
388:
389: /**
390: * Whether the given set of least-derived class names intersects with
391: * the given set of changed classes.
392: */
393: private static boolean intersects(Collection names,
394: Collection changed) {
395: Class cls;
396: Class sup;
397: for (Iterator iter = changed.iterator(); iter.hasNext();) {
398: cls = (Class) iter.next();
399: while ((sup = PCRegistry.getPersistentSuperclass(cls)) != null)
400: cls = sup;
401: if (names.contains(cls.getName()))
402: return true;
403: }
404: return false;
405: }
406:
407: /**
408: * Determine equality based on identifying information. Keys
409: * created for queries that specify a candidate collection are
410: * always not equal.
411: */
412: public boolean equals(Object ob) {
413: if (this == ob)
414: return true;
415: if (ob == null || getClass() != ob.getClass())
416: return false;
417:
418: QueryKey other = (QueryKey) ob;
419: return StringUtils.equals(_candidateClassName,
420: other._candidateClassName)
421: && _subclasses == other._subclasses
422: && _ignoreChanges == other._ignoreChanges
423: && _rangeStart == other._rangeStart
424: && _rangeEnd == other._rangeEnd
425: && StringUtils.equals(_query, other._query)
426: && ObjectUtils.equals(_params, other._params);
427: }
428:
429: /**
430: * Define a hashing algorithm corresponding to the {@link #equals}
431: * method defined above.
432: */
433: public int hashCode() {
434: int code = 37 * 17 + _candidateClassName.hashCode();
435: if (_query != null)
436: code = 37 * code + _query.hashCode();
437: if (_params != null)
438: code = 37 * code + _params.hashCode();
439: return code;
440: }
441:
442: public String toString() {
443: StringBuffer buf = new StringBuffer(255);
444: buf.append(super .toString()).append("[query:[").append(_query)
445: .append("]").append(",access path:").append(
446: _accessPathClassNames).append(",subs:").append(
447: _subclasses).append(",ignoreChanges:").append(
448: _ignoreChanges).append(",startRange:").append(
449: _rangeStart).append(",endRange:").append(
450: _rangeEnd).append(",timeout:").append(_timeout)
451: .append("]");
452: return buf.toString();
453: }
454:
455: // ---------- Externalizable implementation ----------
456:
457: public void writeExternal(ObjectOutput out) throws IOException {
458: out.writeObject(_candidateClassName);
459: out.writeBoolean(_subclasses);
460: out.writeObject(_accessPathClassNames);
461: out.writeObject(_query);
462: out.writeBoolean(_ignoreChanges);
463: out.writeObject(_params);
464: out.writeLong(_rangeStart);
465: out.writeLong(_rangeEnd);
466: out.writeInt(_timeout);
467: }
468:
469: public void readExternal(ObjectInput in) throws IOException,
470: ClassNotFoundException {
471: _candidateClassName = (String) in.readObject();
472: _subclasses = in.readBoolean();
473: _accessPathClassNames = (Set) in.readObject();
474: _query = (String) in.readObject();
475: _ignoreChanges = in.readBoolean();
476: _params = (Map) in.readObject();
477: _rangeStart = in.readLong();
478: _rangeEnd = in.readLong();
479: _timeout = in.readInt();
480: }
481: }
|