You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@continuum.apache.org by ba...@apache.org on 2015/05/04 03:42:06 UTC

svn commit: r1677500 - in /continuum/trunk: continuum-api/src/main/java/org/apache/continuum/dao/ continuum-api/src/main/java/org/apache/maven/continuum/ continuum-core/src/main/java/org/apache/maven/continuum/ continuum-core/src/test/java/org/apache/m...

Author: batkinson
Date: Mon May  4 01:42:05 2015
New Revision: 1677500

URL: http://svn.apache.org/r1677500
Log:
[CONTINUUM-2760] Change API to prevent unbounded build results access

Also addressed:
[CONTINUUM-2746] Running build reports for a large number of builds can crash continuum
[CONTINUUM-2537] Allow navigation to build result details from a build report

Modified:
    continuum/trunk/continuum-api/src/main/java/org/apache/continuum/dao/BuildResultDao.java
    continuum/trunk/continuum-api/src/main/java/org/apache/maven/continuum/Continuum.java
    continuum/trunk/continuum-core/src/main/java/org/apache/maven/continuum/DefaultContinuum.java
    continuum/trunk/continuum-core/src/test/java/org/apache/maven/continuum/DefaultContinuumTest.java
    continuum/trunk/continuum-store/src/main/java/org/apache/continuum/dao/BuildResultDaoImpl.java
    continuum/trunk/continuum-store/src/test/java/org/apache/maven/continuum/store/ContinuumStoreTest.java
    continuum/trunk/continuum-webapp/src/main/java/org/apache/continuum/web/action/ViewBuildsReportAction.java
    continuum/trunk/continuum-webapp/src/main/java/org/apache/maven/continuum/web/action/ProjectGroupAction.java
    continuum/trunk/continuum-webapp/src/main/resources/localization/Continuum.properties
    continuum/trunk/continuum-webapp/src/main/resources/struts.xml
    continuum/trunk/continuum-webapp/src/main/webapp/WEB-INF/jsp/viewProjectBuildsReport.jsp
    continuum/trunk/continuum-webapp/src/test/java/org/apache/continuum/web/action/ViewBuildsReportActionTest.java
    continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-api/src/main/java/org/apache/maven/continuum/xmlrpc/ContinuumService.java
    continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-backup/src/main/java/org/apache/maven/continuum/xmlrpc/backup/Backup.java
    continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/BuildResultsPurge.java
    continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/ContinuumXmlRpcClient.java
    continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/SampleClient.java
    continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-server/src/main/java/org/apache/maven/continuum/xmlrpc/server/ContinuumServiceImpl.java

Modified: continuum/trunk/continuum-api/src/main/java/org/apache/continuum/dao/BuildResultDao.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-api/src/main/java/org/apache/continuum/dao/BuildResultDao.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-api/src/main/java/org/apache/continuum/dao/BuildResultDao.java (original)
+++ continuum/trunk/continuum-api/src/main/java/org/apache/continuum/dao/BuildResultDao.java Mon May  4 01:42:05 2015
@@ -72,12 +72,12 @@ public interface BuildResultDao
     /**
      * Returns the list of build results between the fromdate and the buildResult defined by its toBuildResultId
      *
-     * @param projectId       The project id
-     * @param fromDate        the from date
-     * @param tobuildResultId the build result id
+     * @param projectId    The project id
+     * @param fromResultId the from date
+     * @param toResultId   the build result id
      * @return the list of build results
      */
-    List<BuildResult> getBuildResultsForProjectWithDetails( int projectId, long fromDate, int tobuildResultId );
+    List<BuildResult> getBuildResultsForProjectWithDetails( int projectId, int fromResultId, int toResultId );
 
     /**
      * Returns the number of build results in success since fromDate
@@ -88,9 +88,16 @@ public interface BuildResultDao
      */
     long getNbBuildResultsInSuccessForProject( int projectId, long fromDate );
 
