001: /*
002: * Created on 05-May-2005
003: *
004: */
005: package com.jofti.cache;
006:
007: import java.util.HashMap;
008: import java.util.Iterator;
009: import java.util.LinkedHashMap;
010: import java.util.Map;
011: import java.util.Map.Entry;
012:
013: import org.apache.commons.logging.Log;
014: import org.apache.commons.logging.LogFactory;
015:
016: import com.jofti.api.IndexQuery;
017: import com.jofti.core.INameSpaceAware;
018: import com.jofti.core.IParsedQuery;
019: import com.jofti.core.InternalIndex;
020: import com.jofti.core.QueryId;
021: import com.jofti.core.QueryType;
022: import com.jofti.exception.JoftiException;
023: import com.jofti.introspect.ClassIntrospector;
024: import com.jofti.parser.ClassFieldMethods;
025: import com.jofti.parser.ParserManager;
026: import com.jofti.util.CompositeComparator;
027: import com.jofti.util.ObjectProcedureAdapter;
028: import com.jofti.util.OpenHashMap;
029: import com.jofti.util.ValueTreeMap;
030:
031: /**
032:
033: *
034: * The base class for the adapters. The locking is used to allow a reasonable amount of concurrency in the
035: * cache. There are 31 locks that are obtained as a mod 31 of the Object's hashcode. The premise is that a lock can be obtained on methods that modify or access the cache. The objective is to prevent
036: * concurrent modification for identical keys . However, it is also desirable to allow concurrent usage of the
037: * cache and the index as both should be multi-threaded. Although that is reliant on the implementation.<p>
038:
039: * @author Steve Woodcock<br>
040: *
041: */
042: public abstract class BaseAdaptor implements IBaseAdaptor {
043:
044: // array of locks
045: protected Object[] cacheLocks = new Object[31];
046:
047: int updates = 0;
048: int queries = 0;
049: // is check mutable enabled?
050: protected boolean checkMutable = false;
051:
052: protected String MUTABLE_VALUES = "mutable-values";
053:
054: protected InternalIndex index;
055:
056: // lock that provides two groups of exclusive readers
057: private final QueryUpdateLock queryUpdateLock = new QueryUpdateLock();
058:
059: private static Log log = LogFactory.getLog(BaseAdaptor.class);
060:
061: // initialise the object locks
062: {
063: for (int i = 0; i < 31; i++) {
064: cacheLocks[i] = new Object();
065: }
066: }
067:
068: /* (non-Javadoc)
069: * @see com.jofti.cache.IBaseAdaptor#getCacheLock(java.lang.Object)
070: */
071: public Object getCacheLock(Object key) {
072: int temp = (key.hashCode() % 31);
073: return cacheLocks[temp < 0 ? -temp : temp];
074: }
075:
076: /*
077: * (non-Javadoc)
078: *
079: * @see com.jofti.api.IndexCache#query(com.jofti.api.IndexQuery)
080: */
081: /* (non-Javadoc)
082: * @see com.jofti.cache.IBaseAdaptor#query(com.jofti.api.IndexQuery)
083: */
084: public Map query(IndexQuery query) throws JoftiException {
085:
086: Map temp = null;
087:
088: query = processQuery(query, index.getParserManager());
089:
090: acquireQueryLock();
091: try {
092:
093: temp = index.query(query);
094: } finally {
095: releaseQueryLock();
096: }
097: return getCacheValues(temp, (IParsedQuery) query, index
098: .getIntrospector());
099:
100: }
101:
102: public IndexQuery processQuery(IndexQuery query,
103: ParserManager manager) throws JoftiException {
104: QueryType type = ((QueryId) query).getQueryType();
105: if (type != QueryType.PARSED_QUERY) {
106: query = (IndexQuery) manager.parseQuery(query);
107: }
108:
109: if (((INameSpaceAware) query).getNameSpace() != null) {
110: throw new JoftiException(
111: "namespace not allowed in non name-space cache query");
112: }
113: return query;
114: }
115:
116: /* (non-Javadoc)
117: * @see com.jofti.cache.IBaseAdaptor#getCacheValues(java.util.Map, com.jofti.core.IParsedQuery, com.jofti.introspect.ClassIntrospector)
118: */
119: public Map getCacheValues(Map col, final IParsedQuery query,
120: final ClassIntrospector introspector) throws JoftiException {
121:
122: final Map returnClasses = query.getResultFieldsMap();
123: final CompositeComparator comp = query.getOrderingComparator();
124: final int maxResults = query.getMaxResults();
125: final int startEntry = query.getFirstResult();
126:
127: Map interim = null;
128: if (comp == null || comp.getSize() == 0) {
129: interim = new HashMap(col.size() + 2, 1.00f);
130: } else {
131: interim = new ValueTreeMap(comp);
132: }
133:
134: // return map
135: final Map temp = interim;
136:
137: // the results should always be an OpenHashMap
138: OpenHashMap originalMap = (OpenHashMap) col;
139:
140: // populate the return Map
141: originalMap.forEachPair(new ObjectProcedureAdapter() {
142:
143: public boolean apply(Object key, Object value) {
144:
145: //this will be an alias or null or it is not in the return set and we can ignore it
146: if (value == null || returnClasses == null
147: || returnClasses.containsKey(value)) {
148: // see if we have the class in the return map
149:
150: // strip the key of wrapper if it is noncomparable
151: key = stripKey(key);
152:
153: Object result = getCacheValue(key);
154:
155: if (result == null) {
156: warnOutOfDate(key);
157: } else {
158: // get the methods to be operated on the value
159: if (checkMutable) {
160: result = checkMutable(key, result);
161: // mutability check failed
162: if (result == null) {
163: if (log.isDebugEnabled()) {
164: log
165: .debug("Object under key:"
166: + key
167: + " has changed in cache since it was indexed");
168: }
169: return true;
170: }
171: }
172:
173: if (returnClasses != null) {
174: // we need to get the value from result if we are using fields
175: ClassFieldMethods fieldSet = (ClassFieldMethods) returnClasses
176: .get(value);
177:
178: Map tempMap = fieldSet != null ? fieldSet
179: .getFieldMap() : null;
180:
181: if (tempMap != null && tempMap.size() > 0) {
182: // get the fields and apply the values to them
183:
184: Map resultMap = new LinkedHashMap();
185:
186: resultMap = introspector
187: .getResultValuesFromObject(
188: resultMap, result,
189: tempMap);
190:
191: // no values means no mapping in results
192: if (resultMap.size() > 0) {
193: Object[] res = new Object[resultMap
194: .size()];
195: Iterator it = resultMap.entrySet()
196: .iterator();
197: for (int i = 0; i < resultMap
198: .size(); i++) {
199: Map.Entry entry = (Map.Entry) it
200: .next();
201: res[i] = entry.getValue();
202: }
203: temp.put(key, res);
204: }
205:
206: } else {
207: // there are no fields just return the value
208: temp.put(key, result);
209: }
210: } else {
211: temp.put(key, result);
212: }
213: }
214:
215: }
216: return true;
217: }
218: });
219:
220: if (startEntry < 0) {
221: throw new IllegalArgumentException(
222: "startResult cannot be less than 0:" + startEntry);
223: }
224: if (maxResults < 0) {
225: throw new IllegalArgumentException(
226: "maxResults cannot be less than 0:" + maxResults);
227: }
228: // now limit result size
229: if (maxResults > 0 || startEntry > 0) {
230: return limitResults(temp, startEntry, maxResults);
231: }
232:
233: return temp;
234: }
235:
236: public void warnOutOfDate(Object key) {
237: if (log.isWarnEnabled()) {
238: log.warn("Index and cache have become out of date for key "
239: + key);
240: }
241: }
242:
243: public Map limitResults(Map temp, int startResult, int maxResults) {
244:
245: if (maxResults == 0) {
246: maxResults = temp.size();
247: }
248:
249: if (temp.size() <= (maxResults - startResult)) {
250: return temp;
251: }
252:
253: if (temp instanceof ValueTreeMap) {
254: return ((ValueTreeMap) temp).getSubMap(startResult,
255: maxResults);
256:
257: } else {
258:
259: Iterator it = temp.entrySet().iterator();
260:
261: int count = 0;
262: while (it.hasNext()) {
263: it.next();
264: if (count < startResult
265: || count >= maxResults + startResult) {
266: it.remove();
267: }
268: count++;
269: }
270: return temp;
271: }
272:
273: }
274:
275: /* (non-Javadoc)
276: * @see com.jofti.cache.IBaseAdaptor#acquireUpdateLock()
277: */
278: public void acquireUpdateLock() throws JoftiException {
279: try {
280:
281: queryUpdateLock.updateLock().acquire();
282: } catch (Exception e) {
283: throw new JoftiException(e);
284: }
285:
286: }
287:
288: /* (non-Javadoc)
289: * @see com.jofti.cache.IBaseAdaptor#releaseUpdateLock()
290: */
291: public void releaseUpdateLock() {
292:
293: queryUpdateLock.updateLock().release();
294:
295: }
296:
297: /* (non-Javadoc)
298: * @see com.jofti.cache.IBaseAdaptor#acquireQueryLock()
299: */
300: public void acquireQueryLock() throws JoftiException {
301: try {
302: queryUpdateLock.queryLock().acquire();
303: } catch (Exception e) {
304: throw new JoftiException(e);
305: }
306: }
307:
308: /* (non-Javadoc)
309: * @see com.jofti.cache.IBaseAdaptor#releaseQueryLock()
310: */
311: public void releaseQueryLock() {
312:
313: queryUpdateLock.queryLock().release();
314: }
315:
316: /* (non-Javadoc)
317: * @see com.jofti.cache.IBaseAdaptor#decorateKey(java.lang.Object)
318: */
319: public Object decorateKey(Object key) {
320: if (key != null && !(key instanceof Comparable)) {
321: key = new NonComparableKeyWrapper(key);
322: }
323: return key;
324: }
325:
326: /* (non-Javadoc)
327: * @see com.jofti.cache.IBaseAdaptor#stripKey(java.lang.Object)
328: */
329: public Object stripKey(Object key) {
330: if (key instanceof NonComparableKeyWrapper) {
331: key = ((NonComparableKeyWrapper) key).getKey();
332: }
333: return key;
334: }
335:
336: /**
337: * Returns the value from the Cache for the key. Each adpter is responsible for the specific implementation of this, as
338: * the Cache retrieval mechanism will probably be different for each Cache.
339: * @param key
340: * @return
341: */
342: protected abstract Object getCacheValue(Object key);
343:
344: /**
345: * Checks the multability of a Cache value retrieved from the Cache instance in the adapter. Each
346: * adapter is responsible for providing the implementation.
347: * @param key
348: * @param result
349: * @return The value from the Cache or Null if the value has changed after it was indexed
350: */
351: protected Object checkMutable(Object key, Object result) {
352: try {
353: if (log.isDebugEnabled()) {
354: log.debug("Checking mutability ");
355: }
356: // first parse the object - again
357: Map cacheObjectValues = index.getIntrospector()
358: .getAttributeValues(result);
359:
360: Map indexObjectValues = index
361: .getAttributesByKey(decorateKey(key));
362: if (cacheObjectValues.equals(indexObjectValues)) {
363: return result;
364: } else {
365: if (log.isDebugEnabled()) {
366: log
367: .debug("Object under Key "
368: + key
369: + " - attributes changed without re-insert");
370: }
371: }
372: // then get the values for the key
373:
374: } catch (JoftiException e) {
375: log.warn("Error checking mutability", e);
376: }
377:
378: return null;
379: }
380:
381: /* (non-Javadoc)
382: * @see com.jofti.cache.IBaseAdaptor#getIndex()
383: */
384: public abstract InternalIndex getIndex();
385: }
|