| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openjpa.datacache; |
| |
| import java.io.Externalizable; |
| import java.io.IOException; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| import org.apache.openjpa.enhance.PCRegistry; |
| import org.apache.openjpa.kernel.Query; |
| import org.apache.openjpa.kernel.QueryContext; |
| import org.apache.openjpa.kernel.StoreContext; |
| import org.apache.openjpa.meta.ClassMetaData; |
| import org.apache.openjpa.meta.JavaTypes; |
| import org.apache.openjpa.meta.MetaDataRepository; |
| import org.apache.openjpa.util.ImplHelper; |
| |
| /** |
| * This class stores information about a particular invocation of |
| * a query. It contains a reference to the external properties of the |
| * query that was executed, as well as any parameters used to execute |
| * that query, with one exception: first-class objects used as |
| * parameter values are converted to OIDs. |
| * |
| * @author Patrick Linskey |
| */ |
| public class QueryKey |
| implements Externalizable { |
| |
| // initialize the set of unmodifiable classes. This allows us |
| // to avoid cloning collections that are not modifiable, |
| // provided that they do not contain mutable objects. |
| private static Collection<Class<?>> s_unmod = new HashSet<>(); |
| |
| static { |
| // handle the set types; jdk uses different classes for collection, |
| // set, and sorted set |
| TreeSet<Object> s = new TreeSet<>(); |
| s_unmod.add(Collections.unmodifiableCollection(s).getClass()); |
| s_unmod.add(Collections.unmodifiableSet(s).getClass()); |
| s_unmod.add(Collections.unmodifiableSortedSet(s).getClass()); |
| |
| // handle the list types; jdk uses different classes for standard |
| // and random access lists |
| List<Object> l = new LinkedList<>(); |
| s_unmod.add(Collections.unmodifiableList(l).getClass()); |
| l = new ArrayList<>(0); |
| s_unmod.add(Collections.unmodifiableList(l).getClass()); |
| |
| // handle the constant types |
| s_unmod.add(Collections.EMPTY_SET.getClass()); |
| s_unmod.add(Collections.EMPTY_LIST.getClass()); |
| } |
| |
| // caching state; no need to include parameter and variable declarations |
| // because they are implicit in the filter |
| private String _candidateClassName; |
| private boolean _subclasses; |
| private Set<String> _accessPathClassNames; |
| private Object _query; |
| private boolean _ignoreChanges; |
| private Map<Object,Object> _params; |
| private long _rangeStart; |
| private long _rangeEnd; |
| |
| // ### pcl: 2 May 2003: should this timeout take into account the |
| // ### timeouts for classes in the access path of the query? |
| // ### Currently, it only considers the candidate class and its |
| // ### subclasses. Note that this value is used to decide whether |
| // ### or not OIDs should be registered for expiration callbacks |
| private int _timeout = -1; |
| |
| /** |
| * Return a key for the given query, or null if it is not cacheable. |
| */ |
| public static QueryKey newInstance(Query q) { |
| return newInstance(q, (Object[]) null); |
| } |
| |
| /** |
| * Return a key for the given query, or null if it is not cacheable. |
| */ |
| public static QueryKey newInstance(Query q, Object[] args) { |
| // compile to make sure info encoded in query string is available |
| // via API calls (candidate class, result class, etc) |
| q.compile(); |
| return newInstance(q, false, args, q.getCandidateType(), |
| q.hasSubclasses(), q.getStartRange(), q.getEndRange(), null); |
| } |
| |
| /** |
| * Return a key for the given query, or null if it is not cacheable. |
| */ |
| public static QueryKey newInstance(Query q, Map<Object,Object> args) { |
| // compile to make sure info encoded in query string is available |
| // via API calls (candidate class, result class, etc) |
| q.compile(); |
| return newInstance(q, false, args, q.getCandidateType(), |
| q.hasSubclasses(), q.getStartRange(), q.getEndRange(), null); |
| } |
| |
| /** |
| * Return a key for the given query, or null if it is not cacheable. |
| */ |
| static QueryKey newInstance(QueryContext q, boolean packed, Object[] args, |
| Class<?> candidate, boolean subs, long startIdx, long endIdx, Object parsed) { |
| QueryKey key = createKey(q, packed, candidate, subs, startIdx, endIdx, parsed); |
| if (key != null && setParams(key, q, args)) |
| return key; |
| return null; |
| } |
| |
| /** |
| * Return a key for the given query, or null if it is not cacheable. |
| */ |
| static QueryKey newInstance(QueryContext q, boolean packed, Map<Object,Object> args, |
| Class<?> candidate, boolean subs, long startIdx, long endIdx, Object parsed) { |
| QueryKey key = createKey(q, packed, candidate, subs, startIdx, endIdx, parsed); |
| if (key != null && (args == null || args.isEmpty() || |
| setParams(key, q.getStoreContext(), new HashMap<>(args)))) |
| return key; |
| return null; |
| } |
| |
| /** |
| * Extract the relevant identifying information from |
| * <code>q</code>. This includes information such as candidate |
| * class, query filter, etc. |
| */ |
| private static QueryKey createKey(QueryContext q, boolean packed, |
| Class<?> candidateClass, boolean subclasses, long startIdx, long endIdx, Object parsed) { |
| if (candidateClass == null) |
| return null; |
| |
| // can only cache datastore queries |
| if (q.getCandidateCollection() != null) |
| return null; |
| |
| // no support already-packed results |
| if (q.getResultType() != null && packed) |
| return null; |
| |
| // can't cache non-serializable non-managed complex types |
| Class<?>[] types = q.getProjectionTypes(); |
| for (Class<?> type : types) { |
| switch (JavaTypes.getTypeCode(type)) { |
| case JavaTypes.ARRAY: |
| return null; |
| case JavaTypes.COLLECTION: |
| case JavaTypes.MAP: |
| case JavaTypes.OBJECT: |
| if (!ImplHelper.isManagedType( |
| q.getStoreContext().getConfiguration(), type)) |
| return null; |
| break; |
| } |
| } |
| |
| // we can't cache the query if we don't know which classes are in the |
| // access path |
| ClassMetaData[] metas = q.getAccessPathMetaDatas(); |
| if (metas.length == 0) |
| return null; |
| |
| Set<String> accessPathClassNames = new HashSet<>((int) (metas.length * 1.33 + 1)); |
| ClassMetaData meta; |
| for (ClassMetaData metaData : metas) { |
| // since the class change framework deals with least-derived types, |
| // record the least-derived access path types |
| meta = metaData; |
| accessPathClassNames.add(meta.getDescribedType().getName()); |
| while (meta.getPCSuperclass() != null) { |
| meta = meta.getPCSuperclassMetaData(); |
| } |
| |
| accessPathClassNames.add(meta.getDescribedType().getName()); |
| } |
| |
| // if any of the types are currently dirty, we can't cache this query |
| StoreContext ctx = q.getStoreContext(); |
| if (intersects(accessPathClassNames, ctx.getPersistedTypes()) |
| || intersects(accessPathClassNames, ctx.getUpdatedTypes()) |
| || intersects(accessPathClassNames, ctx.getDeletedTypes())) |
| return null; |
| |
| // calculate the timeout for the key |
| MetaDataRepository repos = ctx.getConfiguration(). |
| getMetaDataRepositoryInstance(); |
| |
| // won't find metadata for interfaces. |
| if (candidateClass.isInterface()) |
| return null; |
| meta = repos.getMetaData(candidateClass, ctx.getClassLoader(), true); |
| int timeout = meta.getDataCacheTimeout(); |
| if (subclasses) { |
| metas = meta.getPCSubclassMetaDatas(); |
| int subTimeout; |
| for (ClassMetaData classMetaData : metas) { |
| if (classMetaData.getDataCache() == null) |
| return null; |
| |
| accessPathClassNames.add(classMetaData.getDescribedType().getName()); |
| subTimeout = classMetaData.getDataCacheTimeout(); |
| if (subTimeout != -1 && subTimeout < timeout) |
| timeout = subTimeout; |
| } |
| } |
| |
| // tests all passed; cacheable |
| QueryKey key = new QueryKey(); |
| key._candidateClassName = candidateClass.getName(); |
| key._subclasses = subclasses; |
| key._accessPathClassNames = accessPathClassNames; |
| key._timeout = timeout; |
| key._query = q.getQueryString(); |
| if (key._query == null && parsed != null) { |
| // this is a criteria query. Store the Stringified query value rather than the full cq. |
| key._query = parsed.toString(); |
| } |
| key._ignoreChanges = q.getIgnoreChanges(); |
| key._rangeStart = startIdx; |
| key._rangeEnd = endIdx; |
| return key; |
| } |
| |
| /** |
| * Convert an array of arguments into the corresponding parameter |
| * map, and do any PC to OID conversion necessary. |
| */ |
| private static boolean setParams(QueryKey key, QueryContext q, |
| Object[] args) { |
| if (args == null || args.length == 0) |
| return true; |
| |
| // Create a map for the given parameters, and convert the |
| // parameter list into a map, using the query's parameter |
| // declaration to determine ordering etc. |
| Map<Object,Class<?>> types = q.getOrderedParameterTypes(); |
| Map<Object,Object> map = new HashMap<>((int) (types.size() * 1.33 + 1)); |
| int idx = 0; |
| for (Iterator<Object> iter = types.keySet().iterator(); iter.hasNext(); idx++) |
| map.put(iter.next(), args[idx]); |
| return setParams(key, q.getStoreContext(), map); |
| } |
| |
| /** |
| * Convert parameters to a form that is cacheable. Mutable params |
| * will be cloned. |
| */ |
| private static boolean setParams(QueryKey key, StoreContext ctx, |
| Map<Object,Object> params) { |
| if (params == null || params.isEmpty()) |
| return true; |
| |
| Object v; |
| for (Map.Entry<Object,Object> e : params.entrySet()) { |
| v = e.getValue(); |
| if (ImplHelper.isManageable(v)) { |
| if (!ctx.isPersistent(v) || ctx.isNew(v) || ctx.isDeleted(v)) |
| return false; |
| e.setValue(ctx.getObjectId(v)); |
| } |
| |
| if (v instanceof Collection) { |
| Collection<Object> c = (Collection<Object>) v; |
| boolean contentsAreDates = false; |
| if (c.iterator().hasNext()) { |
| // this assumes that the collection is homogeneous |
| Object o = c.iterator().next(); |
| if (ImplHelper.isManageable(o)) |
| return false; |
| |
| // pcl: 27 Jun 2004: if we grow this logic to |
| // handle other mutable types that are not |
| // known to be cloneable, we will have to add |
| // logic to handle them. This is because we |
| // can't just cast to Cloneable and invoke |
| // clone(), as clone() is a protected method |
| // in Object. |
| if (o instanceof Date) |
| contentsAreDates = true; |
| |
| // if the collection is not a known immutable |
| // type, or if it contains mutable instances, |
| // clone it for good measure. |
| if (contentsAreDates || !s_unmod.contains(c.getClass())) { |
| // copy the collection |
| Collection<Object> copy; |
| if (c instanceof SortedSet) |
| copy = new TreeSet<>(); |
| else if (c instanceof Set) |
| copy = new HashSet<>(); |
| else |
| copy = new ArrayList<>(c.size()); |
| |
| if (contentsAreDates) { |
| // must go through by hand and do the |
| // copy, since Date is mutable. |
| for (Object value : c) { |
| copy.add(((Date) value).clone()); |
| } |
| } else |
| copy.addAll(c); |
| |
| e.setValue(copy); |
| } |
| } |
| } else if (v instanceof Date) |
| e.setValue(((Date) v).clone()); |
| } |
| |
| key._params = params; |
| return true; |
| } |
| |
| /** |
| * Public constructor for externalization only. |
| */ |
| public QueryKey() { |
| } |
| |
| /** |
| * Returns the candidate class name for this query. |
| */ |
| public String getCandidateTypeName() { |
| return _candidateClassName; |
| } |
| |
| /** |
| * Return the amount of time this key is good for. |
| */ |
| public int getTimeout() { |
| return _timeout; |
| } |
| |
| /** |
| * Returns <code>true</code> if modifications to any of the |
| * classes in <code>changed</code> results in a possible |
| * invalidation of this query; otherwise returns |
| * <code>false</code>. Invalidation is possible if one or more of |
| * the classes in this query key's access path has been changed. |
| */ |
| public boolean changeInvalidatesQuery(Collection<Class<?>> changed) { |
| return intersects(_accessPathClassNames, changed); |
| } |
| |
| /** |
| * Whether the given set of least-derived class names intersects with |
| * the given set of changed classes. |
| */ |
| private static boolean intersects(Collection<String> names, Collection<Class<?>> changed) { |
| Class<?> sup; |
| for (Class<?> cls : changed) { |
| while ((sup = PCRegistry.getPersistentSuperclass(cls)) != null) |
| cls = sup; |
| if (names.contains(cls.getName())) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Determine equality based on identifying information. Keys |
| * created for queries that specify a candidate collection are |
| * always not equal. |
| */ |
| @Override |
| public boolean equals(Object ob) { |
| if (this == ob) |
| return true; |
| if (ob == null || getClass() != ob.getClass()) |
| return false; |
| |
| QueryKey other = (QueryKey) ob; |
| return Objects.equals(_candidateClassName, |
| other._candidateClassName) |
| && _subclasses == other._subclasses |
| && _ignoreChanges == other._ignoreChanges |
| && _rangeStart == other._rangeStart |
| && _rangeEnd == other._rangeEnd |
| && Objects.equals(_query, other._query) |
| && Objects.equals(_params, other._params); |
| } |
| |
| /** |
| * Define a hashing algorithm corresponding to the {@link #equals} |
| * method defined above. |
| */ |
| @Override |
| public int hashCode() { |
| int code = 37 * 17 + _candidateClassName.hashCode(); |
| if (_query != null) |
| code = 37 * code + _query.hashCode(); |
| if (_params != null) |
| code = 37 * code + _params.hashCode(); |
| return code; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder buf = new StringBuilder(1024); |
| buf.append(super.toString()). |
| append("[query:[").append(_query).append("]"). |
| append(",access path:").append(_accessPathClassNames). |
| append(",subs:").append(_subclasses). |
| append(",ignoreChanges:").append(_ignoreChanges). |
| append(",startRange:").append(_rangeStart). |
| append(",endRange:").append(_rangeEnd). |
| append(",timeout:").append(_timeout). |
| append("]"); |
| return buf.toString(); |
| } |
| |
| // ---------- Externalizable implementation ---------- |
| |
| @Override |
| public void writeExternal(ObjectOutput out) |
| throws IOException { |
| out.writeObject(_candidateClassName); |
| out.writeBoolean(_subclasses); |
| out.writeObject(_accessPathClassNames); |
| out.writeObject(_query); |
| out.writeBoolean(_ignoreChanges); |
| out.writeObject(_params); |
| out.writeLong(_rangeStart); |
| out.writeLong(_rangeEnd); |
| out.writeInt(_timeout); |
| } |
| |
| @Override |
| public void readExternal(ObjectInput in) |
| throws IOException, ClassNotFoundException { |
| _candidateClassName = (String) in.readObject(); |
| _subclasses = in.readBoolean(); |
| _accessPathClassNames = (Set<String>) in.readObject(); |
| _query = (String) in.readObject(); |
| _ignoreChanges = in.readBoolean(); |
| _params = (Map<Object,Object>) in.readObject(); |
| _rangeStart = in.readLong(); |
| _rangeEnd = in.readLong (); |
| _timeout = in.readInt (); |
| } |
| |
| /** |
| * Returns the set of the accessPathClassnames that exists in the query |
| * @return -- Returns a set of accesspath classnames. |
| */ |
| public Set<String> getAcessPathClassNames() { |
| return this._accessPathClassNames; |
| } |
| } |