-    List<BuildResult> getBuildResultsForProject( int projectId );
-
-    List<BuildResult> getBuildResultsForProject( int projectId, long startIndex, long endIndex );
+    /**
+     * Looks up a range of build results for a project ordered from most recent to earliest.
+     *
+     * @param projectId   id of project to fetch results for
+     * @param startIndex  zero-based index indicating start of result interval
+     * @param endIndex    zero-based index indicating boundary of result interval
+     * @param fullDetails whether to use a fetch plan that includes full build result details
+     * @return a list of results in the interval [start, end)
+     */
+    List<BuildResult> getBuildResultsForProject( int projectId, long startIndex, long endIndex, boolean fullDetails );
 
     /**
      * @param projectId
@@ -115,5 +122,5 @@ public interface BuildResultDao
     List<BuildResult> getAllBuildsForAProjectByDate( int projectId );
 
     List<BuildResult> getBuildResultsInRange( Date fromDate, Date toDate, int state, String triggeredBy,
-                                              int projectGroupId );
+                                              int projectGroupId, int offset, int length );
 }

Modified: continuum/trunk/continuum-api/src/main/java/org/apache/maven/continuum/Continuum.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-api/src/main/java/org/apache/maven/continuum/Continuum.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-api/src/main/java/org/apache/maven/continuum/Continuum.java (original)
+++ continuum/trunk/continuum-api/src/main/java/org/apache/maven/continuum/Continuum.java Mon May  4 01:42:05 2015
@@ -108,7 +108,6 @@ public interface Continuum
     void removeProject( int projectId )
         throws ContinuumException;
 
-
     /**
      * @param projectId
      * @throws ContinuumException
@@ -199,9 +198,6 @@ public interface Continuum
 
     long getNbBuildResultsForProject( int projectId );
 
-    Collection<BuildResult> getBuildResultsForProject( int projectId )
-        throws ContinuumException;
-
     Collection<BuildResult> getBuildResultsForProject( int projectId, int offset, int length )
         throws ContinuumException;
 
@@ -212,7 +208,7 @@ public interface Continuum
         throws ContinuumException;
 
     List<BuildResult> getBuildResultsInRange( int projectGroupId, Date fromDate, Date toDate, int state,
-                                              String triggeredBy );
+                                              String triggeredBy, int offset, int length );
 
     // ----------------------------------------------------------------------
     // Projects
@@ -300,7 +296,6 @@ public interface Continuum
                                                        boolean useCredentialsCache )
         throws ContinuumException;
 
-
     /**
      * Add a Maven 2 project to the list of projects.
      *
@@ -457,7 +452,6 @@ public interface Continuum
 
     /**
      * returns the default build definition for the project
-     * <p/>
      * 1) if project has default build definition, return that
      * 2) otherwise return default build definition for parent project group
      *

Modified: continuum/trunk/continuum-core/src/main/java/org/apache/maven/continuum/DefaultContinuum.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-core/src/main/java/org/apache/maven/continuum/DefaultContinuum.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-core/src/main/java/org/apache/maven/continuum/DefaultContinuum.java (original)
+++ continuum/trunk/continuum-core/src/main/java/org/apache/maven/continuum/DefaultContinuum.java Mon May  4 01:42:05 2015
@@ -541,9 +541,10 @@ public class DefaultContinuum
     }
 
     public List<BuildResult> getBuildResultsInRange( int projectGroupId, Date fromDate, Date toDate, int state,
-                                                     String triggeredBy )
+                                                     String triggeredBy, int offset, int length )
     {
-        return buildResultDao.getBuildResultsInRange( fromDate, toDate, state, triggeredBy, projectGroupId );
+        return buildResultDao.getBuildResultsInRange( fromDate, toDate, state, triggeredBy, projectGroupId, offset,
+                                                      length );
     }
 
     // ----------------------------------------------------------------------
@@ -630,16 +631,22 @@ public class DefaultContinuum
             project.getDependencies().clear();
             projectDao.updateProject( project );
 
-            Collection<BuildResult> buildResults = getBuildResultsForProject( projectId );
-
-            for ( BuildResult br : buildResults )
-            {
-                br.setBuildDefinition( null );
-                //Remove all modified dependencies to prevent SQL errors
-                br.getModifiedDependencies().clear();
-                buildResultDao.updateBuildResult( br );
-                removeBuildResult( br );
+            // Remove build results in batches: the number of results could be very large
+            int batchSize = 100;
+            Collection<BuildResult> buildResults;
+            do
+            {
+                buildResults = buildResultDao.getBuildResultsForProject( projectId, 0, batchSize, true );
+                for ( BuildResult br : buildResults )
+                {
+                    br.setBuildDefinition( null );
+                    //Remove all modified dependencies to prevent SQL errors
+                    br.getModifiedDependencies().clear();
+                    buildResultDao.updateBuildResult( br );
+                    removeBuildResult( br );
+                }
             }
+            while ( buildResults != null && buildResults.size() > 0 );
 
             File workingDirectory = getWorkingDirectory( projectId );
 
@@ -1145,11 +1152,9 @@ public class DefaultContinuum
         {
             //No previous build in success, Nothing to do
         }
-        long startTime = previousBuildResult == null ? 0 : previousBuildResult.getStartTime();
+        int lastSuccessId = previousBuildResult == null ? 0 : previousBuildResult.getId();
         ArrayList<BuildResult> buildResults = new ArrayList<BuildResult>(
-            buildResultDao.getBuildResultsForProjectWithDetails( projectId, startTime, buildResultId ) );
-
-        Collections.reverse( buildResults );
+            buildResultDao.getBuildResultsForProjectWithDetails( projectId, lastSuccessId, buildResultId ) );
 
         Iterator<BuildResult> buildResultsIterator = buildResults.iterator();
 
@@ -2809,16 +2814,10 @@ public class DefaultContinuum
         return buildResultDao.getNbBuildResultsForProject( projectId );
     }
 
-    public Collection<BuildResult> getBuildResultsForProject( int projectId )
-        throws ContinuumException
-    {
-        return buildResultDao.getBuildResultsForProject( projectId );
-    }
-
     public Collection<BuildResult> getBuildResultsForProject( int projectId, int offset, int length )
         throws ContinuumException
     {
-        return buildResultDao.getBuildResultsForProject( projectId, offset, offset + length );
+        return buildResultDao.getBuildResultsForProject( projectId, offset, offset + length, false );
     }
 
     // ----------------------------------------------------------------------

Modified: continuum/trunk/continuum-core/src/test/java/org/apache/maven/continuum/DefaultContinuumTest.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-core/src/test/java/org/apache/maven/continuum/DefaultContinuumTest.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-core/src/test/java/org/apache/maven/continuum/DefaultContinuumTest.java (original)
+++ continuum/trunk/continuum-core/src/test/java/org/apache/maven/continuum/DefaultContinuumTest.java Mon May  4 01:42:05 2015
@@ -280,7 +280,7 @@ public class DefaultContinuumTest
 
         getBuildResultDao().addBuildResult( project, buildResult );
 
-        Collection<BuildResult> brs = continuum.getBuildResultsForProject( project.getId() );
+        Collection<BuildResult> brs = continuum.getBuildResultsForProject( project.getId(), 0, 5 );
 
         assertEquals( "Build result of project was not added", 1, brs.size() );
 
@@ -295,7 +295,7 @@ public class DefaultContinuumTest
         }
         catch ( ContinuumException expected )
         {
-            brs = continuum.getBuildResultsForProject( project.getId() );
+            brs = continuum.getBuildResultsForProject( project.getId(), 0, 5 );
 
             assertEquals( "Build result of project was not removed", 0, brs.size() );
         }

Modified: continuum/trunk/continuum-store/src/main/java/org/apache/continuum/dao/BuildResultDaoImpl.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-store/src/main/java/org/apache/continuum/dao/BuildResultDaoImpl.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-store/src/main/java/org/apache/continuum/dao/BuildResultDaoImpl.java (original)
+++ continuum/trunk/continuum-store/src/main/java/org/apache/continuum/dao/BuildResultDaoImpl.java Mon May  4 01:42:05 2015
@@ -258,7 +258,7 @@ public class BuildResultDaoImpl
 
             query.setFilter( "this.project.id == projectId && this.buildDefinition.id == buildDefinitionId "
                                  + "&& this.id < buildResultId" );
-            query.setOrdering( "id descending" );
+            query.setOrdering( "this.id descending" );
 
             Object[] params = new Object[3];
             params[0] = projectId;
@@ -520,7 +520,7 @@ public class BuildResultDaoImpl
         }
     }
 
-    public List<BuildResult> getBuildResultsForProjectWithDetails( int projectId, long fromDate, int tobuildResultId )
+    public List<BuildResult> getBuildResultsForProjectWithDetails( int projectId, int fromResultId, int toResultId )
     {
         PersistenceManager pm = getPersistenceManager();
 
@@ -535,29 +535,28 @@ public class BuildResultDaoImpl
 
             Query query = pm.newQuery( extent );
 
-            String parameters = "int projectId, long fromDate";
-            String filter = "this.project.id == projectId && this.startTime > fromDate";
+            String parameters = "int projectId, int fromResultId";
+            String filter = "this.project.id == projectId && this.id > fromResultId";
 
-            if ( tobuildResultId > 0 )
+            if ( toResultId > 0 )
             {
-                parameters += ", int buildResultId";
-                filter += " && this.id < buildResultId";
+                parameters += ", int toResultId";
+                filter += " && this.id < toResultId";
             }
-            query.declareParameters( parameters );
 
+            query.declareParameters( parameters );
             query.setFilter( filter );
-
-            query.setOrdering( "this.id descending" );
+            query.setOrdering( "this.id ascending" );
 
             List<BuildResult> result;
 
-            if ( tobuildResultId > 0 )
+            if ( toResultId > 0 )
             {
-                result = (List<BuildResult>) query.execute( projectId, fromDate, tobuildResultId );
+                result = (List<BuildResult>) query.execute( projectId, fromResultId, toResultId );
             }
             else
             {
-                result = (List<BuildResult>) query.execute( projectId, fromDate );
+                result = (List<BuildResult>) query.execute( projectId, fromResultId );
             }
 
             result = (List<BuildResult>) pm.detachCopyAll( result );
@@ -572,15 +571,10 @@ public class BuildResultDaoImpl
         }
     }
 
-    public List<BuildResult> getBuildResultsForProject( int projectId )
-    {
-        return getBuildResultsForProjectWithDetails( projectId, -1, -1 );
-    }
-
-    public List<BuildResult> getBuildResultsForProject( int projectId, long startIndex, long endIndex )
+    public List<BuildResult> getBuildResultsForProject( int projectId, long startIndex, long endIndex,
+                                                        boolean fullDetails )
     {
         PersistenceManager pm = getPersistenceManager();
-
         Transaction tx = pm.currentTransaction();
 
         try
@@ -589,17 +583,17 @@ public class BuildResultDaoImpl
 
             Extent extent = pm.getExtent( BuildResult.class, true );
 
+            if ( fullDetails )
+            {
+                pm.getFetchPlan().addGroup( BUILD_RESULT_WITH_DETAILS_FETCH_GROUP );
+            }
+
             Query query = pm.newQuery( extent );
 
             query.declareParameters( "int projectId" );
-
             query.setFilter( "this.project.id == projectId" );
-
-            if ( startIndex >= 0 )
-            {
-                query.setRange( startIndex, endIndex );
-            }
             query.setOrdering( "this.id descending" );
+            query.setRange( startIndex, endIndex );
 
             List<BuildResult> result = (List<BuildResult>) query.execute( projectId );
 
@@ -692,41 +686,6 @@ public class BuildResultDaoImpl
         }
     }
 
-    private int getPreviousBuildResultIdInSuccess( int projectId, int buildResultId )
-    {
-        PersistenceManager pm = getPersistenceManager();
-
-        Transaction tx = pm.currentTransaction();
-
-        try
-        {
-            tx.begin();
-
-            Extent extent = pm.getExtent( BuildResult.class, true );
-
-            Query query = pm.newQuery( extent );
-
-            query.declareParameters( "int projectId, int buildResultId" );
-
-            String filter = "this.id < buildResultId && this.state == " + ContinuumProjectState.OK +
-                "  && this.project.id == projectId";
-
-            query.setFilter( filter );
-
-            query.setResult( "max(this.id)" );
-
-            int result = (Integer) query.execute( projectId, buildResultId );
-
-            tx.commit();
-
-            return result;
-        }
-        finally
-        {
-            rollback( tx );
-        }
-    }
-
     public Set<Integer> resolveOrphanedInProgressResults( long ageCutoff )
         throws ContinuumStoreException
     {
@@ -763,13 +722,37 @@ public class BuildResultDaoImpl
     public BuildResult getPreviousBuildResultInSuccess( int projectId, int buildResultId )
         throws ContinuumStoreException
     {
+        PersistenceManager pm = getPersistenceManager();
+
+        Transaction tx = pm.currentTransaction();
+
         try
         {
-            return getBuildResult( getPreviousBuildResultIdInSuccess( projectId, buildResultId ) );
+            tx.begin();
+
+            Extent extent = pm.getExtent( BuildResult.class, true );
+
+            Query query = pm.newQuery( extent );
+
+            query.declareParameters( "int projectId, int buildResultId" );
+
+            String filter = "this.project.id == projectId"
+                + " && this.state == " + ContinuumProjectState.OK
+                + " && this.id < buildResultId";
+
+            query.setFilter( filter );
+            query.setOrdering( "this.id descending" );
+            query.setRange( 0, 1 );
+
+            List<BuildResult> results = (List<BuildResult>) query.execute( projectId, buildResultId );
+
+            tx.commit();
+
+            return results.size() > 0 ? results.get( 0 ) : null;
         }
-        catch ( NullPointerException e )
+        finally
         {
-            return null;
+            rollback( tx );
         }
     }
 
@@ -832,12 +815,10 @@ public class BuildResultDaoImpl
         return null;
     }
 
-    @SuppressWarnings( "unchecked" )
     public List<BuildResult> getBuildResultsInRange( Date fromDate, Date toDate, int state, String triggeredBy,
-                                                     int projectGroupId )
+                                                     int projectGroupId, int offset, int length )
     {
         PersistenceManager pm = getPersistenceManager();
-
         Transaction tx = pm.currentTransaction();
 
         try
@@ -850,17 +831,88 @@ public class BuildResultDaoImpl
 
             Query query = pm.newQuery( extent );
 
-            String parameters = "";
-            String filter = "";
+            InRangeQueryAttrs inRangeQueryAttrs =
+                new InRangeQueryAttrs( fromDate, toDate, state, triggeredBy, projectGroupId, query ).build();
+
+            String parameters = inRangeQueryAttrs.getParameters();
+            String filter = inRangeQueryAttrs.getFilter();
+            Map params = inRangeQueryAttrs.getParams();
+
+            query.declareParameters( parameters );
+            query.setFilter( filter );
+            query.setRange( offset, offset + length );
+
+            List<BuildResult> result = (List<BuildResult>) query.executeWithMap( params );
+
+            result = (List<BuildResult>) pm.detachCopyAll( result );
+
+            tx.commit();
+
+            return result;
+        }
+        finally
+        {
+            rollback( tx );
+        }
+
+    }
+
+    private class InRangeQueryAttrs
+    {
+        private Date fromDate;
+
+        private Date toDate;
+
+        private int state;
+
+        private String triggeredBy;
+
+        private int projectGroupId;
+
+        private Query query;
 
-            Map params = new HashMap();
+        private String parameters;
 
-            int ctr = 0;
+        private String filter;
+
+        private Map params;
+
+        public InRangeQueryAttrs( Date fromDate, Date toDate, int state, String triggeredBy, int projectGroupId,
+                                  Query query )
+        {
+            this.fromDate = fromDate;
+            this.toDate = toDate;
+            this.state = state;
+            this.triggeredBy = triggeredBy;
+            this.projectGroupId = projectGroupId;
+            this.query = query;
+        }
+
+        public String getParameters()
+        {
+            return parameters;
+        }
+
+        public String getFilter()
+        {
+            return filter;
+        }
+
+        public Map getParams()
+        {
+            return params;
+        }
+
+        public InRangeQueryAttrs build()
+        {
+            parameters = "";
+            filter = "";
+
+            params = new HashMap();
 
             if ( state > 0 )
             {
                 params.put( "state", state );
-                ctr++;
                 parameters += "int state, ";
                 filter += "this.state == state && ";
             }
@@ -868,7 +920,6 @@ public class BuildResultDaoImpl
             if ( projectGroupId > 0 )
             {
                 params.put( "projectGroupId", projectGroupId );
-                ctr++;
                 parameters += "int projectGroupId, ";
                 filter += "this.project.projectGroup.id == projectGroupId && ";
             }
@@ -876,7 +927,6 @@ public class BuildResultDaoImpl
             if ( triggeredBy != null && !triggeredBy.equals( "" ) )
             {
                 params.put( "triggeredBy", triggeredBy );
-                ctr++;
                 query.declareImports( "import java.lang.String" );
                 parameters += "String triggeredBy, ";
                 filter += "this.username == triggeredBy && ";
@@ -885,7 +935,6 @@ public class BuildResultDaoImpl
             if ( fromDate != null )
             {
                 params.put( "fromDate", fromDate.getTime() );
-                ctr++;
                 parameters += "long fromDate, ";
                 filter += "this.startTime >= fromDate && ";
             }
@@ -897,7 +946,6 @@ public class BuildResultDaoImpl
                 cal.add( Calendar.DAY_OF_MONTH, 1 );
 
                 params.put( "toDate", cal.getTimeInMillis() );
-                ctr++;
                 parameters += "long toDate";
                 filter += "this.startTime < toDate";
             }
@@ -907,21 +955,7 @@ public class BuildResultDaoImpl
                 filter = filter.substring( 0, filter.length() - 3 );
                 parameters = parameters.substring( 0, parameters.length() - 2 );
             }
-
-            query.declareParameters( parameters );
-            query.setFilter( filter );
-
-            List<BuildResult> result = (List<BuildResult>) query.executeWithMap( params );
-
-            result = (List<BuildResult>) pm.detachCopyAll( result );
-
-            tx.commit();
-
-            return result;
-        }
-        finally
-        {
-            rollback( tx );
+            return this;
         }
     }
 }

Modified: continuum/trunk/continuum-store/src/test/java/org/apache/maven/continuum/store/ContinuumStoreTest.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-store/src/test/java/org/apache/maven/continuum/store/ContinuumStoreTest.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-store/src/test/java/org/apache/maven/continuum/store/ContinuumStoreTest.java (original)
+++ continuum/trunk/continuum-store/src/test/java/org/apache/maven/continuum/store/ContinuumStoreTest.java Mon May  4 01:42:05 2015
@@ -1454,23 +1454,24 @@ public class ContinuumStoreTest
     public void testGetBuildResultsInRange()
         throws Exception
     {
-        List<BuildResult> results = buildResultDao.getBuildResultsInRange( null, null, 0, null, 0 );
+        int maxFetch = 5;
+        List<BuildResult> results = buildResultDao.getBuildResultsInRange( null, null, 0, null, 0, 0, maxFetch );
         assertEquals( "check number of build results returned", 3, results.size() );
 
-        results = buildResultDao.getBuildResultsInRange( null, null, 2, null, 0 );
+        results = buildResultDao.getBuildResultsInRange( null, null, 2, null, 0, 0, maxFetch );
         assertEquals( "check number of build results returned with state == OK", 2, results.size() );
 
-        results = buildResultDao.getBuildResultsInRange( null, null, 0, "user", 0 );
+        results = buildResultDao.getBuildResultsInRange( null, null, 0, "user", 0, 0, maxFetch );
         assertEquals( "check number of build results returned with triggeredBy == user", 1, results.size() );
 
-        results = buildResultDao.getBuildResultsInRange( null, null, 0, "schedule", 0 );
+        results = buildResultDao.getBuildResultsInRange( null, null, 0, "schedule", 0, 0, maxFetch );
         assertEquals( "check number of build results returned with triggeredBy == schedule", 2, results.size() );
 
-        results = buildResultDao.getBuildResultsInRange( null, null, 2, "schedule", 0 );
+        results = buildResultDao.getBuildResultsInRange( null, null, 2, "schedule", 0, 0, maxFetch );
         assertEquals( "check number of build results returned with state == Ok and triggeredBy == schedule", 1,
                       results.size() );
 
-        results = buildResultDao.getBuildResultsInRange( null, null, 3, "user", 0 );
+        results = buildResultDao.getBuildResultsInRange( null, null, 3, "user", 0, 0, maxFetch );
         assertEquals( "check number of build results returned with state == Failed and triggeredBy == user", 0,
                       results.size() );
 
@@ -1478,16 +1479,17 @@ public class ContinuumStoreTest
         cal.setTime( new Date( baseTime ) );
         cal.add( Calendar.DAY_OF_MONTH, 1 );
 
-        results = buildResultDao.getBuildResultsInRange( new Date( baseTime ), cal.getTime(), 0, null, 0 );
+        results = buildResultDao.getBuildResultsInRange( new Date( baseTime ), cal.getTime(), 0, null, 0, 0, maxFetch );
         assertEquals( "check number of build results returned with startDate and endDate", 2, results.size() );
 
-        results = buildResultDao.getBuildResultsInRange( new Date( baseTime ), new Date( baseTime ), 0, null, 0 );
+        results = buildResultDao.getBuildResultsInRange( new Date( baseTime ), new Date( baseTime ), 0, null, 0, 0,
+                                                         maxFetch );
         assertEquals( "check number of build results returned with the same startDate and endDate", 1, results.size() );
 
-        results = buildResultDao.getBuildResultsInRange( null, null, 0, null, 1 );
+        results = buildResultDao.getBuildResultsInRange( null, null, 0, null, 1, 0, maxFetch );
         assertEquals( "check number of build results returned with an existing group id", 3, results.size() );
 
-        results = buildResultDao.getBuildResultsInRange( null, null, 0, null, 2 );
+        results = buildResultDao.getBuildResultsInRange( null, null, 0, null, 2, 0, maxFetch );
         assertEquals( "check number of build results returned with non-existing group id", 0, results.size() );
     }
     // ----------------------------------------------------------------------

Modified: continuum/trunk/continuum-webapp/src/main/java/org/apache/continuum/web/action/ViewBuildsReportAction.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-webapp/src/main/java/org/apache/continuum/web/action/ViewBuildsReportAction.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-webapp/src/main/java/org/apache/continuum/web/action/ViewBuildsReportAction.java (original)
+++ continuum/trunk/continuum-webapp/src/main/java/org/apache/continuum/web/action/ViewBuildsReportAction.java Mon May  4 01:42:05 2015
@@ -19,7 +19,6 @@ package org.apache.continuum.web.action;
  * under the License.
  */
 
