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.module.builders;
011:
012: import java.util.*;
013: import java.text.DateFormat;
014: import org.mmbase.module.core.*;
015: import org.mmbase.storage.search.implementation.*;
016: import org.mmbase.storage.search.*;
017: import org.mmbase.util.*;
018:
019: import org.mmbase.util.logging.*;
020:
021: /**
022: * Daymarkers are used to calculate the age of MMBase objects.
023: * Every day a daymarker is added to the daymarks table. Such an entry
024: * consists of a daycount (number of days from 1970), and a count
025: * (current object number of that day).
026: *
027: * @author Daniel Ockeloen,Rico Jansen
028: * @author Michiel Meeuwissen
029: * @version $Id: DayMarkers.java,v 1.46 2007/11/09 10:38:29 michiel Exp $
030: */
031: public class DayMarkers extends MMObjectBuilder {
032:
033: public static final String FIELD_DAYCOUNT = "daycount";
034: public static final String FIELD_MARK = "mark";
035: public static final long SECONDS_IN_A_DAY = 24 * 3600;
036: public static final long MILLISECONDS_IN_A_DAY = SECONDS_IN_A_DAY * 1000;
037:
038: private static final Logger log = Logging
039: .getLoggerInstance(DayMarkers.class);
040:
041: private int day = 0; // current day number/count
042: private Map<Integer, Integer> daycache = new TreeMap<Integer, Integer>(); // day -> mark, but ordered
043:
044: private int smallestDay; // will be queried when this builder is started
045:
046: /**
047: * Put in cache. This function essentially does the casting to
048: * Integer and wrapping in 'synchronized' for you.
049: */
050: private void cachePut(int day, int mark) {
051: synchronized (daycache) {
052: daycache.put(day, mark);
053: }
054: }
055:
056: /**
057: * set the current day. This is the number of days from 1970.
058: */
059: public DayMarkers() {
060: day = currentDay();
061: }
062:
063: /**
064: * Calculate smallestMark, and smallestDay.
065: * smallestMark is the smallest object number for which a daymark exists.
066: * smallestDay is the first daymarker that was set.
067: */
068: public boolean init() {
069: log.debug("Init of DayMarkers");
070: boolean result;
071: result = super .init();
072: smallestDay = 0;
073:
074: try {
075: NodeSearchQuery query = new NodeSearchQuery(this );
076: StepField field = query.getField(getField(FIELD_NUMBER));
077: query.addSortOrder(field);
078: query.setMaxNumber(1);
079: List<MMObjectNode> resultList = getNodes(query);
080: if (resultList.size() > 0) {
081: MMObjectNode mark = resultList.get(0);
082: smallestDay = mark.getIntValue(FIELD_DAYCOUNT);
083: }
084: if (smallestDay == 0) {
085: smallestDay = currentDay();
086: createMarker();
087: }
088: } catch (SearchQueryException e) {
089: log.error("SQL Exception " + e
090: + ". Could not find smallestMarker, smallestDay");
091: result = false;
092: }
093:
094: return result;
095: }
096:
097: /**
098: * The current time in days since 1-1-1970
099: */
100: private int currentDay() {
101: return (int) (System.currentTimeMillis() / MILLISECONDS_IN_A_DAY);
102: }
103:
104: /**
105: * Creates a mark in the database, if necessary.
106: */
107: private void createMarker() {
108: // test if the node for today exists
109: NodeSearchQuery query = new NodeSearchQuery(this );
110: query.setMaxNumber(1);
111: StepField daycountField = query
112: .getField(getField(FIELD_DAYCOUNT));
113: BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(
114: daycountField, day);
115: query.setConstraint(constraint);
116: try {
117: List<MMObjectNode> resultList = getNodes(query);
118: if (resultList.size() == 0) {
119: // if not, retrieve the mark (highest node number) for today
120: MMObjectBuilder root = mmb.getRootBuilder();
121: query = new NodeSearchQuery(root);
122: ModifiableQuery modifiedQuery = new ModifiableQuery(
123: query);
124: Step step = query.getSteps().get(0);
125: AggregatedField field = new BasicAggregatedField(step,
126: root.getField(FIELD_NUMBER),
127: AggregatedField.AGGREGATION_TYPE_MAX);
128: List<StepField> newFields = new ArrayList<StepField>(1);
129: newFields.add(field);
130: modifiedQuery.setFields(newFields);
131: List<MMObjectNode> results = mmb
132: .getSearchQueryHandler().getNodes(
133: modifiedQuery,
134: new ResultBuilder(mmb, modifiedQuery));
135: MMObjectNode result = results.get(0);
136: int max = result.getIntValue(FIELD_NUMBER);
137: // add a new daymarker node
138: MMObjectNode node = getNewNode(SYSTEM_OWNER);
139: node.setValue(FIELD_DAYCOUNT, day);
140: node.setValue(FIELD_MARK, max);
141: insert(SYSTEM_OWNER, node);
142: }
143: } catch (SearchQueryException e) {
144: log.error(Logging.stackTrace(e));
145: }
146:
147: }
148:
149: /**
150: * This gets called every hour to see if the day has past.
151: */
152: public void probe() {
153: int newday;
154: newday = currentDay();
155: //debug("Days "+newday+" current "+day);
156: if (newday > day) {
157: day = newday;
158: createMarker();
159: }
160: }
161:
162: /**
163: * Returns the age, in days, of a node. So, this does the inverse of most methods in this
164: * class. It converts a node number (which is like a mark) to a day.
165: */
166: public int getAge(int nodeNumber) {
167: // first, check if it accidentily can be found with the cache:
168: Set<Map.Entry<Integer, Integer>> days = daycache.entrySet();
169: Iterator<Map.Entry<Integer, Integer>> i = days.iterator();
170: if (i.hasNext()) { // cache not empty
171: Map.Entry<Integer, Integer> current = i.next();
172: Map.Entry<Integer, Integer> previous = null;
173: while (i.hasNext()
174: && current.getValue().intValue() < nodeNumber) { // search until current > nodeNumber
175: previous = current;
176: current = i.next();
177: }
178: if ((previous != null)
179: && current.getValue().intValue() >= nodeNumber) { // found in cache
180: // if we found a lower and a higher mark on two consecutive days, return the lower.
181: if (current.getKey().intValue()
182: - previous.getKey().intValue() == 1) {
183: return day - previous.getKey().intValue();
184: }
185: }
186:
187: }
188: log.debug("Could not find with daycache " + nodeNumber
189: + ", searching in database now");
190:
191: try {
192: NodeSearchQuery query = new NodeSearchQuery(this );
193: StepField dayCount = query
194: .getField(getField(FIELD_DAYCOUNT));
195: BasicSortOrder sortOrder = query.addSortOrder(dayCount);
196: sortOrder.setDirection(SortOrder.ORDER_DESCENDING);
197: StepField markField = query.getField(getField(FIELD_MARK));
198: BasicFieldValueConstraint cons = new BasicFieldValueConstraint(
199: markField, nodeNumber);
200: cons.setOperator(FieldCompareConstraint.LESS);
201: query.setConstraint(cons);
202: query.setMaxNumber(1);
203:
204: List<MMObjectNode> resultList = getNodes(query);
205: // mark < in stead of mark = will of course only be used in database with are not on line always, such
206: // that some days do not have a mark.
207: if (log.isDebugEnabled()) {
208: log.debug(query);
209: }
210:
211: // String query = "select mark, daycount from " + mmb.baseName + "_" + tableName + " where mark < "+ nodeNumber + " order by daycount desc";
212: if (resultList.size() > 0) {
213: // search the first daycount of which' mark is lower.
214: // that must be the day which we were searching (at least a good estimate)
215: MMObjectNode markNode = resultList.get(0);
216: int mark = markNode.getIntValue(FIELD_MARK);
217: int daycount = markNode.getIntValue(FIELD_DAYCOUNT);
218: cachePut(daycount, mark); // found one, could as well cache it
219: getDayCount(daycount + 1); // next time, this can be count with the cache as well
220: return day - daycount;
221: } else {
222: // hmm, strange, perhaps we have to seek the oldest daycount, but for the moment:
223: log.service("daycount could not be found for node "
224: + nodeNumber);
225: // determining the oldest daycount:
226: query = new NodeSearchQuery(this );
227: StepField number = query
228: .getField(getField(FIELD_NUMBER));
229: sortOrder = query.addSortOrder(number);
230: sortOrder.setDirection(SortOrder.ORDER_ASCENDING);
231: query.setMaxNumber(1);
232: resultList = getNodes(query);
233:
234: if (resultList.size() > 0) {
235: MMObjectNode markNode = resultList.get(0);
236: int mark = markNode.getIntValue(FIELD_MARK);
237: int daycount = markNode.getIntValue(FIELD_DAYCOUNT);
238: cachePut(daycount, mark); // found one, could as well cache it
239: getDayCount(daycount + 1); // next time, this can be count with the cache as well
240: return day - daycount;
241: } else {
242: // no daymarks found at all.
243: return 0; // everything from today.
244: }
245:
246: }
247: } catch (SearchQueryException e) {
248: log.error(Logging.stackTrace(e));
249: return -1;
250: }
251:
252: }
253:
254: /**
255: * The current day count.
256: * @return the number of days from 1970 of today.
257: **/
258: public int getDayCount() {
259: return day;
260: }
261:
262: /**
263: * Given an age, this function returns a mark, _not a day count_, and also _not an age_!
264: * @param daysold a time in days ago.
265: * @return the smallest object number of all objects that are younger than given parameter daysold.
266: **/
267: public int getDayCountAge(int daysold) {
268: int wday = day - daysold;
269: return getDayCount(wday);
270: }
271:
272: /**
273: * Calculates the smallest object number of all objects that are younger than the specified age.
274: * @param wday teh age in number of days from 1970
275: * @return the smallest object number, 0 if it can't be found
276: */
277: private int getDayCount(int wday) {
278: log.debug("finding mark of day " + wday);
279: Integer result = daycache.get(wday);
280: if (result != null) { // already in cache
281: return result.intValue();
282: }
283: log.debug("could not be found in cache");
284:
285: if (wday < smallestDay) { // will not be possible to find in database
286: if (log.isDebugEnabled()) {
287: log.debug("Day " + wday
288: + " is smaller than smallest in database");
289: }
290: return 0;
291: }
292: if (wday <= day) {
293: NodeSearchQuery query = new NodeSearchQuery(this );
294: query.setMaxNumber(1);
295: StepField daycountField = query
296: .getField(getField(FIELD_DAYCOUNT));
297: BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(
298: daycountField, wday);
299: constraint
300: .setOperator(FieldCompareConstraint.GREATER_EQUAL);
301: query.setConstraint(constraint);
302: int mark = 0;
303: try {
304: List<MMObjectNode> resultList = getNodes(query);
305: if (resultList.size() != 0) {
306: MMObjectNode resultNode = resultList.get(0);
307: mark = resultNode.getIntValue(FIELD_MARK);
308: int daycount = resultNode
309: .getIntValue(FIELD_DAYCOUNT);
310: if (daycount != wday) {
311: log.error("Could not find day " + wday
312: + ", surrogated with " + daycount);
313: } else {
314: log
315: .debug("Found in db, will be inserted in cache");
316: }
317: cachePut(wday, mark);
318: }
319: } catch (SearchQueryException e) {
320: log.error(Logging.stackTrace(e));
321: }
322: return mark;
323: } else {
324: return Integer.MAX_VALUE;
325: }
326: }
327:
328: /**
329: * Scan. Known tokens are:
330: * COUNT-X gets an object number of X days after 1970
331: * COUNTAGE-X gets an object number of X days old
332: * COUNTMONTH-X gets an object number of X months after 1970
333: * COUNTNEXTMONTH-X gets an object number of X+1 months after 1970
334: * COUNTPREVMONTH-X gets an object number of X-1 months after 1970
335: * COUNTPREVDELTAMONTH-X-Y gets an object number of X-Y months after 1970
336: * COUNTNEXTDELTAMONTH-X-Y gets an object number of X+Y months after 1970
337: * TIMETOOBJECTNUMBER gets an object number of X seconds after 1970
338: **/
339: public String replace(PageInfo sp, StringTokenizer command) {
340: String rtn = "";
341: int ival;
342: if (command.hasMoreTokens()) {
343: String token = command.nextToken();
344: if (token.equals("COUNT")) {
345: ival = fetchIntValue(command);
346: rtn = "" + getDayCount(ival);
347: } else if (token.equals("COUNTAGE")) {
348: ival = fetchIntValue(command);
349: rtn = "" + getDayCountAge(ival);
350: } else if (token.equals("COUNTMONTH")) {
351: ival = fetchIntValue(command);
352: rtn = "" + getDayCount(getDayCountMonth(ival));
353: } else if (token.equals("COUNTNEXTMONTH")) {
354: ival = fetchIntValue(command);
355: rtn = "" + getDayCount(getDayCountNextMonth(ival));
356: } else if (token.equals("COUNTPREVMONTH")) {
357: ival = fetchIntValue(command);
358: rtn = "" + getDayCount(getDayCountPreviousMonth(ival));
359: } else if (token.equals("COUNTPREVDELTAMONTH")) {
360: ival = fetchIntValue(command);
361: int delta = 0 - fetchIntValue(command);
362: rtn = ""
363: + getDayCount(getDayCountDeltaMonth(ival, delta));
364: } else if (token.equals("COUNTNEXTDELTAMONTH")) {
365: ival = fetchIntValue(command);
366: int delta = fetchIntValue(command);
367: rtn = ""
368: + getDayCount(getDayCountDeltaMonth(ival, delta));
369: } else if (token.equals("TIMETOOBJECTNUMBER")) {
370: ival = fetchIntValue(command);
371: rtn = "" + getDayCount((int) (ival / SECONDS_IN_A_DAY));
372: } else {
373: rtn = "UnknownCommand";
374: }
375: }
376: return rtn;
377: }
378:
379: /**
380: * @javadoc
381: */
382: private int fetchIntValue(StringTokenizer command) {
383: String val;
384: int ival;
385: if (command.hasMoreTokens()) {
386: val = command.nextToken();
387: } else {
388: val = "0";
389: }
390: try {
391: ival = Integer.parseInt(val);
392: } catch (NumberFormatException e) {
393: ival = 0;
394: }
395: return ival;
396: }
397:
398: /**
399: * get a Calendar
400: * @param months number of months from 1970
401: * @return calendar with date specified in months from 1970
402: */
403: private Calendar getCalendarMonths(int months) {
404: int year, month;
405: year = months / 12;
406: month = months % 12;
407: GregorianCalendar cal = new GregorianCalendar();
408: cal.set(year + 1970, month, 1, 0, 0, 0);
409: return cal;
410: }
411:
412: /**
413: * @javadoc
414: */
415: private Calendar getCalendarDays(int days) {
416: GregorianCalendar cal = new GregorianCalendar();
417: java.util.Date d = new java.util.Date((days)
418: * MILLISECONDS_IN_A_DAY);
419: cal.setTime(d);
420: return cal;
421: }
422:
423: /**
424: * @javadoc
425: */
426: private int getDayCountMonth(int months) {
427: Calendar cal = getCalendarMonths(months);
428: return (int) (cal.getTime().getTime() / MILLISECONDS_IN_A_DAY);
429: }
430:
431: /**
432: * @javadoc
433: */
434: private int getDayCountPreviousMonth(int months) {
435: Calendar cal = getCalendarMonths(months);
436: cal.add(Calendar.MONTH, -1);
437: return (int) (cal.getTime().getTime() / MILLISECONDS_IN_A_DAY);
438: }
439:
440: /**
441: * @javadoc
442: */
443: private int getDayCountNextMonth(int months) {
444: Calendar cal = getCalendarMonths(months);
445: cal.add(Calendar.MONTH, 1);
446: return (int) (cal.getTime().getTime() / MILLISECONDS_IN_A_DAY);
447: }
448:
449: /**
450: * @javadoc
451: */
452: private int getDayCountDeltaMonth(int months, int delta) {
453: Calendar cal = getCalendarMonths(months);
454: cal.add(Calendar.MONTH, delta);
455: return (int) (cal.getTime().getTime() / MILLISECONDS_IN_A_DAY);
456: }
457:
458: /**
459: * @javadoc
460: */
461: public int getDayCountByObject(int number) {
462: NodeSearchQuery query = new NodeSearchQuery(this );
463: query.setMaxNumber(1);
464: StepField markField = query.getField(getField(FIELD_MARK));
465: BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(
466: markField, number);
467: constraint.setOperator(FieldCompareConstraint.LESS);
468: query.setConstraint(constraint);
469: ModifiableQuery modifiedQuery = new ModifiableQuery(query);
470: Step step = query.getSteps().get(0);
471: AggregatedField field = new BasicAggregatedField(step,
472: getField(FIELD_DAYCOUNT),
473: AggregatedField.AGGREGATION_TYPE_MAX);
474: List<StepField> newFields = new ArrayList<StepField>(1);
475: newFields.add(field);
476: modifiedQuery.setFields(newFields);
477: try {
478: List<MMObjectNode> results = mmb.getSearchQueryHandler()
479: .getNodes(modifiedQuery,
480: new ResultBuilder(mmb, modifiedQuery));
481: MMObjectNode result = results.get(0);
482: return result.getIntValue(FIELD_DAYCOUNT);
483: } catch (SearchQueryException e) {
484: log.error(Logging.stackTrace(e));
485: return 0;
486: }
487:
488: }
489:
490: /**
491: * @javadoc
492: */
493: public int getMonthsByDayCount(int daycount) {
494: int year, month;
495: Calendar calendar;
496:
497: calendar = getCalendarDays(daycount);
498: year = calendar.get(Calendar.YEAR) - 1970;
499: month = calendar.get(Calendar.MONTH);
500: return month + year * 12;
501: }
502:
503: /**
504: * Returns the date of a daymarker
505: * @param node The node of which the date is wanted
506: * @return a <code>Date</code> which is the date
507: */
508: public java.util.Date getDate(MMObjectNode node) {
509: int dayCount = node.getIntValue(FIELD_DAYCOUNT);
510: return new java.util.Date(dayCount * MILLISECONDS_IN_A_DAY);
511: }
512:
513: /**
514: * Returns gui information for a specific node. This value is retrieved by retrieving the field 'gui()' of the node (node.getStringValue("gui()") )
515: * @param node The node of which the gui information is wanted
516: * @return a <code>String</code> in which the current date is shown
517: */
518: public String getLocaleGUIIndicator(Locale locale, MMObjectNode node) {
519: return DateFormat.getDateInstance(DateFormat.LONG, locale)
520: .format(getDate(node));
521:
522: }
523:
524: }
|