>From 4aa7a69bcf155abf9720c63469de38f75da6fa99 Mon Sep 17 00:00:00 2001
From: Ade Lee <alee@redhat.com>
Date: Thu, 12 Jan 2012 12:24:24 -0500
Subject: [PATCH] Enhanced new REST search interface for keys and key requests

Defined parameters that can be searched for in key and keyrequest searches.

Searches for KeyRequests and Keys will perform VLV searches if those searches are defined.
The results will include links to next and previous pages in the results.

Also added maxTime and maxResults parameters for regular searches.  These will be operational
unless they exceed server defined limits - which are enforced at the repo level.
---
 .../com/netscape/cms/servlet/base/model/Link.java  |   88 ++++++++++++++
 .../com/netscape/cms/servlet/key/KeysResource.java |   55 ++++++++--
 .../com/netscape/cms/servlet/key/model/KeyDAO.java |   32 ++++--
 .../cms/servlet/key/model/KeyDataInfos.java        |   87 ++++++++++++++
 .../cms/servlet/request/KeyRequestsResource.java   |   84 ++++++++++++---
 .../cms/servlet/request/model/KeyRequestDAO.java   |  120 +++++++++++++++++--
 .../cms/servlet/request/model/KeyRequestInfos.java |   89 +++++++++++++++
 7 files changed, 507 insertions(+), 48 deletions(-)
 create mode 100644 pki/base/common/src/com/netscape/cms/servlet/base/model/Link.java
 create mode 100644 pki/base/common/src/com/netscape/cms/servlet/key/model/KeyDataInfos.java
 create mode 100644 pki/base/common/src/com/netscape/cms/servlet/request/model/KeyRequestInfos.java

diff --git a/pki/base/common/src/com/netscape/cms/servlet/base/model/Link.java b/pki/base/common/src/com/netscape/cms/servlet/base/model/Link.java
new file mode 100644
index 0000000000000000000000000000000000000000..b29e380d013d14afba78b096f425b48ca9366682
--- /dev/null
+++ b/pki/base/common/src/com/netscape/cms/servlet/base/model/Link.java
@@ -0,0 +1,88 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2011 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---/**
+package com.netscape.cms.servlet.base.model;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @author alee
+ * 
+ */
+@XmlRootElement(name = "link")
+public class Link {
+    protected String relationship;
+    protected String href;
+    protected String type;
+
+    public Link() {
+        // required for jaxb
+    }
+
+    public Link(String relationship, String href, String type) {
+        this.relationship = relationship;
+        this.href = href;
+        this.type = type;
+    }
+
+    /**
+     * @return the relationship
+     */
+    @XmlAttribute(name = "rel")
+    public String getRelationship() {
+        return relationship;
+    }
+
+    /**
+     * @param relationship the relationship to set
+     */
+    public void setRelationship(String relationship) {
+        this.relationship = relationship;
+    }
+
+    /**
+     * @return the href
+     */
+    @XmlAttribute
+    public String getHref() {
+        return href;
+    }
+
+    /**
+     * @param href the href to set
+     */
+    public void setHref(String href) {
+        this.href = href;
+    }
+
+    /**
+     * @return the type
+     */
+    @XmlAttribute
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * @param type the type to set
+     */
+    public void setType(String type) {
+        this.type = type;
+    }
+
+}
diff --git a/pki/base/common/src/com/netscape/cms/servlet/key/KeysResource.java b/pki/base/common/src/com/netscape/cms/servlet/key/KeysResource.java
index 98f12ae5a611942e3a47d8fd293046854a475074..4cf5db3f44e45a4d9fbf18e413fa854bbdf31358 100644
--- a/pki/base/common/src/com/netscape/cms/servlet/key/KeysResource.java
+++ b/pki/base/common/src/com/netscape/cms/servlet/key/KeysResource.java
@@ -20,21 +20,22 @@
  */
 package com.netscape.cms.servlet.key;
 
+import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
  
-import java.util.List;
-
+import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.cms.servlet.base.CMSResource;
 import com.netscape.cms.servlet.key.model.KeyDAO;