-import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.time.DateUtils;
 import org.apache.maven.continuum.model.project.BuildResult;
@@ -28,25 +27,42 @@ import org.apache.maven.continuum.model.
 import org.apache.maven.continuum.project.ContinuumProjectState;
 import org.apache.maven.continuum.web.action.ContinuumActionSupport;
 import org.apache.maven.continuum.web.exception.AuthorizationRequiredException;
-import org.apache.maven.continuum.web.model.ProjectBuildsSummary;
+import org.apache.struts2.interceptor.ServletResponseAware;
 import org.codehaus.plexus.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import java.io.ByteArrayInputStream;
+import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringReader;
+import java.io.Writer;
+import java.text.DateFormat;
 import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 @Component( role = com.opensymphony.xwork2.Action.class, hint = "projectBuildsReport", instantiationStrategy = "per-lookup" )
 public class ViewBuildsReportAction
     extends ContinuumActionSupport
+    implements ServletResponseAware
 {
+    private static final Logger log = LoggerFactory.getLogger( ViewBuildsReportAction.class );
+
+    private static final int MAX_BROWSE_SIZE = 500;
+
+    private static final int EXPORT_BATCH_SIZE = 4000;
+
+    private static final int MAX_EXPORT_SIZE = 100000;
+
+    private static final String[] datePatterns =
+        new String[] { "MM/dd/yy", "MM/dd/yyyy", "MMMMM/dd/yyyy", "MMMMM/dd/yy", "dd MMMMM yyyy", "dd/MM/yy",
+            "dd/MM/yyyy", "yyyy/MM/dd", "yyyy-MM-dd", "yyyy-dd-MM", "MM-dd-yyyy", "MM-dd-yy" };
+
     private int buildStatus;
 
     private String triggeredBy = "";
@@ -57,25 +73,39 @@ public class ViewBuildsReportAction
 
     private int projectGroupId;
 
-    private int rowCount = 30;
+    private int rowCount = 25;
 
     private int page = 1;
 
-    private int numPages;
+    private int pageTotal;
 
     private Map<Integer, String> buildStatuses;
 
     private Map<Integer, String> projectGroups;
 
-    private List<ProjectBuildsSummary> projectBuilds;
+    private Set<String> permittedGroups = new HashSet<String>();
 
-    private InputStream inputStream;
+    private List<BuildResult> filteredResults = new ArrayList<BuildResult>();
 
-    public static final String SEND_FILE = "send-file";
+    private HttpServletResponse rawResponse;
 
-    private static final String[] datePatterns =
-        new String[] { "MM/dd/yy", "MM/dd/yyyy", "MMMMM/dd/yyyy", "MMMMM/dd/yy", "dd MMMMM yyyy", "dd/MM/yy",
-            "dd/MM/yyyy", "yyyy/MM/dd", "yyyy-MM-dd", "yyyy-dd-MM", "MM-dd-yyyy", "MM-dd-yy" };
+    public void setServletResponse( HttpServletResponse response )
+    {
+        this.rawResponse = response;
+    }
+
+    private boolean isAuthorized( String projectGroupName )
+    {
+        try
+        {
+            checkViewProjectGroupAuthorization( projectGroupName );
+            return true;
+        }
+        catch ( AuthorizationRequiredException authzE )
+        {
+            return false;
+        }
+    }
 
     public void prepare()
         throws Exception
@@ -87,16 +117,24 @@ public class ViewBuildsReportAction
         buildStatuses.put( ContinuumProjectState.OK, "Ok" );
         buildStatuses.put( ContinuumProjectState.FAILED, "Failed" );
         buildStatuses.put( ContinuumProjectState.ERROR, "Error" );
+        buildStatuses.put( ContinuumProjectState.BUILDING, "Building" );
+        buildStatuses.put( ContinuumProjectState.CANCELLED, "Canceled" );
 
         projectGroups = new LinkedHashMap<Integer, String>();
         projectGroups.put( 0, "ALL" );
 
+        // TODO: Use these to limit results at the data layer
         List<ProjectGroup> groups = getContinuum().getAllProjectGroups();
         if ( groups != null )
         {
             for ( ProjectGroup group : groups )
             {
-                projectGroups.put( group.getId(), group.getName() );
+                String groupName = group.getName();
+                if ( isAuthorized( groupName ) )
+                {
+                    projectGroups.put( group.getId(), groupName );
+                    permittedGroups.add( groupName );
+                }
             }
         }
     }
