001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.cache;
011:
012: import java.util.*;
013:
014: import org.mmbase.core.event.*;
015: import org.mmbase.module.core.*;
016: import org.mmbase.storage.search.*;
017: import org.mmbase.util.logging.Logger;
018: import org.mmbase.util.logging.Logging;
019:
020: /**
021: * <p>
022: * This class is the base for all cache release strategies. You should extend
023: * this to create your own. It will contain a number of usefull utility methods
024: * to analyze query objecs and cached search results. Feel free to add those In
025: * case you miss one developing your own strategies.
026: * </p>
027: *
028: * @author Ernst Bunders
029: * @since MMBase-1.8
030: * @version $Id: ReleaseStrategy.java,v 1.23 2007/02/24 21:57:51 nklasens Exp $
031: */
032:
033: public abstract class ReleaseStrategy {
034:
035: private int totalEvaluated = 0;
036: private int totalPreserved = 0;
037:
038: private long totalEvaluationNanoTime = 0;
039:
040: private boolean isActive = true;
041:
042: private static final Logger log = Logging
043: .getLoggerInstance(ReleaseStrategy.class);
044:
045: public ReleaseStrategy() {
046: }
047:
048: public abstract String getName();
049:
050: public abstract String getDescription();
051:
052: /*
053: * (non-Javadoc)
054: *
055: * @see org.mmbase.cache.QueryResultCacheReleaseStrategy#avgEvaluationTimeInMilis()
056: */
057: public int getAvgEvaluationTimeInMilis() {
058: return (int) (totalEvaluationNanoTime / (1000000 * totalEvaluated));
059: }
060:
061: public long getTotalEvaluationTimeMillis() {
062: return totalEvaluationNanoTime / 1000000;
063: }
064:
065: /**
066: * This method checks if evaluation should happen (active), keeps the time
067: * of the operation and updates the statistics. To implement you own
068: * strategy override
069: * {@link #doEvaluate(NodeEvent event, SearchQuery query, List cachedResult)}.
070: *
071: */
072: public final StrategyResult evaluate(final NodeEvent event,
073: final SearchQuery query,
074: final List<MMObjectNode> cachedResult) {
075: final Timer timer = new Timer();
076: if (isActive) {
077: boolean shouldRelease = doEvaluate(event, query,
078: cachedResult);
079: totalEvaluated++;
080: if (!shouldRelease)
081: totalPreserved++;
082: long cost = timer.getNanoTime();
083: totalEvaluationNanoTime += cost;
084: return new StrategyResult(shouldRelease, cost);
085: } else {
086: // if the cache is inactive it can not prevent the flush
087: return new StrategyResult(true, timer.getTimeMillis());
088: }
089: }
090:
091: public final StrategyResult evaluate(RelationEvent event,
092: SearchQuery query, List<MMObjectNode> cachedResult) {
093: Timer timer = new Timer();
094: if (isActive) {
095: boolean shouldRelease = doEvaluate(event, query,
096: cachedResult);
097: totalEvaluated++;
098: if (!shouldRelease)
099: totalPreserved++;
100: long cost = timer.getNanoTime();
101: totalEvaluationNanoTime += cost;
102: return new StrategyResult(shouldRelease, cost);
103: } else {
104: // if the cache is inactive it can not prevent the flush
105: return new StrategyResult(true, timer.getTimeMillis());
106: }
107: }
108:
109: /*
110: * (non-Javadoc)
111: *
112: * @see org.mmbase.cache.QueryResultCacheReleaseStrategy#getTotalPreserved()
113: */
114: public int getTotalPreserved() {
115: return totalPreserved;
116: }
117:
118: /*
119: * (non-Javadoc)
120: *
121: * @see org.mmbase.cache.QueryResultCacheReleaseStrategy#getTotalEvaluations()
122: */
123: public int getTotalEvaluated() {
124: return totalEvaluated;
125: }
126:
127: /**
128: * implement this method to create your own strategy.
129: *
130: * @param event a node event
131: * @param query
132: * @param cachedResult
133: * @return true if the cache entry should be released
134: */
135: protected abstract boolean doEvaluate(NodeEvent event,
136: SearchQuery query, List<MMObjectNode> cachedResult);
137:
138: /**
139: * implement this method to create your own strategy.
140: *
141: * @param event a relation event
142: * @param query
143: * @param cachedResult
144: * @return true if the cache entry should be released
145: */
146: protected abstract boolean doEvaluate(RelationEvent event,
147: SearchQuery query, List<MMObjectNode> cachedResult);
148:
149: /*
150: * (non-Javadoc)
151: *
152: * @see org.mmbase.cache.QueryResultCacheReleaseStrategy#setEnabled(boolean)
153: */
154: public void setEnabled(boolean newStatus) {
155: if (isActive != newStatus) {
156: clear();
157: isActive = newStatus;
158: }
159: }
160:
161: public boolean isEnabled() {
162: return isActive;
163: }
164:
165: public void clear() {
166: totalEvaluated = 0;
167: totalPreserved = 0;
168: totalEvaluationNanoTime = 0;
169: }
170:
171: public boolean equals(Object ob) {
172: return ob instanceof ReleaseStrategy
173: && this .getName().equals(
174: ((ReleaseStrategy) ob).getName());
175: }
176:
177: public int hashCode() {
178: return getName().hashCode();
179: }
180:
181: public String toString() {
182: return getName();
183: }
184:
185: /**
186: * utility for specializations: get all the constraints in the query that apply to
187: * a certain field
188: * TODO MM: This method is used like this:
189: * <code> if(getConstraintsForField(fieldName, eventBuilder, constraint, query).size() > 0){ return false;}</code>
190: * IOW, only the <em>size</em> of the return list is used, and then even whether it is 0 or not. I think it is a waste to construct a complete new list, only for that.
191: * Perhaps the method should return an Iterator?, and can be used with only 'hasNext()', constructing a longer list then necessary is avoided then.
192: * @param fieldName
193: * @param builder
194: * @param constraint
195: * @param query
196: */
197: protected static List<Constraint> getConstraintsForField(
198: String fieldName, final MMObjectBuilder builder,
199: Constraint constraint, final SearchQuery query) {
200: if (constraint == null)
201: constraint = query.getConstraint();
202: if (constraint == null)
203: return Collections.emptyList();
204: List<Constraint> result = new ArrayList<Constraint>();
205:
206: if (constraint instanceof CompositeConstraint) {
207: log.debug("constraint is composite.");
208: for (Constraint c : ((CompositeConstraint) constraint)
209: .getChilds()) {
210: result.addAll(getConstraintsForField(fieldName,
211: builder, c, query));
212: }
213: } else if (constraint instanceof LegacyConstraint) {
214: log.debug("constraint is legacy.");
215: if (query.getSteps().size() > 1) {
216: // how about postfixing with numbers?
217: fieldName = builder.getTableName() + "." + fieldName;
218: }
219: if (((LegacyConstraint) constraint).getConstraint()
220: .indexOf(fieldName) > -1) {
221: result.add(constraint);
222: return result;
223: }
224: } else if (constraint instanceof FieldConstraint) {
225: log.debug("constraint is field constraint.");
226: StepField sf = ((FieldConstraint) constraint).getField();
227: if (sf.getFieldName().equals(fieldName)
228: && (sf.getStep().getTableName().equals(
229: builder.getTableName()) || builder
230: .isExtensionOf(MMBase
231: .getMMBase()
232: .getBuilder(
233: sf.getStep().getTableName())))) {
234: result.add(constraint);
235: return result;
236: }
237: }
238: return result;
239: }
240:
241: /**
242: * utility for specializations: get all the sortorders in the query that apply to
243: * a certain field
244: * TODO MM: See remark at {@link #getConstraintsForField}
245:
246: * @param fieldName
247: * @param builder
248: * @param sortOrders
249: * @param query
250: */
251: protected static List<SortOrder> getSortordersForField(
252: final String fieldName, final MMObjectBuilder builder,
253: List<SortOrder> sortOrders, final SearchQuery query) {
254: if (sortOrders == null)
255: sortOrders = query.getSortOrders();
256: if (sortOrders == null)
257: return Collections.emptyList();
258: List<SortOrder> result = new ArrayList<SortOrder>();
259: for (SortOrder order : sortOrders) {
260: StepField sf = order.getField();
261: String stepName = sf.getStep().getTableName();
262: if (sf.getFieldName().equals(fieldName)
263: && (stepName.equals(builder.getTableName()) || builder
264: .isExtensionOf(MMBase.getMMBase()
265: .getBuilder(stepName)))) {
266: result.add(order);
267: }
268: }
269: return result;
270: }
271:
272: /**
273: * @author Ernst Bunders This class is a bean containing shouldRelease of an
274: * event evaluation
275: */
276: public static class StrategyResult {
277: private final boolean shouldRelease;
278: private final long cost;
279:
280: StrategyResult(boolean shouldRelease, long cost) {
281: this .shouldRelease = shouldRelease;
282: this .cost = cost;
283: }
284:
285: /**
286: * The cost of a node event evaluation. XXX What is the cost?
287: */
288: public long getCost() {
289: return cost;
290: }
291:
292: /**
293: * Whether, according to this strategy, the query must be flushed.
294: */
295: public boolean shouldRelease() {
296: return shouldRelease;
297: }
298: }
299:
300: /**
301: * @author Ernst Bunders This is a utility class to help timing the
302: * evaluation. Just create an instance before the evaluation and
303: * then use it to create the StrategyResult object
304: */
305: protected final static class Timer {
306: private final long start;
307:
308: Timer() {
309: start = System.nanoTime();
310: }
311:
312: public long getNanoTime() {
313: return System.nanoTime() - start;
314: }
315:
316: public long getTimeMillis() {
317: return getNanoTime() / 1000000;
318: }
319: }
320:
321: }
|