-import com.netscape.cms.servlet.key.model.KeyDataInfo;
+import com.netscape.cms.servlet.key.model.KeyDataInfos;
 
 /**
  * @author alee
@@ -43,6 +44,9 @@ import com.netscape.cms.servlet.key.model.KeyDataInfo;
 @Path("/keys")
 public class KeysResource extends CMSResource {
 
+    private static final String DEFAULT_MAXTIME = "10";
+    private static final String DEFAULT_MAXRESULTS = "100";
+
     @Context
     UriInfo uriInfo;
 
@@ -51,20 +55,51 @@ public class KeysResource extends CMSResource {
      */
     @GET
     @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
-    public List<KeyDataInfo> listKeys() {
+    public KeyDataInfos listKeys(@QueryParam("clientID") String clientID,
+                                 @QueryParam("status") String status,
+                                 @DefaultValue(DEFAULT_MAXRESULTS) @QueryParam("maxResults") int maxResults,
+                                 @DefaultValue(DEFAULT_MAXTIME) @QueryParam("maxTime") int maxTime) {
         // auth and authz
-        // parse search parameters from uriInfo and create search filter
-        // String clientID = uriInfo.getQueryParameters().getFirst(CLIENT_ID);
-        String filter = "objectClass=keyRecord";
+        
+        // get ldap filter
+        String filter = createSearchFilter(status, clientID);
+        CMS.debug("listKeys: filter is " + filter);
+       
         KeyDAO dao = new KeyDAO();
-        List<KeyDataInfo> info;
+        KeyDataInfos infos;
         try {
-            info = dao.listKeys(filter, uriInfo);
+            infos = dao.listKeys(filter, maxResults, maxTime, uriInfo);
         } catch (EBaseException e) {
             e.printStackTrace();
             throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
         }
-        return info;
+        return infos;
+    }
+    
+    private String createSearchFilter(String status, String clientID) {
+        String filter = "";
+        int matches = 0;
+        
+        if ((status == null) && (clientID == null)) {
+            filter = "(serialno=*)";
+            return filter;
+        }
+        
+        if (status != null) {
+            filter += "(status=" + status + ")";
+            matches ++;
+        }
+        
+        if (clientID != null) {
+            filter += "(clientID=" + clientID + ")";
+            matches ++;
+        }
+        
+        if (matches > 1) {
+            filter = "(&" + filter + ")";
+        }
+        
+        return filter;
     }
 
 }