@@ -129,8 +167,8 @@ public class ViewBuildsReportAction
             return REQUIRES_AUTHORIZATION;
         }
 
-        Date fromDate = null;
-        Date toDate = null;
+        Date fromDate;
+        Date toDate;
 
         try
         {
@@ -149,42 +187,52 @@ public class ViewBuildsReportAction
             return INPUT;
         }
 
-        if ( rowCount < 10 )
-        {
-            // TODO: move to validation framework
-            addFieldError( "rowCount", "Row count should be at least 10." );
-            return INPUT;
-        }
-
-        List<BuildResult> buildResults = getContinuum().getBuildResultsInRange( projectGroupId, fromDate, toDate,
-                                                                                buildStatus, triggeredBy );
-        projectBuilds = Collections.emptyList();
-
-        if ( buildResults != null && !buildResults.isEmpty() )
-        {
-            projectBuilds = mapBuildResultsToProjectBuildsSummaries( buildResults );
-
-            int extraPage = ( projectBuilds.size() % rowCount ) != 0 ? 1 : 0;
-            numPages = ( projectBuilds.size() / rowCount ) + extraPage;
+        // Users can preview a limited number of records (use export for more)
+        int offset = 0;
+        List<BuildResult> results;
+        populating:
+        do
+        {
+            // Fetch a batch of records (may be filtered based on permissions)
+            results = getContinuum().getBuildResultsInRange( projectGroupId, fromDate, toDate, buildStatus, triggeredBy,
+                                                             offset, MAX_BROWSE_SIZE );
+            offset += MAX_BROWSE_SIZE;
 
-            if ( page > numPages )
+            for ( BuildResult result : results )
             {
-                addActionError(
-                    "Error encountered while generating project builds report :: The requested page exceeds the total number of pages." );
-                return ERROR;
+                if ( permittedGroups.contains( result.getProject().getProjectGroup().getName() ) )
+                {
+                    filteredResults.add( result );
+                }
+
+                if ( filteredResults.size() >= MAX_BROWSE_SIZE )
+                {
+                    break populating;  // Halt when we have filled a batch equivalent with results
+                }
             }
+        }
+        while ( results.size() == MAX_BROWSE_SIZE );  // Keep fetching until batch is empty or incomplete
 
-            int start = rowCount * ( page - 1 );
-            int end = ( start + rowCount );
+        if ( filteredResults.size() == MAX_BROWSE_SIZE )
+        {
+            addActionMessage( "Results may have been limited due to size."
+                                  + " Refine your search criteria or try exporting." );
+        }
 
-            if ( end > projectBuilds.size() )
-            {
-                end = projectBuilds.size();
-            }
+        int resultSize = filteredResults.size();
+        pageTotal = resultSize / rowCount + ( resultSize % rowCount == 0 ? 0 : 1 );
 
-            projectBuilds = projectBuilds.subList( start, end );
+        if ( page < 1 || page > pageTotal )
+        {
+            addActionError( "Specified page does not exist" );
+            return ERROR;
         }
 
+        int pageStart = rowCount * ( page - 1 ), pageEnd = rowCount * page;
+
+        // Restrict results to just the page we will show
+        filteredResults = filteredResults.subList( pageStart, pageEnd > resultSize ? resultSize : pageEnd );
+
         return SUCCESS;
     }
 
@@ -203,8 +251,8 @@ public class ViewBuildsReportAction
             return REQUIRES_AUTHORIZATION;
         }
 
-        Date fromDate = null;
-        Date toDate = null;
+        Date fromDate;
+        Date toDate;
 
         try
         {
@@ -223,59 +271,101 @@ public class ViewBuildsReportAction
             return INPUT;
         }
 
