Index: xwiki-platform-core/xwiki-core/pom.xml =================================================================== --- xwiki-platform-core/xwiki-core/pom.xml (revision 6032) +++ xwiki-platform-core/xwiki-core/pom.xml (working copy) @@ -485,6 +485,11 @@ xwiki-core-component ${version} + + joda-time + joda-time + 1.4 + Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/api/StatsService.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/api/StatsService.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/api/StatsService.java (revision 0) @@ -0,0 +1,209 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ +package com.xpn.xwiki.api; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.stats.api.XWikiStatsService; +import com.xpn.xwiki.stats.impl.DocumentStats; +import com.xpn.xwiki.stats.impl.Duration; +import com.xpn.xwiki.stats.impl.DurationFactory; +import com.xpn.xwiki.stats.impl.Interval; +import com.xpn.xwiki.stats.impl.IntervalFactory; +import com.xpn.xwiki.stats.impl.Period; +import com.xpn.xwiki.stats.impl.PeriodFactory; +import com.xpn.xwiki.stats.impl.Scope; +import com.xpn.xwiki.stats.impl.ScopeFactory; + +/** + * Statistics api. The statistics module need to be activated (xwiki.stats=1 in xwiki.cfg) + */ +public class StatsService extends Api +{ + public StatsService(XWikiContext context) + { + super(context); + } + + /** + * @return true if the statistics application is activated in the xwiki.cfg + */ + public boolean isEnabled() + { + return "1".equals(getXWikiContext().getWiki().Param("xwiki.stats")); + } + + /** + * @return A helper factory for creating Scope objects in velocity. + */ + public ScopeFactory getScopeFactory() + { + return ScopeFactory.getInstance(); + } + + /** + * @return A helper factory for creating a Period objects in velocity. + */ + public PeriodFactory getPeriodFactory() + { + return PeriodFactory.getInstance(); + } + + /** + * @return A helper factory for creating a Period objects in velocity. + */ + public DurationFactory getDurationFactory() + { + return DurationFactory.getInstance(); + } + + /** + * @return A helper factory for creating Interval objects in velocity. + */ + public IntervalFactory getIntervalFactory() + { + return IntervalFactory.getInstance(); + } + + /** + * Retrieves document statistics. + * + * @param action Can be one of: "view", "save", "download" + * @param scope The set of documents for which to retrieve statistics + * @param period The period of time + * @param interval The sub-interval to return from the entire result set. Use this parameter for + * pagination + * @return A list of DocumentStats objects + */ + public List getDocumentStatistics(String action, Scope scope, Period period, Interval interval) + { + XWikiStatsService stats = getXWikiContext().getWiki().getStatsService(getXWikiContext()); + if (stats == null) + return Collections.EMPTY_LIST; + return stats.getDocumentStatistics(action, scope, period, interval, getXWikiContext()); + } + + /** + * Retrieves visit statistics + * + * @param action The action the results should be ordered by + * @param period The period of time + * @param interval The sub-interval to return from the entire result set. Use this parameter for + * pagination + * @return A list of VisitStats objects + */ + public List getVisitStatistics(String action, Period period, Interval interval) + { + XWikiStatsService stats = getXWikiContext().getWiki().getStatsService(getXWikiContext()); + if (stats == null) + return Collections.EMPTY_LIST; + return stats.getVisitStatistics(action, period, interval, getXWikiContext()); + } + + /** + * Retrieves referrer statistics. + * + * @param domain The domain for which to retrieve statistics. To retrieve statistics for all + * domains use the empty string. + * @param scope The scope of referred documents to use for filtering the results. + * @param period The period of time + * @param interval The sub-interval to return from the entire result set. Use this parameter for + * pagination + * @return A list of RefererStats objects + */ + public List getRefererStatistics(String domain, Scope scope, Period period, Interval interval) + { + XWikiStatsService stats = getXWikiContext().getWiki().getStatsService(getXWikiContext()); + if (stats == null) + return Collections.EMPTY_LIST; + return stats.getRefererStatistics(domain, scope, period, interval, getXWikiContext()); + } + + /** + * Retrieves back-link statistics. + * + * @param domain The domain used for filtering the results + * @param scope The scope of referred documents for which to retrieve statistics. + * @param period The period of time + * @param interval The sub-interval to return from the entire result set. Use this parameter for + * pagination + * @return A list of DocumentStats objects + */ + public List getBackLinkStatistics(String domain, Scope scope, Period period, Interval interval) + { + XWikiStatsService stats = getXWikiContext().getWiki().getStatsService(getXWikiContext()); + if (stats == null) + return Collections.EMPTY_LIST; + return stats.getBackLinkStatistics(domain, scope, period, interval, getXWikiContext()); + } + + /** + * Shows how the statistics for the specified action have evolved over the specified period of + * time. + * + * @param action + * @param scope The set of documents to consider + * @param period The period of time + * @param step The step used for sampling the period + * @return A map of (date, actionCount) pairs + */ + public Map getActionStatistics(String action, Scope scope, Period period, Duration step) + { + XWikiStatsService stats = getXWikiContext().getWiki().getStatsService(getXWikiContext()); + if (stats == null) + return Collections.EMPTY_MAP; + return stats.getActionStatistics(action, scope, period, step, getXWikiContext()); + } + + /** + * API to access the current starts for the Wiki for a specific action It retrieves the number + * of times the action was performed for the whole wiki. + * + * @param action action for which to retrieve statistics (view/save/download) + * @return A DocumentStats object with number of actions performed, unique visitors, number of + * visits + * @deprecated use {@link #getDocumentStatistics(String, Scope, Period, Interval)} instead + */ + public DocumentStats getCurrentMonthXWikiStats(String action) + { + return getXWikiContext().getWiki().getStatsService(getXWikiContext()).getDocMonthStats( + "", action, new Date(), getXWikiContext()); + } + + /** + * Returns the recently visited pages for a specific action + * + * @param action ("view" or "edit") + * @param size how many recent actions to retrieve + * @return a ArrayList of document names + */ + public java.util.Collection getRecentActions(String action, int size) + { + XWikiStatsService stats = getXWikiContext().getWiki().getStatsService(getXWikiContext()); + if (stats == null) + return Collections.EMPTY_LIST; + return stats.getRecentActions(action, size, getXWikiContext()); + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/api/XWiki.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/api/XWiki.java (revision 6032) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/api/XWiki.java (working copy) @@ -50,6 +50,11 @@ protected static final Log LOG = LogFactory.getLog(XWiki.class); private com.xpn.xwiki.XWiki xwiki; + + /** + * @see #getStatsService() + */ + private StatsService statsService; /** * XWiki API Constructor @@ -61,6 +66,7 @@ { super(context); this.xwiki = xwiki; + this.statsService = new StatsService(context); } /** @@ -1821,6 +1827,7 @@ * @param action action for which to retrieve statistics (view/save/download) * @return A DocumentStats object with number of actions performed, unique visitors, number of * visits + * @deprecated use {@link #getStatsService()} instead */ public DocumentStats getCurrentMonthXWikiStats(String action) { @@ -2099,6 +2106,7 @@ * @param action ("view" or "edit") * @param size how many recent actions to retrieve * @return a ArrayList of document names + * @deprecated use {@link #getStatsService()} instead */ public java.util.Collection getRecentActions(String action, int size) { @@ -2933,4 +2941,14 @@ long c = Long.parseLong(a) + Long.parseLong(b); return "" + c; } + + /** + * Access statistics api + * + * @return a StatsService instance that can be used to retrieve different xwiki statistics + */ + public StatsService getStatsService() + { + return this.statsService; + } } Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/api/XWikiStatsService.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/api/XWikiStatsService.java (revision 6032) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/api/XWikiStatsService.java (working copy) @@ -21,6 +21,11 @@ package com.xpn.xwiki.stats.api; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; + import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.notify.XWikiActionNotificationInterface; @@ -25,16 +30,61 @@ import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.notify.XWikiActionNotificationInterface; import com.xpn.xwiki.stats.impl.DocumentStats; - -import java.util.Collection; -import java.util.Date; -import java.util.List; +import com.xpn.xwiki.stats.impl.Duration; +import com.xpn.xwiki.stats.impl.Interval; +import com.xpn.xwiki.stats.impl.Period; +import com.xpn.xwiki.stats.impl.Scope; public interface XWikiStatsService extends XWikiActionNotificationInterface { public void init(XWikiContext context); + + /** + * @deprecated use {@link #getDocumentStatistics(String, Scope, Period, Interval, XWikiContext)} instead + */ public DocumentStats getDocTotalStats(String docname, String action, XWikiContext context); + + /** + * @deprecated use {@link #getDocumentStatistics(String, Scope, Period, Interval, XWikiContext)} instead + */ public DocumentStats getDocMonthStats(String docname, String action, Date month, XWikiContext context); + + /** + * @deprecated use {@link #getDocumentStatistics(String, Scope, Period, Interval, XWikiContext)} instead + */ public DocumentStats getDocDayStats(String docname, String action, Date day, XWikiContext context); + + /** + * @deprecated use {@link #getRefererStatistics(Period, Interval, XWikiContext)} instead + */ public List getRefMonthStats(String docName, Date month, XWikiContext context) throws XWikiException; public Collection getRecentActions(String action, int size, XWikiContext context); + + /** + * @see com.xpn.xwiki.api.StatsService#getDocumentStatistics(String, Scope, Period, Interval) + */ + List getDocumentStatistics(String action, Scope scope, Period period, Interval interval, + XWikiContext context); + + /** + * @see com.xpn.xwiki.api.StatsService#getActionStatistics(String, Scope, Period, Period) + */ + List getVisitStatistics(String action, Period period, Interval interval, XWikiContext context); + + /** + * @see com.xpn.xwiki.api.StatsService#getRefererStatistics(String, Scope, Period, Interval) + */ + List getRefererStatistics(String domain, Scope scope, Period period, Interval interval, + XWikiContext context); + + /** + * @see com.xpn.xwiki.api.StatsService#getBackLinkStatistics(String, Scope, Period, Interval) + */ + List getBackLinkStatistics(String domain, Scope scope, Period period, Interval interval, + XWikiContext context); + + /** + * @see com.xpn.xwiki.api.StatsService#getActionStatistics(String, Scope, Period, Period) + */ + Map getActionStatistics(String action, Scope scope, Period period, Duration step, + XWikiContext context); } Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/Duration.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/Duration.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/Duration.java (revision 0) @@ -0,0 +1,56 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +package com.xpn.xwiki.stats.impl; + +/** + * Immutable duration for retrieving statistics. A duration of time is uniquely identified by its + * span. It does not depend on the bounds used for its creation. + */ +public class Duration +{ + private org.joda.time.Period span; + + public Duration(int years, int months, int weeks, int days) + { + span = new org.joda.time.Period(years, months, weeks, days, 0, 0, 0, 0); + } + + public int getYears() + { + return span.getYears(); + } + + public int getMonths() + { + return span.getMonths(); + } + + public int getWeeks() + { + return span.getWeeks(); + } + + public int getDays() + { + return span.getDays(); + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/DurationFactory.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/DurationFactory.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/DurationFactory.java (revision 0) @@ -0,0 +1,104 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +package com.xpn.xwiki.stats.impl; + +/** + * Helper factory class for creating Duration objects in velocity + */ +public class DurationFactory +{ + public static final Duration DAY = createDuration(0, 0, 0, 1); + + public static final Duration WEEK = createDuration(0, 0, 1, 0); + + public static final Duration MONTH = createDuration(0, 1, 0, 0); + + public static final Duration YEAR = createDuration(1, 0, 0, 0); + + private static final DurationFactory instance = new DurationFactory(); + + private DurationFactory() + { + } + + public static DurationFactory getInstance() + { + return instance; + } + + public static Duration createDuration(int years, int months, int weeks, int days) + { + return new Duration(years, months, weeks, days); + } + + public static Duration createDays(int days) + { + return createDuration(0, 0, 0, days); + } + + public static Duration createWeeks(int weeks) + { + return createDuration(0, 0, weeks, 0); + } + + public static Duration createMonths(int months) + { + return createDuration(0, months, 0, 0); + } + + public static Duration createYears(int years) + { + return createDuration(years, 0, 0, 0); + } + + /** + * Helper method for accessing {@link #DAY} static field in velocity + */ + public static Duration getDAY() + { + return DAY; + } + + /** + * Helper method for accessing {@link #WEEK} static field in velocity + */ + public static Duration getWEEK() + { + return WEEK; + } + + /** + * Helper method for accessing {@link #MONTH} static field in velocity + */ + public static Duration getMONTH() + { + return MONTH; + } + + /** + * Helper method for accessing {@link #YEAR} static field in velocity + */ + public static Duration getYEAR() + { + return YEAR; + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/Interval.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/Interval.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/Interval.java (revision 0) @@ -0,0 +1,58 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +package com.xpn.xwiki.stats.impl; + +/** + * Immutable continuous integer interval. It can be used for pagination + */ +public class Interval +{ + private int start; + + private int size; + + public Interval(int start, int size) + { + this.start = start; + this.size = size; + } + + public int getStart() + { + return start; + } + + public int getAbsoluteStart() + { + return Math.abs(start); + } + + public int getSize() + { + return size; + } + + public int getAbsoluteSize() + { + return Math.abs(size); + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/IntervalFactory.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/IntervalFactory.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/IntervalFactory.java (revision 0) @@ -0,0 +1,84 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +package com.xpn.xwiki.stats.impl; + +/** + * Helper factory class for creating Interval objects in velocity + */ +public class IntervalFactory +{ + public static final Interval ALL = createInterval(0, 0); + + public static final Interval FIRST = createInterval(0, 1); + + public static final Interval LAST = createInterval(0, -1); + + private static final IntervalFactory instance = new IntervalFactory(); + + private IntervalFactory() + { + } + + public static IntervalFactory getInstance() + { + return instance; + } + + public static Interval createInterval(int start, int size) + { + return new Interval(start, size); + } + + public static Interval createHeadInterval(int size) + { + return createInterval(0, size); + } + + public static Interval createTailInterval(int size) + { + return createInterval(0, -size); + } + + /** + * Helper method for accessing {@link #ALL} static field in velocity + */ + public static Interval getALL() + { + return ALL; + } + + /** + * Helper method for accessing {@link #FIRST} static field in velocity + */ + public static Interval getFIRST() + { + return FIRST; + } + + /** + * Helper method for accessing {@link #LAST} static field in velocity + */ + public static Interval getLAST() + { + return LAST; + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/Period.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/Period.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/Period.java (revision 0) @@ -0,0 +1,74 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +package com.xpn.xwiki.stats.impl; + +import org.joda.time.Interval; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +/** + * Immutable period for retrieving statistics. A period of time is uniquely identified by its + * bounds, and not by its span. + */ +public class Period +{ + private static final DateTimeFormatter DAY_PERIOD_FORMATTER = + DateTimeFormat.forPattern("yyyyMMdd"); + + private static final DateTimeFormatter MONTH_PERIOD_FORMATTER = + DateTimeFormat.forPattern("yyyyMM"); + + private Interval interval; + + public Period(long start, long end) + { + interval = new Interval(start, end); + } + + public long getStart() + { + return interval.getStartMillis(); + } + + public long getEnd() + { + return interval.getEndMillis(); + } + + public int getStartCode() + { + return Integer.parseInt(getFormatter().print(interval.getStart())); + } + + public int getEndCode() + { + return Integer.parseInt(getFormatter().print(interval.getEnd())); + } + + private DateTimeFormatter getFormatter() + { + if (interval.toPeriod().getMonths() >= 1) { + return MONTH_PERIOD_FORMATTER; + } + return DAY_PERIOD_FORMATTER; + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/PeriodFactory.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/PeriodFactory.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/PeriodFactory.java (revision 0) @@ -0,0 +1,225 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +package com.xpn.xwiki.stats.impl; + +import org.joda.time.DateTime; +import org.joda.time.MutableDateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +/** + * Helper factory class for creating Period objects in velocity + */ +public class PeriodFactory +{ + /** + * The minimum date considered when retrieving all-time statistics + */ + private static final DateTime MIN_DATE = new DateTime(1000, 1, 1, 0, 0, 0, 0); + + /** + * The maximum date considered when retrieving all-time statistics + */ + private static final DateTime MAX_DATE = new DateTime(9999, 12, 31, 23, 59, 59, 999); + + public static final Period ALL_TIME = + createPeriod(MIN_DATE.getMillis(), MAX_DATE.getMillis()); + + private static final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyyMMdd"); + + private static final PeriodFactory instance = new PeriodFactory(); + + private PeriodFactory() + { + } + + public static PeriodFactory getInstance() + { + return instance; + } + + public static Period createPeriod(long start, long end) + { + return new Period(start, end); + } + + /** + * Creates a new custom period. The start and end dates must have the following format: + * "yyyyMMdd" + * + * @param start The start date + * @param end The end date + * @return A new Period instance + * @see java.text.SimpleDateFormat + */ + public static Period createPeriod(String start, String end) + { + return createPeriod(formatter.parseMillis(start), formatter.parseMillis(end)); + } + + public static Period createDayPeriod(long timestamp) + { + MutableDateTime mdt = new MutableDateTime(timestamp); + return createPeriod(toDayStart(mdt).getMillis(), toDayEnd(mdt).getMillis()); + } + + /** + * @param date The string representation of a date uniquely identifying a day. Use the "yyyyMMdd" + * format. + * @return The corresponding Period object + * @see java.text.SimpleDateFormat + */ + public static Period createDayPeriod(String date) + { + return createDayPeriod(formatter.parseMillis(date)); + } + + public static Period getCurrentDay() + { + return createDayPeriod(new DateTime().getMillis()); + } + + public static Period createWeekPeriod(long timestamp) + { + MutableDateTime mdt = new MutableDateTime(timestamp); + return createPeriod(toWeekStart(mdt).getMillis(), toWeekEnd(mdt).getMillis()); + } + + /** + * @param date The string representation of a date uniquely identifying a week. Use the + * "yyyyMMdd" format. + * @return The corresponding Period object + * @see java.text.SimpleDateFormat + */ + public static Period createWeekPeriod(String date) + { + return createWeekPeriod(formatter.parseMillis(date)); + } + + public static Period getCurrentWeek() + { + return createWeekPeriod(new DateTime().getMillis()); + } + + public static Period createMonthPeriod(long timestamp) + { + MutableDateTime mdt = new MutableDateTime(timestamp); + return createPeriod(toMonthStart(mdt).getMillis(), toMonthEnd(mdt).getMillis()); + } + + /** + * @param date The string representation of a date uniquely identifying a month. Use the + * "yyyyMMdd" format. + * @return The corresponding Period object + * @see java.text.SimpleDateFormat + */ + public static Period createMonthPeriod(String date) + { + return createMonthPeriod(formatter.parseMillis(date)); + } + + public static Period getCurrentMonth() + { + return createMonthPeriod(new DateTime().getMillis()); + } + + public static Period createYearPeriod(long timestamp) + { + MutableDateTime mdt = new MutableDateTime(timestamp); + return createPeriod(toYearStart(mdt).getMillis(), toYearEnd(mdt).getMillis()); + } + + /** + * @param date The string representation of a date uniquely identifying a year. Use the + * "yyyyMMdd" format. + * @return The corresponding Period object + * @see java.text.SimpleDateFormat + */ + public static Period createYearPeriod(String date) + { + return createYearPeriod(formatter.parseMillis(date)); + } + + public static Period getCurrentYear() + { + return createYearPeriod(new DateTime().getMillis()); + } + + private static MutableDateTime toDayStart(MutableDateTime mdt) + { + mdt.setHourOfDay(mdt.hourOfDay().getMinimumValue()); + mdt.setMinuteOfHour(mdt.minuteOfHour().getMinimumValue()); + mdt.setSecondOfMinute(mdt.secondOfMinute().getMinimumValue()); + mdt.setMillisOfSecond(mdt.millisOfSecond().getMinimumValue()); + return mdt; + } + + private static MutableDateTime toDayEnd(MutableDateTime mdt) + { + mdt.addDays(1); + return toDayStart(mdt); + } + + private static MutableDateTime toWeekStart(MutableDateTime mdt) + { + mdt.setDayOfWeek(mdt.dayOfWeek().getMinimumValue()); + return toDayStart(mdt); + } + + private static MutableDateTime toWeekEnd(MutableDateTime mdt) + { + mdt.setDayOfWeek(mdt.dayOfWeek().getMaximumValue()); + return toDayEnd(mdt); + } + + private static MutableDateTime toMonthStart(MutableDateTime mdt) + { + mdt.setDayOfMonth(mdt.dayOfMonth().getMinimumValue()); + return toDayStart(mdt); + } + + private static MutableDateTime toMonthEnd(MutableDateTime mdt) + { + mdt.setDayOfMonth(mdt.dayOfMonth().getMaximumValue()); + return toDayEnd(mdt); + } + + private static MutableDateTime toYearStart(MutableDateTime mdt) + { + mdt.setDayOfYear(mdt.dayOfYear().getMinimumValue()); + return toDayStart(mdt); + } + + private static MutableDateTime toYearEnd(MutableDateTime mdt) + { + mdt.setDayOfYear(mdt.dayOfYear().getMaximumValue()); + return toDayEnd(mdt); + } + + /** + * Helper method for accessing {@link #ALL_TIME} static field in velocity + */ + public static Period getALL_TIME() + { + return ALL_TIME; + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/RefererStats.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/RefererStats.java (revision 6032) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/RefererStats.java (working copy) @@ -21,6 +21,8 @@ package com.xpn.xwiki.stats.impl; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Date; public class RefererStats extends XWikiStats { @@ -45,4 +47,13 @@ public void setReferer(String referer) { setStringValue("referer", referer); } + + public URL getURL() + { + try { + return new URL(getReferer()); + } catch (MalformedURLException e) { + return null; + } + } } Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/Scope.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/Scope.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/Scope.java (revision 0) @@ -0,0 +1,102 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +package com.xpn.xwiki.stats.impl; + +/** + * Immutable scope for retrieving statistics + */ +public class Scope +{ + public static final int PAGE_SCOPE = 1; + + public static final int SPACE_SCOPE = 2; + + public static final int WIKI_SCOPE = 3; + + public static final int GLOBAL_SCOPE = 4; + + private int type; + + private String name; + + private boolean deep; + + public Scope(int type, String name, boolean deep) + { + this.type = type; + this.name = name; + this.deep = deep; + } + + public int getType() + { + return type; + } + + public String getName() + { + return name; + } + + public boolean isDeep() + { + return deep; + } + + public String getPattern() + { + switch (type) { + case PAGE_SCOPE: + return getPagePattern(); + case SPACE_SCOPE: + return getSpacePattern(); + default: + return getGlobalPattern(); + } + } + + private String getPagePattern() + { + // ignore deep + if ("".equals(name)) { + // a pattern to match any page name + return "%.%"; + } + return name; + } + + private String getSpacePattern() + { + if ("".equals(name)) { + // TODO a pattern to match any space name + return null; + } else if (deep) { + return name + ".%"; + } + return name; + } + + private String getGlobalPattern() + { + return ""; + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/ScopeFactory.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/ScopeFactory.java (revision 0) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/ScopeFactory.java (revision 0) @@ -0,0 +1,109 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +package com.xpn.xwiki.stats.impl; + +/** + * Helper factory class for creating Scope objects in velocity + */ +public class ScopeFactory +{ + private static final ScopeFactory instance = new ScopeFactory(); + + public static final Scope ALL_PAGES = createScope(Scope.PAGE_SCOPE, "", false); + + public static final Scope ALL_SPACES = createScope(Scope.SPACE_SCOPE, "", false); + + public static final Scope ALL_WIKIS = createScope(Scope.WIKI_SCOPE, "", false); + + public static final Scope ALL = createScope(Scope.GLOBAL_SCOPE, "", false); + + private ScopeFactory() + { + } + + public static ScopeFactory getInstance() + { + return instance; + } + + public static Scope createScope(int type, String name, boolean deep) + { + return new Scope(type, name, deep); + } + + public static Scope createPageScope(String pageName) + { + return createScope(Scope.PAGE_SCOPE, pageName, false); + } + + public static Scope createSpaceScope(String spaceName) + { + return createSpaceScope(spaceName, true); + } + + public static Scope createSpaceScope(String spaceName, boolean deep) + { + return createScope(Scope.SPACE_SCOPE, spaceName, deep); + } + + public static Scope createWikiScope(String wikiName) + { + return createWikiScope(wikiName, true); + } + + public static Scope createWikiScope(String wikiName, boolean deep) + { + return createScope(Scope.WIKI_SCOPE, wikiName, deep); + } + + /** + * Helper method for accessing {@link #ALL_PAGES} static field in velocity + */ + public static Scope getALL_PAGES() + { + return ALL_PAGES; + } + + /** + * Helper method for accessing {@link #ALL_SPACES} static field in velocity + */ + public static Scope getALL_SPACES() + { + return ALL_SPACES; + } + + /** + * Helper method for accessing {@link #ALL_WIKIS} static field in velocity + */ + public static Scope getALL_WIKIS() + { + return ALL_WIKIS; + } + + /** + * Helper method for accessing {@link #ALL} static field in velocity + */ + public static Scope getALL() + { + return ALL; + } +} Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/XWikiStats.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/XWikiStats.java (revision 6032) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/XWikiStats.java (working copy) @@ -63,11 +63,7 @@ } public int hashCode() { - String str = getName()+getClassName(); - int nb = getNumber(); - if (nb>0) - str += "_" + nb; - return str.hashCode(); + return (getName() + getClassName() + "_" + getNumber()).hashCode(); } public void setId(int id) { Index: xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/XWikiStatsServiceImpl.java =================================================================== --- xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/XWikiStatsServiceImpl.java (revision 6032) +++ xwiki-platform-core/xwiki-core/src/main/java/com/xpn/xwiki/stats/impl/XWikiStatsServiceImpl.java (working copy) @@ -27,6 +27,9 @@ import com.xpn.xwiki.notify.XWikiActionRule; import com.xpn.xwiki.notify.XWikiNotificationRule; import com.xpn.xwiki.stats.api.XWikiStatsService; +import com.xpn.xwiki.stats.impl.DocumentStats; +import com.xpn.xwiki.stats.impl.StatsUtil; +import com.xpn.xwiki.stats.impl.VisitStats; import com.xpn.xwiki.store.XWikiHibernateStore; import com.xpn.xwiki.store.XWikiStoreInterface; import com.xpn.xwiki.store.jcr.XWikiJcrStore; @@ -43,6 +46,7 @@ import org.apache.portals.graffito.jcr.query.QueryManager; import org.hibernate.Query; import org.hibernate.Session; +import org.joda.time.DateTime; import javax.servlet.http.Cookie; import javax.servlet.http.HttpSession; @@ -566,6 +570,328 @@ return cookie; } -} + /** + * {@inheritDoc} + */ + public Map getActionStatistics(String action, Scope scope, Period period, Duration step, + XWikiContext context) + { + DateTime stepStart = new DateTime(period.getStart()); + DateTime periodEnd = new DateTime(period.getEnd()); + org.joda.time.Period stepDuration = + new org.joda.time.Period(step.getYears(), step.getMonths(), step.getWeeks(), step + .getDays(), 0, 0, 0, 0); + Map activity = new HashMap(); + while (stepStart.compareTo(periodEnd) < 0) { + DateTime stepEnd = stepStart.plus(stepDuration); + if (stepEnd.compareTo(periodEnd) > 0) { + stepEnd = periodEnd; + } + List stats = + this.getDocumentStatistics(action, scope, new Period(stepStart.getMillis(), + stepEnd.getMillis()), IntervalFactory.FIRST, context); + int actionCount = 0; + if (stats.size() > 0) { + actionCount = ((DocumentStats) stats.get(0)).getPageViews(); + } + activity.put(stepStart, new Integer(actionCount)); + stepStart = stepEnd; + } + return activity; + } + + /** + * {@inheritDoc} + */ + public List getDocumentStatistics(String action, Scope scope, Period period, + Interval interval, XWikiContext context) + { + String nameFilter = "name like :name"; + boolean hasNameParam = true; + if (scope.getType() == Scope.SPACE_SCOPE && "".equals(scope.getName())) { + nameFilter = "name not like '%.%' and name <> ''"; + hasNameParam = false; + } + String sortOrder = "desc"; + if (interval.getSize() < 0) { + sortOrder = "asc"; + } + XWikiHibernateStore store = null; + try { + store = context.getWiki().getHibernateStore(); + store.beginTransaction(context); + Session session = store.getSession(context); + Query query = + session + .createQuery("select name, sum(pageViews) from DocumentStats where action=:action and " + + nameFilter + + " and :startDate <= period and period <= :endDate group by name order by sum(pageViews) " + + sortOrder); + query.setString("action", action); + if (hasNameParam) { + query.setString("name", scope.getPattern()); + } + query.setInteger("startDate", period.getStartCode()); + query.setInteger("endDate", period.getEndCode()); + + List results = + getDocumentStatistics(store.search(query, interval.getAbsoluteSize(), interval + .getAbsoluteStart(), context), action); + if (interval.getSize() < 0) { + Collections.reverse(results); + } + return results; + } catch (XWikiException e) { + return Collections.EMPTY_LIST; + } finally { + try { + store.endTransaction(context, false); + } catch (Exception e) { + } + } + } + + /** + * Converts the rows retrieved from the database to a list of DocumentStats instances + * + * @param resultSet the result of a database query for document statistics + * @param action the action for which the statistics were retrieved + * @return a list of {@link com.xpn.xwiki.stats.impl.DocumentStats} objects + * @see #getDocumentStatistics(String, Scope, Period, Interval, XWikiContext) + */ + private List getDocumentStatistics(List resultSet, String action) + { + Date now = Calendar.getInstance().getTime(); + List stats = new ArrayList(resultSet.size()); + Iterator it = resultSet.iterator(); + while (it.hasNext()) { + Object[] result = (Object[]) it.next(); + // We can't represent a custom period (e.g. year, week or some time interval) in the + // database and thus we use a default one, which sould be ignored + DocumentStats docStats = + new DocumentStats((String) result[0], action, now, StatsUtil.PERIOD_DAY); + docStats.setPageViews(((Integer) result[1]).intValue()); + stats.add(docStats); + } + return stats; + } + + /** + * {@inheritDoc} + */ + public List getBackLinkStatistics(String domain, Scope scope, Period period, + Interval interval, XWikiContext context) + { + if (domain == null || domain.trim().length() == 0) { + domain = "%"; + } + String nameFilter = "name like :name"; + boolean hasNameParam = true; + if (scope.getType() == Scope.SPACE_SCOPE && "".equals(scope.getName())) { + nameFilter = "name not like '%.%' and name <> ''"; + hasNameParam = false; + } + String sortOrder = "desc"; + if (interval.getSize() < 0) { + sortOrder = "asc"; + } + XWikiHibernateStore store = null; + try { + store = context.getWiki().getHibernateStore(); + store.beginTransaction(context); + Session session = store.getSession(context); + Query query = + session + .createQuery("select name, sum(pageViews) from RefererStats where " + + nameFilter + + " and referer like :referer and :startDate <= period and period <= :endDate group by name order by sum(pageViews) " + + sortOrder); + if (hasNameParam) { + query.setString("name", scope.getPattern()); + } + query.setString("referer", domain); + query.setInteger("startDate", period.getStartCode()); + query.setInteger("endDate", period.getEndCode()); + + List results = + getDocumentStatistics(store.search(query, interval.getAbsoluteSize(), interval + .getAbsoluteStart(), context), "refer"); + if (interval.getSize() < 0) { + Collections.reverse(results); + } + return results; + } catch (XWikiException e) { + return Collections.EMPTY_LIST; + } finally { + try { + store.endTransaction(context, false); + } catch (Exception e) { + } + } + } + /** + * {@inheritDoc} + */ + public List getRefererStatistics(String domain, Scope scope, Period period, + Interval interval, XWikiContext context) + { + if (domain == null || domain.trim().length() == 0) { + domain = "%"; + } + String nameFilter = "name like :name"; + boolean hasNameParam = true; + if (scope.getType() == Scope.SPACE_SCOPE && "".equals(scope.getName())) { + nameFilter = "name not like '%.%' and name <> ''"; + hasNameParam = false; + } + String sortOrder = "desc"; + if (interval.getSize() < 0) { + sortOrder = "asc"; + } + XWikiHibernateStore store = null; + try { + store = context.getWiki().getHibernateStore(); + store.beginTransaction(context); + Session session = store.getSession(context); + Query query = + session + .createQuery("select referer, sum(pageViews) from RefererStats where " + + nameFilter + + " and referer like :referer and :startDate <= period and period <= :endDate group by referer order by sum(pageViews) " + + sortOrder); + if (hasNameParam) { + query.setString("name", scope.getPattern()); + } + query.setString("referer", domain); + query.setInteger("startDate", period.getStartCode()); + query.setInteger("endDate", period.getEndCode()); + List results = + getRefererStatistics(store.search(query, interval.getAbsoluteSize(), interval + .getAbsoluteStart(), context)); + if (interval.getSize() < 0) { + Collections.reverse(results); + } + return results; + } catch (XWikiException e) { + return Collections.EMPTY_LIST; + } finally { + try { + store.endTransaction(context, false); + } catch (Exception e) { + } + } + } + + /** + * Converts the rows retrieved from the database to a list of + * {@link com.xpn.xwiki.stats.impl.RefererStats} instances + * + * @param resultSet The result of a database query for referer statistics + * @return A list of {@link com.xpn.xwiki.stats.impl.RefererStats} objects + * @see #getRefererStatistics(String, Scope, Period, Interval, XWikiContext) + */ + private List getRefererStatistics(List resultSet) + { + Date now = Calendar.getInstance().getTime(); + List stats = new ArrayList(resultSet.size()); + Iterator it = resultSet.iterator(); + while (it.hasNext()) { + Object[] result = (Object[]) it.next(); + // We can't represent a custom period (e.g. year, week or some time interval) in the + // database and thus we use a default one, which sould be ignored + RefererStats refStats = + new RefererStats("", (String) result[0], now, StatsUtil.PERIOD_DAY); + refStats.setPageViews(((Integer) result[1]).intValue()); + stats.add(refStats); + } + return stats; + } + + /** + * {@inheritDoc} + */ + public List getVisitStatistics(String action, Period period, Interval interval, + XWikiContext context) + { + String sortOrder = "desc"; + if (interval.getSize() < 0) { + sortOrder = "asc"; + } + String orderByClause = + "order by sum(pageSaves) " + sortOrder + ", sum(pageViews) " + sortOrder + + ", sum(downloads) " + sortOrder; + if (action.equals("save")) { + orderByClause = "order by sum(pageSaves) " + sortOrder; + } else if (action.equals("view")) { + orderByClause = "order by sum(pageViews) " + sortOrder; + } else if (action.equals("download")) { + orderByClause = "order by sum(downloads) " + sortOrder; + } + XWikiHibernateStore store = null; + try { + store = context.getWiki().getHibernateStore(); + store.beginTransaction(context); + Session session = store.getSession(context); + Query query = + session + .createQuery("select name, uniqueID, cookie, IP, userAgent, sum(pageSaves), sum(pageViews), sum(downloads) from VisitStats where :startDate <= startDate and endDate < :endDate group by name " + + orderByClause); + query.setDate("startDate", new Date(period.getStart())); + query.setDate("endDate", new Date(period.getEnd())); + + List results = + getVisitStatistics(store.search(query, interval.getAbsoluteSize(), interval + .getAbsoluteStart(), context), new DateTime(period.getStart()), + new DateTime(period.getEnd())); + if (interval.getSize() < 0) { + Collections.reverse(results); + } + return results; + } catch (XWikiException e) { + return Collections.EMPTY_LIST; + } finally { + try { + store.endTransaction(context, false); + } catch (Exception e) { + } + } + } + + /** + * Converts the rows retrieved from the database to a list of VisitStats instances + * + * @param resultSet the result of a database query for visitor statistics + * @param startDate the start date used in the query + * @param endDate the end date used in the query + * @return a list of {@link com.xpn.xwiki.stats.impl.VisitStats} objects + * @see #getVisitStatistics(Period, Interval, XWikiContext) + */ + private List getVisitStatistics(List resultSet, DateTime startDate, DateTime endDate) + { + List stats = new ArrayList(resultSet.size()); + Iterator it = resultSet.iterator(); + while (it.hasNext()) { + Object[] result = (Object[]) it.next(); + String name = (String) result[0]; + String uniqueID = (String) result[1]; + String cookie = (String) result[2]; + String ip = (String) result[3]; + String userAgent = (String) result[4]; + int pageSaves = ((Integer) result[5]).intValue(); + int pageViews = ((Integer) result[6]).intValue(); + int downloads = ((Integer) result[7]).intValue(); + VisitStats vs = + new VisitStats(name, uniqueID, cookie, ip, userAgent, new Date(startDate + .getMillis()), StatsUtil.PERIOD_DAY); + vs.setStartDate(new Date(startDate.getMillis())); + vs.setEndDate(new Date(endDate.getMillis())); + vs.setPageSaves(pageSaves); + vs.setPageViews(pageViews); + vs.setDownloads(downloads); + stats.add(vs); + } + return stats; + } +}