diff --git a/pki/base/common/src/com/netscape/cms/servlet/key/model/KeyDAO.java b/pki/base/common/src/com/netscape/cms/servlet/key/model/KeyDAO.java
index b7a2d8ed1dab7b3ac0505c46df28612b723c0db7..5fd17a333bf8c577c5e5afc69f21ece4a0efbcee 100644
--- a/pki/base/common/src/com/netscape/cms/servlet/key/model/KeyDAO.java
+++ b/pki/base/common/src/com/netscape/cms/servlet/key/model/KeyDAO.java
@@ -38,8 +38,6 @@ import com.netscape.cms.servlet.request.model.RecoveryRequestData;
 public class KeyDAO {
 
     private IKeyRepository repo;
-    private int maxSize = 100;
-    private int maxTime = 20;
     
     public KeyDAO() {
         IKeyRecoveryAuthority kra = null;
@@ -47,25 +45,37 @@ public class KeyDAO {
         repo = kra.getKeyRepository();
     }
     /**
-     * This will find the keys in the database matching the specified search parameters
-     * Needs input validation and probably paging, maybe using the vlv functions
-     * @throws EBaseException 
+     * Returns list of keys meeting specified search filter.
+     * Currently, vlv searches are not used for keys.
+     * 
+     * @param filter
+     * @param maxResults
+     * @param maxTime
+     * @param uriInfo
+     * @return
+     * @throws EBaseException
      */
-    public List<KeyDataInfo> listKeys(String filter, UriInfo uriInfo) throws EBaseException {
-        List <KeyDataInfo> list = new ArrayList<KeyDataInfo>();         
+    public KeyDataInfos listKeys(String filter, int maxResults, int maxTime, UriInfo uriInfo) 
+        throws EBaseException {
+        List <KeyDataInfo> list = new ArrayList<KeyDataInfo>();
         Enumeration<IKeyRecord> e = null;
         
-        e = repo.searchKeys(filter, maxSize, maxTime);
-         
+        e = repo.searchKeys(filter, maxResults, maxTime); 
         if (e == null) {
             throw new EBaseException("search results are null");
         }
         
         while (e.hasMoreElements()) {
             IKeyRecord rec = e.nextElement();
-            list.add(createKeyDataInfo(rec, uriInfo));
+            if (rec != null) {
+                list.add(createKeyDataInfo(rec, uriInfo));
+            }
         }
-        return list;
+        
+        KeyDataInfos ret = new KeyDataInfos();
+        ret.setKeyInfos(list);
+        
+        return ret;
     }
     
     public KeyData getKey(String keyId, RecoveryRequestData data) throws EBaseException {
diff --git a/pki/base/common/src/com/netscape/cms/servlet/key/model/KeyDataInfos.java b/pki/base/common/src/com/netscape/cms/servlet/key/model/KeyDataInfos.java
new file mode 100644
index 0000000000000000000000000000000000000000..b01184708ad7e41655357d58d97082c9635a3af9
--- /dev/null
+++ b/pki/base/common/src/com/netscape/cms/servlet/key/model/KeyDataInfos.java
@@ -0,0 +1,87 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package com.netscape.cms.servlet.key.model;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlElementRef;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+import com.netscape.cms.servlet.base.model.Link;
+
+@XmlRootElement(name = "SecurityDataInfos")
+public class KeyDataInfos {
+    
+    protected Collection<KeyDataInfo> keyInfos;
+    protected List<Link> links;
+    
+    /**
+     * @return the keyInfos
+     */
+    @XmlElementRef
+    public Collection<KeyDataInfo> getKeyInfos() {
+        return keyInfos;
+    }
+    /**
+     * @param keyInfos the keyInfos to set
+     */
+    public void setKeyInfos(Collection<KeyDataInfo> keyInfos) {
+        this.keyInfos = keyInfos;
+    }
+    /**
+     * @return the links
+     */
+    @XmlElementRef
+    public List<Link> getLinks() {
+        return links;
+    }
+    /**
+     * @param links the links to set
+     */
+    public void setLinks(List<Link> links) {
+        this.links = links;
+    }
+    
+    @XmlTransient
+    public String getNext() {
+        if (links == null) {
+            return null;
+        }
+        for (Link link : links) {
+            if ("next".equals(link.getRelationship())) {
+                return link.getHref();
+            }
+        }
+        return null;
+    }
+
+    @XmlTransient
+    public String getPrevious() {
+        if (links == null) {
+            return null;
+        }
+        for (Link link : links) {
+            if ("previous".equals(link.getRelationship())) {
+                return link.getHref();
+            }
+        }
+        return null;
+    }
+}
diff --git a/pki/base/common/src/com/netscape/cms/servlet/request/KeyRequestsResource.java b/pki/base/common/src/com/netscape/cms/servlet/request/KeyRequestsResource.java
index 3624b1bd69377f7c5c4333d7b6a6ac408ba94932..ce53053d4733f0936249d04fcdc9fe118f0a3f2a 100644
--- a/pki/base/common/src/com/netscape/cms/servlet/request/KeyRequestsResource.java
+++ b/pki/base/common/src/com/netscape/cms/servlet/request/KeyRequestsResource.java
@@ -18,11 +18,11 @@
  
 package com.netscape.cms.servlet.request;
 
-import java.util.List;
-
+import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
@@ -33,7 +33,7 @@ import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.cms.servlet.base.CMSResource;
 import com.netscape.cms.servlet.request.model.KeyRequestDAO;
-import com.netscape.cms.servlet.request.model.KeyRequestInfo;
+import com.netscape.cms.servlet.request.model.KeyRequestInfos;
  
 /**
  * @author alee
@@ -41,7 +41,12 @@ import com.netscape.cms.servlet.request.model.KeyRequestInfo;
  */
 @Path("/keyrequests")
 public class KeyRequestsResource extends CMSResource {
- 
+
+    private static final int DEFAULT_START = 0;
+    private static final String DEFAULT_PAGESIZE = "20";
+    private static final String DEFAULT_MAXRESULTS = "100";
+    private static final String DEFAULT_MAXTIME = "10";
+
     @Context
     UriInfo uriInfo;
 
@@ -50,24 +55,75 @@ public class KeyRequestsResource extends CMSResource {
      */
     @GET
     @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
-    public List<KeyRequestInfo> listRequests() {
+    public KeyRequestInfos listRequests(@QueryParam("requestState") String requestState,
+                                        @QueryParam("requestType") String requestType,
+                                        @QueryParam("clientID") String clientID,
+                                        @QueryParam("start") String start_s,
+                                        @DefaultValue(DEFAULT_PAGESIZE) @QueryParam("pageSize") int pageSize,
+                                        @DefaultValue(DEFAULT_MAXRESULTS) @QueryParam("maxResults") int maxResults,
+                                        @DefaultValue(DEFAULT_MAXTIME) @QueryParam("maxTime") int maxTime) {
         // auth and authz
-        // parse search parameters from uriInfo and create search filter
-        // String clientID = uriInfo.getQueryParameters().getFirst(CLIENT_ID);
-        String filter = "requestState=complete";
+        
+        // get ldap filter
+        String filter = createSearchFilter(requestState, requestType, clientID);
+        CMS.debug("listRequests: filter is " + filter);
+       
+        
+        // get start marker
+        int start = DEFAULT_START;
+        if (start_s != null) {
+            try {
+                if (start_s.trim().startsWith("0x")) {
+                    start = Integer.parseInt(start_s.trim().substring(2), 16);
+                } else {
+                    start = Integer.parseInt(start_s.trim());
+                }
+            } catch (NumberFormatException e) {
+                CMS.debug("listRequests: NumberformatException: Invalid value for start " + start_s);
+                throw new WebApplicationException(Response.Status.NOT_FOUND);
+            }
+        }
+        
         KeyRequestDAO reqDAO = new KeyRequestDAO();
-        List<KeyRequestInfo> requests;
+        KeyRequestInfos requests;
         try {
-            CMS.debug("alee: getting requests");
-            requests = reqDAO.listRequests(filter, uriInfo);
-            CMS.debug("alee: got request");
+            requests = reqDAO.listRequests(filter, start, pageSize, maxResults, maxTime, uriInfo);
         } catch (EBaseException e) {
-            // log error
+            CMS.debug("listRequests: error in obtaining request results" + e);
             e.printStackTrace();
             throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
         }
-        CMS.debug("going into return");
         return requests;
     }
+
+    private String createSearchFilter(String requestState, String requestType, String clientID) {
+        String filter = "";
+        int matches = 0;
         
+        if ((requestState == null) && (requestType == null) && (clientID == null)) {
+            filter = "(requeststate=*)";
+            return filter;
+        }
+        
+        if (requestState != null) {
+            filter += "(requeststate=" + requestState + ")";
+            matches ++;
+        }
+        
+        if (requestType != null) {
+            filter += "(requesttype=" + requestType + ")";
+            matches ++;
+        }
+        
+        if (clientID != null) {
+            filter += "(clientID=" + clientID + ")";
+            matches ++;
+        }
+        
+        if (matches > 1) {
+            filter = "(&" + filter + ")";
+        }
+        
+        return filter;
+    }
 }
diff --git a/pki/base/common/src/com/netscape/cms/servlet/request/model/KeyRequestDAO.java b/pki/base/common/src/com/netscape/cms/servlet/request/model/KeyRequestDAO.java
index b15e17c6dac39ef2164db184970b2b413bcd5bf3..16da23d8b5fd4472f9522264911b67d8e65b3eb1 100644
--- a/pki/base/common/src/com/netscape/cms/servlet/request/model/KeyRequestDAO.java
+++ b/pki/base/common/src/com/netscape/cms/servlet/request/model/KeyRequestDAO.java
@@ -17,9 +17,11 @@
 // --- END COPYRIGHT BLOCK ---
 package com.netscape.cms.servlet.request.model;
 
+import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 
@@ -29,8 +31,10 @@ import com.netscape.certsrv.kra.IKeyRecoveryAuthority;
 import com.netscape.certsrv.request.IRequest;
 import com.netscape.certsrv.request.IRequestList;
 import com.netscape.certsrv.request.IRequestQueue;
+import com.netscape.certsrv.request.IRequestVirtualList;
 import com.netscape.certsrv.request.RequestId;
 import com.netscape.certsrv.request.RequestStatus;
+import com.netscape.cms.servlet.base.model.Link;
 
 /**
  * @author alee
@@ -39,6 +43,19 @@ import com.netscape.certsrv.request.RequestStatus;
 public class KeyRequestDAO {
     private IRequestQueue queue;
     
+    private String[] vlvFilters = {
+            "(requeststate=*)", "(requesttype=enrollment)",
+            "(requesttype=recovery)", "(requeststate=canceled)",
+            "(&(requeststate=canceled)(requesttype=enrollment))",
+            "(&(requeststate=canceled)(requesttype=recovery))", 
+            "(requeststate=rejected)", 
+            "(&(requeststate=rejected)(requesttype=enrollment))",
+            "(&(requeststate=rejected)(requesttype=recovery))",
+            "(requeststate=complete)",
+            "(&(requeststate=complete)(requesttype=enrollment))",
+            "(&(requeststate=complete)(requesttype=recovery))"
+    };
+    
     public KeyRequestDAO() {
         IKeyRecoveryAuthority kra = null;
         kra = ( IKeyRecoveryAuthority ) CMS.getSubsystem( "kra" );
@@ -46,20 +63,88 @@ public class KeyRequestDAO {
     }
 
     /**
-     * This will find the requests in the database matching the specified search parameters
-     * Needs input validation and probably paging, maybe using the vlv functions
-     * @throws EBaseException 
+     * Finds list of requests matching the specified search filter.  
+     * 
+     * If the filter corresponds to a VLV search, then that search is executed and the pageSize 
+     * and start parameters are used.  Otherwise, the maxResults and maxTime parameters are
+     * used in the regularly indexed search.
+     * 
+     * @param filter - ldap search filter
+     * @param start - start position for VLV search
+     * @param pageSize - page size for VLV search
+     * @param maxResults - max results to be returned in normal search
+     * @param maxTime - max time for normal search
+     * @param uriInfo - uri context of request
+     * @return collection of key request info
+     * @throws EBaseException
      */
-    public List<KeyRequestInfo> listRequests(String filter, UriInfo uriInfo) throws EBaseException {
-        List <KeyRequestInfo> list = new ArrayList<KeyRequestInfo>();  
-        IRequestList requests = queue.listRequestsByFilter(filter);
-        while (requests.hasMoreElements()) {
-            RequestId rid = (RequestId) requests.nextElement();
-            IRequest request;
-            request = queue.findRequest(rid);
-            list.add(createKeyRequestInfo(request, uriInfo));
-        }
-        return list;
+    public KeyRequestInfos listRequests(String filter, int start, int pageSize, int maxResults, int maxTime, 
+            UriInfo uriInfo) throws EBaseException {
+        List <KeyRequestInfo> list = new ArrayList<KeyRequestInfo>();
+        List <Link> links = new ArrayList<Link>();
+        int totalSize = 0;
+        int current = 0;
+        
+        if (isVLVSearch(filter)) {
+            RequestId id = new RequestId(Integer.toString(start));
+            IRequestVirtualList vlvlist = queue.getPagedRequestsByFilter(id, false, filter, 
+                                                                         pageSize +1 , "requestId");
+            totalSize = vlvlist.getSize();
+            current = vlvlist.getCurrentIndex();
+            
+            int numRecords = (totalSize > (current + pageSize)) ? pageSize :
+                totalSize - current;
+            
+            for (int i=0; i < numRecords; i++) {
+                IRequest request = vlvlist.getElementAt(i);
+                list.add(createKeyRequestInfo(request, uriInfo));
+            }
+        } else {
+            // The non-vlv requests are indexed, but are not paginated.
+            // We should think about whether they should be, or if we need to
+            // limit the number of results returned.
+            IRequestList requests = queue.listRequestsByFilter(filter, maxResults, maxTime);
+            while (requests.hasMoreElements()) {
+                RequestId rid = (RequestId) requests.nextElement();
+                IRequest request = queue.findRequest(rid);
+                if (request != null) {
+                    list.add(createKeyRequestInfo(request, uriInfo));
+                }
+            }
+        }
+        
+        // builder for vlv links
+        MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
+        UriBuilder builder = uriInfo.getAbsolutePathBuilder();
+        if (params.containsKey("requestState")) {
+            builder.queryParam("requestState", params.getFirst("requestState"));
+        }
+        if (params.containsKey("requestType")) {
+            builder.queryParam("requestType", params.getFirst("requestType"));
+        }
+        builder.queryParam("start", "{start}");
+        builder.queryParam("pageSize", "{pageSize}");
+        
+        // next link
+        if (totalSize > current + pageSize) {
+            int next = current + pageSize + 1;
+            URI nextUri = builder.clone().build(next,pageSize);
+            Link nextLink = new Link("next", nextUri.toString(), "application/xml");
+            links.add(nextLink);
+        }
+        
+        // previous link
+        if (current >0) {
+            int previous = current - pageSize;
+            URI previousUri = builder.clone().build(previous,pageSize);
+            Link previousLink = new Link("previous", previousUri.toString(), "application/xml");
+            links.add(previousLink);
+        }
+        
+        KeyRequestInfos ret = new KeyRequestInfos();
+        ret.setRequests(list);
+        ret.setLinks(links);
+        return ret;
     }
     
     /**
@@ -135,4 +220,13 @@ public class KeyRequestDAO {
         
         return ret;
     }
+    
+    private boolean isVLVSearch(String filter) {
+        for (int i=0; i < vlvFilters.length; i++) {
+            if (vlvFilters[i].equalsIgnoreCase(filter)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/pki/base/common/src/com/netscape/cms/servlet/request/model/KeyRequestInfos.java b/pki/base/common/src/com/netscape/cms/servlet/request/model/KeyRequestInfos.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc1b6a5e4c8392a56fb2ec9471ebac52fb6d512f
--- /dev/null
+++ b/pki/base/common/src/com/netscape/cms/servlet/request/model/KeyRequestInfos.java
@@ -0,0 +1,89 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2011 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+package com.netscape.cms.servlet.request.model;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlElementRef;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+import com.netscape.cms.servlet.base.model.Link;
+
+@XmlRootElement(name = "SecurityDataRequestInfos")
+public class KeyRequestInfos {
+    protected Collection<KeyRequestInfo> requests;
+    protected List<Link> links;
+
+    /**
+     * @return the requests
+     */
+    @XmlElementRef
+    public Collection<KeyRequestInfo> getRequests() {
+        return requests;
+    }
+
+    /**
+     * @param requests the requests to set
+     */
+    public void setRequests(Collection<KeyRequestInfo> requests) {
+        this.requests = requests;
+    }
+
+    /**
+     * @return the links
+     */
+    @XmlElementRef
+    public List<Link> getLinks() {
+        return links;
+    }
+
+    /**
+     * @param links the links to set
+     */
+    public void setLinks(List<Link> links) {
+        this.links = links;
+    }
+
+    @XmlTransient
+    public String getNext() {
+        if (links == null) {
+            return null;
+        }
+        for (Link link : links) {
+            if ("next".equals(link.getRelationship())) {
+                return link.getHref();
+            }
+        }
+        return null;
+    }
+
+    @XmlTransient
+    public String getPrevious() {
+        if (links == null) {
+            return null;
+        }
+        for (Link link : links) {
+            if ("previous".equals(link.getRelationship())) {
+                return link.getHref();
+            }
+        }
+        return null;
+    }
+}
-- 
1.7.6.4