-        List<BuildResult> buildResults = getContinuum().getBuildResultsInRange( projectGroupId, fromDate, toDate,
-                                                                                buildStatus, triggeredBy );
-        List<ProjectBuildsSummary> builds = Collections.emptyList();
-
-        StringBuffer input = new StringBuffer( "Project Group,Project Name,Build Date,Triggered By,Build Status\n" );
-
-        if ( buildResults != null && !buildResults.isEmpty() )
+        try
         {
-            builds = mapBuildResultsToProjectBuildsSummaries( buildResults );
+            // First, build the output file
+            rawResponse.setContentType( "text/csv" );
+            rawResponse.addHeader( "Content-disposition", "attachment;filename=continuum_project_builds_report.csv" );
+            Writer output = rawResponse.getWriter();
 
-            for ( ProjectBuildsSummary build : builds )
+            DateFormat dateTimeFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ" );
+
+            try
             {
-                input.append( build.getProjectGroupName() ).append( "," );
-                input.append( build.getProjectName() ).append( "," );
+                // Write the header
+                output.append( "Group,Project,ID,Build#,Started,Duration,Triggered By,Status\n" );
 
-                input.append( new Date( build.getBuildDate() ) ).append( "," );
+                // Build the output file by walking through the results in batches
+                int offset = 0, exported = 0;
+                List<BuildResult> results;
+                export:
+                do
+                {
+                    results = getContinuum().getBuildResultsInRange( projectGroupId, fromDate, toDate, buildStatus,
+                                                                     triggeredBy, offset, EXPORT_BATCH_SIZE );
 
-                input.append( build.getBuildTriggeredBy() ).append( "," );
+                    offset += EXPORT_BATCH_SIZE;  // Ensure we advance through results
 
-                String status;
-                switch ( build.getBuildState() )
-                {
-                    case 2:
-                        status = "Ok";
-                        break;
-                    case 3:
-                        status = "Failed";
-                        break;
-                    case 4:
-                        status = "Error";
-                        break;
-                    case 6:
-                        status = "Building";
-                        break;
-                    case 7:
-                        status = "Checking Out";
-                        break;
-                    case 8:
-                        status = "Updating";
-                        break;
-                    default:
-                        status = "";
+                    // Convert each build result to a line in the CSV file
+                    for ( BuildResult result : results )
+                    {
+
+                        if ( !permittedGroups.contains( result.getProject().getProjectGroup().getName() ) )
+                        {
+                            continue;
+                        }
+
+                        exported += 1;
+
+                        Project project = result.getProject();
+                        ProjectGroup projectGroup = project.getProjectGroup();
+
+                        // Decode status into human-readable form
+                        String state;
+                        switch ( result.getState() )
+                        {
+                            case 2:
+                                state = "Ok";
+                                break;
+                            case 3:
+                                state = "Failed";
+                                break;
+                            case 4:
+                                state = "Error";
+                                break;
+                            case 6:
+                                state = "Building";
+                                break;
+                            case 7:
+                                state = "Checking Out";
+                                break;
+                            case 8:
+                                state = "Updating";
+                                break;
+                            case 11:
+                                state = "Canceled";
+                                break;
+                            default:
+                                state = "";
+                        }
+
+                        String buildTime = dateTimeFormat.format( new Date( result.getStartTime() ) );
+                        long buildDuration = ( result.getEndTime() - result.getStartTime() ) / 1000;
+
+                        String formattedLine = String.format( "%s,%s,%s,%s,%s,%s,%s,%s\n",
+                                                              projectGroup.getName(),
+                                                              project.getName(),
+                                                              result.getId(),
+                                                              result.getBuildNumber(),
+                                                              buildTime,
+                                                              buildDuration,
+                                                              result.getUsername(),
+                                                              state );
+                        output.append( formattedLine );
+
+                        if ( exported >= MAX_EXPORT_SIZE )
+                        {
+                            log.warn( "build report export hit limit of {} records", MAX_EXPORT_SIZE );
+                            break export;
+                        }
+                    }
                 }
-                input.append( status );
-                input.append( "\n" );
+                while ( results.size() == EXPORT_BATCH_SIZE );
+            }
+            finally
+            {
+                output.flush();
             }
-        }
-
-        StringReader reader = new StringReader( input.toString() );
-
-        try
-        {
-            inputStream = new ByteArrayInputStream( IOUtils.toByteArray( reader ) );
         }
         catch ( IOException e )
         {
@@ -283,34 +373,7 @@ public class ViewBuildsReportAction
             return ERROR;
         }
 
-        return SEND_FILE;
-    }
-
-    private List<ProjectBuildsSummary> mapBuildResultsToProjectBuildsSummaries( List<BuildResult> buildResults )
-    {
-        List<ProjectBuildsSummary> buildsSummary = new ArrayList<ProjectBuildsSummary>();
-
-        for ( BuildResult buildResult : buildResults )
-        {
-            Project project = buildResult.getProject();
-
-            // check if user is authorised to view build result
-            if ( !isAuthorized( project.getProjectGroup().getName() ) )
-            {
-                continue;
-            }
-
-            ProjectBuildsSummary summary = new ProjectBuildsSummary();
-            summary.setProjectGroupName( project.getProjectGroup().getName() );
-            summary.setProjectName( project.getName() );
-            summary.setBuildDate( buildResult.getStartTime() );
-            summary.setBuildState( buildResult.getState() );
-            summary.setBuildTriggeredBy( buildResult.getUsername() );
-
-            buildsSummary.add( summary );
-        }
-
-        return buildsSummary;
+        return null;
     }
 
     private Date getStartDateInDateFormat()
@@ -394,26 +457,11 @@ public class ViewBuildsReportAction
         return rowCount;
     }
 
-    public void setRowCount( int rowCount )
-    {
-        this.rowCount = rowCount;
-    }
-
     public Map<Integer, String> getBuildStatuses()
     {
         return buildStatuses;
     }
 
-    public Map<Integer, String> getProjectGroups()
-    {
-        return projectGroups;
-    }
-
-    public List<ProjectBuildsSummary> getProjectBuilds()
-    {
-        return projectBuilds;
-    }
-
     public int getPage()
     {
         return page;
@@ -424,26 +472,18 @@ public class ViewBuildsReportAction
         this.page = page;
     }
 
-    public int getNumPages()
+    public int getPageTotal()
     {
-        return numPages;
+        return pageTotal;
     }
 
-    public InputStream getInputStream()
+    public List<BuildResult> getFilteredResults()
     {
-        return inputStream;
+        return filteredResults;
     }
 
-    private boolean isAuthorized( String projectGroupName )
+    public Map<Integer, String> getProjectGroups()
     {
-        try
-        {
-            checkViewProjectGroupAuthorization( projectGroupName );
-            return true;
-        }
-        catch ( AuthorizationRequiredException authzE )
-        {
-            return false;
-        }
+        return projectGroups;
     }
-}
+}
\ No newline at end of file

Modified: continuum/trunk/continuum-webapp/src/main/java/org/apache/maven/continuum/web/action/ProjectGroupAction.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-webapp/src/main/java/org/apache/maven/continuum/web/action/ProjectGroupAction.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-webapp/src/main/java/org/apache/maven/continuum/web/action/ProjectGroupAction.java (original)
+++ continuum/trunk/continuum-webapp/src/main/java/org/apache/maven/continuum/web/action/ProjectGroupAction.java Mon May  4 01:42:05 2015
@@ -529,11 +529,17 @@ public class ProjectGroupAction
                 project.setProjectGroup( newProjectGroup );
 
                 // CONTINUUM-1512
-                Collection<BuildResult> results = getContinuum().getBuildResultsForProject( project.getId() );
-                for ( BuildResult br : results )
+                int batchSize = 100;
+                Collection<BuildResult> results;
+                do
                 {
-                    getContinuum().removeBuildResult( br.getId() );
+                    results = getContinuum().getBuildResultsForProject( project.getId(), 0, batchSize );
+                    for ( BuildResult br : results )
+                    {
+                        getContinuum().removeBuildResult( br.getId() );
+                    }
                 }
+                while ( results != null && results.size() > 0 );
 
                 getContinuum().updateProject( project );
             }

Modified: continuum/trunk/continuum-webapp/src/main/resources/localization/Continuum.properties
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-webapp/src/main/resources/localization/Continuum.properties?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-webapp/src/main/resources/localization/Continuum.properties (original)
+++ continuum/trunk/continuum-webapp/src/main/resources/localization/Continuum.properties Mon May  4 01:42:05 2015
@@ -1314,6 +1314,7 @@ projectBuilds.report.title=Continuum - P
 projectBuilds.report.section.title=Project Builds Report
 projectBuilds.report.button.reset=Reset
 projectBuilds.report.project.group=Project Group
+projectBuilds.report.buildNumber=Build Number
 projectBuilds.report.startDate=Start Date
 projectBuilds.report.endDate=End Date
 projectBuilds.report.triggeredBy=Triggered By

Modified: continuum/trunk/continuum-webapp/src/main/resources/struts.xml
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-webapp/src/main/resources/struts.xml?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-webapp/src/main/resources/struts.xml (original)
+++ continuum/trunk/continuum-webapp/src/main/resources/struts.xml Mon May  4 01:42:05 2015
@@ -741,10 +741,6 @@
     <action name="downloadProjectBuildsReport" class="projectBuildsReport" method="downloadBuildsReport">
       <result name="input">/WEB-INF/jsp/viewProjectBuildsReport.jsp</result>
       <result name="success">/WEB-INF/jsp/viewProjectBuildsReport.jsp</result>
-      <result name="send-file" type="stream">
-        <param name="contentType">%{contentType}</param>
-        <param name="contentDisposition">attachment; filename="continuum_project_builds_report.csv"</param>
-      </result>
     </action>
   </package>
 

Modified: continuum/trunk/continuum-webapp/src/main/webapp/WEB-INF/jsp/viewProjectBuildsReport.jsp
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-webapp/src/main/webapp/WEB-INF/jsp/viewProjectBuildsReport.jsp?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-webapp/src/main/webapp/WEB-INF/jsp/viewProjectBuildsReport.jsp (original)
+++ continuum/trunk/continuum-webapp/src/main/webapp/WEB-INF/jsp/viewProjectBuildsReport.jsp Mon May  4 01:42:05 2015
@@ -39,7 +39,6 @@
           $('#startDate').val('')
           $('#endDate').val('')
           $('#triggeredBy').val('')
-          $('#rowCount').val('30')
           $("#buildStatus option[value='0']").attr('selected', 'selected')
           $("#projectGroupId option[value='0']").attr('selected', 'selected')
         });
@@ -55,19 +54,20 @@
   <body>
     <h3><s:text name="projectBuilds.report.section.title"/></h3>
  
-    <s:form name="generateReportForm" action="generateProjectBuildsReport">
-      <c:if test="${!empty actionErrors || !empty errorMessages}">
-        <tr><td>
-        <div class="errormessage">
-          <s:iterator value="actionErrors">
-            <p><s:property/></p>
-          </s:iterator>
-          <c:forEach items="${errorMessages}" var="errorMessage">
-            <p><c:out value="${errorMessage}"/></p>
-          </c:forEach>
-        </div>
-        </td></tr>
-      </c:if>
+    <s:form name="generateReportForm" action="generateProjectBuildsReport" method="GET">
+
+      <tr><td>
+        <s:if test="hasActionErrors()">
+            <div class="errormessage">
+                <s:actionerror/>
+            </div>
+        </s:if>
+        <s:if test="hasActionMessages()">
+            <div class="warningmessage">
+                <s:actionmessage/>
+            </div>
+        </s:if>
+      </td></tr>
 
       <tr><td>
       <div class="axial">
@@ -77,7 +77,6 @@
             <s:textfield label="%{getText('projectBuilds.report.endDate')}" name="endDate" id="endDate" size="20"/>
             <s:select label="%{getText('projectBuilds.report.buildStatus')}" name="buildStatus" id="buildStatus" list="buildStatuses"/>
             <s:textfield label="%{getText('projectBuilds.report.triggeredBy')}" name="triggeredBy" id="triggeredBy" size="40"/>
-            <s:textfield label="%{getText('projectBuilds.report.rowCount')}" name="rowCount" id="rowCount" size="10"/>
         </table>
         <div class="functnbar3">
           <s:submit value="%{getText('projectBuilds.report.view')}" theme="simple" />
@@ -86,145 +85,101 @@
       </div>
       </td></tr>
     </s:form>
-    
-    </p>
 
-    <c:if test="${projectBuilds != null}">
       <div id="h3">
      	  <h3>Results</h3>
-     	  <c:choose>
-       	  <c:when test="${not empty projectBuilds}">
-       	    
+       	  <s:if test="filteredResults.size() > 0">
        	    <table>
          	    <tr>
            	    <td>
-             	    <%-- Pagination --%>
-                  <c:set var="prevPageUrl">
-                    <s:url action="generateProjectBuildsReport">    
-                      <s:param name="projectGroupId" value="%{#attr.projectGroupId}"/>
-                      <s:param name="triggeredBy" value="%{#attr.triggeredBy}"/>
-                      <s:param name="buildStatus" value="%{#attr.buildStatus}"/>
-                      <s:param name="rowCount" value="%{#attr.rowCount}"/>
-                      <s:param name="startDate" value="%{#attr.startDate}"/>                      
-                      <s:param name="endDate" value="%{#attr.endDate}"/>
-                      <s:param name="page" value="%{#attr.page - 1}"/>
-                    </s:url>
-                  </c:set>
-                  <c:set var="nextPageUrl">
-                    <s:url action="generateProjectBuildsReport">    
-                      <s:param name="projectGroupId" value="%{#attr.projectGroupId}"/>
-                      <s:param name="triggeredBy" value="%{#attr.triggeredBy}"/>
-                      <s:param name="buildStatus" value="%{#attr.buildStatus}"/>
-                      <s:param name="rowCount" value="%{#attr.rowCount}"/>
-                      <s:param name="startDate" value="%{#attr.startDate}"/>                      
-                      <s:param name="endDate" value="%{#attr.endDate}"/>          
-                      <s:param name="page" value="%{#attr.page + 1}"/>
-                    </s:url>
-                  </c:set>
-                  <c:choose>
-                    <c:when test="${page == 1}">                               
-                      <s:text name="projectBuilds.report.prev"/>
-                    </c:when>
-                    <c:otherwise>
-                      <a href="${prevPageUrl}">
-                        <s:text name="projectBuilds.report.prev"/>
-                      </a>
-                    </c:otherwise>
-                  </c:choose>
-
-                  <c:choose>
-                    <c:when test="${numPages > 11}">
-                      <c:choose>
-                        <c:when test="${(page - 5) < 0}">
-                          <c:set var="beginVal">0</c:set>
-                          <c:set var="endVal">10</c:set> 
-                        </c:when>			        
-                        <c:when test="${(page + 5) > (numPages - 1)}">
-                          <c:set var="beginVal">${(numPages - 1) - 10}</c:set>
-                          <c:set var="endVal">${numPages - 1}</c:set>
-                        </c:when>
-                        <c:otherwise>
-                          <c:set var="beginVal">${page - 5}</c:set>
-                          <c:set var="endVal">${page + 5}</c:set>
-                        </c:otherwise>
-                      </c:choose>  
-                    </c:when>
-                    <c:otherwise>
-                      <c:set var="beginVal">0</c:set>
-                      <c:set var="endVal">${numPages - 1}</c:set>
-                    </c:otherwise>
-                  </c:choose>
-
-                  <c:forEach var="i" begin="${beginVal}" end="${endVal}">      
-                    <c:choose>                   			    
-                      <c:when test="${i != (page - 1)}">
-                        <c:set var="specificPageUrl">
-                          <s:url action="generateProjectBuildsReport">    
-                            <s:param name="projectGroupId" value="%{#attr.projectGroupId}"/>
-                            <s:param name="triggeredBy" value="%{#attr.triggeredBy}"/>
-                            <s:param name="buildStatus" value="%{#attr.buildStatus}"/>
-                            <s:param name="rowCount" value="%{#attr.rowCount}"/>
-                            <s:param name="startDate" value="%{#attr.startDate}"/>                      
-                            <s:param name="endDate" value="%{#attr.endDate}"/>          
-                            <s:param name="page" value="%{#attr.i + 1}"/>
-                          </s:url>
-                        </c:set>
-                        <a href="${specificPageUrl}">${i + 1}</a>
-                      </c:when>
-                      <c:otherwise>		
-                        <b>${i + 1}</b>   
-                      </c:otherwise>				  			    
-                    </c:choose>      
-                  </c:forEach>
-
-                  <c:choose>
-                    <c:when test="${page == numPages}">
-                      <s:text name="projectBuilds.report.next"/>
-                    </c:when>
-            	      <c:otherwise>
-                      <a href="${nextPageUrl}">
-                        <s:text name="projectBuilds.report.next"/>
-                      </a>
-                    </c:otherwise>   
-                  </c:choose>
+
+             	  <%-- Pagination --%>
+                  <s:url var="prevUrl" action="generateProjectBuildsReport">
+                    <s:param name="projectGroupId" value="projectGroupId"/>
+                    <s:param name="buildStatus" value="buildStatus"/>
+                    <s:param name="triggeredBy" value="triggeredBy"/>
+                    <s:param name="startDate" value="startDate"/>
+                    <s:param name="endDate" value="endDate"/>
+                    <s:param name="page" value="page - 1"/>
+                  </s:url>
+                  <s:url var="nextUrl" action="generateProjectBuildsReport">
+                    <s:param name="projectGroupId" value="projectGroupId"/>
+                    <s:param name="triggeredBy" value="triggeredBy"/>
+                    <s:param name="buildStatus" value="buildStatus"/>
+                    <s:param name="startDate" value="startDate"/>
+                    <s:param name="endDate" value="endDate"/>
+                    <s:param name="page" value="page + 1"/>
+                  </s:url>
+
+                  <s:if test="page <= 1">
+                    <s:text name="projectBuilds.report.prev"/>
+                  </s:if>
+                  <s:else>
+                    <s:a href="%{#prevUrl}"><s:text name="projectBuilds.report.prev"/></s:a>
+                  </s:else>
+
+                  <s:iterator var="page" begin="1" end="pageTotal">
+                    <s:url var="pageUrl" action="generateProjectBuildsReport">
+                      <s:param name="projectGroupId" value="projectGroupId"/>
+                        <s:param name="triggeredBy" value="triggeredBy"/>
+                        <s:param name="buildStatus" value="buildStatus"/>
+                        <s:param name="startDate" value="startDate"/>
+                        <s:param name="endDate" value="endDate"/>
+                        <s:param name="page" value="#page"/>
+                      </s:url>
+                      <s:if test="page == #page">
+                        <b><s:property value="#page"/></b>
+                      </s:if>
+                      <s:else>
+                        <s:a href="%{#pageUrl}"><s:property value="#page"/></s:a>
+                      </s:else>
+                  </s:iterator>
+
+                  <%-- Can not determine exact end of results due to filtering --%>
+                  <s:if test="page >= pageTotal">
+                    <s:text name="projectBuilds.report.next"/>
+                  </s:if>
+                  <s:else>
+                    <s:a href="%{#nextUrl}"><s:text name="projectBuilds.report.next"/></s:a>
+                  </s:else>
+
            	    </td>
            	    <td>
                   <%-- Export to CSV link --%>
                   <s:url id="downloadProjectBuildsReportUrl" action="downloadProjectBuildsReport" namespace="/">
-                    <s:param name="projectGroupId" value="%{#attr.projectGroupId}"/>
-                    <s:param name="triggeredBy" value="%{#attr.triggeredBy}"/>
-                    <s:param name="buildStatus" value="%{#attr.buildStatus}"/>
-                    <s:param name="startDate" value="%{#attr.startDate}"/>
-                    <s:param name="endDate" value="%{#attr.endDate}"/>
+                    <s:param name="projectGroupId" value="projectGroupId"/>
+                    <s:param name="triggeredBy" value="triggeredBy"/>
+                    <s:param name="buildStatus" value="buildStatus"/>
+                    <s:param name="startDate" value="startDate"/>
+                    <s:param name="endDate" value="endDate"/>
                   </s:url>
                   <s:a href="%{downloadProjectBuildsReportUrl}">Export to CSV</s:a>
                 </td>
               </tr>
             </table>
 
-            <s:set name="projectBuilds" value="projectBuilds" scope="request"/>
-            <ec:table items="projectBuilds"
-                        var="projectBuild"
+            <s:set value="filteredResults" name="buildResults" scope="page"/>
+            <ec:table items="buildResults"
+                        var="buildResult"
                         showExports="false"
                         showPagination="false"
                         showStatusBar="false"
                         sortable="false"
                         filterable="false">
               <ec:row highlightRow="true">
-                <ec:column property="projectGroupName" title="projectBuilds.report.projectGroup"/>
-                <ec:column property="projectName" title="projectBuilds.report.project"/>
-                <ec:column property="buildDate" title="projectBuilds.report.buildDate" cell="date"/>
-                <ec:column property="buildTriggeredBy" title="projectBuilds.report.triggeredBy"/>
-                <ec:column property="buildState" title="projectBuilds.report.buildStatus" cell="org.apache.maven.continuum.web.view.buildresults.StateCell"/>
+                <ec:column property="project.projectGroup.name" title="projectBuilds.report.projectGroup"/>
+                <ec:column property="project.name" title="projectBuilds.report.project"/>
+                <ec:column property="buildNumber" title="projectBuilds.report.buildNumber"/>
+                <ec:column property="startTime" title="projectBuilds.report.buildDate" cell="date"/>
+                <ec:column property="username" title="projectBuilds.report.triggeredBy"/>
+                <ec:column property="state" title="projectBuilds.report.buildStatus" cell="org.apache.maven.continuum.web.view.buildresults.StateCell"/>
               </ec:row>
             </ec:table>
-          </c:when>
-          <c:otherwise>
+          </s:if>
+          <s:else>
             <s:text name="projectBuilds.report.noResult"/></p>
-          </c:otherwise>
-        </c:choose>
+          </s:else>
       </div>
-    </c:if>
   </body>
   </s:i18n>
 </html>

Modified: continuum/trunk/continuum-webapp/src/test/java/org/apache/continuum/web/action/ViewBuildsReportActionTest.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-webapp/src/test/java/org/apache/continuum/web/action/ViewBuildsReportActionTest.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-webapp/src/test/java/org/apache/continuum/web/action/ViewBuildsReportActionTest.java (original)
+++ continuum/trunk/continuum-webapp/src/test/java/org/apache/continuum/web/action/ViewBuildsReportActionTest.java Mon May  4 01:42:05 2015
@@ -105,7 +105,7 @@ public class ViewBuildsReportActionTest
     public void testStartDateSameWithEndDate()
     {
         when( continuum.getBuildResultsInRange( anyInt(), any( Date.class ), any( Date.class ), anyInt(),
-                                                anyString() ) ).thenReturn( buildResults );
+                                                anyString(), anyInt(), anyInt() ) ).thenReturn( buildResults );
 
         action.setStartDate( "04/25/2010" );
         action.setEndDate( "04/25/2010" );
@@ -118,7 +118,7 @@ public class ViewBuildsReportActionTest
     public void testEndDateWithNoStartDate()
     {
         when( continuum.getBuildResultsInRange( anyInt(), any( Date.class ), any( Date.class ), anyInt(),
-                                                anyString() ) ).thenReturn( buildResults );
+                                                anyString(), anyInt(), anyInt() ) ).thenReturn( buildResults );
         action.setEndDate( "04/25/2010" );
         String result = action.execute();
 
@@ -135,7 +135,7 @@ public class ViewBuildsReportActionTest
         List<BuildResult> results = createBuildResult( cal.getTimeInMillis() );
 
         when( continuum.getBuildResultsInRange( anyInt(), any( Date.class ), any( Date.class ), anyInt(),
-                                                anyString() ) ).thenReturn( results );
+                                                anyString(), anyInt(), anyInt() ) ).thenReturn( results );
         action.setProjectGroupId( 0 );
         action.setBuildStatus( 0 );
         action.setStartDate( "" );

Modified: continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-api/src/main/java/org/apache/maven/continuum/xmlrpc/ContinuumService.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-api/src/main/java/org/apache/maven/continuum/xmlrpc/ContinuumService.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-api/src/main/java/org/apache/maven/continuum/xmlrpc/ContinuumService.java (original)
+++ continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-api/src/main/java/org/apache/maven/continuum/xmlrpc/ContinuumService.java Mon May  4 01:42:05 2015
@@ -680,20 +680,24 @@ public interface ContinuumService
      * Returns the project build result summary list.
      *
      * @param projectId The project id
+     * @param offset the zero-based offset to fetch from
+     * @param length the maximum number of results to fetch, starting from offset
      * @return The build result list
      * @throws Exception
      */
-    List<BuildResultSummary> getBuildResultsForProject( int projectId )
+    List<BuildResultSummary> getBuildResultsForProject( int projectId, int offset, int length )
         throws Exception;
 
     /**
      * Same method but compatible with standard XMLRPC
      *
      * @param projectId The project id
+     * @param offset the zero-based offset of result set to start from
+     * @param length the number of results to return, starting from the offset
      * @return The build result list as RPC value
      * @throws Exception
      */
-    List<Object> getBuildResultsForProjectRPC( int projectId )
+    List<Object> getBuildResultsForProjectRPC( int projectId, int offset, int length )
         throws Exception;
 
     /**

Modified: continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-backup/src/main/java/org/apache/maven/continuum/xmlrpc/backup/Backup.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-backup/src/main/java/org/apache/maven/continuum/xmlrpc/backup/Backup.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-backup/src/main/java/org/apache/maven/continuum/xmlrpc/backup/Backup.java (original)
+++ continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-backup/src/main/java/org/apache/maven/continuum/xmlrpc/backup/Backup.java Mon May  4 01:42:05 2015
@@ -29,17 +29,7 @@ import org.apache.log4j.BasicConfigurato
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 import org.apache.maven.continuum.xmlrpc.client.ContinuumXmlRpcClient;
-import org.apache.maven.continuum.xmlrpc.project.BuildDefinition;
-import org.apache.maven.continuum.xmlrpc.project.BuildDefinitionTemplate;
-import org.apache.maven.continuum.xmlrpc.project.BuildResult;
-import org.apache.maven.continuum.xmlrpc.project.BuildResultSummary;
-import org.apache.maven.continuum.xmlrpc.project.Project;
-import org.apache.maven.continuum.xmlrpc.project.ProjectDependency;
-import org.apache.maven.continuum.xmlrpc.project.ProjectDeveloper;
-import org.apache.maven.continuum.xmlrpc.project.ProjectGroup;
-import org.apache.maven.continuum.xmlrpc.project.ProjectNotifier;
-import org.apache.maven.continuum.xmlrpc.project.ProjectSummary;
-import org.apache.maven.continuum.xmlrpc.project.Schedule;
+import org.apache.maven.continuum.xmlrpc.project.*;
 import org.apache.maven.continuum.xmlrpc.scm.ChangeFile;
 import org.apache.maven.continuum.xmlrpc.scm.ChangeSet;
 import org.apache.maven.continuum.xmlrpc.scm.ScmResult;
@@ -415,17 +405,24 @@ public class Backup
             endTag( "notifiers", true );
         }
 
-        List<BuildResultSummary> brs = client.getBuildResultsForProject( p.getId() );
-        if ( brs != null && !brs.isEmpty() )
+        int batchSize = 100, offset = 0;
+        List<BuildResultSummary> brs;
+        do
         {
-            startTag( "buildResults", true );
-            for ( BuildResultSummary brSummary : brs )
+            brs = client.getBuildResultsForProject( p.getId(), offset, batchSize );
+            if ( brs != null && !brs.isEmpty() )
             {
-                BuildResult br = client.getBuildResult( p.getId(), brSummary.getId() );
-                backupBuildResult( br );
+                startTag( "buildResults", true );
+                for ( BuildResultSummary brSummary : brs )
+                {
+                    BuildResult br = client.getBuildResult( p.getId(), brSummary.getId() );
+                    backupBuildResult( br );
+                }
+                endTag( "buildResults", true );
             }
-            endTag( "buildResults", true );
+            offset += batchSize;
         }
+        while ( brs != null && brs.size() == batchSize );
         endTag( "project", true );
     }
 

Modified: continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/BuildResultsPurge.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/BuildResultsPurge.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/BuildResultsPurge.java (original)
+++ continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/BuildResultsPurge.java Mon May  4 01:42:05 2015
@@ -30,7 +30,6 @@ import java.util.List;
 
 /**
  * Utility class to purge old build results.
- * <p/>
  * The easiest way to use it is to change the exec plugin config in the pom to execute this class instead of
  * SampleClient, change RETENTION_DAYS if desired, and type 'mvn clean install exec:exec'
  */
@@ -72,26 +71,37 @@ public class BuildResultsPurge
 
                 System.out.println( " Project [" + project.getId() + "] " + project.getName() );
 
-                List<BuildResultSummary> results = client.getBuildResultsForProject( project.getId() );
+                int batchSize = 100, offset = 0;
+                List<BuildResultSummary> results;
 
-                for ( BuildResultSummary brs : results )
+                do
                 {
+                    int retained = 0;
+                    results = client.getBuildResultsForProject( project.getId(), offset, batchSize );
 
-                    BuildResult br = client.getBuildResult( project.getId(), brs.getId() );
+                    for ( BuildResultSummary brs : results )
+                    {
 
-                    System.out.print( "  Build Result [" + br.getId() + "] ended " + new Date( br.getEndTime() ) );
+                        BuildResult br = client.getBuildResult( project.getId(), brs.getId() );
 
-                    if ( br.getEndTime() > 0 && br.getEndTime() < purgeDate )
-                    {
+                        System.out.print( "  Build Result [" + br.getId() + "] ended " + new Date( br.getEndTime() ) );
 
-                        client.removeBuildResult( br );
-                        System.out.println( " ...removed." );
-                    }
-                    else
-                    {
-                        System.out.println( " ...retained." );
+                        if ( br.getEndTime() > 0 && br.getEndTime() < purgeDate )
+                        {
+
+                            client.removeBuildResult( br );
+                            System.out.println( " ...removed." );
+                        }
+                        else
+                        {
+                            System.out.println( " ...retained." );
+                            retained++;
+                        }
                     }
+
+                    offset += retained;  // Only need to advance past items we keep
                 }
+                while ( results != null && results.size() == batchSize );
             }
 
         }

Modified: continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/ContinuumXmlRpcClient.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/ContinuumXmlRpcClient.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/ContinuumXmlRpcClient.java (original)
+++ continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/ContinuumXmlRpcClient.java Mon May  4 01:42:05 2015
@@ -386,10 +386,10 @@ public class ContinuumXmlRpcClient
         return continuum.getBuildResult( projectId, buildId );
     }
 
-    public List<BuildResultSummary> getBuildResultsForProject( int projectId )
+    public List<BuildResultSummary> getBuildResultsForProject( int projectId, int offset, int length )
         throws Exception
     {
-        return continuum.getBuildResultsForProject( projectId );
+        return continuum.getBuildResultsForProject( projectId, offset, length );
     }
 
     public int removeBuildResult( BuildResult br )
@@ -928,10 +928,10 @@ public class ContinuumXmlRpcClient
         return continuum.getBuildResultRPC( projectId, buildId );
     }
 
-    public List<Object> getBuildResultsForProjectRPC( int projectId )
+    public List<Object> getBuildResultsForProjectRPC( int projectId, int offset, int length )
         throws Exception
     {
-        return continuum.getBuildResultsForProjectRPC( projectId );
+        return continuum.getBuildResultsForProjectRPC( projectId, offset, length );
     }
 
     public Map<String, Object> getInstallationRPC( int installationId )

Modified: continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/SampleClient.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/SampleClient.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/SampleClient.java (original)
+++ continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-client/src/main/java/org/apache/maven/continuum/xmlrpc/client/SampleClient.java Mon May  4 01:42:05 2015
@@ -53,7 +53,7 @@ public class SampleClient
 
         // Test for [CONTINUUM-2641]: (test with distributed builds with multiple build agents or parallel builds with > 1 build queue)
         // make sure to set the projectIds to the actual projectIds of your projects added in Continuum
-        int projectIds[] = new int[]{2, 3, 4, 5, 6};
+        int projectIds[] = new int[] { 2, 3, 4, 5, 6 };
 
         List<Thread> threads = new ArrayList<Thread>();
 
@@ -174,15 +174,20 @@ public class SampleClient
 
         System.out.println( "Removing build results." );
         System.out.println( "============================" );
-        BuildResultSummary brs;
-        List results = client.getBuildResultsForProject( ps.getId() );
-        for ( Iterator i = results.iterator(); i.hasNext(); )
-        {
-            brs = (BuildResultSummary) i.next();
-            System.out.print( "Removing build result (" + brs.getId() + ") - " );
-            BuildResult br = client.getBuildResult( ps.getId(), brs.getId() );
-            System.out.println( ( client.removeBuildResult( br ) == 0 ? "OK" : "Error" ) );
+
+        int batchSize = 100;
+        List<BuildResultSummary> results;
+        do
+        {
+            results = client.getBuildResultsForProject( ps.getId(), 0, batchSize );
+            for ( BuildResultSummary brs : results )
+            {
+                System.out.print( "Removing build result (" + brs.getId() + ") - " );
+                BuildResult br = client.getBuildResult( ps.getId(), brs.getId() );
+                System.out.println( ( client.removeBuildResult( br ) == 0 ? "OK" : "Error" ) );
+            }
         }
+        while ( results != null && results.size() > 0 );
         System.out.println( "Done." );
 
         System.out.println();

Modified: continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-server/src/main/java/org/apache/maven/continuum/xmlrpc/server/ContinuumServiceImpl.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-server/src/main/java/org/apache/maven/continuum/xmlrpc/server/ContinuumServiceImpl.java?rev=1677500&r1=1677499&r2=1677500&view=diff
==============================================================================
--- continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-server/src/main/java/org/apache/maven/continuum/xmlrpc/server/ContinuumServiceImpl.java (original)
+++ continuum/trunk/continuum-xmlrpc/continuum-xmlrpc-server/src/main/java/org/apache/maven/continuum/xmlrpc/server/ContinuumServiceImpl.java Mon May  4 01:42:05 2015
@@ -881,7 +881,7 @@ public class ContinuumServiceImpl
         return populateBuildResult( continuum.getBuildResult( buildId ) );
     }
 
-    public List<BuildResultSummary> getBuildResultsForProject( int projectId )
+    public List<BuildResultSummary> getBuildResultsForProject( int projectId, int offset, int length )
         throws ContinuumException
     {
         ProjectSummary ps = getProjectSummary( projectId );
@@ -889,7 +889,7 @@ public class ContinuumServiceImpl
 
         List<BuildResultSummary> result = new ArrayList<BuildResultSummary>();
         Collection<org.apache.maven.continuum.model.project.BuildResult> buildResults =
-            continuum.getBuildResultsForProject( projectId );
+            continuum.getBuildResultsForProject( projectId, offset, length );
         if ( buildResults != null )
         {
             for ( org.apache.maven.continuum.model.project.BuildResult buildResult : buildResults )
@@ -3471,10 +3471,10 @@ public class ContinuumServiceImpl
         return serializeObject( this.getBuildResult( projectId, buildId ) );
     }
 
-    public List<Object> getBuildResultsForProjectRPC( int projectId )
+    public List<Object> getBuildResultsForProjectRPC( int projectId, int offset, int length )
         throws Exception
     {
-        return serializeObject( this.getBuildResultsForProject( projectId ) );
+        return serializeObject( this.getBuildResultsForProject( projectId, offset, length ) );
     }
 
     public Map<String, Object> getInstallationRPC( int installationId )